mirror of
https://gitlab.acidiclight.dev/sociallydistant/sociallydistant.git
synced 2025-01-22 01:21:50 -05:00
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:
parent
7f8f7dbf42
commit
75c8b5432c
1256 changed files with 7908 additions and 7874 deletions
0
.config/dotnet-tools.json
Normal file → Executable file
0
.config/dotnet-tools.json
Normal file → Executable file
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
|||
# Rider caches
|
||||
.idea/
|
||||
|
||||
# docfx
|
||||
api/
|
||||
public/
|
||||
|
|
0
.gitlab-ci.yml
Normal file → Executable file
0
.gitlab-ci.yml
Normal file → Executable file
0
Assets/Assets/Core/WebSites.meta
Normal file → Executable file
0
Assets/Assets/Core/WebSites.meta
Normal file → Executable file
0
Assets/Assets/Core/WebSites/404.prefab
Normal file → Executable file
0
Assets/Assets/Core/WebSites/404.prefab
Normal file → Executable file
0
Assets/Assets/Core/WebSites/404.prefab.meta
Normal file → Executable file
0
Assets/Assets/Core/WebSites/404.prefab.meta
Normal file → Executable file
0
Assets/Assets/Core/WebSites/TheMissingPage.asset
Normal file → Executable file
0
Assets/Assets/Core/WebSites/TheMissingPage.asset
Normal file → Executable file
0
Assets/Assets/Core/WebSites/TheMissingPage.asset.meta
Normal file → Executable file
0
Assets/Assets/Core/WebSites/TheMissingPage.asset.meta
Normal file → Executable file
0
Assets/Assets/Main/Websites/NewCipherToday.asset
Normal file → Executable file
0
Assets/Assets/Main/Websites/NewCipherToday.asset
Normal file → Executable file
0
Assets/Assets/Main/Websites/NewCipherToday.asset.meta
Normal file → Executable file
0
Assets/Assets/Main/Websites/NewCipherToday.asset.meta
Normal file → Executable file
0
Assets/Assets/Main/Websites/NewCipherToday.prefab
Normal file → Executable file
0
Assets/Assets/Main/Websites/NewCipherToday.prefab
Normal file → Executable file
0
Assets/Assets/Main/Websites/NewCipherToday.prefab.meta
Normal file → Executable file
0
Assets/Assets/Main/Websites/NewCipherToday.prefab.meta
Normal file → Executable file
0
Assets/Editor/CustomImporters/ArticleImporter.cs
Normal file → Executable file
0
Assets/Editor/CustomImporters/ArticleImporter.cs
Normal file → Executable file
0
Assets/Scripts/UI/Applications/WebBrowser/ItsAllFuckingGone.cs
Normal file → Executable file
0
Assets/Scripts/UI/Applications/WebBrowser/ItsAllFuckingGone.cs
Normal file → Executable file
0
Assets/Scripts/UI/Applications/WebBrowser/ItsAllFuckingGone.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Applications/WebBrowser/ItsAllFuckingGone.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Shell/ShellNavigationLink.cs
Normal file → Executable file
0
Assets/Scripts/UI/Shell/ShellNavigationLink.cs
Normal file → Executable file
0
Assets/Scripts/UI/Shell/ShellNavigationLink.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Shell/ShellNavigationLink.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Websites/News.meta
Normal file → Executable file
0
Assets/Scripts/UI/Websites/News.meta
Normal file → Executable file
0
Assets/Scripts/UI/Websites/News/NewsWebSite.cs
Normal file → Executable file
0
Assets/Scripts/UI/Websites/News/NewsWebSite.cs
Normal file → Executable file
0
Assets/Scripts/UI/Websites/News/NewsWebSite.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Websites/News/NewsWebSite.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Prefabs/SocialPost.prefab
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Prefabs/SocialPost.prefab
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Prefabs/SocialPost.prefab.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Prefabs/SocialPost.prefab.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Prefabs/WebSearchResult.prefab
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Prefabs/WebSearchResult.prefab
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Prefabs/WebSearchResult.prefab.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Prefabs/WebSearchResult.prefab.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Settings/SocialPostWidget.cs
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Settings/SocialPostWidget.cs
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Settings/SocialPostWidget.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/Settings/SocialPostWidget.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/SocialPostWidgetController.cs
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/SocialPostWidgetController.cs
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/SocialPostWidgetController.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/SocialPostWidgetController.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/WebSearchResultWidgetController.cs
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/WebSearchResultWidgetController.cs
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/WebSearchResultWidgetController.cs.meta
Normal file → Executable file
0
Assets/Scripts/UI/Widgets/WebSearchResultWidgetController.cs.meta
Normal file → Executable file
0
CONTRIBUTING.md
Normal file → Executable file
0
CONTRIBUTING.md
Normal file → Executable file
0
LICENSE
Normal file → Executable file
0
LICENSE
Normal file → Executable file
0
README.md
Normal file → Executable file
0
README.md
Normal file → Executable file
0
mgfxc-wine-setup.sh
Normal file → Executable file
0
mgfxc-wine-setup.sh
Normal file → Executable file
13
src/.idea/.idea.sociallydistant/.idea/.gitignore
vendored
13
src/.idea/.idea.sociallydistant/.idea/.gitignore
vendored
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
sociallydistant
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
0
src/AcidicGUI/AcidicGUI.csproj
Normal file → Executable file
0
src/AcidicGUI/ColorHelpers.cs
Normal file → Executable file
0
src/AcidicGUI/ColorHelpers.cs
Normal file → Executable file
0
src/AcidicGUI/CustomProperties/CanvasAnchors.cs
Normal file → Executable file
0
src/AcidicGUI/CustomProperties/CanvasAnchors.cs
Normal file → Executable file
0
src/AcidicGUI/CustomProperties/CustomPropertyObject.cs
Normal file → Executable file
0
src/AcidicGUI/CustomProperties/CustomPropertyObject.cs
Normal file → Executable file
2
src/AcidicGUI/CustomProperties/FlexPanelProperties.cs
Normal file → Executable file
2
src/AcidicGUI/CustomProperties/FlexPanelProperties.cs
Normal file → Executable 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
0
src/AcidicGUI/CustomProperties/StructProperty.cs
Normal file → Executable file
18
src/AcidicGUI/Effects/IEffect.cs
Normal file → Executable file
18
src/AcidicGUI/Effects/IEffect.cs
Normal file → Executable 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
0
src/AcidicGUI/Effects/IWidgetEffect.cs
Normal file → Executable file
0
src/AcidicGUI/Events/FocusEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/FocusEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/GuiEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/GuiEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IDragEndHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IDragEndHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IDragHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IDragHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IDragStartHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IDragStartHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IGainFocusHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IGainFocusHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IKeyCharHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IKeyCharHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IKeyDownHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IKeyDownHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IKeyUpHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IKeyUpHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/ILoseFocusHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/ILoseFocusHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseClickHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseClickHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseDownHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseDownHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseEnterHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseEnterHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseLeaveHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseLeaveHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseMoveHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseMoveHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseScrollHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseScrollHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseUpHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IMouseUpHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IPreviewKeyCharHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IPreviewKeyCharHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IPreviewKeyDownHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IPreviewKeyDownHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IPreviewKeyUpHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IPreviewKeyUpHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IUpdateHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/IUpdateHandler.cs
Normal file → Executable file
0
src/AcidicGUI/Events/KeyCharEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/KeyCharEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/KeyEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/KeyEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/ModifierKeys.cs
Normal file → Executable file
0
src/AcidicGUI/Events/ModifierKeys.cs
Normal file → Executable file
0
src/AcidicGUI/Events/MouseButtonEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/MouseButtonEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/MouseEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/MouseEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/MouseMoveEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/MouseMoveEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/MouseScrollEvent.cs
Normal file → Executable file
0
src/AcidicGUI/Events/MouseScrollEvent.cs
Normal file → Executable file
901
src/AcidicGUI/GuiManager.cs
Normal file → Executable file
901
src/AcidicGUI/GuiManager.cs
Normal file → Executable 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
0
src/AcidicGUI/GuiSynchronizationContext.cs
Normal file → Executable file
62
src/AcidicGUI/IGuiContext.cs
Normal file → Executable file
62
src/AcidicGUI/IGuiContext.cs
Normal file → Executable 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
0
src/AcidicGUI/IOrderedCollection.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/Direction.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/Direction.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/FlexMode.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/FlexMode.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/HorizontalAlignment.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/HorizontalAlignment.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/LayoutRect.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/LayoutRect.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/Padding.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/Padding.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/TextAlignment.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/TextAlignment.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/VerticalAlignment.cs
Normal file → Executable file
0
src/AcidicGUI/Layout/VerticalAlignment.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/DataHelper.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/DataHelper.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/INotifyDataChanged.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/INotifyDataChanged.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/ListAdapter.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/ListAdapter.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/RecyclableWidgetController.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/RecyclableWidgetController.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/RecycleBin.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/RecycleBin.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/ViewHolder.cs
Normal file → Executable file
0
src/AcidicGUI/ListAdapters/ViewHolder.cs
Normal file → Executable file
729
src/AcidicGUI/Rendering/GeometryHelper.cs
Normal file → Executable file
729
src/AcidicGUI/Rendering/GeometryHelper.cs
Normal file → Executable 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
0
src/AcidicGUI/Rendering/GuiMesh.cs
Normal file → Executable file
0
src/AcidicGUI/Rendering/GuiMeshBuilder.cs
Normal file → Executable file
0
src/AcidicGUI/Rendering/GuiMeshBuilder.cs
Normal file → Executable file
448
src/AcidicGUI/Rendering/Vertex.cs
Normal file → Executable file
448
src/AcidicGUI/Rendering/Vertex.cs
Normal file → Executable 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
0
src/AcidicGUI/Sdl@platform.cs
Normal file → Executable file
0
src/AcidicGUI/TextRendering/DynamicFont.cs
Normal file → Executable file
0
src/AcidicGUI/TextRendering/DynamicFont.cs
Normal file → Executable file
0
src/AcidicGUI/TextRendering/Font.cs
Normal file → Executable file
0
src/AcidicGUI/TextRendering/Font.cs
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue