Fix various platform issues and slightly improve UI

* Fix crash on lower screen resolutions when clicking on Terminal

Mouse coordinates were not being scaled from screenspace into
canvas space, resulting in a crash when clicking on the Terminal.

Also add documentation for internal VirtualScreen
implementation, and define dynamic properties for
CanvasWidth and CanvasHeight in the UI system.

Issue: #13
Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Remove old GuiService virtual screen and document IVirtualScreen

Whoever wrote that old code was high, fuck... wait a minute...

Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Fix various errors after emergency rebase

* Remove Rider caches

* Attempt to enable Wayland support

* Add support for enabling/disabling Wayland support on Linux

* Move SettingsManager to GameApplication so settings can be loaded before the game engine fully boots.

* Test commit.

Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Move Terminals to their own tile

* Add support for getting CPU name on Windows via registry

* Add support for disabling background blur

* Fix window hints not being restored when switching between main tool tabs

* Testing the DCO acknowledgement job

Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>
This commit is contained in:
Ritchie Frodomar 2024-10-28 03:17:01 +00:00
parent 7f8f7dbf42
commit 75c8b5432c
1256 changed files with 7908 additions and 7874 deletions

0
.config/dotnet-tools.json Normal file → Executable file
View file

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
# Rider caches
.idea/
# docfx
api/
public/

0
.gitlab-ci.yml Normal file → Executable file
View file

0
Assets/Assets/Core/WebSites.meta Normal file → Executable file
View file

0
Assets/Assets/Core/WebSites/404.prefab Normal file → Executable file
View file

0
Assets/Assets/Core/WebSites/404.prefab.meta Normal file → Executable file
View file

0
Assets/Assets/Core/WebSites/TheMissingPage.asset Normal file → Executable file
View file

0
Assets/Assets/Core/WebSites/TheMissingPage.asset.meta Normal file → Executable file
View file

0
Assets/Assets/Main/Websites/NewCipherToday.asset Normal file → Executable file
View file

0
Assets/Assets/Main/Websites/NewCipherToday.asset.meta Normal file → Executable file
View file

0
Assets/Assets/Main/Websites/NewCipherToday.prefab Normal file → Executable file
View file

0
Assets/Assets/Main/Websites/NewCipherToday.prefab.meta Normal file → Executable file
View file

0
Assets/Editor/CustomImporters/ArticleImporter.cs Normal file → Executable file
View file

View file

View file

0
Assets/Scripts/UI/Shell/ShellNavigationLink.cs Normal file → Executable file
View file

0
Assets/Scripts/UI/Shell/ShellNavigationLink.cs.meta Normal file → Executable file
View file

0
Assets/Scripts/UI/Websites/News.meta Normal file → Executable file
View file

0
Assets/Scripts/UI/Websites/News/NewsWebSite.cs Normal file → Executable file
View file

0
Assets/Scripts/UI/Websites/News/NewsWebSite.cs.meta Normal file → Executable file
View file

0
Assets/Scripts/UI/Widgets/Prefabs/SocialPost.prefab Normal file → Executable file
View file

View file

View file

View file

0
Assets/Scripts/UI/Widgets/Settings/SocialPostWidget.cs Normal file → Executable file
View file

View file

View file

View file

View file

View file

0
CONTRIBUTING.md Normal file → Executable file
View file

0
LICENSE Normal file → Executable file
View file

0
README.md Normal file → Executable file
View file

0
mgfxc-wine-setup.sh Normal file → Executable file
View file

View file

@ -1,13 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/.idea.sociallydistant.iml
/projectSettingsUpdater.xml
/modules.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -1 +0,0 @@
sociallydistant

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders>
<Path>../docs</Path>
</attachedFolders>
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

0
src/AcidicGUI/AcidicGUI.csproj Normal file → Executable file
View file

0
src/AcidicGUI/ColorHelpers.cs Normal file → Executable file
View file

0
src/AcidicGUI/CustomProperties/CanvasAnchors.cs Normal file → Executable file
View file

0
src/AcidicGUI/CustomProperties/CustomPropertyObject.cs Normal file → Executable file
View file

2
src/AcidicGUI/CustomProperties/FlexPanelProperties.cs Normal file → Executable file
View file

@ -14,7 +14,7 @@ public sealed class FlexPanelProperties : CustomPropertyObject
get => proportionalValue;
set
{
proportionalValue = MathHelper.Clamp(value, 0f, 1f);
proportionalValue = Math.Max(0, value);
Widget.InvalidateLayout();
}
}

0
src/AcidicGUI/CustomProperties/StructProperty.cs Normal file → Executable file
View file

18
src/AcidicGUI/Effects/IEffect.cs Normal file → Executable file
View file

@ -1,11 +1,9 @@
using Microsoft.Xna.Framework;
namespace AcidicGUI.Effects;
public interface IEffect
{
int PassesCount { get; }
void Use(int pass);
void UpdateWidgetParameters(float opacity, Matrix widgetTransform);
namespace AcidicGUI.Effects;
public interface IEffect
{
int PassesCount { get; }
void Use(int pass);
void UpdateOpacity(float opacity);
}

0
src/AcidicGUI/Effects/IWidgetEffect.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/FocusEvent.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/GuiEvent.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IDragEndHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IDragHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IDragStartHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IGainFocusHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IKeyCharHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IKeyDownHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IKeyUpHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/ILoseFocusHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IMouseClickHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IMouseDownHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IMouseEnterHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IMouseHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IMouseLeaveHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IMouseMoveHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IMouseScrollHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IMouseUpHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IPreviewKeyCharHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IPreviewKeyDownHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IPreviewKeyUpHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/IUpdateHandler.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/KeyCharEvent.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/KeyEvent.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/ModifierKeys.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/MouseButtonEvent.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/MouseEvent.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/MouseMoveEvent.cs Normal file → Executable file
View file

0
src/AcidicGUI/Events/MouseScrollEvent.cs Normal file → Executable file
View file

901
src/AcidicGUI/GuiManager.cs Normal file → Executable file
View file

@ -1,446 +1,457 @@
using AcidicGUI.Animation;
using AcidicGUI.Events;
using AcidicGUI.Layout;
using AcidicGUI.Rendering;
using AcidicGUI.TextRendering;
using AcidicGUI.VisualStyles;
using AcidicGUI.Widgets;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace AcidicGUI;
public sealed class GuiManager : IFontFamilyProvider
{
private readonly IGuiContext context;
private readonly Widget.TopLevelCollection topLevels;
private readonly GuiRenderer renderer;
private readonly FallbackVisualStyle fallbackVisualStyle = new FallbackVisualStyle();
private MouseButton previousMouseButtonFlags;
private ButtonState leftControl;
private ButtonState rightControl;
private ButtonState leftShift;
private ButtonState rightShift;
private ButtonState leftAlt;
private ButtonState rightAlt;
private IVisualStyle? visualStyleOverride;
private int screenWidth;
private int screenHeight;
private bool isRendering;
private Widget? hoveredWidget;
private Widget? widgetBeingDragged;
private Widget? keyboardFocus;
private MouseState? previousMouseState;
private bool reachedFirstUpdate;
public bool IsRendering => isRendering;
public IOrderedCollection<Widget> TopLevels => topLevels;
public GuiManager(IGuiContext context, IVisualStyle? globalStyle = null)
{
this.context = context;
topLevels = new Widget.TopLevelCollection(this);
renderer = new GuiRenderer(context);
visualStyleOverride = globalStyle;
}
public bool IsFocused(Widget widget)
{
return widget == keyboardFocus;
}
public void SetFocusedWidget(Widget? widgetToFocus)
{
if (widgetToFocus == keyboardFocus)
return;
var e = new FocusEvent(widgetToFocus);
if (keyboardFocus != null)
{
Bubble<ILoseFocusHandler, FocusEvent>(keyboardFocus, e, x => x.OnFocusLost);
}
keyboardFocus = widgetToFocus;
if (keyboardFocus != null)
{
Bubble<IGainFocusHandler, FocusEvent>(keyboardFocus, e, x => x.OnFocusGained);
}
}
public void SendMouseButton(MouseButtonEventArgs e)
{
var previousState = (previousMouseButtonFlags & e.Button) != 0
? ButtonState.Pressed
: ButtonState.Released;
HandleMouseButton(e.Button, previousState, e.ButtonState, e.Position, Point.Zero);
if (e.ButtonState == ButtonState.Pressed)
{
previousMouseButtonFlags = previousMouseButtonFlags | e.Button;
}
else
{
previousMouseButtonFlags = previousMouseButtonFlags & ~e.Button;
}
}
public void SendCharacter(Keys key, char character)
{
var e = new KeyCharEvent(key, GetKeyModifiers(), character);
if (hoveredWidget != null)
{
Bubble<IPreviewKeyCharHandler, KeyCharEvent>(hoveredWidget, e, x => x.OnPreviewKeyChar);
}
if (keyboardFocus != null)
{
Bubble<IKeyCharHandler, KeyCharEvent>(keyboardFocus, e, x => x.OnKeyChar);
}
}
private ModifierKeys GetKeyModifiers()
{
var result = ModifierKeys.None;
if (leftControl == ButtonState.Pressed || rightControl == ButtonState.Pressed)
result |= ModifierKeys.Control;
if (leftShift == ButtonState.Pressed || rightShift == ButtonState.Pressed)
result |= ModifierKeys.Shift;
if (leftAlt == ButtonState.Pressed || rightAlt == ButtonState.Pressed)
result |= ModifierKeys.Alt;
return result;
}
public void SendKey(Keys key, ButtonState state)
{
switch (key)
{
case Keys.LeftControl:
leftControl = state;
break;
case Keys.RightControl:
rightControl = state;
break;
case Keys.LeftShift:
leftShift = state;
break;
case Keys.RightShift:
rightShift = state;
break;
case Keys.LeftAlt:
leftAlt = state;
break;
case Keys.RightAlt:
rightAlt = state;
break;
}
var e = new KeyEvent(key, GetKeyModifiers());
if (hoveredWidget != null)
{
if (state == ButtonState.Pressed)
{
Bubble<IPreviewKeyDownHandler, KeyEvent>(hoveredWidget, e, x => x.OnPreviewKeyDown);
}
else
{
Bubble<IPreviewKeyUpHandler, KeyEvent>(hoveredWidget, e, x => x.OnPreviewKeyUp);
}
}
if (keyboardFocus != null)
{
if (state == ButtonState.Pressed)
{
Bubble<IKeyDownHandler, KeyEvent>(keyboardFocus, e, x => x.OnKeyDown);
}
else
{
Bubble<IKeyUpHandler, KeyEvent>(keyboardFocus, e, x => x.OnKeyUp);
}
}
}
public void SetMouseState(MouseState state)
{
if (previousMouseState == null)
previousMouseState = state;
else if (previousMouseState.Value == state)
return;
HandleMouseEvents(previousMouseState.Value, state);
previousMouseState = state;
}
public void UpdateLayout()
{
reachedFirstUpdate = true;
var mustRebuildLayout = false;
var tolerance = 0.001f;
if (MathF.Abs(screenWidth - context.PhysicalScreenWidth) >= tolerance
|| MathF.Abs(screenHeight - context.PhysicalScreenHeight) >= tolerance)
{
screenWidth = context.PhysicalScreenWidth;
screenHeight = context.PhysicalScreenHeight;
mustRebuildLayout = true;
}
if (mustRebuildLayout)
{
foreach (Widget topLevel in topLevels)
{
topLevel.InvalidateLayout();
}
}
}
public void Update(float deltaTime)
{
Animator.Update(deltaTime);
foreach (Widget widget in this.CollapseChildren())
{
if (widget is IUpdateHandler handler)
handler.Update(deltaTime);
}
}
public IVisualStyle GetVisualStyle()
{
if (fallbackVisualStyle.FallbackFont == null)
fallbackVisualStyle.FallbackFont = context.GetFallbackFont();
return visualStyleOverride ?? fallbackVisualStyle;
}
public void Render()
{
isRendering = true;
//renderer.SetLayer(-32768);
foreach (Widget widget in topLevels)
widget.RenderInternal(renderer, Matrix.Identity);
isRendering = false;
}
internal void SubmitForLayoutUpdateInternal(Widget widget)
{
if (!reachedFirstUpdate)
return;
if (widget.Parent != null)
{
widget.UpdateLayout(context, widget.ContentArea);
}
else
{
widget.UpdateLayout(context, new LayoutRect(0, 0, screenWidth, screenHeight));
}
}
public IFontFamily GetFont(PresetFontFamily family)
{
return GetVisualStyle().GetFont(family);
}
internal GraphicsDevice GetGraphicsDeviceInternal()
{
return context.GraphicsDevice;
}
private IEnumerable<Widget> CollapseChildren()
{
for (var i = topLevels.Count - 1; i >= 0; i--)
{
Widget toplevel = topLevels[i];
if (!toplevel.Enabled)
continue;
foreach (Widget child in toplevel.CollapseChildren(true, false))
{
yield return child;
}
yield return toplevel;
}
}
private Point TranslateMousePosition(Point mousePosition)
{
return new Vector2(
((float)mousePosition.X / context.GraphicsDevice.PresentationParameters.BackBufferWidth) *
context.PhysicalScreenWidth,
((float)mousePosition.Y / context.GraphicsDevice.PresentationParameters.BackBufferHeight) *
context.PhysicalScreenHeight
).ToPoint();
}
private void HandleMouseEvents(MouseState previous, MouseState current)
{
var prevPosition = TranslateMousePosition(previous.Position);
var currPosition = TranslateMousePosition(current.Position);
var prevWheel = previous.ScrollWheelValue;
var currWheel = current.ScrollWheelValue;
var positionDelta = currPosition - prevPosition;
var wheelDelta = -(currWheel - prevWheel);
HandleMouseMovement(new MouseMoveEvent(currPosition, positionDelta));
HandleMouseScroll(new MouseScrollEvent(currPosition, wheelDelta));
HandleDragEvents(MouseButton.Left, previous.LeftButton, current.LeftButton, currPosition, positionDelta);
HandleDragEvents(MouseButton.Middle, previous.MiddleButton, current.MiddleButton, currPosition, positionDelta);
HandleDragEvents(MouseButton.Right, previous.RightButton, current.RightButton, currPosition, positionDelta);
HandleDragEvents(MouseButton.X1, previous.XButton1, current.XButton1, currPosition, positionDelta);
HandleDragEvents(MouseButton.X2, previous.XButton2, current.XButton2, currPosition, positionDelta);
}
private void HandleDragEvents(
MouseButton button,
ButtonState previousState,
ButtonState currentState,
Point position,
Point movement
)
{
if (previousState != currentState)
return;
if (currentState == ButtonState.Released)
return;
if (widgetBeingDragged == null)
return;
var e = new MouseButtonEvent(position, movement, button, currentState);
Bubble<IDragHandler, MouseButtonEvent>(widgetBeingDragged, e, x => x.OnDrag);
}
private void HandleMouseButton(MouseButton button, ButtonState previous, ButtonState current, Point position, Point delta)
{
var e = new MouseButtonEvent(position, delta, button, current);
if (previous == ButtonState.Released)
{
if (hoveredWidget == null)
return;
Bubble<IMouseDownHandler, MouseButtonEvent>(hoveredWidget, e, x => x.OnMouseDown);
if (e.Handled)
e.Unhandle();
widgetBeingDragged = hoveredWidget;
Bubble<IDragStartHandler, MouseButtonEvent>(hoveredWidget, e, x => x.OnDragStart);
}
else if (previous == ButtonState.Pressed)
{
if (widgetBeingDragged != null)
{
Bubble<IDragEndHandler, MouseButtonEvent>(widgetBeingDragged, e, x => x.OnDragEnd);
widgetBeingDragged = null;
}
if (hoveredWidget != null)
{
Bubble<IMouseClickHandler, MouseButtonEvent>(hoveredWidget, e, x => x.OnMouseClick);
Bubble<IMouseUpHandler, MouseButtonEvent>(hoveredWidget, e, x => x.OnMouseUp);
}
if (!e.FocusWanted && hoveredWidget?.IsFocused == false)
SetFocusedWidget(null);
}
}
private void Bubble<THandler, TEvent>(Widget widget, TEvent e, Func<THandler, Action<TEvent>> getHandler)
where TEvent : GuiEvent
{
Widget? next = widget;
while (next != null && !e.Handled)
{
if (next is THandler handler)
{
getHandler(handler)(e);
}
if (e.FocusWanted)
{
SetFocusedWidget(next);
break;
}
next = next.Parent;
}
}
private void HandleMouseMovement(MouseMoveEvent e)
{
Widget? newHoveredWidget = null;
foreach (Widget widget in CollapseChildren())
{
if (!widget.IsVisible)
continue;
if (widget is not IMouseHandler)
continue;
if (widget.ClippedContentArea.Contains(e.Position))
{
newHoveredWidget = widget;
break;
}
}
if (newHoveredWidget != hoveredWidget)
{
if (hoveredWidget != null && !hoveredWidget.ContainsChild(newHoveredWidget))
{
Bubble<IMouseLeaveHandler, MouseMoveEvent>(hoveredWidget, e, x => x.OnMouseLeave);
}
hoveredWidget = newHoveredWidget;
if (hoveredWidget != null)
{
Bubble<IMouseEnterHandler, MouseMoveEvent>(hoveredWidget, e, x => x.OnMouseEnter);
}
}
if (hoveredWidget != null)
{
Bubble<IMouseMoveHandler, MouseMoveEvent>(hoveredWidget, e, x => x.OnMouseMove);
}
}
private void HandleMouseScroll(MouseScrollEvent e)
{
if (e.ScrollDelta == 0)
return;
if (hoveredWidget == null)
return;
Bubble<IMouseScrollHandler, MouseScrollEvent>(hoveredWidget, e, x => x.OnMouseScroll);
}
using AcidicGUI.Events;
using AcidicGUI.Layout;
using AcidicGUI.Rendering;
using AcidicGUI.TextRendering;
using AcidicGUI.VisualStyles;
using AcidicGUI.Widgets;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace AcidicGUI;
public sealed class GuiManager : IFontFamilyProvider
{
private readonly IGuiContext context;
private readonly Widget.TopLevelCollection topLevels;
private readonly GuiRenderer renderer;
private readonly FallbackVisualStyle fallbackVisualStyle = new FallbackVisualStyle();
private MouseButton previousMouseButtonFlags;
private ButtonState leftControl;
private ButtonState rightControl;
private ButtonState leftShift;
private ButtonState rightShift;
private ButtonState leftAlt;
private ButtonState rightAlt;
private IVisualStyle? visualStyleOverride;
private int screenWidth;
private int screenHeight;
private bool isRendering;
private Widget? hoveredWidget;
private Widget? widgetBeingDragged;
private Widget? keyboardFocus;
private MouseState? previousMouseState;
private bool reachedFirstUpdate;
public bool IsRendering => isRendering;
public IOrderedCollection<Widget> TopLevels => topLevels;
public GuiManager(IGuiContext context, IVisualStyle? globalStyle = null)
{
this.context = context;
topLevels = new Widget.TopLevelCollection(this);
renderer = new GuiRenderer(context);
visualStyleOverride = globalStyle;
}
/// <summary>
/// Invalidates the geometry of all interface elements recursively. Use this when changing a global UI setting that affects how all widgets render.
/// Note: This is expensive. That should be obvious. So do not call it every frame.
/// </summary>
public void InvalidateAllGeometry()
{
foreach (var toplevel in TopLevels)
{
toplevel.InvalidateGeometry(true);
}
}
public bool IsFocused(Widget widget)
{
return widget == keyboardFocus;
}
public void SetFocusedWidget(Widget? widgetToFocus)
{
if (widgetToFocus == keyboardFocus)
return;
var e = new FocusEvent(widgetToFocus);
if (keyboardFocus != null)
{
Bubble<ILoseFocusHandler, FocusEvent>(keyboardFocus, e, x => x.OnFocusLost);
}
keyboardFocus = widgetToFocus;
if (keyboardFocus != null)
{
Bubble<IGainFocusHandler, FocusEvent>(keyboardFocus, e, x => x.OnFocusGained);
}
}
public void SendMouseButton(MouseButtonEventArgs e)
{
var previousState = (previousMouseButtonFlags & e.Button) != 0
? ButtonState.Pressed
: ButtonState.Released;
HandleMouseButton(e.Button, previousState, e.ButtonState, TranslateMousePosition(e.Position), Point.Zero);
if (e.ButtonState == ButtonState.Pressed)
{
previousMouseButtonFlags = previousMouseButtonFlags | e.Button;
}
else
{
previousMouseButtonFlags = previousMouseButtonFlags & ~e.Button;
}
}
public void SendCharacter(Keys key, char character)
{
var e = new KeyCharEvent(key, GetKeyModifiers(), character);
if (hoveredWidget != null)
{
Bubble<IPreviewKeyCharHandler, KeyCharEvent>(hoveredWidget, e, x => x.OnPreviewKeyChar);
}
if (keyboardFocus != null)
{
Bubble<IKeyCharHandler, KeyCharEvent>(keyboardFocus, e, x => x.OnKeyChar);
}
}
private ModifierKeys GetKeyModifiers()
{
var result = ModifierKeys.None;
if (leftControl == ButtonState.Pressed || rightControl == ButtonState.Pressed)
result |= ModifierKeys.Control;
if (leftShift == ButtonState.Pressed || rightShift == ButtonState.Pressed)
result |= ModifierKeys.Shift;
if (leftAlt == ButtonState.Pressed || rightAlt == ButtonState.Pressed)
result |= ModifierKeys.Alt;
return result;
}
public void SendKey(Keys key, ButtonState state)
{
switch (key)
{
case Keys.LeftControl:
leftControl = state;
break;
case Keys.RightControl:
rightControl = state;
break;
case Keys.LeftShift:
leftShift = state;
break;
case Keys.RightShift:
rightShift = state;
break;
case Keys.LeftAlt:
leftAlt = state;
break;
case Keys.RightAlt:
rightAlt = state;
break;
}
var e = new KeyEvent(key, GetKeyModifiers());
if (hoveredWidget != null)
{
if (state == ButtonState.Pressed)
{
Bubble<IPreviewKeyDownHandler, KeyEvent>(hoveredWidget, e, x => x.OnPreviewKeyDown);
}
else
{
Bubble<IPreviewKeyUpHandler, KeyEvent>(hoveredWidget, e, x => x.OnPreviewKeyUp);
}
}
if (keyboardFocus != null)
{
if (state == ButtonState.Pressed)
{
Bubble<IKeyDownHandler, KeyEvent>(keyboardFocus, e, x => x.OnKeyDown);
}
else
{
Bubble<IKeyUpHandler, KeyEvent>(keyboardFocus, e, x => x.OnKeyUp);
}
}
}
public void SetMouseState(MouseState state)
{
if (previousMouseState == null)
previousMouseState = state;
else if (previousMouseState.Value == state)
return;
HandleMouseEvents(previousMouseState.Value, state);
previousMouseState = state;
}
public void UpdateLayout()
{
reachedFirstUpdate = true;
var mustRebuildLayout = false;
var tolerance = 0.001f;
if (MathF.Abs(screenWidth - context.CanvasWidth) >= tolerance
|| MathF.Abs(screenHeight - context.CanvasHeight) >= tolerance)
{
screenWidth = context.CanvasWidth;
screenHeight = context.CanvasHeight;
mustRebuildLayout = true;
}
if (mustRebuildLayout)
{
foreach (Widget topLevel in topLevels)
{
topLevel.InvalidateLayout();
}
}
}
public void Update(float deltaTime)
{
foreach (Widget widget in CollapseChildren())
{
if (widget is IUpdateHandler handler)
handler.Update(deltaTime);
}
}
public IVisualStyle GetVisualStyle()
{
if (fallbackVisualStyle.FallbackFont == null)
fallbackVisualStyle.FallbackFont = context.GetFallbackFont();
return visualStyleOverride ?? fallbackVisualStyle;
}
public void Render()
{
isRendering = true;
renderer.SetLayer(-32768);
foreach (Widget widget in topLevels)
widget.RenderInternal(renderer);
renderer.RenderBatches();
isRendering = false;
}
internal void SubmitForLayoutUpdateInternal(Widget widget)
{
if (!reachedFirstUpdate)
return;
if (widget.Parent != null)
{
widget.UpdateLayout(context, widget.ContentArea);
}
else
{
widget.UpdateLayout(context, new LayoutRect(0, 0, screenWidth, screenHeight));
}
}
public IFontFamily GetFont(PresetFontFamily family)
{
return GetVisualStyle().GetFont(family);
}
internal GraphicsDevice GetGraphicsDeviceInternal()
{
return context.GraphicsDevice;
}
private IEnumerable<Widget> CollapseChildren()
{
for (var i = topLevels.Count - 1; i >= 0; i--)
{
Widget toplevel = topLevels[i];
if (!toplevel.Enabled)
continue;
foreach (Widget child in toplevel.CollapseChildren(true, false))
{
yield return child;
}
yield return toplevel;
}
}
private Point TranslateMousePosition(Point mousePosition)
{
return new Vector2(
((float)mousePosition.X / context.GraphicsDevice.PresentationParameters.BackBufferWidth) *
context.CanvasWidth,
((float)mousePosition.Y / context.GraphicsDevice.PresentationParameters.BackBufferHeight) *
context.CanvasHeight
).ToPoint();
}
private void HandleMouseEvents(MouseState previous, MouseState current)
{
var prevPosition = TranslateMousePosition(previous.Position);
var currPosition = TranslateMousePosition(current.Position);
var prevWheel = previous.ScrollWheelValue;
var currWheel = current.ScrollWheelValue;
var positionDelta = currPosition - prevPosition;
var wheelDelta = -(currWheel - prevWheel);
HandleMouseMovement(new MouseMoveEvent(currPosition, positionDelta));
HandleMouseScroll(new MouseScrollEvent(currPosition, wheelDelta));
HandleDragEvents(MouseButton.Left, previous.LeftButton, current.LeftButton, currPosition, positionDelta);
HandleDragEvents(MouseButton.Middle, previous.MiddleButton, current.MiddleButton, currPosition, positionDelta);
HandleDragEvents(MouseButton.Right, previous.RightButton, current.RightButton, currPosition, positionDelta);
HandleDragEvents(MouseButton.X1, previous.XButton1, current.XButton1, currPosition, positionDelta);
HandleDragEvents(MouseButton.X2, previous.XButton2, current.XButton2, currPosition, positionDelta);
}
private void HandleDragEvents(
MouseButton button,
ButtonState previousState,
ButtonState currentState,
Point position,
Point movement
)
{
if (previousState != currentState)
return;
if (currentState == ButtonState.Released)
return;
if (widgetBeingDragged == null)
return;
var e = new MouseButtonEvent(position, movement, button, currentState);
Bubble<IDragHandler, MouseButtonEvent>(widgetBeingDragged, e, x => x.OnDrag);
}
private void HandleMouseButton(MouseButton button, ButtonState previous, ButtonState current, Point position, Point delta)
{
var e = new MouseButtonEvent(position, delta, button, current);
if (previous == ButtonState.Released)
{
if (hoveredWidget == null)
return;
Bubble<IMouseDownHandler, MouseButtonEvent>(hoveredWidget, e, x => x.OnMouseDown);
if (e.Handled)
e.Unhandle();
widgetBeingDragged = hoveredWidget;
Bubble<IDragStartHandler, MouseButtonEvent>(hoveredWidget, e, x => x.OnDragStart);
}
else if (previous == ButtonState.Pressed)
{
if (widgetBeingDragged != null)
{
Bubble<IDragEndHandler, MouseButtonEvent>(widgetBeingDragged, e, x => x.OnDragEnd);
widgetBeingDragged = null;
}
if (hoveredWidget != null)
{
Bubble<IMouseClickHandler, MouseButtonEvent>(hoveredWidget, e, x => x.OnMouseClick);
Bubble<IMouseUpHandler, MouseButtonEvent>(hoveredWidget, e, x => x.OnMouseUp);
}
if (!e.FocusWanted && hoveredWidget?.IsFocused == false)
SetFocusedWidget(null);
}
}
private void Bubble<THandler, TEvent>(Widget widget, TEvent e, Func<THandler, Action<TEvent>> getHandler)
where TEvent : GuiEvent
{
Widget? next = widget;
while (next != null && !e.Handled)
{
if (next is THandler handler)
{
getHandler(handler)(e);
}
if (e.FocusWanted)
{
SetFocusedWidget(next);
break;
}
next = next.Parent;
}
}
private void HandleMouseMovement(MouseMoveEvent e)
{
Widget? newHoveredWidget = null;
foreach (Widget widget in CollapseChildren())
{
if (!widget.IsVisible)
continue;
if (widget is not IMouseHandler)
continue;
if (widget.ClippedContentArea.Contains(e.Position))
{
newHoveredWidget = widget;
break;
}
}
if (newHoveredWidget != hoveredWidget)
{
if (hoveredWidget != null && !hoveredWidget.ContainsChild(newHoveredWidget))
{
Bubble<IMouseLeaveHandler, MouseMoveEvent>(hoveredWidget, e, x => x.OnMouseLeave);
}
hoveredWidget = newHoveredWidget;
if (hoveredWidget != null)
{
Bubble<IMouseEnterHandler, MouseMoveEvent>(hoveredWidget, e, x => x.OnMouseEnter);
}
}
if (hoveredWidget != null)
{
Bubble<IMouseMoveHandler, MouseMoveEvent>(hoveredWidget, e, x => x.OnMouseMove);
}
}
private void HandleMouseScroll(MouseScrollEvent e)
{
if (e.ScrollDelta == 0)
return;
if (hoveredWidget == null)
return;
Bubble<IMouseScrollHandler, MouseScrollEvent>(hoveredWidget, e, x => x.OnMouseScroll);
}
}

0
src/AcidicGUI/GuiSynchronizationContext.cs Normal file → Executable file
View file

62
src/AcidicGUI/IGuiContext.cs Normal file → Executable file
View file

@ -1,36 +1,28 @@
using AcidicGUI.Effects;
using AcidicGUI.Layout;
using AcidicGUI.TextRendering;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace AcidicGUI;
public interface IGuiContext
{
int PhysicalScreenWidth { get; }
int PhysicalScreenHeight { get; }
GraphicsDevice GraphicsDevice { get; }
void Render(
VertexBuffer vertices,
IndexBuffer indices,
int offset,
int primitiveCount,
Texture2D? texture,
LayoutRect? clipRect = null,
IEffect? effectOverride = null,
float opacity = 1,
Matrix? widgetTransform = null
);
/// <summary>
/// Renders the user interface to the given RenderTarget2D. Useful for certain widget rendering techniques such as background blurs.
/// </summary>
/// <param name="destination"></param>
void Grab(RenderTarget2D destination);
void RestoreRenderState();
IFontFamily GetFallbackFont();
using AcidicGUI.Effects;
using AcidicGUI.Layout;
using AcidicGUI.TextRendering;
using Microsoft.Xna.Framework.Graphics;
namespace AcidicGUI;
public interface IGuiContext
{
int PhysicalWidth { get; }
int PhysicalHeight { get; }
int CanvasWidth { get; }
int CanvasHeight { get; }
GraphicsDevice GraphicsDevice { get; }
void Render(VertexPositionColorTexture[] vertices, int[] indices, Texture2D? texture, LayoutRect? clipRect = null);
void Render(VertexBuffer vertices, IndexBuffer indices, int offset, int primitiveCount, Texture2D? texture, LayoutRect? clipRect = null, IEffect? effectOverride = null, float opacity = 1);
/// <summary>
/// Renders the user interface to the given RenderTarget2D. Useful for certain widget rendering techniques such as background blurs.
/// </summary>
/// <param name="destination"></param>
void Grab(RenderTarget2D destination);
void RestoreRenderState();
IFontFamily GetFallbackFont();
}

0
src/AcidicGUI/IOrderedCollection.cs Normal file → Executable file
View file

0
src/AcidicGUI/Layout/Direction.cs Normal file → Executable file
View file

0
src/AcidicGUI/Layout/FlexMode.cs Normal file → Executable file
View file

0
src/AcidicGUI/Layout/HorizontalAlignment.cs Normal file → Executable file
View file

0
src/AcidicGUI/Layout/LayoutRect.cs Normal file → Executable file
View file

0
src/AcidicGUI/Layout/Padding.cs Normal file → Executable file
View file

0
src/AcidicGUI/Layout/TextAlignment.cs Normal file → Executable file
View file

0
src/AcidicGUI/Layout/VerticalAlignment.cs Normal file → Executable file
View file

0
src/AcidicGUI/ListAdapters/DataHelper.cs Normal file → Executable file
View file

0
src/AcidicGUI/ListAdapters/INotifyDataChanged.cs Normal file → Executable file
View file

0
src/AcidicGUI/ListAdapters/ListAdapter.cs Normal file → Executable file
View file

View file

0
src/AcidicGUI/ListAdapters/RecycleBin.cs Normal file → Executable file
View file

0
src/AcidicGUI/ListAdapters/ViewHolder.cs Normal file → Executable file
View file

729
src/AcidicGUI/Rendering/GeometryHelper.cs Normal file → Executable file
View file

@ -1,363 +1,368 @@
using AcidicGUI.Layout;
using FontStashSharp.Interfaces;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace AcidicGUI.Rendering;
public class GeometryHelper : IFontStashRenderer2
{
private readonly GuiMeshBuilder whiteMesh;
private readonly Dictionary<Texture2D, GuiMeshBuilder> meshes = new();
private readonly GuiRenderer guiRenderer;
private readonly bool desaturate;
private readonly LayoutRect? clipRect;
private readonly List<GuiSubMesh> flushedMeshes = new();
public float Layer => guiRenderer.Layer;
public GeometryHelper(GuiRenderer guiRenderer, bool desaturate, LayoutRect? clipRect)
{
this.desaturate = desaturate;
this.guiRenderer = guiRenderer;
this.clipRect = clipRect;
whiteMesh = new GuiMeshBuilder(this, null, desaturate);
}
private void Flush()
{
flushedMeshes.Add(whiteMesh.ExportSubMesh());
whiteMesh.Clear();
foreach (GuiMeshBuilder mesh in meshes.Values)
{
flushedMeshes.Add(mesh.ExportSubMesh());
mesh.Clear();
}
}
public GuiMesh ExportMesh()
{
Flush();
var meshList = flushedMeshes.ToArray();
return new GuiMesh(meshList, clipRect);
}
public GuiMeshBuilder GetMeshBuilder(Texture2D? texture)
{
if (texture == null)
return whiteMesh;
if (!meshes.TryGetValue(texture, out GuiMeshBuilder? builder))
{
builder = new GuiMeshBuilder(this, texture, desaturate);
meshes.Add(texture, builder);
}
return meshes[texture];
}
public void AddRoundedRectangle(LayoutRect rectangle, float uniformRadius, Color color, Texture2D? texture = null)
{
AddRoundedRectangle(rectangle, uniformRadius, uniformRadius, uniformRadius, uniformRadius, color, texture);
}
public void AddQuadOutline(
LayoutRect rectangle,
float thickness,
Color color,
Texture2D? texture = null
)
{
float smallerHalf = Math.Min(rectangle.Width, rectangle.Height) / 2f;
if (smallerHalf <= thickness)
{
AddQuad(rectangle, color, texture);
return;
}
float texelWidth = 1f / (texture?.Width ?? 1f);
float texelHeight = 1f / (texture?.Height ?? 1f);
var mesh = GetMeshBuilder(texture);
var tl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top), color, new Vector2(0, 0));
var tr = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top), color, new Vector2(1, 0));
var bl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom), color, new Vector2(0, 1));
var br = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom), color, new Vector2(1, 1));
var tlInner = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Top + thickness), color, new Vector2(texelWidth * thickness, texelHeight * thickness));
var trInner = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Top + thickness), color, new Vector2(1 - texelWidth * thickness, texelHeight * thickness));
var blInner = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Bottom - thickness), color, new Vector2(texelWidth * thickness, 1 - texelHeight * thickness));
var brInner = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Bottom - thickness), color, new Vector2(1 - texelWidth * thickness, 1 - texelHeight * thickness));
mesh.AddQuad(tl, tr, tlInner, trInner);
mesh.AddQuad(tl, tlInner, bl, blInner);
mesh.AddQuad(trInner, tr, brInner, br);
mesh.AddQuad(blInner, brInner, bl, br);
}
public void AddQuad(LayoutRect rectangle, Color color, Texture2D? texture = null)
{
if (color.A == 0)
return;
var mesh = GetMeshBuilder(texture);
int tl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top), color, new Vector2(0, 0));
int tr = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top), color, new Vector2(1, 0));
int bl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom), color, new Vector2(0, 1));
int br = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom), color, new Vector2(1, 1));
mesh.AddQuad(tl, tr, bl, br);
}
public void AddRoundedRectangle(
LayoutRect rectangle,
float radiusTopLeft,
float radiusTopRight,
float radiusBottomLeft,
float radiusBottomRight,
Color color,
Texture2D? texture = null
)
{
var mesh = GetMeshBuilder(texture);
float halfWidth = rectangle.Width / 2f;
float halfHeight = rectangle.Height / 2f;
float smallerHalf = MathF.Min(halfWidth, halfHeight);
radiusTopLeft = MathHelper.Clamp(radiusTopLeft, 0, smallerHalf);
radiusBottomLeft = MathHelper.Clamp(radiusBottomLeft, 0, smallerHalf);
radiusTopRight = MathHelper.Clamp(radiusTopRight, 0, smallerHalf);
radiusBottomRight = MathHelper.Clamp(radiusBottomRight, 0, smallerHalf);
if (radiusTopLeft <= 0 && radiusTopRight <= 0 && radiusBottomLeft <= 0 && radiusBottomRight <= 0)
{
AddQuad(rectangle, color);
return;
}
float radiusTopLeftUv = radiusTopLeft / rectangle.Width;
float radiusTopRightUv = radiusTopRight / rectangle.Width;
float radiusBottomLeftUv = radiusBottomLeft / rectangle.Width;
float radiusBottomRightUv = radiusBottomRight / rectangle.Width;
int innerTl = mesh.AddVertex(new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top + radiusTopLeft), color, new Vector2(radiusTopLeftUv, radiusTopLeftUv));
int innerTr = mesh.AddVertex(new Vector2(rectangle.Right - radiusTopRight, rectangle.Top + radiusTopRight), color, new Vector2(1 - radiusTopRightUv, radiusTopRightUv));
int innerBl = mesh.AddVertex(new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom - radiusBottomLeft), color, new Vector2(radiusBottomLeftUv, 1 - radiusBottomLeftUv));
int innerBr = mesh.AddVertex(new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom - radiusBottomRight), color, new Vector2(1 - radiusBottomRightUv, 1 - radiusBottomRightUv));
int outerTl1 = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top + radiusTopLeft), color, new Vector2(0, radiusTopLeftUv));
int outerTl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top), color, new Vector2(radiusTopLeftUv, 0));
int outerTr1 = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top + radiusTopRight), color, new Vector2(1, radiusTopRightUv));
int outerTr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusTopRight, rectangle.Top), color, new Vector2(1 - radiusTopRightUv, 0));
int outerBl1 = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom - radiusBottomLeft), color, new Vector2(0, 1 - radiusBottomLeftUv));
int outerBl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom), color, new Vector2(radiusBottomLeftUv, 1));
int outerBr1 = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom - radiusBottomRight), color, new Vector2(1, 1 - radiusBottomRightUv));
int outerBr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom), color, new Vector2(1 - radiusBottomRightUv, 1));
mesh.AddQuad(outerTl1, innerTl, outerBl1, innerBl);
mesh.AddQuad(outerTl2, outerTr2, innerTl, innerTr);
mesh.AddQuad(innerTr, outerTr1, innerBr, outerBr1);
mesh.AddQuad(innerBl, innerBr, outerBl2, outerBr2);
mesh.AddQuad(innerTl, innerTr, innerBl, innerBr);
AddQuarterCircleAroundPivotVertices(mesh, innerTl, outerTl2, radiusTopLeft, -1, -1, true, color, radiusTopLeftUv, radiusTopLeftUv, radiusTopLeftUv);
AddQuarterCircleAroundPivotVertices(mesh, innerTr, outerTr2, radiusTopRight, 1, -1, false, color, 1 - radiusTopRightUv, radiusTopRightUv, radiusTopRightUv);
AddQuarterCircleAroundPivotVertices(mesh, innerBl, outerBl2, radiusBottomLeft, -1, 1, false, color, radiusBottomLeftUv, 1 - radiusBottomLeftUv, radiusBottomLeftUv);
AddQuarterCircleAroundPivotVertices(mesh, innerBr, outerBr2, radiusBottomRight, 1, 1, true, color, 1 - radiusBottomRightUv, 1 - radiusBottomRightUv, radiusBottomRightUv);
}
private void AddQuarterCircleAroundPivotVertices(GuiMeshBuilder mesh, int pivotVertex, int extent, float radius, float directionH, float directionV, bool reverseWinding, Color color, float uvX, float uvY, float texelRadius)
{
Vector3 center = mesh[pivotVertex].Position;
int last = extent;
const int segments = 16;
for (var i = 0; i < segments; i++)
{
var t = (i / (segments - 1f)) * MathF.PI * 0.5f;
var x = MathF.Sin(t);
var y = MathF.Cos(t);
float x1 = x * (radius * directionH) + center.X;
float y1 = y * (radius * directionV) + center.Y;
float u = (x * (texelRadius * directionH)) + uvX;
float v = (y * (texelRadius * directionV)) + uvY;
int next = mesh.AddVertex(new Vector2(x1, y1), color, new Vector2(u, v));
if (reverseWinding)
mesh.AddTriangle(pivotVertex, next, last);
else
mesh.AddTriangle(last, next, pivotVertex);
last = next;
}
}
public void AddRoundedRectangleOutline(LayoutRect rectangle, float thickness, float uniformRadius, Color color, Texture2D? texture = null)
{
AddRoundedRectangleOutline(rectangle, thickness, uniformRadius, uniformRadius, uniformRadius, uniformRadius,
color, texture);
}
public void AddRoundedRectangleOutline(LayoutRect rectangle, float thickness, float radiusTopLeft,
float radiusTopRight, float radiusBottomLeft, float radiusBottomRight, Color color, Texture2D? texture = null)
{
if (thickness <= 0)
return;
float halfWidth = rectangle.Width / 2f;
float halfHeight = rectangle.Height / 2f;
float smallerHalf = MathF.Min(halfWidth, halfHeight);
if (thickness >= smallerHalf)
{
AddRoundedRectangle(rectangle, radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight, color, texture);
return;
}
radiusTopLeft = MathHelper.Clamp(radiusTopLeft, 0, smallerHalf);
radiusBottomLeft = MathHelper.Clamp(radiusBottomLeft, 0, smallerHalf);
radiusTopRight = MathHelper.Clamp(radiusTopRight, 0, smallerHalf);
radiusBottomRight = MathHelper.Clamp(radiusBottomRight, 0, smallerHalf);
if (radiusTopLeft <= 0 && radiusTopRight <= 0 && radiusBottomLeft <= 0 && radiusBottomRight <= 0)
return;
var mesh = GetMeshBuilder(texture);
int innerTl = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Top + thickness), color);
int innerTr = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Top + thickness), color);
int innerBl = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Bottom - thickness), color);
int innerBr = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Bottom - thickness), color);
int innerTl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top + thickness), color);
int innerTr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusTopRight, rectangle.Top + thickness), color);
int innerBl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom - thickness), color);
int innerBr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom - thickness), color);
int innerTl3 = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Top + radiusTopLeft), color);
int innerTr3 = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Top + radiusTopRight), color);
int innerBl3 = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Bottom - radiusBottomLeft), color);
int innerBr3 = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Bottom - radiusBottomRight), color);
int outerTl1 = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top + radiusTopLeft), color);
int outerTl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top), color);
int outerTr1 = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top + radiusTopRight), color);
int outerTr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusTopRight, rectangle.Top), color);
int outerBl1 = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom - radiusBottomLeft), color);
int outerBl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom), color);
int outerBr1 = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom - radiusBottomRight), color);
int outerBr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom), color);
Vector2 cTl = new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top + radiusTopLeft);
Vector2 cTr = new Vector2(rectangle.Right - radiusTopRight, rectangle.Top + radiusTopRight);
Vector2 cBl = new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom - radiusBottomLeft);
Vector2 cBr = new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom - radiusBottomRight);
mesh.AddQuad(outerTl1, innerTl3, outerBl1, innerBl3);
mesh.AddQuad(outerTl2, outerTr2, innerTl2, innerTr2);
mesh.AddQuad(innerTr3, outerTr1, innerBr3, outerBr1);
mesh.AddQuad(innerBl2, innerBr2, outerBl2, outerBr2);
AddRoundedRectangleOutlineCurve(mesh, cTl, innerTl, thickness, radiusTopLeft, -1, -1, true, color);
AddRoundedRectangleOutlineCurve(mesh, cTr, innerTr, thickness, radiusTopRight, 1, -1, false, color);
AddRoundedRectangleOutlineCurve(mesh, cBl, innerBl, thickness, radiusBottomLeft, -1, 1, false, color);
AddRoundedRectangleOutlineCurve(mesh, cBr, innerBr, thickness, radiusBottomRight, 1, 1, true, color);
}
private void AddRoundedRectangleOutlineCurve(GuiMeshBuilder mesh, Vector2 center, int innerCorner, float thickness, float radius, float directionH, float directionV, bool reverseWinding, Color color)
{
const int segments = 16;
var connectToInnerCorner = false;
if (radius < thickness)
{
connectToInnerCorner = true;
thickness = radius;
}
int currentInner = 0;
int currentOuter = 0;
for (var i = 0; i < segments; i++)
{
var t = (i / (segments - 1f)) * MathF.PI * 0.5f;
var x = MathF.Sin(t);
var y = MathF.Cos(t);
var xOuter = x * (radius * directionH) + center.X;
var yOuter = y * (radius * directionV) + center.Y;
var xInner = x * ((radius * directionH) + (thickness * -directionH)) + center.X;
var yInner = y * ((radius * directionV) + (thickness * -directionV)) + center.Y;
int nextInner = mesh.AddVertex(new Vector2(xInner, yInner), color);
int nextOuter = mesh.AddVertex(new Vector2(xOuter, yOuter), color);
if (reverseWinding)
{
if (i > 0)
{
mesh.AddTriangle(nextInner, currentOuter, currentInner);
mesh.AddTriangle(nextOuter, currentOuter, nextInner);
}
if (connectToInnerCorner)
{
if (i == 0 || i == segments - 1)
{
mesh.AddTriangle(innerCorner, nextInner, nextOuter);
}
}
}
else
{
if (i > 0)
{
mesh.AddTriangle(currentInner, currentOuter, nextInner);
mesh.AddTriangle(nextInner, currentOuter, nextOuter);
}
if (connectToInnerCorner)
{
if (i == 0 || i == segments - 1)
{
mesh.AddTriangle(nextOuter, nextInner, innerCorner);
}
}
}
currentInner = nextInner;
currentOuter = nextOuter;
}
}
public void DrawQuad(Texture2D texture, ref VertexPositionColorTexture topLeft, ref VertexPositionColorTexture topRight,
ref VertexPositionColorTexture bottomLeft, ref VertexPositionColorTexture bottomRight)
{
var mesh = GetMeshBuilder(texture);
int i1 = mesh.AddVertex(topLeft);
int i2 = mesh.AddVertex(topRight);
int i3 = mesh.AddVertex(bottomLeft);
int i4 = mesh.AddVertex(bottomRight);
mesh.AddTriangle(i1, i2, i3);
mesh.AddTriangle(i3, i2, i4);
}
public GraphicsDevice GraphicsDevice => guiRenderer.GraphicsDevice;
using AcidicGUI.Layout;
using FontStashSharp.Interfaces;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace AcidicGUI.Rendering;
public class GeometryHelper : IFontStashRenderer2
{
private readonly GuiMeshBuilder whiteMesh;
private readonly Dictionary<Texture2D, GuiMeshBuilder> meshes = new();
private readonly GuiRenderer guiRenderer;
private readonly bool desaturate;
private readonly LayoutRect? clipRect;
private readonly List<GuiSubMesh> flushedMeshes = new();
public float Layer => guiRenderer.Layer;
public GeometryHelper(GuiRenderer guiRenderer, bool desaturate, LayoutRect? clipRect)
{
this.desaturate = desaturate;
this.guiRenderer = guiRenderer;
this.clipRect = clipRect;
whiteMesh = new GuiMeshBuilder(this, null, desaturate);
}
public void PushLayer()
{
guiRenderer.PushLayer();
}
private void Flush()
{
flushedMeshes.Add(whiteMesh.ExportSubMesh());
whiteMesh.Clear();
foreach (GuiMeshBuilder mesh in meshes.Values)
{
flushedMeshes.Add(mesh.ExportSubMesh());
mesh.Clear();
}
}
public GuiMesh ExportMesh()
{
Flush();
var meshList = flushedMeshes.ToArray();
return new GuiMesh(meshList, clipRect);
}
public GuiMeshBuilder GetMeshBuilder(Texture2D? texture)
{
if (texture == null)
return whiteMesh;
if (!meshes.TryGetValue(texture, out GuiMeshBuilder? builder))
{
builder = new GuiMeshBuilder(this, texture, desaturate);
meshes.Add(texture, builder);
}
return meshes[texture];
}
public void AddRoundedRectangle(LayoutRect rectangle, float uniformRadius, Color color, Texture2D? texture = null)
{
AddRoundedRectangle(rectangle, uniformRadius, uniformRadius, uniformRadius, uniformRadius, color, texture);
}
public void AddQuadOutline(
LayoutRect rectangle,
float thickness,
Color color,
Texture2D? texture = null
)
{
float smallerHalf = Math.Min(rectangle.Width, rectangle.Height) / 2f;
if (smallerHalf <= thickness)
{
AddQuad(rectangle, color, texture);
return;
}
float texelWidth = 1f / (texture?.Width ?? 1f);
float texelHeight = 1f / (texture?.Height ?? 1f);
var mesh = GetMeshBuilder(texture);
var tl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top), color, new Vector2(0, 0));
var tr = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top), color, new Vector2(1, 0));
var bl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom), color, new Vector2(0, 1));
var br = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom), color, new Vector2(1, 1));
var tlInner = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Top + thickness), color, new Vector2(texelWidth * thickness, texelHeight * thickness));
var trInner = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Top + thickness), color, new Vector2(1 - texelWidth * thickness, texelHeight * thickness));
var blInner = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Bottom - thickness), color, new Vector2(texelWidth * thickness, 1 - texelHeight * thickness));
var brInner = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Bottom - thickness), color, new Vector2(1 - texelWidth * thickness, 1 - texelHeight * thickness));
mesh.AddQuad(tl, tr, tlInner, trInner);
mesh.AddQuad(tl, tlInner, bl, blInner);
mesh.AddQuad(trInner, tr, brInner, br);
mesh.AddQuad(blInner, brInner, bl, br);
}
public void AddQuad(LayoutRect rectangle, Color color, Texture2D? texture = null)
{
if (color.A == 0)
return;
var mesh = GetMeshBuilder(texture);
int tl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top), color, new Vector2(0, 0));
int tr = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top), color, new Vector2(1, 0));
int bl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom), color, new Vector2(0, 1));
int br = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom), color, new Vector2(1, 1));
mesh.AddQuad(tl, tr, bl, br);
}
public void AddRoundedRectangle(
LayoutRect rectangle,
float radiusTopLeft,
float radiusTopRight,
float radiusBottomLeft,
float radiusBottomRight,
Color color,
Texture2D? texture = null
)
{
var mesh = GetMeshBuilder(texture);
float halfWidth = rectangle.Width / 2f;
float halfHeight = rectangle.Height / 2f;
float smallerHalf = MathF.Min(halfWidth, halfHeight);
radiusTopLeft = MathHelper.Clamp(radiusTopLeft, 0, smallerHalf);
radiusBottomLeft = MathHelper.Clamp(radiusBottomLeft, 0, smallerHalf);
radiusTopRight = MathHelper.Clamp(radiusTopRight, 0, smallerHalf);
radiusBottomRight = MathHelper.Clamp(radiusBottomRight, 0, smallerHalf);
if (radiusTopLeft <= 0 && radiusTopRight <= 0 && radiusBottomLeft <= 0 && radiusBottomRight <= 0)
{
AddQuad(rectangle, color);
return;
}
float radiusTopLeftUv = radiusTopLeft / rectangle.Width;
float radiusTopRightUv = radiusTopRight / rectangle.Width;
float radiusBottomLeftUv = radiusBottomLeft / rectangle.Width;
float radiusBottomRightUv = radiusBottomRight / rectangle.Width;
int innerTl = mesh.AddVertex(new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top + radiusTopLeft), color, new Vector2(radiusTopLeftUv, radiusTopLeftUv));
int innerTr = mesh.AddVertex(new Vector2(rectangle.Right - radiusTopRight, rectangle.Top + radiusTopRight), color, new Vector2(1 - radiusTopRightUv, radiusTopRightUv));
int innerBl = mesh.AddVertex(new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom - radiusBottomLeft), color, new Vector2(radiusBottomLeftUv, 1 - radiusBottomLeftUv));
int innerBr = mesh.AddVertex(new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom - radiusBottomRight), color, new Vector2(1 - radiusBottomRightUv, 1 - radiusBottomRightUv));
int outerTl1 = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top + radiusTopLeft), color, new Vector2(0, radiusTopLeftUv));
int outerTl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top), color, new Vector2(radiusTopLeftUv, 0));
int outerTr1 = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top + radiusTopRight), color, new Vector2(1, radiusTopRightUv));
int outerTr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusTopRight, rectangle.Top), color, new Vector2(1 - radiusTopRightUv, 0));
int outerBl1 = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom - radiusBottomLeft), color, new Vector2(0, 1 - radiusBottomLeftUv));
int outerBl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom), color, new Vector2(radiusBottomLeftUv, 1));
int outerBr1 = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom - radiusBottomRight), color, new Vector2(1, 1 - radiusBottomRightUv));
int outerBr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom), color, new Vector2(1 - radiusBottomRightUv, 1));
mesh.AddQuad(outerTl1, innerTl, outerBl1, innerBl);
mesh.AddQuad(outerTl2, outerTr2, innerTl, innerTr);
mesh.AddQuad(innerTr, outerTr1, innerBr, outerBr1);
mesh.AddQuad(innerBl, innerBr, outerBl2, outerBr2);
mesh.AddQuad(innerTl, innerTr, innerBl, innerBr);
AddQuarterCircleAroundPivotVertices(mesh, innerTl, outerTl2, radiusTopLeft, -1, -1, true, color, radiusTopLeftUv, radiusTopLeftUv, radiusTopLeftUv);
AddQuarterCircleAroundPivotVertices(mesh, innerTr, outerTr2, radiusTopRight, 1, -1, false, color, 1 - radiusTopRightUv, radiusTopRightUv, radiusTopRightUv);
AddQuarterCircleAroundPivotVertices(mesh, innerBl, outerBl2, radiusBottomLeft, -1, 1, false, color, radiusBottomLeftUv, 1 - radiusBottomLeftUv, radiusBottomLeftUv);
AddQuarterCircleAroundPivotVertices(mesh, innerBr, outerBr2, radiusBottomRight, 1, 1, true, color, 1 - radiusBottomRightUv, 1 - radiusBottomRightUv, radiusBottomRightUv);
}
private void AddQuarterCircleAroundPivotVertices(GuiMeshBuilder mesh, int pivotVertex, int extent, float radius, float directionH, float directionV, bool reverseWinding, Color color, float uvX, float uvY, float texelRadius)
{
Vector3 center = mesh[pivotVertex].Position;
int last = extent;
const int segments = 16;
for (var i = 0; i < segments; i++)
{
var t = (i / (segments - 1f)) * MathF.PI * 0.5f;
var x = MathF.Sin(t);
var y = MathF.Cos(t);
float x1 = x * (radius * directionH) + center.X;
float y1 = y * (radius * directionV) + center.Y;
float u = (x * (texelRadius * directionH)) + uvX;
float v = (y * (texelRadius * directionV)) + uvY;
int next = mesh.AddVertex(new Vector2(x1, y1), color, new Vector2(u, v));
if (reverseWinding)
mesh.AddTriangle(pivotVertex, next, last);
else
mesh.AddTriangle(last, next, pivotVertex);
last = next;
}
}
public void AddRoundedRectangleOutline(LayoutRect rectangle, float thickness, float uniformRadius, Color color, Texture2D? texture = null)
{
AddRoundedRectangleOutline(rectangle, thickness, uniformRadius, uniformRadius, uniformRadius, uniformRadius,
color, texture);
}
public void AddRoundedRectangleOutline(LayoutRect rectangle, float thickness, float radiusTopLeft,
float radiusTopRight, float radiusBottomLeft, float radiusBottomRight, Color color, Texture2D? texture = null)
{
if (thickness <= 0)
return;
float halfWidth = rectangle.Width / 2f;
float halfHeight = rectangle.Height / 2f;
float smallerHalf = MathF.Min(halfWidth, halfHeight);
if (thickness >= smallerHalf)
{
AddRoundedRectangle(rectangle, radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight, color, texture);
return;
}
radiusTopLeft = MathHelper.Clamp(radiusTopLeft, 0, smallerHalf);
radiusBottomLeft = MathHelper.Clamp(radiusBottomLeft, 0, smallerHalf);
radiusTopRight = MathHelper.Clamp(radiusTopRight, 0, smallerHalf);
radiusBottomRight = MathHelper.Clamp(radiusBottomRight, 0, smallerHalf);
if (radiusTopLeft <= 0 && radiusTopRight <= 0 && radiusBottomLeft <= 0 && radiusBottomRight <= 0)
return;
var mesh = GetMeshBuilder(texture);
int innerTl = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Top + thickness), color);
int innerTr = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Top + thickness), color);
int innerBl = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Bottom - thickness), color);
int innerBr = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Bottom - thickness), color);
int innerTl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top + thickness), color);
int innerTr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusTopRight, rectangle.Top + thickness), color);
int innerBl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom - thickness), color);
int innerBr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom - thickness), color);
int innerTl3 = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Top + radiusTopLeft), color);
int innerTr3 = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Top + radiusTopRight), color);
int innerBl3 = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Bottom - radiusBottomLeft), color);
int innerBr3 = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Bottom - radiusBottomRight), color);
int outerTl1 = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top + radiusTopLeft), color);
int outerTl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top), color);
int outerTr1 = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top + radiusTopRight), color);
int outerTr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusTopRight, rectangle.Top), color);
int outerBl1 = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom - radiusBottomLeft), color);
int outerBl2 = mesh.AddVertex(new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom), color);
int outerBr1 = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom - radiusBottomRight), color);
int outerBr2 = mesh.AddVertex(new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom), color);
Vector2 cTl = new Vector2(rectangle.Left + radiusTopLeft, rectangle.Top + radiusTopLeft);
Vector2 cTr = new Vector2(rectangle.Right - radiusTopRight, rectangle.Top + radiusTopRight);
Vector2 cBl = new Vector2(rectangle.Left + radiusBottomLeft, rectangle.Bottom - radiusBottomLeft);
Vector2 cBr = new Vector2(rectangle.Right - radiusBottomRight, rectangle.Bottom - radiusBottomRight);
mesh.AddQuad(outerTl1, innerTl3, outerBl1, innerBl3);
mesh.AddQuad(outerTl2, outerTr2, innerTl2, innerTr2);
mesh.AddQuad(innerTr3, outerTr1, innerBr3, outerBr1);
mesh.AddQuad(innerBl2, innerBr2, outerBl2, outerBr2);
AddRoundedRectangleOutlineCurve(mesh, cTl, innerTl, thickness, radiusTopLeft, -1, -1, true, color);
AddRoundedRectangleOutlineCurve(mesh, cTr, innerTr, thickness, radiusTopRight, 1, -1, false, color);
AddRoundedRectangleOutlineCurve(mesh, cBl, innerBl, thickness, radiusBottomLeft, -1, 1, false, color);
AddRoundedRectangleOutlineCurve(mesh, cBr, innerBr, thickness, radiusBottomRight, 1, 1, true, color);
}
private void AddRoundedRectangleOutlineCurve(GuiMeshBuilder mesh, Vector2 center, int innerCorner, float thickness, float radius, float directionH, float directionV, bool reverseWinding, Color color)
{
const int segments = 16;
var connectToInnerCorner = false;
if (radius < thickness)
{
connectToInnerCorner = true;
thickness = radius;
}
int currentInner = 0;
int currentOuter = 0;
for (var i = 0; i < segments; i++)
{
var t = (i / (segments - 1f)) * MathF.PI * 0.5f;
var x = MathF.Sin(t);
var y = MathF.Cos(t);
var xOuter = x * (radius * directionH) + center.X;
var yOuter = y * (radius * directionV) + center.Y;
var xInner = x * ((radius * directionH) + (thickness * -directionH)) + center.X;
var yInner = y * ((radius * directionV) + (thickness * -directionV)) + center.Y;
int nextInner = mesh.AddVertex(new Vector2(xInner, yInner), color);
int nextOuter = mesh.AddVertex(new Vector2(xOuter, yOuter), color);
if (reverseWinding)
{
if (i > 0)
{
mesh.AddTriangle(nextInner, currentOuter, currentInner);
mesh.AddTriangle(nextOuter, currentOuter, nextInner);
}
if (connectToInnerCorner)
{
if (i == 0 || i == segments - 1)
{
mesh.AddTriangle(innerCorner, nextInner, nextOuter);
}
}
}
else
{
if (i > 0)
{
mesh.AddTriangle(currentInner, currentOuter, nextInner);
mesh.AddTriangle(nextInner, currentOuter, nextOuter);
}
if (connectToInnerCorner)
{
if (i == 0 || i == segments - 1)
{
mesh.AddTriangle(nextOuter, nextInner, innerCorner);
}
}
}
currentInner = nextInner;
currentOuter = nextOuter;
}
}
public void DrawQuad(Texture2D texture, ref VertexPositionColorTexture topLeft, ref VertexPositionColorTexture topRight,
ref VertexPositionColorTexture bottomLeft, ref VertexPositionColorTexture bottomRight)
{
var mesh = GetMeshBuilder(texture);
int i1 = mesh.AddVertex(topLeft);
int i2 = mesh.AddVertex(topRight);
int i3 = mesh.AddVertex(bottomLeft);
int i4 = mesh.AddVertex(bottomRight);
mesh.AddTriangle(i1, i2, i3);
mesh.AddTriangle(i3, i2, i4);
}
public GraphicsDevice GraphicsDevice => guiRenderer.GraphicsDevice;
}

0
src/AcidicGUI/Rendering/GuiMesh.cs Normal file → Executable file
View file

0
src/AcidicGUI/Rendering/GuiMeshBuilder.cs Normal file → Executable file
View file

448
src/AcidicGUI/Rendering/Vertex.cs Normal file → Executable file
View file

@ -1,229 +1,221 @@
using AcidicGUI.Effects;
using AcidicGUI.Layout;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace AcidicGUI.Rendering;
public sealed class GuiRenderer
{
private readonly Dictionary<Texture2D, GuiBatch> batches = new();
private readonly IGuiContext context;
private readonly Stack<Matrix> previousMatrices = new();
private Matrix currentMatrix = Matrix.Identity;
private GuiBatch? whiteBatch;
private float layer;
public GuiRenderer(IGuiContext context)
{
this.context = context;
}
public float Layer => layer;
public void PushTransformMatrix(Matrix matrix)
{
previousMatrices.Push(currentMatrix);
currentMatrix *= matrix;
}
public void PopTransformMatrix()
{
if (previousMatrices.Count == 0)
{
currentMatrix = Matrix.Identity;
return;
}
currentMatrix = previousMatrices.Pop();
}
public void RenderGuiMesh(GuiMesh mesh)
{
foreach (GuiSubMesh subMesh in mesh.SubMeshes)
{
SubmitSubMesh(subMesh);
}
}
public int GetVertexCount(Texture2D? texture)
{
if (texture == null)
{
if (whiteBatch==null)
whiteBatch = new GuiBatch(context, GraphicsDevice, null);
return whiteBatch.VertexCount;
}
if (!batches.TryGetValue(texture, out GuiBatch? batch))
{
batch = new GuiBatch(context, GraphicsDevice, texture);
batches.Add(texture, batch);
}
return batch.VertexCount;
}
private void SubmitSubMesh(GuiSubMesh subMesh)
{
if (subMesh.Texture != null)
{
if (!batches.TryGetValue(subMesh.Texture, out GuiBatch? batch))
{
batch = new GuiBatch(context, GraphicsDevice, subMesh.Texture);
batches.Add(subMesh.Texture, batch);
}
batch.Submit(subMesh);
return;
}
if (whiteBatch==null)
whiteBatch = new GuiBatch(context, GraphicsDevice, null);
whiteBatch.Submit(subMesh);
}
public void RenderBatches(IEffect? effectOverride = null, float opacity = 1, LayoutRect? clipRect = null)
{
whiteBatch?.DrawBatch(effectOverride, opacity, clipRect, currentMatrix);
foreach (GuiBatch batch in batches.Values)
batch.DrawBatch(effectOverride, opacity, clipRect, currentMatrix);
}
public void Grab(RenderTarget2D destination)
{
context.Grab(destination);
}
public void Restore()
{
context.RestoreRenderState();
}
public GraphicsDevice GraphicsDevice => context.GraphicsDevice;
}
public sealed class GrowingList<T>
{
private readonly List<T> list = new();
private int insertIndex;
public IEnumerator<T> GetEnumerator()
{
return list.GetEnumerator();
}
public void AddRange(IEnumerable<T> items)
{
foreach (T item in items)
Add(item);
}
public void Add(T item)
{
if (insertIndex == list.Count)
list.Add(item);
else
list[insertIndex] = item;
insertIndex++;
}
public void Clear()
{
insertIndex = 0;
}
public int Count => insertIndex;
public T[] ToArray()
{
return list.ToArray().AsSpan().Slice(0, insertIndex).ToArray();
}
}
public sealed class GuiBatch
{
private readonly GraphicsDevice device;
private VertexBuffer? vertexBuffer;
private IndexBuffer? indexBuffer;
private readonly Texture2D? texture;
private readonly IGuiContext context;
private readonly GrowingList<VertexPositionColorTexture> cpuVertices = new();
private readonly GrowingList<int> cpuIndices = new();
private int vertexDirtiness;
private int indexDirtiness;
private bool dirty;
public GuiBatch(IGuiContext context, GraphicsDevice device, Texture2D? texture)
{
this.context = context;
this.device = device;
this.texture = texture;
}
public int VertexCount => cpuVertices.Count;
public void Submit(GuiSubMesh subMesh)
{
if (subMesh.Vertices.Length == 0 || subMesh.Indices.Length == 0)
return;
if (subMesh.IsNew)
{
subMesh.MarkAsOld();
if (!dirty)
{
vertexDirtiness = cpuVertices.Count;
indexDirtiness = cpuIndices.Count;
}
dirty = true;
}
cpuVertices.AddRange(subMesh.Vertices);
cpuIndices.AddRange(subMesh.Indices);
}
public void DrawBatch(IEffect? effectOverride, float opacity, LayoutRect? clipRect, Matrix widgetTransform)
{
if (cpuIndices.Count == 0 || cpuVertices.Count == 0)
return;
if (vertexBuffer == null || vertexBuffer.VertexCount < cpuVertices.Count)
{
vertexBuffer?.Dispose();
dirty = true;
vertexDirtiness = 0;
vertexBuffer = new VertexBuffer(device, typeof(VertexPositionColorTexture), cpuVertices.Count, BufferUsage.None);
}
if (indexBuffer == null || indexBuffer.IndexCount < cpuIndices.Count)
{
indexBuffer?.Dispose();
dirty = true;
indexDirtiness = 0;
indexBuffer = new IndexBuffer(device, typeof(int), cpuIndices.Count, BufferUsage.None);
}
if (dirty)
{
var vertexData = cpuVertices.ToArray();
var indexData = cpuIndices.ToArray();
vertexBuffer.SetData(vertexData, vertexDirtiness, vertexData.Length - vertexDirtiness);
indexBuffer.SetData(indexData, indexDirtiness, indexData.Length - indexDirtiness);
dirty = false;
}
context.Render(vertexBuffer, indexBuffer, 0, cpuIndices.Count / 3, texture, clipRect, effectOverride, opacity, widgetTransform);
cpuVertices.Clear();
cpuIndices.Clear();
}
using AcidicGUI.Effects;
using AcidicGUI.Layout;
using Microsoft.Xna.Framework.Graphics;
namespace AcidicGUI.Rendering;
public sealed class GuiRenderer
{
private GuiBatch? whiteBatch;
private readonly Dictionary<Texture2D, GuiBatch> batches = new();
private readonly IGuiContext context;
private float layer;
public GuiRenderer(IGuiContext context)
{
this.context = context;
}
public float Layer => layer;
public void SetLayer(float newLayer)
{
layer = newLayer;
}
public void PushLayer()
{
layer += 0.5f;
}
public void PopLayer()
{
layer--;
}
public void RenderGuiMesh(GuiMesh mesh)
{
foreach (GuiSubMesh subMesh in mesh.SubMeshes)
{
SubmitSubMesh(subMesh);
}
}
public int GetVertexCount(Texture2D? texture)
{
if (texture == null)
{
if (whiteBatch==null)
whiteBatch = new GuiBatch(context, GraphicsDevice, null);
return whiteBatch.VertexCount;
}
if (!batches.TryGetValue(texture, out GuiBatch? batch))
{
batch = new GuiBatch(context, GraphicsDevice, texture);
batches.Add(texture, batch);
}
return batch.VertexCount;
}
private void SubmitSubMesh(GuiSubMesh subMesh)
{
if (subMesh.Texture != null)
{
if (!batches.TryGetValue(subMesh.Texture, out GuiBatch? batch))
{
batch = new GuiBatch(context, GraphicsDevice, subMesh.Texture);
batches.Add(subMesh.Texture, batch);
}
batch.Submit(subMesh);
return;
}
if (whiteBatch==null)
whiteBatch = new GuiBatch(context, GraphicsDevice, null);
whiteBatch.Submit(subMesh);
}
public void RenderBatches(IEffect? effectOverride = null, float opacity = 1, LayoutRect? clipRect = null)
{
whiteBatch?.DrawBatch(effectOverride, opacity, clipRect);
foreach (GuiBatch batch in batches.Values)
batch.DrawBatch(effectOverride, opacity, clipRect);
}
public void Grab(RenderTarget2D destination)
{
context.Grab(destination);
}
public void Restore()
{
context.RestoreRenderState();
}
public GraphicsDevice GraphicsDevice => context.GraphicsDevice;
}
public sealed class GrowingList<T>
{
private readonly List<T> list = new();
private int insertIndex;
public IEnumerator<T> GetEnumerator()
{
return list.GetEnumerator();
}
public void AddRange(IEnumerable<T> items)
{
foreach (T item in items)
Add(item);
}
public void Add(T item)
{
if (insertIndex == list.Count)
list.Add(item);
else
list[insertIndex] = item;
insertIndex++;
}
public void Clear()
{
insertIndex = 0;
}
public int Count => insertIndex;
public T[] ToArray()
{
return list.ToArray().AsSpan().Slice(0, insertIndex).ToArray();
}
}
public sealed class GuiBatch
{
private readonly GraphicsDevice device;
private VertexBuffer? vertexBuffer;
private IndexBuffer? indexBuffer;
private readonly Texture2D? texture;
private readonly IGuiContext context;
private readonly GrowingList<VertexPositionColorTexture> cpuVertices = new();
private readonly GrowingList<int> cpuIndices = new();
private int vertexDirtiness;
private int indexDirtiness;
private bool dirty;
public GuiBatch(IGuiContext context, GraphicsDevice device, Texture2D? texture)
{
this.context = context;
this.device = device;
this.texture = texture;
}
public int VertexCount => cpuVertices.Count;
public void Submit(GuiSubMesh subMesh)
{
if (subMesh.IsNew)
{
subMesh.MarkAsOld();
if (!dirty)
{
vertexDirtiness = cpuVertices.Count;
indexDirtiness = cpuIndices.Count;
}
dirty = true;
}
cpuVertices.AddRange(subMesh.Vertices);
cpuIndices.AddRange(subMesh.Indices);
}
public void DrawBatch(IEffect? effectOverride, float opacity, LayoutRect? clipRect)
{
if (cpuIndices.Count == 0 || cpuVertices.Count == 0)
return;
if (vertexBuffer == null || vertexBuffer.VertexCount < cpuVertices.Count)
{
vertexBuffer?.Dispose();
dirty = true;
vertexDirtiness = 0;
vertexBuffer = new VertexBuffer(device, typeof(VertexPositionColorTexture), cpuVertices.Count, BufferUsage.None);
}
if (indexBuffer == null || indexBuffer.IndexCount < cpuIndices.Count)
{
indexBuffer?.Dispose();
dirty = true;
indexDirtiness = 0;
indexBuffer = new IndexBuffer(device, typeof(int), cpuIndices.Count, BufferUsage.None);
}
if (dirty)
{
var vertexData = cpuVertices.ToArray();
var indexData = cpuIndices.ToArray();
vertexBuffer.SetData(vertexData, vertexDirtiness, vertexData.Length - vertexDirtiness);
indexBuffer.SetData(indexData, indexDirtiness, indexData.Length - indexDirtiness);
dirty = false;
}
context.Render(vertexBuffer, indexBuffer, 0, cpuIndices.Count / 3, texture, clipRect, effectOverride, opacity);
cpuVertices.Clear();
cpuIndices.Clear();
}
}

0
src/AcidicGUI/Sdl@platform.cs Normal file → Executable file
View file

0
src/AcidicGUI/TextRendering/DynamicFont.cs Normal file → Executable file
View file

0
src/AcidicGUI/TextRendering/Font.cs Normal file → Executable file
View file

Some files were not shown because too many files have changed in this diff Show more