diff --git a/Client/Gui/GuiCanvas.cs b/Client/Gui/GuiCanvas.cs index 56cef1e..1455ad7 100644 --- a/Client/Gui/GuiCanvas.cs +++ b/Client/Gui/GuiCanvas.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; -using System.Text.Encodings.Web; using GlmSharp; -using SixLabors.ImageSharp; -using Veldrid.OpenGLBinding; -using Veldrid.Sdl2; using Voxel.Client.Rendering.Gui; using Voxel.Client.Rendering.Texture; using Voxel.Client.Rendering.VertexTypes; @@ -16,36 +12,8 @@ namespace Voxel.Client.Gui; public static class GuiCanvas { // TODO: Update this as the window changes size public static ivec2 ReferenceResolution { get; private set; } = ivec2.Zero; - public static vec2 ScreenToPixel(vec2 s, vec2 referenceResolution) - => (s + vec2.Ones) / 2 * referenceResolution; - public static vec2 PixelToScreen(vec2 p, vec2 referenceResolution) - => p / referenceResolution * 2 - vec2.Ones; private static GuiRenderer renderer; - // The root of the GUI tree - public static GuiRect screen; - - public static void Init(GuiRenderer renderer) { - GuiCanvas.renderer = renderer; - ReferenceResolution = new(renderer.Client.NativeWindow.Width, renderer.Client.NativeWindow.Height); - screen = new GuiRect(-vec2.Ones, -vec2.Ones, vec2.Ones); - - var healthbar = screen.AddChild(new GuiRect(new(1, 1), new(1, 1), new vec2(0.8f, 0.1f), "test")); - for (int i = 0; i < 7; i++) { - healthbar.AddChild(new(new(1, 0), new(1 - 0.11f * i, 0), GuiRect.FromPixelAspectRatioAndHeight(9, 8, 1), "test")); - } - } - - internal static Atlas.Sprite GetSprite(string spriteName) { - if (renderer.GuiAtlas.TryGetSprite($"gui/{spriteName}", out var sprite)) { - return sprite; - } else { - // TODO: Log warning - Console.WriteLine($"GUI sprite {spriteName} does not exist"); - return null; - } - } - // internal so GuiRect.Rebuild() can bypass the needs rebuilt check internal static readonly GuiVertex[] _QuadCache = new GuiVertex[1024]; public static GuiVertex[] QuadCache { @@ -69,19 +37,47 @@ public static GuiVertex[] QuadCache { } public static uint QuadCount { get; internal set; } + private static bool quadCacheNeedsRebuilt = true; + internal static SortedSet branchesToRebuild = new SortedSet(new GuiRect.ByTreeDepth()); + + // The root of the GUI tree + public static GuiRect screen; + + public static vec2 ScreenToPixel(vec2 s, vec2 referenceResolution) + => (s + vec2.Ones) / 2 * referenceResolution; + public static vec2 PixelToScreen(vec2 p, vec2 referenceResolution) + => p / referenceResolution * 2 - vec2.Ones; + + public static void Init(GuiRenderer renderer) { + GuiCanvas.renderer = renderer; + ReferenceResolution = new(renderer.Client.NativeWindow.Width, renderer.Client.NativeWindow.Height); + screen = new GuiRect(-vec2.Ones, -vec2.Ones, vec2.Ones); + + var healthbar = screen.AddChild(new GuiRect(new(1, 1), new(1, 1), new vec2(0.8f, 0.1f))); + for (int i = 0; i < 7; i++) { + healthbar.AddChild(new(new(1, 0), new(1 - 0.11f * i, 0), GuiRect.FromPixelAspectRatioAndHeight(9, 8, 1), "heart")); + } + } + + internal static Atlas.Sprite GetSprite(string spriteName) { + if (renderer.GuiAtlas.TryGetSprite($"gui/{spriteName}", out var sprite)) { + return sprite; + } else { + // TODO: Log warning + Console.WriteLine($"GUI sprite {spriteName} does not exist"); + return null; + } + } + + public static void InvalidateQuadCache() { + quadCacheNeedsRebuilt = true; + } // Assign each GuiRect a new quadIdx, then rebuild all of them internal static void RebuildQuadCache() { QuadCount = 0; branchesToRebuild.Clear(); screen!.Rebuild(-vec2.Ones, vec2.Ones, true); } - - private static bool quadCacheNeedsRebuilt = true; - public static void InvalidateQuadCache() { - quadCacheNeedsRebuilt = true; - } - - internal static SortedSet branchesToRebuild = new SortedSet(new GuiRect.ByTreeDepth()); } public class GuiRect { @@ -161,10 +157,21 @@ public class ByTreeDepth : IComparer public int Compare(GuiRect? lhs, GuiRect? rhs) => (int)(lhs!.treeDepth - rhs!.treeDepth); } - - // modifies rect based on the parent public delegate void SizeInitializer(GuiRect parent, GuiRect rect); + /// + /// a closure that encapsulates these parameters and will give return a GuiRect with the proper dimensions + /// + public static SizeInitializer FromPixelAspectRatioAndHeight(float ratioWidth, float ratioHeight, float screenHeightConstraint) { + return (GuiRect parent, GuiRect rect) => { + float heightToWidth = ratioWidth / ratioHeight; + float pixelHeight = parent.ReferenceResolution.y * screenHeightConstraint; + float pixelWidth = heightToWidth * pixelHeight; + + rect.pixelSize = new(pixelWidth, pixelHeight); + }; + } + public GuiRect(vec2 screenAnchor, vec2 localScreenPosition, vec2 localScreenSize, string image = "") { this.screenAnchor = screenAnchor; this.localScreenPosition = localScreenPosition; @@ -177,51 +184,37 @@ public GuiRect(vec2 screenAnchor, vec2 localScreenPosition, SizeInitializer size this.sizeInitializer = sizeInitializer; this.image = image; } - - // returns a closure that encapsulates these parameters and will give return a GuiRect with the proper dimensions - public static SizeInitializer FromPixelAspectRatioAndHeight(float ratioWidth, float ratioHeight, float screenHeightConstraint) { - return (GuiRect parent, GuiRect rect) => { - float heightToWidth = ratioWidth / ratioHeight; - float pixelHeight = parent.ReferenceResolution.y * screenHeightConstraint; - float pixelWidth = heightToWidth * pixelHeight; - - rect.pixelSize = new(pixelWidth, pixelHeight); - }; - } + + /// + /// index into the GuiCanvas._QuadCache + /// + private uint quadIdx = 0; + /// + /// used in GuiCanvas for rebuilding individual branches + /// + private uint treeDepth = 0; public GuiRect? parent = null; public List children = new(); - private readonly SizeInitializer? sizeInitializer = null; - // returns the added child - public GuiRect AddChild(GuiRect rect) { - children.Add(rect); - rect.parent = this; - rect.treeDepth = treeDepth + 1; - - GuiCanvas.InvalidateQuadCache(); - // this needs a complete rebuild to keep indices contiguous when recursively iterating through the gui tree - // contiguous indices ensure the draw order of GUI elements is correct - - if (rect.sizeInitializer != null) rect!.sizeInitializer(this, rect); - - return rect; - } - - // add to a deletion queue in GuiCanvas - public void Delete() { - throw new NotImplementedException(); - } + /// + /// Takes information from this GuiRect's parent to initialize its size when added to the GUI tree. + /// + private SizeInitializer? sizeInitializer = null; // allows for partial pixel resolutions. Note that GuiCanvas.ReferenceResolution must still be in whole pixels + /// + /// The pixel resolution of this GuiRect + /// public vec2 ReferenceResolution { get => (parent?.ReferenceResolution ?? GuiCanvas.ReferenceResolution) * localScreenSize; } - - // name of the texture rendered onto this GuiRect - // a GuiRect with an image of "" will not be rendered - // setting this updates the uv coordinates of this GuiRect's vertices - private string _image = ""; + + /// + /// name of the texture rendered onto this GuiRect
+ /// a GuiRect with an image of "" will not be rendered
+ /// setting this updates the uv coordinates of this GuiRect's vertices. + ///
public string image { get => _image; set { @@ -230,14 +223,16 @@ public string image { } } - + private string _image = ""; + // Screen members are used for pixel member calculations along with ReferenceResolution - // x and y are both bound between -1 and 1. - // The anchor is a point inside (or on the edge of) a GuiRect; - // it defines how the width and height of the rect make it expand, - // and is what gets moved to the GuiRect's position - private vec2 _screenAnchor; + /// + /// x and y are both bound between -1 and 1.
+ /// The anchor is a point inside (or on the edge of) a GuiRect; + /// it defines how the width and height of the rect make it expand, + /// and is what gets moved to the GuiRect's position + ///
public vec2 screenAnchor { get => _screenAnchor; set { @@ -245,10 +240,13 @@ public vec2 screenAnchor { _screenAnchor = value; } } + private vec2 _screenAnchor; - // x and y can be any real number - // The position of this GuiRect in its parent GuiRect - private vec2 _localScreenPosition; + /// + /// x and y can be any real number
+ /// The position of this GuiRect in its parent GuiRect. + /// The origin is the bottom left corner of the parent. + ///
public vec2 localScreenPosition { get => _localScreenPosition; set { @@ -256,6 +254,8 @@ public vec2 localScreenPosition { _localScreenPosition = value; } } + private vec2 _localScreenPosition; + // reading and setting this is expensive, and caching it is impractical for now. public vec2 globalScreenPosition { get { @@ -273,12 +273,13 @@ public vec2 globalScreenPosition { } } - // x and y can be any real number - // The width and height of the GuiRect, from the anchor. - // 1 is the width of the entire parent GuiRect - // if the anchor's x position is 0.5, and the width is 12, - // the rect will extend 9 units left and 3 units right from the anchor - private vec2 _localScreenSize; + /// + /// x and y can be any real number
+ /// The width and height of the GuiRect, from the anchor. ex:
+ /// 1 is the width of the entire parent GuiRect + /// if the anchor's x position is 0.5, and the width is 12, + /// the rect will extend 9 units left and 3 units right from the anchor + ///
public vec2 localScreenSize { get => _localScreenSize; set { @@ -286,56 +287,90 @@ public vec2 localScreenSize { _localScreenSize = value; } } + private vec2 _localScreenSize; + // reading and setting this is expensive, and caching it is impractical for now. public vec2 globalScreenSize { get => localScreenSize * parent?.globalScreenSize ?? vec2.Ones; set => localScreenSize = value / parent?.globalScreenSize ?? vec2.Ones; } - // Pixel members are derived from screen values + /* ----- Pixel members are derived from screen values ----- */ // TODO: The pixel size changes with window resolution. Implement some mechanism for keeping GuiRects at a constant pixel size // These dont have a local/global distinction, because they refer to physical size on the monitor + public vec2 pixelPosition { get => GuiCanvas.ScreenToPixel(globalScreenPosition, GuiCanvas.ReferenceResolution); set => globalScreenPosition = GuiCanvas.PixelToScreen(value, GuiCanvas.ReferenceResolution); } public vec2 pixelSize { - get => localScreenSize * ReferenceResolution; - set => localScreenSize = value / GuiCanvas.ReferenceResolution; + get => localScreenSize * (parent?.ReferenceResolution ?? GuiCanvas.ReferenceResolution); + set => localScreenSize = value / (parent?.ReferenceResolution ?? GuiCanvas.ReferenceResolution); } public Extents extents { get => new Extents(this); } - // Completely rebuilds this node of the GUI tree and all of its children - internal void Rebuild(vec2 globalParentPosition, vec2 globalParentSize, bool rebuildingEntireQuadCache = false) { + /// + /// Adds a child into the GUI tree. + /// + /// + /// the added child + /// + public GuiRect AddChild(GuiRect rect) { + children.Add(rect); + rect.parent = this; + rect.treeDepth = treeDepth + 1; + + GuiCanvas.InvalidateQuadCache(); + // this needs a complete rebuild to keep indices contiguous when recursively iterating through the gui tree + // contiguous indices ensure the draw order of GUI elements is correct + + if (rect.sizeInitializer != null) rect!.sizeInitializer(this, rect); + + return rect; + } + + /// + /// add this node and all its children to a deletion queue in GuiCanvas + /// + public void Delete() { + throw new NotImplementedException(); + } + + /// + /// Completely rebuilds this node of the GUI tree and all of its children + /// + internal void Rebuild(vec2 globalParentBottomLeftPosition, vec2 globalParentSize, bool rebuildingEntireQuadCache = false) { if (rebuildingEntireQuadCache) quadIdx = GuiCanvas.QuadCount++ * 4; else // we're in the middle of a partial rebuild GuiCanvas.branchesToRebuild.Remove(this); var globalSize = localScreenSize * globalParentSize; - var globalPos = globalParentPosition + (localScreenPosition + 1) * globalParentSize; // TODO: Add rotation + var globalPos = globalParentBottomLeftPosition + (localScreenPosition + 1) * globalParentSize; // TODO: Add rotation //if (image == "") image = "test"; // TODO: test code, delete later var e = new Extents(screenAnchor, globalPos, globalSize); Console.WriteLine($"Sprite {image} at {e.PixelBottomLeft} to {e.PixelTopRight}"); - GuiCanvas._QuadCache[quadIdx + 0] = new GuiVertex(e.ScreenBottomRight); - GuiCanvas._QuadCache[quadIdx + 1] = new GuiVertex(e.ScreenBottomLeft); - GuiCanvas._QuadCache[quadIdx + 2] = new GuiVertex(e.ScreenTopLeft); - GuiCanvas._QuadCache[quadIdx + 3] = new GuiVertex(e.ScreenTopRight); + GuiCanvas._QuadCache[quadIdx + 0] = new GuiVertex(e.ScreenBottomRight, GuiCanvas._QuadCache[quadIdx + 0].uv); + GuiCanvas._QuadCache[quadIdx + 1] = new GuiVertex(e.ScreenBottomLeft , GuiCanvas._QuadCache[quadIdx + 1].uv); + GuiCanvas._QuadCache[quadIdx + 2] = new GuiVertex(e.ScreenTopLeft , GuiCanvas._QuadCache[quadIdx + 2].uv); + GuiCanvas._QuadCache[quadIdx + 3] = new GuiVertex(e.ScreenTopRight , GuiCanvas._QuadCache[quadIdx + 3].uv); // After the quadIdx has been set and the vertices have been set up if(rebuildingEntireQuadCache) UpdateVertexUVs(); foreach (var c in children) { - c.Rebuild(globalPos, globalSize, rebuildingEntireQuadCache); + c.Rebuild(e.ScreenBottomLeft, globalSize, rebuildingEntireQuadCache); } } - // Relies on quadIdx being correct, and the GuiVertices in QuadCache having correct screen coordinates + /// + /// Relies on quadIdx being correct, and the GuiVertices in QuadCache having correct screen coordinates + /// private void UpdateVertexUVs() { // GuiRects without images are still included in the QuadCache // This just sets their screen position outside of clip space so they'll be discarded @@ -358,7 +393,4 @@ private void UpdateVertexUVs() { GuiCanvas._QuadCache[quadIdx + 2] = new GuiVertex(GuiCanvas._QuadCache[quadIdx + 2].position, uvTopLeft ); GuiCanvas._QuadCache[quadIdx + 3] = new GuiVertex(GuiCanvas._QuadCache[quadIdx + 3].position, uvTopRight ); } - - private uint quadIdx = 0; // index into the GuiCanvas._QuadCache - private uint treeDepth = 0; // used in GuiCanvas for rebuilding individual branches }