diff --git a/ArgonUI.Backends.Headless/ArgonUI.Backends.Headless.csproj b/ArgonUI.Backends.Headless/ArgonUI.Backends.Headless.csproj
index db76f9d..1d3ade5 100644
--- a/ArgonUI.Backends.Headless/ArgonUI.Backends.Headless.csproj
+++ b/ArgonUI.Backends.Headless/ArgonUI.Backends.Headless.csproj
@@ -7,7 +7,7 @@
disable
enable
ArgonUI Headless Backend
- 0.1.2-pre
+ 0.2.1-pre
Thomas Mathieson
Copyright © Thomas Mathieson 2025
https://github.com/space928/ArgonUI
diff --git a/ArgonUI.Backends.Headless/HeadlessDrawContext.cs b/ArgonUI.Backends.Headless/HeadlessDrawContext.cs
index a64653e..9b0c72a 100644
--- a/ArgonUI.Backends.Headless/HeadlessDrawContext.cs
+++ b/ArgonUI.Backends.Headless/HeadlessDrawContext.cs
@@ -29,22 +29,42 @@ public void DrawGradient(Bounds2D bounds, Vector4 colourA, Vector4 colourB, Vect
}
- public void DrawRect(Bounds2D bounds, Vector4 colour, float rounding)
+ public void DrawLine(Vector2 start, Vector2 end, Vector4 colourStart, Vector4 colourEnd, float thickness)
{
}
- public void DrawShadow(Bounds2D bounds, Vector4 colour, float rounding, float blur)
+ public void DrawOutlineGradient(Bounds2D bounds, Vector4 colourA, Vector4 colourB, Vector4 colourC, Vector4 colourD, float outlineThickness, float rounding)
+ {
+
+ }
+
+ public void DrawOutlineRect(Bounds2D bounds, Vector4 colour, float outlineThickness, float rounding)
+ {
+
+ }
+
+ public void DrawPolyFill(IEnumerable points)
{
}
- public void DrawText(Bounds2D bounds, float size, string s, BMFont font, Vector4 colour)
+ public void DrawPolyLine(IEnumerable points, float thickness)
+ {
+
+ }
+
+ public void DrawRect(Bounds2D bounds, Vector4 colour, float rounding)
+ {
+
+ }
+
+ public void DrawShadow(Bounds2D bounds, Vector4 colour, float rounding, float blur)
{
}
- public void DrawText(Bounds2D bounds, float size, string s, BMFont font, Vector4 colour, float wordSpacing = 0, float charSpacing = 0, float skew = 0, float weight = 0.5F, float width = 1)
+ public void DrawText(Bounds2D bounds, ReadOnlySpan s, BMFont font, float size, Vector4 colour, float wordSpacing = 0, float charSpacing = 0, float skew = 0, float weight = 0.5F, float width = 1)
{
}
diff --git a/ArgonUI.Backends.OpenGL/ArgonUI.Backends.OpenGL.csproj b/ArgonUI.Backends.OpenGL/ArgonUI.Backends.OpenGL.csproj
index 277a5d1..5c81afd 100644
--- a/ArgonUI.Backends.OpenGL/ArgonUI.Backends.OpenGL.csproj
+++ b/ArgonUI.Backends.OpenGL/ArgonUI.Backends.OpenGL.csproj
@@ -7,7 +7,7 @@
disable
enable
ArgonUI OpenGL Backend
- 0.3.1-pre
+ 0.4.1-pre
Thomas Mathieson
Copyright © Thomas Mathieson 2025
https://github.com/space928/ArgonUI
diff --git a/ArgonUI.Backends.OpenGL/OpenGLDrawContext.cs b/ArgonUI.Backends.OpenGL/OpenGLDrawContext.cs
index 3de96df..8e00d9a 100644
--- a/ArgonUI.Backends.OpenGL/OpenGLDrawContext.cs
+++ b/ArgonUI.Backends.OpenGL/OpenGLDrawContext.cs
@@ -18,14 +18,8 @@ internal class OpenGLDrawContext : IDrawContext
private readonly GL gl;
private UIWindow? window;
private Vector2 resolution;
- private Shader? rectShader;
- private Shader? textShader;
- private Shader? textureShader;
- private VertexArrayObject? rectVAO;
- private VertexArrayObject? textVAO;
-
- private VertexArrayObject? activeVao;
- private Shader? activeShader;
+ private ShaderManager shaderManager;
+
private Texture2D? activeTexture;
private int vertPos;
private int vertCount;
@@ -37,6 +31,7 @@ public OpenGLDrawContext(GL gl)
{
this.gl = gl;
vertBuff = new uint[32*1024];
+ shaderManager = new();
}
#if DEBUG_LATENCY || DEBUG
@@ -53,11 +48,6 @@ public void MarkLatencyTimerEnd(string? msg = null)
public void InitRenderer(UIWindow window)
{
this.window = window;
- //try
- //{
- rectShader = new(gl, "ui_vert.glsl", "ui_frag.glsl", ["#define SUPPORT_ROUNDING", "#define SUPPORT_ALPHA"]);
- textShader = new(gl, "ui_vert.glsl", "ui_frag.glsl", ["#define SUPPORT_TEXT", "#define SUPPORT_ALPHA"]);
- textureShader = new(gl, "ui_vert.glsl", "ui_frag.glsl", ["#define SUPPORT_TEXTURE", "#define SUPPORT_ROUNDING", "#define SUPPORT_ALPHA"]);
gl.FrontFace(FrontFaceDirection.CW);
//gl.Enable(EnableCap.DepthTest);
@@ -73,8 +63,7 @@ public void InitRenderer(UIWindow window)
gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
gl.Enable(EnableCap.ScissorTest);
- InitRectVAO();
- InitTextVAO();
+ shaderManager.Init(gl);
//vao = new(gl, new BufferObject(gl, [], BufferTargetARB.ArrayBuffer), null);
/*}
catch (Exception ex)
@@ -85,12 +74,7 @@ public void InitRenderer(UIWindow window)
public void Dispose()
{
- rectVAO?.Dispose();
- textVAO?.Dispose();
- //vao?.Dispose();
- rectShader?.Dispose();
- textShader?.Dispose();
- textureShader?.Dispose();
+ shaderManager.Dispose();
gl.Dispose();
}
@@ -141,95 +125,177 @@ public void Clear(Vector4 colour)
///
/// Checks if the vertex buffer has enough space for this draw.
///
- ///
- ///
+ /// The type of vertex to check space for.
+ /// The number of vertices to check for space for.
/// if the batch needs to be flushed.
- private bool CheckSpace(int elems)
+ private bool CheckSpace(int elems)
{
- return (vertBuff.Length * Unsafe.SizeOf()) - vertPos < Unsafe.SizeOf() * elems;
+ return (vertBuff.Length * Unsafe.SizeOf()) - vertPos < Unsafe.SizeOf() * elems;
}
- private void InitRectVAO()
+ #region Rectangles
+ public void DrawRect(Bounds2D bounds, Vector4 colour, float rounding)
{
- rectVAO?.Dispose();
-
- VertexArrayObject vao = new(gl, new BufferObject(gl, [], BufferTargetARB.ArrayBuffer), null);
- vao.Bind();
- vao.VertexAttributePointer(0, 2, VertexAttribType.Float, 11, 0);
- vao.VertexAttributePointer(1, 4, VertexAttribType.Float, 11, 2);
- vao.VertexAttributePointer(3, 2, VertexAttribType.Float, 11, 6);
- vao.VertexAttributePointer(4, 3, VertexAttribType.Float, 11, 8);
- vao.Unbind();
- rectVAO = vao;
- }
+ ShaderFeature shaderFeatures = ShaderFeature.Rounding | ShaderFeature.Alpha;
+ if (!shaderManager.IsShaderActive(shaderFeatures))
+ {
+ FlushBatch();
+ shaderManager.SetActiveShader(gl, shaderFeatures);
+ }
- private void InitTextVAO()
- {
- textVAO?.Dispose();
-
- VertexArrayObject vao = new(gl, new BufferObject(gl, [], BufferTargetARB.ArrayBuffer), null);
- vao.Bind();
- vao.VertexAttributePointer(0, 2, VertexAttribType.Float, 9, 0);
- vao.VertexAttributePointer(1, 4, VertexAttribType.Float, 9, 2);
- vao.VertexAttributePointer(2, 3, VertexAttribType.Float, 9, 6);
- vao.Unbind();
- textVAO = vao;
+ if (CheckSpace(6))
+ FlushBatch();
+
+ Vector4 rectProps = new(bounds.Size, rounding, 0);
+
+ var writer = vertBuff.GetBinaryWriter(vertPos);
+ writer.Write(new RectRoundVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colour, new(0, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colour, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colour, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colour, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colour, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colour, new(1, 0), rectProps));
+ vertCount += 6;
+ vertPos = writer.Offset;
}
public void DrawGradient(Bounds2D bounds, Vector4 colourA, Vector4 colourB, Vector4 colourC, Vector4 colourD, float rounding)
{
- if (activeShader != rectShader || CheckSpace(6))
+ ShaderFeature shaderFeatures = ShaderFeature.Rounding | ShaderFeature.Alpha;
+ if (!shaderManager.IsShaderActive(shaderFeatures))
+ {
+ FlushBatch();
+ shaderManager.SetActiveShader(gl, shaderFeatures);
+ }
+
+ if (CheckSpace(6))
FlushBatch();
- if (activeShader != rectShader)
+ Vector4 rectProps = new(bounds.Size, rounding, 0);
+
+ var writer = vertBuff.GetBinaryWriter(vertPos);
+ writer.Write(new RectRoundVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colourB, new(0, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colourA, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colourD, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colourD, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colourA, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colourC, new(1, 0), rectProps));
+ vertCount += 6;
+ vertPos = writer.Offset;
+ }
+
+ public void DrawShadow(Bounds2D bounds, Vector4 colour, float rounding, float blur)
+ {
+ ShaderFeature shaderFeatures = ShaderFeature.Rounding | ShaderFeature.Blur | ShaderFeature.Alpha;
+ if (!shaderManager.IsShaderActive(shaderFeatures))
{
- activeShader = rectShader;
- activeVao = rectVAO;
- activeTexture = null;
+ FlushBatch();
+ shaderManager.SetActiveShader(gl, shaderFeatures);
}
- Vector3 rectProps = new(bounds.Width, bounds.Height, rounding);
+
+ if (CheckSpace(6))
+ FlushBatch();
+
+ Vector4 rectProps = new(bounds.Size, rounding, blur);
var writer = vertBuff.GetBinaryWriter(vertPos);
- writer.Write(new RectVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colourB, new(0, 1), rectProps));
- writer.Write(new RectVert(bounds.topLeft, colourA, new(0, 0), rectProps));
- writer.Write(new RectVert(bounds.bottomRight, colourD, new(1, 1), rectProps));
- writer.Write(new RectVert(bounds.bottomRight, colourD, new(1, 1), rectProps));
- writer.Write(new RectVert(bounds.topLeft, colourA, new(0, 0), rectProps));
- writer.Write(new RectVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colourC, new(1, 0), rectProps));
+ writer.Write(new RectRoundVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colour, new(0, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colour, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colour, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colour, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colour, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colour, new(1, 0), rectProps));
vertCount += 6;
vertPos = writer.Offset;
}
- public void DrawRect(Bounds2D bounds, Vector4 colour, float rounding)
+ public void DrawOutlineRect(Bounds2D bounds, Vector4 colour, float outlineThickness, float rounding)
{
- if (activeShader != rectShader || CheckSpace(6))
+ ShaderFeature shaderFeatures = ShaderFeature.Outline | ShaderFeature.Rounding | ShaderFeature.Alpha;
+ if (!shaderManager.IsShaderActive(shaderFeatures))
+ {
FlushBatch();
+ shaderManager.SetActiveShader(gl, shaderFeatures);
+ }
+
+ if (CheckSpace(6))
+ FlushBatch();
+
+ Vector4 rectProps = new(bounds.Size, rounding, outlineThickness);
+
+ var writer = vertBuff.GetBinaryWriter(vertPos);
+ writer.Write(new RectRoundVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colour, new(0, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colour, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colour, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colour, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colour, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colour, new(1, 0), rectProps));
+ vertCount += 6;
+ vertPos = writer.Offset;
+ }
- if (activeShader != rectShader)
+ public void DrawOutlineGradient(Bounds2D bounds, Vector4 colourA, Vector4 colourB, Vector4 colourC, Vector4 colourD, float outlineThickness, float rounding)
+ {
+ ShaderFeature shaderFeatures = ShaderFeature.Outline | ShaderFeature.Rounding | ShaderFeature.Alpha;
+ if (!shaderManager.IsShaderActive(shaderFeatures))
{
- activeShader = rectShader;
- activeVao = rectVAO;
- activeTexture = null;
+ FlushBatch();
+ shaderManager.SetActiveShader(gl, shaderFeatures);
}
- Vector3 rectProps = new(bounds.Width, bounds.Height, rounding);
+ if (CheckSpace(6))
+ FlushBatch();
+
+ Vector4 rectProps = new(bounds.Size, rounding, 0);
var writer = vertBuff.GetBinaryWriter(vertPos);
- writer.Write(new RectVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colour, new(0, 1), rectProps));
- writer.Write(new RectVert(bounds.topLeft, colour, new(0, 0), rectProps));
- writer.Write(new RectVert(bounds.bottomRight, colour, new(1, 1), rectProps));
- writer.Write(new RectVert(bounds.bottomRight, colour, new(1, 1), rectProps));
- writer.Write(new RectVert(bounds.topLeft, colour, new(0, 0), rectProps));
- writer.Write(new RectVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colour, new(1, 0), rectProps));
+ writer.Write(new RectRoundVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colourB, new(0, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colourA, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colourD, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colourD, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colourA, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colourC, new(1, 0), rectProps));
vertCount += 6;
vertPos = writer.Offset;
}
- public void DrawShadow(Bounds2D bounds, Vector4 colour, float rounding, float blur)
+ public void DrawTexture(Bounds2D bounds, ITextureHandle texture, float rounding)
{
- throw new NotImplementedException();
+ if (texture is not Texture2D tex)
+ throw new NotSupportedException("Attempted to call DrawTexture with an incompatible texture object!");
+
+ ShaderFeature shaderFeatures = ShaderFeature.Rounding | ShaderFeature.Alpha | ShaderFeature.Texture;
+ if (!shaderManager.IsShaderActive(shaderFeatures))
+ {
+ FlushBatch();
+ shaderManager.SetActiveShader(gl, shaderFeatures);
+ }
+
+ if (CheckSpace(6))
+ FlushBatch();
+
+ if (activeTexture != tex)
+ {
+ FlushBatch();
+ activeTexture = tex;
+ }
+
+ Vector4 rectProps = new(bounds.Size, rounding, 0);
+ var colour = Vector4.One;
+
+ var writer = vertBuff.GetBinaryWriter(vertPos);
+ writer.Write(new RectRoundVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colour, new(0, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colour, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colour, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.bottomRight, colour, new(1, 1), rectProps));
+ writer.Write(new RectRoundVert(bounds.topLeft, colour, new(0, 0), rectProps));
+ writer.Write(new RectRoundVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colour, new(1, 0), rectProps));
+ vertCount += 6;
+ vertPos = writer.Offset;
}
+ #endregion
+ #region Text
public void DrawChar(Bounds2D bounds, float size, char c, BMFont font, Vector4 colour)
{
float fontSize = size / font.Size;
@@ -254,7 +320,7 @@ public void DrawText(Bounds2D bounds, float size, string s, BMFont font, Vector4
}
}
- public void DrawText(Bounds2D bounds, float size, string s, BMFont font, Vector4 colour,
+ public void DrawText(Bounds2D bounds, ReadOnlySpan s, BMFont font, float size, Vector4 colour,
float wordSpacing = 0, float charSpacing = 0, float skew = 0, float weight = 0.5F, float width = 1)
{
var tex = font.FontTexture?.TextureHandle;
@@ -290,24 +356,22 @@ private float DrawCharInternal(in Vector2 pos, float size, float skew, float wei
if (tex.Width == 0 || tex.Height == 0)
return 0f;
- if (activeShader != textShader || CheckSpace(6))
- FlushBatch();
-
- if (activeShader != textShader)
+ ShaderFeature shaderFeatures = ShaderFeature.Text | ShaderFeature.Alpha;
+ if (!shaderManager.IsShaderActive(shaderFeatures))
{
- activeShader = textShader;
- textShader?.Use();
- textShader?.SetUniform("uFontTex", 0);
- activeVao = textVAO;
+ FlushBatch();
+ shaderManager.SetActiveShader(gl, shaderFeatures);
}
+ if (CheckSpace(6))
+ FlushBatch();
+
if (activeTexture != tex)
{
FlushBatch();
activeTexture = tex;
}
- float size_x = size * width;
Vector2 fontSizeVec = new(size * width, size);
Vector2 tl = c.offset * fontSizeVec;
Vector2 br = (c.offset + c.size) * fontSizeVec;
@@ -329,58 +393,66 @@ private float DrawCharInternal(in Vector2 pos, float size, float skew, float wei
return c.xAdvance * fontSizeVec.X;
}
+ #endregion
- public void DrawTexture(Bounds2D bounds, ITextureHandle texture, float rounding)
+ #region Lines & Polys
+ public void DrawLine(Vector2 start, Vector2 end, Vector4 colourStart, Vector4 colourEnd, float thickness)
{
- if (texture is not Texture2D tex)
- throw new NotSupportedException("Attempted to call DrawTexture with an incompatible texture object!");
-
- if (activeShader != textShader || CheckSpace(6))
- FlushBatch();
-
- if (activeShader != textureShader)
+ ShaderFeature shaderFeatures = ShaderFeature.Alpha;
+ if (!shaderManager.IsShaderActive(shaderFeatures))
{
- activeShader = textureShader;
- textureShader?.Use();
- textureShader?.SetUniform("uMainTex", 0);
- activeVao = rectVAO;
+ FlushBatch();
+ shaderManager.SetActiveShader(gl, shaderFeatures);
}
- if (activeTexture != tex)
- {
+ if (CheckSpace(6))
FlushBatch();
- activeTexture = tex;
- }
- Vector3 rectProps = new(bounds.Width, bounds.Height, rounding);
- var colour = Vector4.One;
+ Vector2 dir = end - start;
+ dir = Vector2.Normalize(dir);
+ Vector2 perp = new Vector2(-dir.Y, dir.X) * thickness;
+ Vector2 a_0 = start + perp;
+ Vector2 a_1 = start - perp;
+ Vector2 b_0 = end + perp;
+ Vector2 b_1 = end - perp;
var writer = vertBuff.GetBinaryWriter(vertPos);
- writer.Write(new RectVert(new(bounds.topLeft.X, bounds.bottomRight.Y), colour, new(0, 1), rectProps));
- writer.Write(new RectVert(bounds.topLeft, colour, new(0, 0), rectProps));
- writer.Write(new RectVert(bounds.bottomRight, colour, new(1, 1), rectProps));
- writer.Write(new RectVert(bounds.bottomRight, colour, new(1, 1), rectProps));
- writer.Write(new RectVert(bounds.topLeft, colour, new(0, 0), rectProps));
- writer.Write(new RectVert(new(bounds.bottomRight.X, bounds.topLeft.Y), colour, new(1, 0), rectProps));
+ writer.Write(new RectVert(a_0, colourStart, new(0, 1)));
+ writer.Write(new RectVert(a_1, colourStart, new(0, 0)));
+ writer.Write(new RectVert(b_0, colourEnd, new(1, 1)));
+ writer.Write(new RectVert(b_0, colourEnd, new(1, 1)));
+ writer.Write(new RectVert(a_1, colourStart, new(0, 0)));
+ writer.Write(new RectVert(b_1, colourEnd, new(1, 0)));
vertCount += 6;
vertPos = writer.Offset;
}
+ public void DrawPolyFill(IEnumerable points)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void DrawPolyLine(IEnumerable points, float thickness)
+ {
+ throw new NotImplementedException();
+ }
+ #endregion
+
public void FlushBatch()
{
if (vertPos == 0)
return;
- if (activeShader != null)
+ if (shaderManager.ActiveShader != null)
{
- activeShader.Use();
+ //shaderManager.ActiveShader.Use();
- activeShader.SetUniform("uResolution", resolution);
+ shaderManager.ActiveShader.SetUniform("uResolution", resolution);
// Set all other program properties...
// Bind textures and stuff
activeTexture?.Bind(0);
- var vao = activeVao!;
+ var vao = shaderManager.ActiveVAO!;
vao.VBO.Update(vertBuff.AsSpan(0, vertPos >> 2));
@@ -402,24 +474,4 @@ public ITextureHandle LoadTexture(TextureData data, string? name = null, Texture
{
return Texture2DLoader.LoadTexture(gl, name, data, compression);
}
-
- [StructLayout(LayoutKind.Explicit)]
- readonly struct RectVert(Vector2 pos, Vector4 col, Vector2 texcoord, Vector3 rounding)
- {
- [FieldOffset(0)] public readonly Vector2 pos = pos;
- [FieldOffset(0x8)] public readonly Vector4 col = col;
- [FieldOffset(0x18)] public readonly Vector2 texcoord = texcoord;
- [FieldOffset(0x20)] public readonly Vector3 rounding = rounding;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- readonly struct CharVert(Vector2 pos, Vector4 col, Vector3 charData)
- {
- [FieldOffset(0)] public readonly Vector2 pos = pos;
- [FieldOffset(0x8)] public readonly Vector4 col = col;
- ///
- /// The uv coordinates into the font texture are stored in xy, and z stores the font weight.
- ///
- [FieldOffset(0x18)] public readonly Vector3 charData = charData;
- }
}
diff --git a/ArgonUI.Backends.OpenGL/Shader.cs b/ArgonUI.Backends.OpenGL/Shader.cs
index 15819ca..1e1a76d 100644
--- a/ArgonUI.Backends.OpenGL/Shader.cs
+++ b/ArgonUI.Backends.OpenGL/Shader.cs
@@ -18,7 +18,7 @@ public partial class Shader : IDisposable
private readonly StringDict uniformLocationCache = [];//Dictionary uniformLocationCache = [];
- public Shader(GL gl, string vertexPath, string fragmentPath, string[]? defines = null)
+ public Shader(GL gl, string vertexPath, string fragmentPath, ICollection? defines = null)
{
this.gl = gl;
@@ -112,7 +112,7 @@ public void Dispose()
gl.DeleteProgram(handle);
}
- private uint LoadShader(ShaderType type, string path, string[]? defines)
+ private uint LoadShader(ShaderType type, string path, ICollection? defines)
{
string src;
try
@@ -146,9 +146,9 @@ private uint LoadShader(ShaderType type, string path, string[]? defines)
///
///
///
- private static string ApplyDefines(string source, string[]? defines)
+ private static string ApplyDefines(string source, ICollection? defines)
{
- if (defines == null || defines.Length == 0)
+ if (defines == null || defines.Count == 0)
return source;
StringBuilder sb = new(source.Length);
diff --git a/ArgonUI.Backends.OpenGL/ShaderManager.cs b/ArgonUI.Backends.OpenGL/ShaderManager.cs
new file mode 100644
index 0000000..72f70c0
--- /dev/null
+++ b/ArgonUI.Backends.OpenGL/ShaderManager.cs
@@ -0,0 +1,231 @@
+using ArgonUI.Helpers;
+using Silk.NET.OpenGL;
+using System;
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.IO;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace ArgonUI.Backends.OpenGL;
+
+internal class ShaderManager : IDisposable
+{
+ private readonly Dictionary shaders = [];
+ private readonly Dictionary> vaos = [];
+ private readonly FrozenDictionary shaderFeatureDefines;
+
+ public ShaderFeature ActiveFeatures { get; private set; }
+ public Shader? ActiveShader { get; private set; }
+ public VertexArrayObject? ActiveVAO { get; private set; }
+
+ public readonly ShaderFeature[] PRECOMPILED_SHADERS = [
+ ShaderFeature.Text | ShaderFeature.Alpha,
+ ShaderFeature.Rounding | ShaderFeature.Alpha,
+ ShaderFeature.Outline | ShaderFeature.Rounding | ShaderFeature.Alpha,
+ ShaderFeature.Texture | ShaderFeature.Rounding | ShaderFeature.Alpha,
+ ShaderFeature.Blur | ShaderFeature.Rounding | ShaderFeature.Alpha,
+ ];
+
+ public ShaderManager()
+ {
+ Dictionary shaderFeatureStrings = [];
+ shaderFeatureStrings.Add(ShaderFeature.Text, "#define SUPPORT_TEXT");
+ shaderFeatureStrings.Add(ShaderFeature.Texture, "#define SUPPORT_TEXTURE");
+ shaderFeatureStrings.Add(ShaderFeature.Outline, "#define SUPPORT_OUTLINE");
+ shaderFeatureStrings.Add(ShaderFeature.TextShadow, "#define SUPPORT_TEXT_SHADOW");
+ shaderFeatureStrings.Add(ShaderFeature.Alpha, "#define SUPPORT_ALPHA");
+ shaderFeatureStrings.Add(ShaderFeature.Rounding, "#define SUPPORT_ROUNDING");
+ shaderFeatureStrings.Add(ShaderFeature.Blur, "#define SUPPORT_BLUR");
+ shaderFeatureDefines = shaderFeatureStrings.ToFrozenDictionary();
+ }
+
+ public void Init(GL gl)
+ {
+ foreach (var shader in shaders.Values)
+ shader.Dispose();
+ foreach (var vao in vaos.Values)
+ vao.Dispose();
+ shaders.Clear();
+ vaos.Clear();
+
+ foreach (var precompile in PRECOMPILED_SHADERS)
+ SetActiveShader(gl, precompile);
+ }
+
+ public bool IsShaderActive(ShaderFeature features) => features == ActiveFeatures;
+
+ ///
+ /// Sets the active shader to one which supports the given features. Compiles and caches a new shader if needed.
+ ///
+ /// Also updates the active VAO to match.
+ ///
+ ///
+ /// The required shader features.
+ /// if the shader was changed.
+ ///
+ public bool SetActiveShader(GL gl, ShaderFeature features)
+ {
+ if (features == ActiveFeatures)
+ return false;
+
+ VertexArrayObject? vao;
+ var vertLayout = GetVertexLayout(features);
+ if (shaders.TryGetValue(features, out var shader))
+ {
+ vao = vaos[vertLayout];
+ goto ReturnActiveShader;
+ }
+
+ shader = InitShader(gl, features);
+
+ if (!vaos.TryGetValue(vertLayout, out vao))
+ {
+ vao = vertLayout switch
+ {
+ ShaderVertexLayout.Rect => InitRectVAO(gl),
+ ShaderVertexLayout.RectRound => InitRectRoundVAO(gl),
+ ShaderVertexLayout.Char => InitTextVAO(gl),
+ _ => throw new NotImplementedException(),
+ };
+ vaos.Add(vertLayout, vao);
+ }
+
+ shaders.Add(features, shader);
+
+ ReturnActiveShader:
+ shader.Use();
+ ActiveFeatures = features;
+ ActiveShader = shader;
+ ActiveVAO = vao;
+
+ if ((features & ShaderFeature.Text) != 0)
+ shader?.SetUniform("uFontTex", 0);
+ if ((features & ShaderFeature.Texture) != 0)
+ shader?.SetUniform("uMainTex", 0);
+
+ return true;
+ }
+
+ private static ShaderVertexLayout GetVertexLayout(ShaderFeature features)
+ {
+ // TODO: This is likely to cause bugs, many features share vertex layouts
+ if ((features & ShaderFeature.Text) != 0)
+ return ShaderVertexLayout.Char;
+ if ((features & ShaderFeature.Rounding) != 0)
+ return ShaderVertexLayout.RectRound;
+ return ShaderVertexLayout.Rect;
+ }
+
+ private Shader InitShader(GL gl, ShaderFeature features)
+ {
+ // Build a list of defines to add to the shader
+ using TemporaryList featuresList = [];
+ for (int i = 1; i < (int)ShaderFeature.MAX_VALUE; i <<= 1)
+ if (((int)features & i) != 0)
+ featuresList.Add(shaderFeatureDefines[(ShaderFeature)i]);
+
+ return new(gl, "ui_vert.glsl", "ui_frag.glsl", featuresList);
+ }
+
+ private static VertexArrayObject InitRectVAO(GL gl)
+ {
+ VertexArrayObject vao = new(gl, new BufferObject(gl, [], BufferTargetARB.ArrayBuffer), null);
+ vao.Bind();
+ vao.VertexAttributePointer(0, 2, VertexAttribType.Float, 8, 0);
+ vao.VertexAttributePointer(1, 4, VertexAttribType.Float, 8, 2);
+ vao.VertexAttributePointer(3, 2, VertexAttribType.Float, 8, 6);
+ vao.Unbind();
+ return vao;
+ }
+
+ private static VertexArrayObject InitRectRoundVAO(GL gl)
+ {
+ VertexArrayObject vao = new(gl, new BufferObject(gl, [], BufferTargetARB.ArrayBuffer), null);
+ vao.Bind();
+ vao.VertexAttributePointer(0, 2, VertexAttribType.Float, 12, 0);
+ vao.VertexAttributePointer(1, 4, VertexAttribType.Float, 12, 2);
+ vao.VertexAttributePointer(3, 2, VertexAttribType.Float, 12, 6);
+ vao.VertexAttributePointer(4, 4, VertexAttribType.Float, 12, 8);
+ vao.Unbind();
+ return vao;
+ }
+
+ private static VertexArrayObject InitTextVAO(GL gl)
+ {
+ VertexArrayObject vao = new(gl, new BufferObject(gl, [], BufferTargetARB.ArrayBuffer), null);
+ vao.Bind();
+ vao.VertexAttributePointer(0, 2, VertexAttribType.Float, 9, 0);
+ vao.VertexAttributePointer(1, 4, VertexAttribType.Float, 9, 2);
+ vao.VertexAttributePointer(2, 3, VertexAttribType.Float, 9, 6);
+ vao.Unbind();
+ return vao;
+ }
+
+ public void Dispose()
+ {
+ foreach (var shader in shaders.Values)
+ shader.Dispose();
+ foreach (var vao in vaos.Values)
+ vao.Dispose();
+ shaders.Clear();
+ vaos.Clear();
+ }
+}
+
+[Flags]
+internal enum ShaderFeature : int
+{
+ None = 0,
+ Text = 1 << 0,
+ Texture = 1 << 1,
+ Outline = 1 << 2,
+ TextShadow = 1 << 3,
+ Alpha = 1 << 4,
+ Rounding = 1 << 5,
+ Blur = 1 << 6,
+
+ ///
+ /// Used internally to count the number of possible features, do not move.
+ ///
+ MAX_VALUE
+}
+
+internal enum ShaderVertexLayout
+{
+ Rect,
+ RectRound,
+ Char
+}
+
+[StructLayout(LayoutKind.Explicit)]
+readonly struct RectVert(Vector2 pos, Vector4 col, Vector2 texcoord)
+{
+ [FieldOffset(0)] public readonly Vector2 pos = pos;
+ [FieldOffset(0x8)] public readonly Vector4 col = col;
+ [FieldOffset(0x18)] public readonly Vector2 texcoord = texcoord;
+}
+
+[StructLayout(LayoutKind.Explicit)]
+readonly struct RectRoundVert(Vector2 pos, Vector4 col, Vector2 texcoord, Vector4 rounding)
+{
+ [FieldOffset(0)] public readonly Vector2 pos = pos;
+ [FieldOffset(0x8)] public readonly Vector4 col = col;
+ [FieldOffset(0x18)] public readonly Vector2 texcoord = texcoord;
+ ///
+ /// (XY: The size of the rectangle in pixels, Z: The rounding radius in pixels, W: The blur radius in pixels)
+ ///
+ [FieldOffset(0x20)] public readonly Vector4 rounding = rounding;
+}
+
+[StructLayout(LayoutKind.Explicit)]
+readonly struct CharVert(Vector2 pos, Vector4 col, Vector3 charData)
+{
+ [FieldOffset(0)] public readonly Vector2 pos = pos;
+ [FieldOffset(0x8)] public readonly Vector4 col = col;
+ ///
+ /// The uv coordinates into the font texture are stored in xy, and z stores the font weight.
+ ///
+ [FieldOffset(0x18)] public readonly Vector3 charData = charData;
+}
diff --git a/ArgonUI.Backends.OpenGL/Shaders/ui_frag.glsl b/ArgonUI.Backends.OpenGL/Shaders/ui_frag.glsl
index 4ea0724..d137c75 100644
--- a/ArgonUI.Backends.OpenGL/Shaders/ui_frag.glsl
+++ b/ArgonUI.Backends.OpenGL/Shaders/ui_frag.glsl
@@ -1,24 +1,37 @@
#version 420 core
+#ifdef SUPPORT_OUTLINE
+#if !defined(SUPPORT_ROUNDING)
+#define SUPPORT_ROUNDING
+#endif
+#endif
+
#ifdef SUPPORT_TEXT
uniform sampler2D uFontTex;
#endif // SUPPORT_TEXT
+
#ifdef SUPPORT_TEXTURE
uniform sampler2D uMainTex;
#endif // SUPPORT_TEXTURE
out vec4 fragColor;
+
layout(location = 0) in vec4 v_color;
+
#ifdef SUPPORT_TEXT
layout(location = 1) in vec3 v_char;
#endif // SUPPORT_TEXT
-#if defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING)
+
+#if defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING) || defined(SUPPORT_BLUR)
layout(location = 2) in vec2 v_texcoord;
#endif // defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING)
-#ifdef SUPPORT_ROUNDING
-layout(location = 3) in flat vec3 v_size;
+
+#if defined(SUPPORT_ROUNDING) || defined(SUPPORT_BLUR)
+layout(location = 3) in flat vec4 v_size;
#endif // SUPPORT_ROUNDING
+
+
#ifdef SUPPORT_TEXT
float median(vec3 x) {
return max(min(x.r, x.g), min(max(x.r, x.g), x.b));
@@ -46,6 +59,14 @@ float hint(float mask, float sdf) {
}
#endif // SUPPORT_TEXT
+#ifdef SUPPORT_BLUR
+float smootherstep(float edge0, float edge1, float x) {
+ x = clamp((x - edge0) / (edge1 - edge0), 0., 1.);
+
+ return x * x * x * (x * (6. * x - 15.) + 10.);
+}
+#endif // SUPPORT_BLUR
+
void main() {
fragColor = v_color;
@@ -59,14 +80,14 @@ void main() {
// Heuristic based hinting works, but has artifacts...
//mask = hint(mask, sdf);
fragColor.a *= mask;
- #ifdef SUPPORT_SHADOW
+ #ifdef SUPPORT_TEXT_SHADOW
float shadow_sdf = median(texture(uFontTex, v_char.xy-0.01).rgb);
float shadow = smoothstep(max(v_char.z - smoothing*2., 0.05), min(v_char.z + smoothing*2., 0.95), shadow_sdf);
float shadow_exp = smoothstep(max(v_char.z-.2 - smoothing, 0.05), min(v_char.z-.2 + smoothing, 0.95), shadow_sdf);
float shadow_alpha = max(shadow_exp-fragColor.a, 0.);
fragColor.rgb = (fragColor.rgb*0.2)*shadow_alpha + fragColor.rgb*(1.-shadow_alpha);
fragColor.a = min(fragColor.a+shadow*0.75, 1.);
- #endif // SUPPORT_SHADOW
+ #endif // SUPPORT_TEXT_SHADOW
#endif // SUPPORT_TEXT
#ifdef SUPPORT_TEXTURE
@@ -74,14 +95,28 @@ void main() {
#endif // SUPPORT_TEXTURE
#ifdef SUPPORT_ROUNDING
+ #ifdef SUPPORT_BLUR
+ float blur = v_size.w;
+ #else
+ float blur = 0.;
+ #endif
float radius = v_size.z;
radius = min(radius, min(v_size.x, v_size.y)/4.);
vec2 uv = v_texcoord * 2. - 1.;
- vec2 r = abs(uv*v_size.xy/4.) - v_size.xy/4. + radius;
+ vec2 r = abs(uv*v_size.xy/4.) - v_size.xy/4. + radius + blur;
float mask = length(max(r, 0.)) + min(max(r.x, r.y), 0.0) - radius;
+
+ // Apply either the blur or the rounding
+ #ifdef SUPPORT_BLUR
+ mask = max(mask/blur, 0.);
+ fragColor.a *= exp(-(mask*mask*2.)) * (1.-mask*mask*mask);
+ #else
fragColor.a *= smoothstep(0.5, -.25, mask);
+ #endif // SUPPORT_BLUR
+
#ifdef SUPPORT_OUTLINE
- fragColor.rgb *= smoothstep(0.4, 1., abs(mask))*.5+.5;
+ float outlineThick = v_size.w;
+ fragColor.a = smoothstep(outlineThick, outlineThick-1., abs(mask+outlineThick));
#endif // SUPPORT_OUTLINE
#endif // SUPPORT_ROUNDING
diff --git a/ArgonUI.Backends.OpenGL/Shaders/ui_vert.glsl b/ArgonUI.Backends.OpenGL/Shaders/ui_vert.glsl
index 61d11ab..1b90ff5 100644
--- a/ArgonUI.Backends.OpenGL/Shaders/ui_vert.glsl
+++ b/ArgonUI.Backends.OpenGL/Shaders/ui_vert.glsl
@@ -2,6 +2,12 @@
uniform vec2 uResolution;
+#ifdef SUPPORT_OUTLINE
+#if !defined(SUPPORT_ROUNDING)
+#define SUPPORT_ROUNDING
+#endif
+#endif
+
layout(location = 0) in vec2 in_vert;
layout(location = 1) in vec4 in_color;
layout(location = 0) out vec4 v_color;
@@ -10,16 +16,16 @@ layout(location = 0) out vec4 v_color;
layout(location = 2) in vec3 in_char;
layout(location = 1) out vec3 v_char;
#endif // SUPPORT_TEXT
-#if defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING)
+#if defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING) || defined(SUPPORT_BLUR)
layout(location = 3) in vec2 in_texcoord;
layout(location = 2) out vec2 v_texcoord;
-#endif // defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING)
-#ifdef SUPPORT_ROUNDING
+#endif // defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING) || defined(SUPPORT_BLUR)
+#if defined(SUPPORT_ROUNDING) || defined(SUPPORT_BLUR)
// To get the correct aspect ratio, in_size.xy stores the size of the rect to be rounded in pixels
// in_size.z stores the rounding radius in pixels
-layout(location = 4) in vec3 in_size;
-layout(location = 3) out flat vec3 v_size;
-#endif // T_SUPPORT_ROUNDING
+layout(location = 4) in vec4 in_size;
+layout(location = 3) out flat vec4 v_size;
+#endif // SUPPORT_ROUNDING || SUPPORT_BLUR
void main() {
gl_Position = vec4((in_vert/uResolution.xy)*2.-1., -0.9, 1.0);
@@ -28,10 +34,10 @@ void main() {
#ifdef SUPPORT_TEXT
v_char = in_char;
#endif // SUPPORT_TEXT
- #if defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING)
+ #if defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING) || defined(SUPPORT_BLUR)
v_texcoord = in_texcoord;
- #endif // defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING)
- #ifdef SUPPORT_ROUNDING
+ #endif // defined(SUPPORT_TEXTURE) || defined(SUPPORT_ROUNDING) || defined(SUPPORT_BLUR)
+ #if defined(SUPPORT_ROUNDING) || defined(SUPPORT_BLUR)
v_size = in_size;
- #endif // SUPPORT_ROUNDING
+ #endif // SUPPORT_ROUNDING || SUPPORT_BLUR
}
diff --git a/ArgonUI.Backends.OpenGL/Texture2D.cs b/ArgonUI.Backends.OpenGL/Texture2D.cs
index 80eafd5..333b7db 100644
--- a/ArgonUI.Backends.OpenGL/Texture2D.cs
+++ b/ArgonUI.Backends.OpenGL/Texture2D.cs
@@ -31,6 +31,7 @@ public class Texture2D : ITextureHandle, IDisposable
protected uint height;
private static uint currentTexture = 0;
+ private static uint currentUnit = uint.MaxValue;
private static Texture2D? missingTexture;
public Texture2D(GL gl, string? name = null)
@@ -117,9 +118,13 @@ public void Bind(uint unit)
return;
}
- gl.ActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + unit));
- gl.BindTexture(TextureTarget.Texture2D, handle);
- currentTexture = handle;
+ if (currentTexture != handle || currentUnit != unit)
+ {
+ gl.ActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + unit));
+ gl.BindTexture(TextureTarget.Texture2D, handle);
+ currentTexture = handle;
+ currentUnit = unit;
+ }
}
internal void BindInvalid()
diff --git a/ArgonUI.Docs/docfx.json b/ArgonUI.Docs/docfx.json
index c1cf42c..f8a0865 100644
--- a/ArgonUI.Docs/docfx.json
+++ b/ArgonUI.Docs/docfx.json
@@ -7,7 +7,8 @@
"files": [
//"**/ArgonUI.csproj"
"**/bin/**/ArgonUI*.dll"
- ]
+ ],
+ "exclude": "*_Styles.g.cs",
}
],
"dest": "api",
diff --git a/ArgonUI.Examples.DemoApp/Program.cs b/ArgonUI.Examples.DemoApp/Program.cs
index 48d5b35..772472e 100644
--- a/ArgonUI.Examples.DemoApp/Program.cs
+++ b/ArgonUI.Examples.DemoApp/Program.cs
@@ -16,23 +16,24 @@ public static void Main(string[] args)
wnd.RootElement.Style = new([
new Style([
- ArgonUIStyles.Rounding(10),
+ ArgonUIStyles.Rounding(5),
//ArgonUIStyles.FontSize(30),
+ ArgonUIStyles.TextColour(Vector4.One)
])
]);
StyleSet buttonStyle = new([
new Style([
- ArgonUIStyles.Rounding(10),
- ArgonUIStyles.Colour(new(0, 1.0f, 0.1f, 1)),
+ ArgonUIStyles.Rounding(5),
+ ArgonUIStyles.Colour(new(0.35f, 0.35f, 0.35f, 1)),
]),
new Style(new HoveredSelector(), [
- ArgonUIStyles.Rounding(5),
- ArgonUIStyles.Colour(new(1, .1f, 0, 1)),
+ ArgonUIStyles.Rounding(10),
+ ArgonUIStyles.Colour(new(0.45f, 0.45f, 0.45f, 1)),
])
]);
- wnd.RootElement.BGColour = new(0, 0.5f, 1, 1);
+ wnd.RootElement.BGColour = new(0.2f, 0.23f, 0.25f, 1);
var stackPanel = new StackPanel();
stackPanel.InnerPadding = new(2);
@@ -53,23 +54,25 @@ public static void Main(string[] args)
int counter = 0;
- btn.OnMouseDown += (im, button) =>
+ btn.OnMouseDown += args =>
{
#if DEBUG_LATENCY
((OpenGLWindow)wnd).LogLatency(DateTime.UtcNow.Ticks, "rect OnMouseDown");
rect.logLatencyNow = true;
rect.Colour = (counter & 1) == 0 ? new(1, 0, 0, 1) : new(0, 0, 1, 1);
#endif
- //rect.Dirty(DirtyFlags.Content);
+ //rect.Dirty(DirtyFlag.Content);
//Console.WriteLine("Rectangle clicked!");
label.Text = $"Hello World! {counter++}";
- btn.Colour = new(0.7f, 0.05f, 0, 1);
+ btn.Colour = new(0.30f, 0.30f, 0.30f, 1);
+ args.Handled = true;
};
- btn.OnMouseUp += (im, button) =>
+ btn.OnMouseUp += args =>
{
btn.Colour = ((StylableProp)buttonStyle[1]["Colour"]).Value;
+ args.Handled = true;
};
- btn.OnMouseEnter += (im, pos) =>
+ btn.OnMouseEnter += args =>
{
#if DEBUG_LATENCY
((OpenGLWindow)wnd).LogLatency(DateTime.UtcNow.Ticks, "rect OnMouseEnter");
@@ -87,16 +90,18 @@ public static void Main(string[] args)
Button newButton = new();
newButton.Width = 20;
newButton.Height = 20;
+ newButton.HorizontalAlignment = Alignment.Centre;
+ newButton.VerticalAlignment = Alignment.Centre;
((Label)newButton.Content!).FontSize = 8;
newButton.Style = buttonStyle;
newStackPanel.AddChild(newButton);
- newButton.OnMouseEnter += (im, pos) =>
+ newButton.OnMouseEnter += args =>
{
newButton.Width = 24;
newButton.Height = 24;
};
- newButton.OnMouseLeave += (im, pos) =>
+ newButton.OnMouseLeave += args =>
{
newButton.Width = 20;
newButton.Height = 20;
diff --git a/ArgonUI.SourceGenerator.Test/ArgonUI.SourceGenerator.Test.csproj b/ArgonUI.SourceGenerator.Test/ArgonUI.SourceGenerator.Test.csproj
index 684c7ab..dd00ccf 100644
--- a/ArgonUI.SourceGenerator.Test/ArgonUI.SourceGenerator.Test.csproj
+++ b/ArgonUI.SourceGenerator.Test/ArgonUI.SourceGenerator.Test.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/ArgonUI.SourceGenerator.Test/Program.cs b/ArgonUI.SourceGenerator.Test/Program.cs
index 926cab5..c45085e 100644
--- a/ArgonUI.SourceGenerator.Test/Program.cs
+++ b/ArgonUI.SourceGenerator.Test/Program.cs
@@ -1,5 +1,5 @@
using ArgonUI.Styling;
-using ArgonUI.UIElements;
+using ArgonUI.UIElements.Abstract;
using System.Numerics;
namespace ArgonUI.SourceGenerator.Test;
@@ -15,14 +15,14 @@ static void Main(string[] args)
[UIClonable]
public partial class ReactiveTest : UIElement
{
- [Reactive("SpecialExample")] private int exampleValue;
+ [Reactive("SpecialExample"), Stylable] private uint exampleValue;
[Reactive(propName: "SpecialExample1")] private int exampleValue1;
- [Reactive, Dirty(DirtyFlags.ChildContent)] private UIElement? test2;
- [Reactive, Dirty(DirtyFlags.Layout), CustomGet(nameof(GetTest3))] private float test3;
+ [Reactive, Dirty(DirtyFlag.ChildContent)] private UIElement? test2;
+ [Reactive, Dirty(DirtyFlag.Layout), CustomGet(nameof(GetTest3))] private float test3;
///
/// An example property.
///
- [Reactive, Dirty(DirtyFlags.Layout), CustomSet(nameof(SetTest4)), Stylable] private float test4;
+ [Reactive, Dirty(DirtyFlag.Layout), CustomSet(nameof(SetTest4)), Stylable] private float test4;
///
/// A vector example.
///
@@ -68,7 +68,7 @@ internal static partial class Test_Styles
public abstract class UIElement : ReactiveObject
{
- private DirtyFlags dirtyFlag;
+ private DirtyFlag dirtyFlag;
public UIElement? Parent { get; set; }
@@ -76,16 +76,16 @@ public abstract class UIElement : ReactiveObject
/// Marks this element as dirty, forcing the UI engine to redraw this element and it's children when it's next dispatched.
///
/// Which to set.
- public virtual void Dirty(DirtyFlags flags)
+ public virtual void Dirty(DirtyFlag flags)
{
- UpdateProperty(ref dirtyFlag, dirtyFlag | flags, nameof(DirtyFlags));
+ UpdateProperty(ref dirtyFlag, dirtyFlag | flags, nameof(DirtyFlag));
// Propagate dirty flags up
- if ((flags & DirtyFlags.Layout) != 0)
- Parent?.Dirty(DirtyFlags.ChildLayout);
+ if ((flags & DirtyFlag.Layout) != 0)
+ Parent?.Dirty(DirtyFlag.ChildLayout);
- if ((flags & DirtyFlags.Content) != 0)
- Parent?.Dirty(DirtyFlags.ChildContent);
+ if ((flags & DirtyFlag.Content) != 0)
+ Parent?.Dirty(DirtyFlag.ChildContent);
}
public virtual UIElement Clone() => throw new NotImplementedException();
diff --git a/ArgonUI.SourceGenerator/ArgonUI.SourceGenerator.csproj b/ArgonUI.SourceGenerator/ArgonUI.SourceGenerator.csproj
index 0c23715..1f55b08 100644
--- a/ArgonUI.SourceGenerator/ArgonUI.SourceGenerator.csproj
+++ b/ArgonUI.SourceGenerator/ArgonUI.SourceGenerator.csproj
@@ -9,7 +9,7 @@
true
true
ArgonUI.SourceGenerator
- 0.3.18-pre
+ 0.3.24-pre
Thomas Mathieson
Copyright © Thomas Mathieson 2025
https://github.com/space928/ArgonUI
@@ -36,7 +36,7 @@
-
+
diff --git a/ArgonUI.SourceGenerator/MergeStylesGenerator.Emitter.cs b/ArgonUI.SourceGenerator/MergeStylesGenerator.Emitter.cs
index d8a1982..0819c90 100644
--- a/ArgonUI.SourceGenerator/MergeStylesGenerator.Emitter.cs
+++ b/ArgonUI.SourceGenerator/MergeStylesGenerator.Emitter.cs
@@ -75,7 +75,7 @@ private static void Emit(SourceProductionContext context, MergeStylesResult resu
using (sb.EnterCurlyBracket())
{
foreach (var styled in prop.StyledTypes)
- sb.AppendLine($"case {styled} _{styled}: _{styled}.{prop.Name} = typedProp.Value; break;");
+ sb.AppendLine($"case {styled.Namespace}.{styled.Type} _{styled.Type}: _{styled.Type}.{prop.Name} = typedProp.Value; break;");
sb.AppendLine($"default: break;");
//sb.AppendLine($"default: throw new InvalidOperationException(\"Can't set property '{prop.Name}' on element of type '\" + elem.GetType().Name + \"'\");");
}
diff --git a/ArgonUI.SourceGenerator/MergeStylesGenerator.Parser.cs b/ArgonUI.SourceGenerator/MergeStylesGenerator.Parser.cs
index f02fe98..977b77f 100644
--- a/ArgonUI.SourceGenerator/MergeStylesGenerator.Parser.cs
+++ b/ArgonUI.SourceGenerator/MergeStylesGenerator.Parser.cs
@@ -88,6 +88,7 @@ public IEnumerable Parse(EquatableArray Parse(EquatableArray ReactiveFields);
- public record MergedStylableProp(string Name, string Type, string? DocComment, EquatableArray StyledTypes);
- public record MergedStylablePropTemp(string Name, string Type, string? DocComment, List StyledTypes);
+ public record MergedStylableProp(string Name, string Type, string? DocComment, EquatableArray StyledTypes);
+ public record MergedStylablePropTemp(string Name, string Type, string? DocComment, List StyledTypes);
+ public record MergedStylablePropType(string Type, string Namespace);
}
diff --git a/ArgonUI.SourceGenerator/ReactiveObjectGenerator.Emitter.cs b/ArgonUI.SourceGenerator/ReactiveObjectGenerator.Emitter.cs
index ea17a87..3de7f8f 100644
--- a/ArgonUI.SourceGenerator/ReactiveObjectGenerator.Emitter.cs
+++ b/ArgonUI.SourceGenerator/ReactiveObjectGenerator.Emitter.cs
@@ -70,9 +70,9 @@ private static void Emit(SourceProductionContext context, ReactiveObjectResult r
prefix = "this.";
sb.AppendLine($"UpdateProperty(ref {prefix}{prop.FieldName}, value);");
}
- if (prop.DirtyFlags != UIElements.DirtyFlags.None)
+ if (prop.DirtyFlags != UIElements.Abstract.DirtyFlag.None)
{
- sb.AppendLine($"Dirty(ArgonUI.UIElements.DirtyFlags.{prop.DirtyFlags});");
+ sb.AppendLine($"Dirty(ArgonUI.UIElements.Abstract.DirtyFlag.{prop.DirtyFlags});");
}
}
}
@@ -132,6 +132,9 @@ private static void EmitStylableClass(SourceProductionContext context, ReactiveO
{
foreach (var prop in model.ReactiveFields)
{
+ if (prop.Stylable == null)
+ continue;
+
// Generate a factory method
if (!string.IsNullOrEmpty(prop.DocComment))
Helpers.PrintDocComment(sb, prop.DocComment!);
diff --git a/ArgonUI.SourceGenerator/ReactiveObjectGenerator.Parser.cs b/ArgonUI.SourceGenerator/ReactiveObjectGenerator.Parser.cs
index 8cb4e60..72b9835 100644
--- a/ArgonUI.SourceGenerator/ReactiveObjectGenerator.Parser.cs
+++ b/ArgonUI.SourceGenerator/ReactiveObjectGenerator.Parser.cs
@@ -1,4 +1,5 @@
using ArgonUI.UIElements;
+using ArgonUI.UIElements.Abstract;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -73,9 +74,9 @@ public IEnumerable Parse(EquatableArray Parse(EquatableArray Parse(EquatableArray= 1)
- dirtyFlags = (DirtyFlags)args[0].Value!;
+ dirtyFlags = (DirtyFlag)args[0].Value!;
break;
case nameof(StylableAttribute):
stylable = new();
@@ -158,7 +159,7 @@ public record ReactiveObjectResult(ReactiveObjectClass? Class, Diagnostic? Diagn
public record ReactiveObjectClass(Accessibility Accessibility, string Namespace, string Assembly, string ClassName,
bool EnableNullable, EquatableArray ReactiveFields);
public record ReactiveObjectField(string FieldType, string FieldName, string PropName, string? DocComment,
- DirtyFlags DirtyFlags, string? OnGetFunc, bool GetInline, string? OnSetAction, bool SetInline,
+ DirtyFlag DirtyFlags, string? OnGetFunc, bool GetInline, string? OnSetAction, bool SetInline,
string? CustomAccessibility, StylableField? Stylable);
public record StylableField();
}
diff --git a/ArgonUI.SourceGenerator/UIClonableGenerator.Emitter.cs b/ArgonUI.SourceGenerator/UIClonableGenerator.Emitter.cs
index 5a69557..033f553 100644
--- a/ArgonUI.SourceGenerator/UIClonableGenerator.Emitter.cs
+++ b/ArgonUI.SourceGenerator/UIClonableGenerator.Emitter.cs
@@ -40,8 +40,11 @@ private static void Emit(SourceProductionContext context, UIClonableResult resul
sb.AppendLine($"{model.Accessibility.GetText()} partial class {model.ClassName}");
using (sb.EnterCurlyBracket())
{
- sb.AppendLine($"public override UIElement Clone() => Clone(new {model.ClassName}());");
- sb.AppendLine();
+ if (!model.IsAbstract)
+ {
+ sb.AppendLine($"public override UIElement Clone() => Clone(new {model.ClassName}());");
+ sb.AppendLine();
+ }
sb.AppendLine("public override UIElement Clone(UIElement target)");
using (sb.EnterCurlyBracket())
{
diff --git a/ArgonUI.SourceGenerator/UIClonableGenerator.Parser.cs b/ArgonUI.SourceGenerator/UIClonableGenerator.Parser.cs
index 0aae23b..a3b1c35 100644
--- a/ArgonUI.SourceGenerator/UIClonableGenerator.Parser.cs
+++ b/ArgonUI.SourceGenerator/UIClonableGenerator.Parser.cs
@@ -126,7 +126,7 @@ internal IEnumerable Parse(EquatableArray Parse(EquatableArray ClonableFields);
+ bool IsAbstract, bool EnableNullable, bool EnableCustomClone, EquatableArray ClonableFields);
private record UIClonableField(string FieldName, bool HasCloneMethod);
}
diff --git a/ArgonUI/ArgonUI.csproj b/ArgonUI/ArgonUI.csproj
index 53c577f..c0b7efb 100644
--- a/ArgonUI/ArgonUI.csproj
+++ b/ArgonUI/ArgonUI.csproj
@@ -7,7 +7,7 @@
disable
enable
ArgonUI
- 0.4.1-pre
+ 0.5.1-pre
Thomas Mathieson
Copyright © Thomas Mathieson 2025
https://github.com/space928/ArgonUI
diff --git a/ArgonUI/Bounds2D.cs b/ArgonUI/Bounds2D.cs
index 8c3989b..4efa237 100644
--- a/ArgonUI/Bounds2D.cs
+++ b/ArgonUI/Bounds2D.cs
@@ -174,8 +174,8 @@ public readonly Bounds2D Union(Bounds2D bounds)
/// A new bounds which has been shrunk.
public readonly Bounds2D SubtractMargin(Thickness margin)
{
- var sub = new Vector4(-margin.left, -margin.top, margin.right, margin.bottom);
- var res = _value - sub;
+ var add = new Vector4(margin.leftTop, -margin.right, -margin.bottom);
+ var res = _value + add;
var ret = new Bounds2D(res);
if (!ret.IsValid)
ret.topLeft = ret.bottomRight = ret.Centre;
@@ -189,14 +189,62 @@ public readonly Bounds2D SubtractMargin(Thickness margin)
/// A new bounds which has been grown.
public readonly Bounds2D AddMargin(Thickness margin)
{
- var sub = new Vector4(margin.left, margin.top, -margin.right, -margin.bottom);
- var res = _value - sub;
+ var add = new Vector4(-margin.leftTop, margin.right, margin.bottom);
+ var res = _value + add;
var ret = new Bounds2D(res);
if (!ret.IsValid)
ret.topLeft = ret.bottomRight = ret.Centre;
return ret;
}
+ ///
+ /// Creates a new with the same top-left coordinate as
+ /// bounds, but with the given .
+ ///
+ /// The final of the new bounds.
+ /// A new bounds with the given size.
+ public readonly Bounds2D WithSize(Vector2 size)
+ {
+ var tl = topLeft;
+ var br = size + tl;
+ return new(tl, br);
+ }
+
+ ///
+ /// Creates a new with the same top-left coordinate as
+ /// bounds, but with the given . For any dimension of
+ /// less than or equal to zero, this bounds' original size is returned.
+ ///
+ /// The final of the new bounds.
+ /// A new bounds with the given size.
+ public readonly Bounds2D WithSizeNonZero(Vector2 size)
+ {
+ var tl = topLeft;
+ var br = size + tl;
+ if (size.X <= 0)
+ br.X = bottomRight.X;
+ if (size.Y <= 0)
+ br.Y = bottomRight.Y;
+ return new(tl, br);
+ }
+
+ ///
+ /// Creates a new with the same coordinate as
+ /// bounds, but with the given .
+ ///
+ /// The final of the new bounds.
+ /// A new bounds with the given size.
+ public readonly Bounds2D WithSizeCentred(Vector2 size)
+ {
+ var centre = bottomRight + topLeft;
+
+ var sizeV4 = new Vector4(-size, size.X, size.Y);
+ var res = new Vector4(centre, centre.X, centre.Y) + sizeV4;
+ res *= 0.5f;
+
+ return new(res);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Equals(Bounds2D other) => _value == other._value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/ArgonUI/Drawing/BMFont.cs b/ArgonUI/Drawing/BMFont.cs
index f48f65c..a546f03 100644
--- a/ArgonUI/Drawing/BMFont.cs
+++ b/ArgonUI/Drawing/BMFont.cs
@@ -29,6 +29,7 @@ public class BMFont : Font
private readonly ReadOnlyCollection kerningsRO;
private FrozenDictionary> kerningsDictFrozen;
private FrozenDictionary charsDictFrozen;
+ private readonly Dictionary prescaledAlternatives = [];
///
/// This is the distance in pixels between each line of text.
@@ -106,7 +107,13 @@ public class BMFont : Font
///
public float Outline { get; private set; }
+ ///
+ /// The type of signed-distance field (if any) contained in the font's bitmap.
+ ///
public BMFontSDFType SDFType { get; private set; }
+ ///
+ /// The distance range of the signed-distance field (if used) in pixels.
+ ///
public float SDFDistanceRange { get; private set; }
public ReadOnlyCollection Pages => pagesRO;
@@ -124,6 +131,17 @@ public BMFont()
charsDictFrozen = FrozenDictionary.Empty;
}
+ ///
+ /// Trys to get a instance of this font optimised for rendering at the
+ /// given font size.
+ ///
+ /// The font size to target (integer values are recommended)
+ /// A instance for that font size or if one doesn't exist.
+ public BMFont? GetScaledFont(float fontSize)
+ {
+ return null;
+ }
+
///
/// Measures the approximate amount of space the font will require on screen in pixels.
///
diff --git a/ArgonUI/Drawing/Gradient.cs b/ArgonUI/Drawing/Gradient.cs
new file mode 100644
index 0000000..7be2bab
--- /dev/null
+++ b/ArgonUI/Drawing/Gradient.cs
@@ -0,0 +1,79 @@
+using ArgonUI.SourceGenerator;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace ArgonUI.Drawing;
+
+///
+/// Represents a gradient which can be applied to a UI element.
+///
+public partial class Gradient : ReactiveObject
+{
+ [Reactive] private Vector4 colourTL;
+ [Reactive] private Vector4 colourTR;
+ [Reactive] private Vector4 colourBL;
+ [Reactive] private Vector4 colourBR;
+
+ ///
+ /// Creates a new 4-point gradient.
+ ///
+ /// The top-left colour.
+ /// The top-right colour.
+ /// The bottom-left colour.
+ /// The bottom-right colour.
+ public Gradient(Vector4 colourTL, Vector4 colourTR, Vector4 colourBL, Vector4 colourBR)
+ {
+ this.colourTL = colourTL;
+ this.colourTR = colourTR;
+ this.colourBL = colourBL;
+ this.colourBR = colourBR;
+ }
+
+ ///
+ /// Creates a new horizontal gradient from a start and end colour.
+ ///
+ ///
+ ///
+ ///
+ public static Gradient CreateHorizontal(Vector4 colourLeft, Vector4 colourRight) => new(colourLeft, colourRight, colourLeft, colourRight);
+ ///
+ /// Creates a new vertical gradient from a start and end colour.
+ ///
+ ///
+ ///
+ ///
+ public static Gradient CreateVertical(Vector4 colourTop, Vector4 colourBottom) => new(colourTop, colourTop, colourBottom, colourBottom);
+ ///
+ /// Creates a new diagonal gradient going from the top-left corner to the bottom-right corner.
+ ///
+ ///
+ ///
+ ///
+ public static Gradient CreateDiagonalTopLeft(Vector4 colourTopLeft, Vector4 colourBottomRight)
+ {
+ var mid = (colourTopLeft + colourBottomRight) * 0.5f;
+ return new(colourTopLeft, mid, mid, colourBottomRight);
+ }
+ ///
+ /// Creates a new diagonal gradient going from the bottom-left corner to the top-right corner.
+ ///
+ ///
+ ///
+ ///
+ public static Gradient CreateDiagonalBottomLeft(Vector4 colourBottomLeft, Vector4 colourTopRight)
+ {
+ var mid = (colourBottomLeft + colourTopRight) * 0.5f;
+ return new(mid, colourTopRight, colourBottomLeft, mid);
+ }
+ ///
+ /// Creates a new 4-point gradient.
+ ///
+ /// The top-left colour.
+ /// The top-right colour.
+ /// The bottom-left colour.
+ /// The bottom-right colour.
+ ///
+ public static Gradient CreateFourCorner(Vector4 colourTL, Vector4 colourTR, Vector4 colourBL, Vector4 colourBR) => new(colourTL, colourTR, colourBL, colourBR);
+}
diff --git a/ArgonUI/Drawing/IDrawContext.cs b/ArgonUI/Drawing/IDrawContext.cs
index 2598d31..6fc4beb 100644
--- a/ArgonUI/Drawing/IDrawContext.cs
+++ b/ArgonUI/Drawing/IDrawContext.cs
@@ -53,23 +53,96 @@ public interface IDrawContext : IDisposable
/// The corner rounding radius in pixels.
public void DrawRect(Bounds2D bounds, Vector4 colour, float rounding);
///
- /// Draws a string of text within the specified bounds.
+ /// Draws a rounded rectangle with a four-point gradient.
///
- /// The bounds in which this string should be drawn.
- /// Overflowing text will be truncated.
- /// The font size to render the text with.
- /// The string to draw.
- /// The font to draw the string with.
- /// The colour to draw the text in.
- public void DrawText(Bounds2D bounds, float size, string s, BMFont font, Vector4 colour);
+ /// The absolute window-space bounds of the rectangle.
+ /// The colour of the top-left corner.
+ /// The colour of the top-right corner.
+ /// The colour of the bottom-left corner.
+ /// The colour of the bottom-right corner.
+ /// The corner rounding radius in pixels.
+ public void DrawGradient(Bounds2D bounds, Vector4 colourA, Vector4 colourB, Vector4 colourC, Vector4 colourD, float rounding);
+ ///
+ /// Draws a blurred, rounded rectangle.
+ ///
+ /// The absolute window-space bounds of the rectangle.
+ /// The colour of the rectangle.
+ /// The corner rounding radius in pixels.
+ /// The blur radius in pixels.
+ public void DrawShadow(Bounds2D bounds, Vector4 colour, float rounding, float blur);
+ ///
+ /// Draws an outline of a rounded rectangle.
+ ///
+ /// The absolute window-space bounds of the rectangle.
+ /// The colour of the rectangle.
+ /// The thickness of the outline in pixels.
+ /// The corner rounding radius in pixels.
+ public void DrawOutlineRect(Bounds2D bounds, Vector4 colour, float outlineThickness, float rounding);
+ ///
+ /// Draws an outline of a rounded rectangle with a four-point gradient.
+ ///
+ /// The absolute window-space bounds of the rectangle.
+ /// The colour of the top-left corner.
+ /// The colour of the top-right corner.
+ /// The colour of the bottom-left corner.
+ /// The colour of the bottom-right corner.
+ /// The thickness of the outline in pixels.
+ /// The corner rounding radius in pixels.
+ public void DrawOutlineGradient(Bounds2D bounds, Vector4 colourA, Vector4 colourB, Vector4 colourC, Vector4 colourD, float outlineThickness, float rounding);
+ ///
+ /// Draws a straight line connecting the given start and end points.
+ ///
+ /// The colour of the line is interpolated from the start-point to the end-point.
+ ///
+ /// The starting point of the line, in window-space coordinates.
+ /// The end point of the line, in window-space coordinates.
+ /// The colour at the start of the line.
+ /// The colour at the end of the line.
+ /// The thickness of the line in pixels.
+ public void DrawLine(Vector2 start, Vector2 end, Vector4 colourStart, Vector4 colourEnd, float thickness);
+ ///
+ /// Draws a filled polygonal shape from a collection of points.
+ ///
+ /// Points should define a clockwise triangle-strip (see ).
+ ///
+ /// For instance to define the following shape, use the points described below:
+ ///
+ /// B---D---F
+ /// | \ | \ |
+ /// A---C---E
+ ///
+ /// positions = [A, B, C, D, E, F];
+ ///
+ ///
+ /// The enumerable of points which defines the shape.
+ public void DrawPolyFill(IEnumerable points);
+ ///
+ /// Draws a series of line segments connecting the given points.
+ ///
+ /// The colour of the line is interpolated between each of the points.
+ ///
+ /// The enumerable of points which defines the line.
+ /// The thickness of the line in pixels.
+ public void DrawPolyLine(IEnumerable points, float thickness);
+ ///
+ /// Draws a rounded rectangle filled with a texture.
+ ///
+ /// The absolute window-space bounds of the rectangle.
+ /// The texture to fill this rectangle with.
+ /// The corner rounding radius in pixels.
+ public void DrawTexture(Bounds2D bounds, ITextureHandle texture, float rounding);
+ // TODO: Currently text shaping is done purely by font metrics and text is always drawn left-to-right,
+ // for compatibility with non-latin writing systems, shaping should really be done during the layout
+ // phase and passed in to this method. Maybe we could have a ShapedFont class deriving from Font
+ // which implements this?
///
/// Draws a string of text within the specified bounds.
///
/// The bounds in which this string should be drawn.
/// Overflowing text will be truncated.
- /// The font size to render the text with.
/// The string to draw.
/// The font to draw the string with.
+ /// The font size to render the text with.
/// The colour to draw the text in.
/// The space between words, represented by the size of the space character in pixels.
/// An adjustment to the space between individual characters in pixels.
@@ -77,7 +150,7 @@ public interface IDrawContext : IDisposable
/// The weight to render the characters with, where 0.5
/// represents the font's native weight. Values close to 0 or 1 are likely to show visual artifacts.
/// A width scale factor to stretch the font. A value of 1 represents no stretching.
- public void DrawText(Bounds2D bounds, float size, string s, BMFont font, Vector4 colour,
+ public void DrawText(Bounds2D bounds, ReadOnlySpan s, BMFont font, float size, Vector4 colour,
float wordSpacing = 0, float charSpacing = 0, float skew = 0, float weight = 0.5f, float width = 1);
///
/// Draws a single character within the specified bounds.
@@ -88,15 +161,6 @@ public void DrawText(Bounds2D bounds, float size, string s, BMFont font, Vector4
/// The font to draw the char with.
/// The colour to draw the char in.
public void DrawChar(Bounds2D bounds, float size, char c, BMFont font, Vector4 colour);
- ///
- /// Draws a rounded rectangle filled with a texture.
- ///
- /// The absolute window-space bounds of the rectangle.
- /// The texture to fill this rectangle with.
- /// The corner rounding radius in pixels.
- public void DrawTexture(Bounds2D bounds, ITextureHandle texture, float rounding);
- public void DrawGradient(Bounds2D bounds, Vector4 colourA, Vector4 colourB, Vector4 colourC, Vector4 colourD, float rounding);
- public void DrawShadow(Bounds2D bounds, Vector4 colour, float rounding, float blur);
///
/// Draw calls are expected to be automatically batched by the implementor to improve performance.
@@ -115,4 +179,10 @@ public ITextureHandle LoadTexture(TextureData data, string? name = null,
#if DEBUG_LATENCY
public void MarkLatencyTimerEnd(string? msg = null);
#endif
+
+ public struct PolyVert
+ {
+ public Vector2 pos;
+ public Vector4 colour;
+ }
}
diff --git a/ArgonUI/Helpers/ObjectPool.cs b/ArgonUI/Helpers/ObjectPool.cs
new file mode 100644
index 0000000..d1c0534
--- /dev/null
+++ b/ArgonUI/Helpers/ObjectPool.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.Helpers;
+
+///
+/// Represents a very simple, non thread-safe pool of objects.
+///
+///
+public static class ObjectPool
+ where T : class, new()
+{
+ private const int MAX_POOLED_ITEMS = 128;
+ private static readonly Stack pooledObjects = [];
+
+ public static Action? factoryMethod;
+
+ public static T Rent()
+ {
+ if (pooledObjects.TryPop(out var res))
+ return res;
+
+ return new();
+ }
+
+ public static void Return(T obj)
+ {
+ if (pooledObjects.Count < MAX_POOLED_ITEMS)
+ pooledObjects.Push(obj);
+ }
+}
diff --git a/ArgonUI/Helpers/PropertyChangedArgsPool.cs b/ArgonUI/Helpers/PropertyChangedArgsPool.cs
index 21d69f1..19e3c7e 100644
--- a/ArgonUI/Helpers/PropertyChangedArgsPool.cs
+++ b/ArgonUI/Helpers/PropertyChangedArgsPool.cs
@@ -1,5 +1,8 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.ComponentModel;
+using System.Threading;
namespace ArgonUI.Helpers;
@@ -11,40 +14,123 @@ namespace ArgonUI.Helpers;
///
public static class PropertyChangedArgsPool
{
- private static readonly Stack propChangedPool = [];
- private static readonly Stack propChangingPool = [];
+ //private static readonly Stack propChangedPool = [];
+ //private static readonly Stack propChangingPool = [];
+
+ private static readonly ReusablePropertyChangedEventArgs?[] propChangedPool = new ReusablePropertyChangedEventArgs?[MAX_POOLED_ITEMS];
+ private static readonly ReusablePropertyChangingEventArgs?[] propChangingPool = new ReusablePropertyChangingEventArgs?[MAX_POOLED_ITEMS];
+
+ private static ReusablePropertyChangedEventArgs? propChangedInst;
+ private static ReusablePropertyChangingEventArgs? propChangingInst;
+
private const int MAX_POOLED_ITEMS = 128;
public static ReusablePropertyChangedEventArgs RentChanged(string? propName)
{
- if (propChangedPool.TryPop(out var res))
+ var res = propChangedInst;
+ if (res != null && res == Interlocked.CompareExchange(ref propChangedInst, null, res))
{
res.propertyName = propName;
return res;
}
+ return RentChangedSlow(propName);
+ }
+
+ private static ReusablePropertyChangedEventArgs RentChangedSlow(string? propName)
+ {
+ for (int i = 0; i < propChangedPool.Length; i++)
+ {
+ var res = propChangedPool[i];
+ if (res != null)
+ {
+ if (res == Interlocked.CompareExchange(ref propChangedPool[i], null, res))
+ {
+ res.propertyName = propName;
+ return res;
+ }
+ break;
+ }
+ }
return new(propName);
}
public static ReusablePropertyChangingEventArgs RentChanging(string? propName)
{
- if (propChangingPool.TryPop(out var res))
+ var res = propChangingInst;
+ if (res != null && res == Interlocked.CompareExchange(ref propChangingInst, null, res))
{
res.propertyName = propName;
return res;
}
+ return RentChangingSlow(propName);
+ }
+
+ private static ReusablePropertyChangingEventArgs RentChangingSlow(string? propName)
+ {
+ for (int i = 0; i < propChangingPool.Length; i++)
+ {
+ var res = propChangingPool[i];
+ if (res != null)
+ {
+ if (res == Interlocked.CompareExchange(ref propChangingPool[i], null, res))
+ {
+ res.propertyName = propName;
+ return res;
+ }
+ break;
+ }
+ }
return new(propName);
}
public static void Return(ReusablePropertyChangedEventArgs e)
{
- if (propChangedPool.Count < MAX_POOLED_ITEMS)
- propChangedPool.Push(e);
+ // No need to interlock here, worst case is we overwrite an existing pooled object, no big deal.
+ if (propChangedInst == null)
+ {
+ propChangedInst = e;
+ return;
+ }
+
+ ReturnSlow(e);
+ }
+
+ private static void ReturnSlow(ReusablePropertyChangedEventArgs e)
+ {
+ var items = propChangedPool;
+ for (var i = 0; i < items.Length; i++)
+ {
+ if (items[i] == null)
+ {
+ items[i] = e;
+ break;
+ }
+ }
}
public static void Return(ReusablePropertyChangingEventArgs e)
{
- if (propChangingPool.Count < MAX_POOLED_ITEMS)
- propChangingPool.Push(e);
+ // No need to interlock here, worst case is we overwrite an existing pooled object, no big deal.
+ if (propChangingInst == null)
+ {
+ propChangingInst = e;
+ return;
+ }
+
+ ReturnSlow(e);
+ }
+
+ private static void ReturnSlow(ReusablePropertyChangingEventArgs e)
+ {
+ var items = propChangingPool;
+ for (var i = 0; i < items.Length; i++)
+ {
+ if (items[i] == null)
+ {
+ items[i] = e;
+ break;
+ }
+ }
}
}
diff --git a/ArgonUI/Input/InputManager.cs b/ArgonUI/Input/InputManager.cs
index 418eab9..f634083 100644
--- a/ArgonUI/Input/InputManager.cs
+++ b/ArgonUI/Input/InputManager.cs
@@ -5,6 +5,7 @@
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
+using ArgonUI.Helpers;
using ArgonUI.UIElements;
namespace ArgonUI.Input;
@@ -16,12 +17,13 @@ public class InputManager
{
private readonly ArgonManager argonManager;
private long lastClickTime;
- private long lastMouseMoveTime;
+ //private long lastMouseMoveTime;
private VectorInt2 lastMousePos;
private UIElement? lastHoveredElement;
private UIElement? kbFocussedElement;
private UIElement? mouseCaptureElement;
private readonly Dictionary pressedKeys;
+ private readonly long doubleClickTime;
public InputManager(ArgonManager argonManager)
{
@@ -33,20 +35,39 @@ public InputManager(ArgonManager argonManager)
#else
pressedKeys = new(Enum.GetValues().Select(x => new KeyValuePair(x, false)));
#endif
+ doubleClickTime = TimeSpan.FromMilliseconds(300).Ticks;
}
///
/// Gets or sets whichever element currently has keyboard focus.
/// A value of indicates no element has keyboard focus.
///
- public UIElement? FocussedElement
- {
+ public UIElement? FocussedElement
+ {
get => kbFocussedElement;
set
{
- kbFocussedElement?.InvokeOnFocusLost(this);
+ if (value == kbFocussedElement)
+ return;
+
+ var args = ObjectPool.Rent();
+ args.InputManager = this;
+ args.Target = kbFocussedElement;
+ var obj = kbFocussedElement;
+ do
+ {
+ obj?.InvokeOnFocusLost(args);
+ } while (!args.Handled && (obj = obj?.Parent) != null);
+
kbFocussedElement = value;
- kbFocussedElement?.InvokeOnFocusGot(this);
+
+ obj = kbFocussedElement;
+ do
+ {
+ obj?.InvokeOnFocusGot(args);
+ } while (!args.Handled && (obj = obj?.Parent) != null);
+
+ ObjectPool.Return(args);
}
}
@@ -87,11 +108,35 @@ internal void OnMouseMove(UIWindow sender, VectorInt2 mousePos)
#if DEBUG && DEBUG_PROP_UPDATES
Debug.WriteLine($"[InputManager] Hovered element changed {lastHoveredElement} -> {hit}");
#endif
- lastHoveredElement?.InvokeOnMouseLeave(this, mousePos);
- hit?.InvokeOnMouseEnter(this, mousePos);
+ var args = ObjectPool.Rent();
+ args.InputManager = this;
+ args.Target = lastHoveredElement;
+ args.MousePosition = mousePos;
+ var obj = lastHoveredElement;
+ do
+ {
+ obj?.InvokeOnMouseLeave(args);
+ } while (!args.Handled && (obj = obj?.Parent) != null);
+
+ args.Target = hit;
+ obj = hit;
+ do
+ {
+ obj?.InvokeOnMouseEnter(args);
+ } while (!args.Handled && (obj = obj?.Parent) != null);
+ ObjectPool.Return(args);
}
- hit?.InvokeOnMouseOver(this, mousePos);
+ var hoverArgs = ObjectPool.Rent();
+ hoverArgs.InputManager = this;
+ hoverArgs.Target = hit;
+ hoverArgs.MousePosition = mousePos;
+ var hoverObj = hit;
+ do
+ {
+ hoverObj?.InvokeOnMouseOver(hoverArgs);
+ } while (!hoverArgs.Handled && (hoverObj = hoverObj?.Parent) != null);
+ ObjectPool.Return(hoverArgs);
lastHoveredElement = hit;
lastMousePos = mousePos;
@@ -99,29 +144,83 @@ internal void OnMouseMove(UIWindow sender, VectorInt2 mousePos)
internal void OnMouseUp(UIWindow sender, MouseButton mouseButton)
{
- lastHoveredElement?.InvokeOnMouseUp(this, mouseButton);
+ var args = ObjectPool.Rent();
+ args.InputManager = this;
+ args.Target = lastHoveredElement;
+ args.MouseButton = mouseButton;
+ var hoverObj = lastHoveredElement;
+ do
+ {
+ hoverObj?.InvokeOnMouseUp(args);
+ } while (!args.Handled && (hoverObj = hoverObj?.Parent) != null);
+ ObjectPool.Return(args);
}
internal void OnMouseDown(UIWindow sender, MouseButton mouseButton)
{
- lastHoveredElement?.InvokeOnMouseDown(this, mouseButton);
+ var now = DateTime.UtcNow.Ticks;
+ var args = ObjectPool.Rent();
+ args.InputManager = this;
+ args.Target = lastHoveredElement;
+ args.MouseButton = mouseButton;
+ var hoverObj = lastHoveredElement;
+ if (now - lastClickTime <= doubleClickTime)
+ {
+ do
+ {
+ hoverObj?.InvokeOnDoubleClick(args);
+ } while (!args.Handled && (hoverObj = hoverObj?.Parent) != null);
+ }
+ do
+ {
+ hoverObj?.InvokeOnMouseDown(args);
+ } while (!args.Handled && (hoverObj = hoverObj?.Parent) != null);
+ ObjectPool.Return(args);
+ lastClickTime = now;
}
internal void OnMouseWheel(UIWindow sender, Vector2 delta)
{
- lastHoveredElement?.InvokeOnMouseWheel(this, delta);
+ var args = ObjectPool.Rent();
+ args.InputManager = this;
+ args.Target = lastHoveredElement;
+ args.MouseScroll = delta;
+ var hoverObj = lastHoveredElement;
+ do
+ {
+ hoverObj?.InvokeOnMouseWheel(args);
+ } while (!args.Handled && (hoverObj = hoverObj?.Parent) != null);
+ ObjectPool.Return(args);
}
internal void OnKeyDown(UIWindow sender, KeyCode key)
{
pressedKeys[key] = true;
- lastHoveredElement?.InvokeOnKeyDown(this, key);
+ var args = ObjectPool.Rent();
+ args.InputManager = this;
+ args.Target = FocussedElement ?? lastHoveredElement;
+ args.Key = key;
+ var hoverObj = args.Target;
+ do
+ {
+ hoverObj?.InvokeOnKeyDown(args);
+ } while (!args.Handled && (hoverObj = hoverObj?.Parent) != null);
+ ObjectPool.Return(args);
}
internal void OnKeyUp(UIWindow sender, KeyCode key)
{
pressedKeys[key] = false;
- lastHoveredElement?.InvokeOnKeyUp(this, key);
+ var args = ObjectPool.Rent();
+ args.InputManager = this;
+ args.Target = FocussedElement ?? lastHoveredElement;
+ args.Key = key;
+ var hoverObj = args.Target;
+ do
+ {
+ hoverObj?.InvokeOnKeyUp(args);
+ } while (!args.Handled && (hoverObj = hoverObj?.Parent) != null);
+ ObjectPool.Return(args);
}
///
@@ -181,3 +280,83 @@ public enum MouseButton
Mouse8,
Mouse9,
}
+
+public class InputEventArgs(InputManager? inputManager, UIElement? target, bool handled)
+{
+ ///
+ /// The input manager which sent this event.
+ ///
+ public InputManager? InputManager { get; internal set; } = inputManager;
+ ///
+ /// The which initially received this event.
+ ///
+ public UIElement? Target { get; internal set; } = target;
+ ///
+ /// Whether this event has been handled yet. Once set to , this event
+ /// will stop propagating up to parent elements.
+ ///
+ public bool Handled { get; set; } = handled;
+
+ public InputEventArgs() : this(null, null, false) { }
+}
+
+public sealed class FocusInputEventArgs : InputEventArgs
+{
+ public FocusInputEventArgs(InputManager? inputManager, UIElement? target, bool handled) : base(inputManager, target, handled)
+ {
+ }
+
+ public FocusInputEventArgs() : this(null, null, false) { }
+}
+
+public sealed class MousePositionInputEventArgs : InputEventArgs
+{
+ public VectorInt2 MousePosition { get; internal set; }
+
+ public MousePositionInputEventArgs(InputManager? inputManager, UIElement? target, bool handled, VectorInt2 mousePos)
+ : base(inputManager, target, handled)
+ {
+ MousePosition = mousePos;
+ }
+
+ public MousePositionInputEventArgs() : this(null, null, false, VectorInt2.Zero) { }
+}
+
+public sealed class MouseButtonInputEventArgs : InputEventArgs
+{
+ public MouseButton MouseButton { get; internal set; }
+
+ public MouseButtonInputEventArgs(InputManager? inputManager, UIElement? target, bool handled, MouseButton mouseButton)
+ : base(inputManager, target, handled)
+ {
+ MouseButton = mouseButton;
+ }
+
+ public MouseButtonInputEventArgs() : this(null, null, false, MouseButton.Mouse0) { }
+}
+
+public sealed class MouseScrollInputEventArgs : InputEventArgs
+{
+ public Vector2 MouseScroll { get; internal set; }
+
+ public MouseScrollInputEventArgs(InputManager? inputManager, UIElement? target, bool handled, Vector2 mouseScroll)
+ : base(inputManager, target, handled)
+ {
+ MouseScroll = mouseScroll;
+ }
+
+ public MouseScrollInputEventArgs() : this(null, null, false, Vector2.Zero) { }
+}
+
+public sealed class KeyboardInputEventArgs : InputEventArgs
+{
+ public KeyCode Key { get; internal set; }
+
+ public KeyboardInputEventArgs(InputManager? inputManager, UIElement? target, bool handled, KeyCode key)
+ : base(inputManager, target, handled)
+ {
+ Key = key;
+ }
+
+ public KeyboardInputEventArgs() : this(null, null, false, KeyCode.Unknown) { }
+}
diff --git a/ArgonUI/SourceGenerator/DirtyAttribute.cs b/ArgonUI/SourceGenerator/DirtyAttribute.cs
index 280fdfd..000978c 100644
--- a/ArgonUI/SourceGenerator/DirtyAttribute.cs
+++ b/ArgonUI/SourceGenerator/DirtyAttribute.cs
@@ -1,4 +1,5 @@
using ArgonUI.UIElements;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,14 +10,14 @@ namespace ArgonUI.SourceGenerator;
///
/// When used on a property generated using a , generates a call to
-/// the method when the property is set.
+/// the method when the property is set.
/// This attribute can only be used in classes which derive from .
///
/// The dirty flags to set when the generated property is set.
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
-public sealed class DirtyAttribute(DirtyFlags dirtyFlags) : Attribute
+public sealed class DirtyAttribute(DirtyFlag dirtyFlags) : Attribute
{
- private readonly DirtyFlags dirtyFlags = dirtyFlags;
+ private readonly DirtyFlag dirtyFlags = dirtyFlags;
- public DirtyFlags DirtyFlags => dirtyFlags;
+ public DirtyFlag DirtyFlags => dirtyFlags;
}
diff --git a/ArgonUI/Thickness.cs b/ArgonUI/Thickness.cs
index 97596cb..85301ee 100644
--- a/ArgonUI/Thickness.cs
+++ b/ArgonUI/Thickness.cs
@@ -1,6 +1,9 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+#if !NETSTANDARD
+using System.Runtime.Intrinsics;
+#endif
namespace ArgonUI;
@@ -10,15 +13,34 @@ namespace ArgonUI;
[StructLayout(LayoutKind.Explicit)]
public struct Thickness
{
- // Specified as a vector of (Top, Right, Bottom, Left).
+ ///
+ /// Specified as a vector of (Left, Top, Right, Bottom).
+ ///
+ /// This is an alias for the , , , and fields.
+ ///
[FieldOffset(0x0)] public Vector4 value;
- [FieldOffset(0x0)] public float top;
+
+ ///
+ /// Alias for (, ).
+ ///
+ [FieldOffset(0x0)] public Vector2 leftTop;
+ ///
+ /// Alias for (, ).
+ ///
+ [FieldOffset(0x8)] public Vector2 rightBottom;
+
+ [FieldOffset(0x0)] public float left;
[FieldOffset(0x4)] public float right;
- [FieldOffset(0x8)] public float bottom;
- [FieldOffset(0xC)] public float left;
+ [FieldOffset(0x8)] public float top;
+ [FieldOffset(0xC)] public float bottom;
public static Thickness Zero => new();
+ ///
+ /// Gets the total width and height of this .
+ ///
+ public readonly Vector2 Size => leftTop + rightBottom;
+
public Thickness(float all)
{
Unsafe.SkipInit(out this);
@@ -42,7 +64,7 @@ public Thickness(float top, float right, float bottom, float left)
}
///
- /// Constructs a thickness from a vector of (Top, Right, Bottom, Left).
+ /// Constructs a thickness from a vector of (Left, Top, Right, Bottom).
///
///
public Thickness(Vector4 value)
diff --git a/ArgonUI/UIElements/Abstract/DirtyFlag.cs b/ArgonUI/UIElements/Abstract/DirtyFlag.cs
new file mode 100644
index 0000000..d83cb7d
--- /dev/null
+++ b/ArgonUI/UIElements/Abstract/DirtyFlag.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace ArgonUI.UIElements.Abstract;
+
+///
+/// Flags used to indicate if a needs
+/// to redrawing or re-laying out.
+///
+[Flags]
+public enum DirtyFlag
+{
+ None,
+ ///
+ /// The layout of this element is invalid, it's
+ /// may need to change. Dirtying the layout of an element implies that it's
+ /// is also dirty.
+ ///
+ Layout = 1 << 0,
+ ///
+ /// The content of this element is invalid, it's bounds have not changed but a property
+ /// affecting it's inner content (such as it's colour) has changed.
+ ///
+ Content = 1 << 1,
+ ///
+ /// A child element of this has a dirty .
+ ///
+ ChildLayout = 1 << 2,
+ ///
+ /// A child element of this has a dirty .
+ ///
+ ChildContent = 1 << 3,
+
+ ///
+ /// Both the and are dirty.
+ ///
+ ContentAndLayout = Content | Layout,
+ ///
+ /// Both the and are dirty.
+ ///
+ ChildContentAndLayout = ChildContent | ChildLayout,
+ ///
+ /// All s are set.
+ ///
+ All = ContentAndLayout | ChildContentAndLayout,
+}
diff --git a/ArgonUI/UIElements/ElementPresenterBase.cs b/ArgonUI/UIElements/Abstract/ElementPresenterBase.cs
similarity index 98%
rename from ArgonUI/UIElements/ElementPresenterBase.cs
rename to ArgonUI/UIElements/Abstract/ElementPresenterBase.cs
index 29b3e92..e76d3a1 100644
--- a/ArgonUI/UIElements/ElementPresenterBase.cs
+++ b/ArgonUI/UIElements/Abstract/ElementPresenterBase.cs
@@ -7,7 +7,7 @@
using System.Text;
using System.Threading.Tasks;
-namespace ArgonUI.UIElements;
+namespace ArgonUI.UIElements.Abstract;
///
/// Abstract class for a UIElement which contains a single child element.
diff --git a/ArgonUI/UIElements/Abstract/IRectangleProps.cs b/ArgonUI/UIElements/Abstract/IRectangleProps.cs
new file mode 100644
index 0000000..7c9a161
--- /dev/null
+++ b/ArgonUI/UIElements/Abstract/IRectangleProps.cs
@@ -0,0 +1,41 @@
+using ArgonUI.Drawing;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace ArgonUI.UIElements.Abstract;
+
+///
+/// Represents the properties which a generic rectangle should expose.
+///
+///
+/// This interface exists to help ensure that s which draw a rectangle have feature and property name parity.
+///
+internal interface IRectangleProps
+{
+ ///
+ /// The colour of this rectangle.
+ ///
+ public Vector4 Colour { get; set; }
+ ///
+ /// The outline colour.
+ ///
+ public Vector4 OutlineColour { get; set; }
+ ///
+ /// The thickness of the outline in pixels.
+ ///
+ public float OutlineThickness { get; set; }
+ ///
+ /// The radius of the corners of this rectangle.
+ ///
+ public float Rounding { get; set; }
+ ///
+ /// The texture to fill the rectangle with.
+ ///
+ public ArgonTexture? Texture { get; set; }
+ ///
+ /// The gradient to fill the rectangle with.
+ ///
+ public Gradient? GradientFill { get; set; }
+}
diff --git a/ArgonUI/UIElements/Panel.cs b/ArgonUI/UIElements/Abstract/Panel.cs
similarity index 73%
rename from ArgonUI/UIElements/Panel.cs
rename to ArgonUI/UIElements/Abstract/Panel.cs
index 56a50d9..264c77d 100644
--- a/ArgonUI/UIElements/Panel.cs
+++ b/ArgonUI/UIElements/Abstract/Panel.cs
@@ -1,5 +1,6 @@
using ArgonUI.Drawing;
using ArgonUI.SourceGenerator;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -9,18 +10,16 @@
using System.Text;
using System.Threading.Tasks;
-namespace ArgonUI.UIElements;
+namespace ArgonUI.UIElements.Abstract;
+///
+/// A very basic type of which draws each of it's children in order, on top of each other.
+///
[UIClonable]
-public partial class Panel : UIContainer
+public partial class Panel : UIContainerRectangle
{
private readonly List children;
private readonly ReadOnlyCollection childrenRO;
- ///
- /// The background colour of this panel.
- ///
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- private Vector4 colour;
public override IReadOnlyList Children => childrenRO;
@@ -76,22 +75,16 @@ public override void ClearChildren()
}
}
- protected internal override VectorInt2 Measure()
+ protected internal override Vector2 Measure()
{
if (children.Count == 0)
return base.Measure();
- VectorInt2 res = VectorInt2.Zero;
+ var res = Vector2.Zero;
foreach (var child in children)
- res = VectorInt2.Max(res, child.desiredSize);
- res += new VectorInt2((int)(InnerPadding.left + InnerPadding.right), (int)(InnerPadding.top + InnerPadding.bottom));
+ res = Vector2.Max(res, child.desiredSize);
+ res += InnerPadding.Size;
return res;
}
-
- protected internal override void Draw(IDrawContext ctx)
- {
- if (colour.W != 0)
- ctx.DrawRect(RenderedBoundsAbsolute, colour, 0);
- }
}
diff --git a/ArgonUI/UIElements/Abstract/RectangleBase.cs b/ArgonUI/UIElements/Abstract/RectangleBase.cs
new file mode 100644
index 0000000..0cf4996
--- /dev/null
+++ b/ArgonUI/UIElements/Abstract/RectangleBase.cs
@@ -0,0 +1,89 @@
+using ArgonUI.Drawing;
+using ArgonUI.SourceGenerator;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace ArgonUI.UIElements.Abstract;
+
+///
+/// Represents the base class for all s which draw a rectangle background.
+///
+[UIClonable]
+public abstract partial class RectangleBase : UIElement, IRectangleProps
+{
+ ///
+ /// The colour of this rectangle.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected Vector4 colour;
+ ///
+ /// The outline colour.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected Vector4 outlineColour;
+ ///
+ /// The thickness of the outline in pixels.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected float outlineThickness;
+ ///
+ /// The radius of the corners of this rectangle.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected float rounding;
+ ///
+ /// The texture to fill the rectangle with.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected ArgonTexture? texture;
+ ///
+ /// The gradient to fill the rectangle with.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected Gradient? gradientFill;
+
+
+#if DEBUG_LATENCY
+ public bool logLatencyNow;
+#endif
+
+ ///
+ /// Draws this rectangle using the given drawing context.
+ ///
+ /// This method exists outside of so that elements derived
+ /// from this class can call the rectangle drawing method whenever they want within their own
+ /// method implementation.
+ ///
+ ///
+ protected void DrawRectangle(IDrawContext ctx)
+ {
+ if (texture != null)
+ {
+ texture.ExecuteDrawCommands(ctx);
+ if (!texture.IsLoaded)
+ {
+ // Can't render yet, the font texture isn't ready, try again next frame.
+ Dirty(DirtyFlag.Content);
+ return;
+ }
+ ctx.DrawTexture(RenderedBoundsAbsolute, texture.TextureHandle!, Rounding);
+ }
+ else if (gradientFill != null)
+ {
+ ctx.DrawGradient(RenderedBoundsAbsolute, gradientFill.ColourTL, gradientFill.ColourTR, gradientFill.ColourBL, gradientFill.ColourBR, Rounding);
+ }
+ else if (Colour.W > 0)
+ {
+ ctx.DrawRect(RenderedBoundsAbsolute, Colour, Rounding);
+ }
+
+ if (outlineThickness > 0 && outlineColour.W > 0)
+ ctx.DrawOutlineRect(RenderedBoundsAbsolute, outlineColour, outlineThickness, rounding);
+#if DEBUG_LATENCY
+ if (logLatencyNow)
+ commands.Add(ctx => ctx.MarkLatencyTimerEnd($"{Colour.Y}"));
+ logLatencyNow = false;
+#endif
+ }
+
+ protected internal override void Draw(IDrawContext ctx)
+ {
+ DrawRectangle(ctx);
+ }
+}
diff --git a/ArgonUI/UIElements/Abstract/TextBase.cs b/ArgonUI/UIElements/Abstract/TextBase.cs
new file mode 100644
index 0000000..de94677
--- /dev/null
+++ b/ArgonUI/UIElements/Abstract/TextBase.cs
@@ -0,0 +1,165 @@
+using ArgonUI.Drawing;
+using ArgonUI.SourceGenerator;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace ArgonUI.UIElements.Abstract;
+
+///
+/// The base class for any which draws simple text. For more complex text drawing, see .
+///
+[UIClonable]
+public abstract partial class TextBase : UIElement
+{
+ ///
+ /// The text represented by this element.
+ ///
+ [Reactive, Dirty(DirtyFlag.Layout)]
+ protected string? text;
+ ///
+ /// The font size of this element's text.
+ ///
+ [Reactive("FontSize"), Dirty(DirtyFlag.Layout), Stylable]
+ protected float size;
+ ///
+ /// The text colour of this element's text.
+ ///
+ [Reactive("TextColour"), Dirty(DirtyFlag.Layout), Stylable]
+ protected Vector4 textColour;
+ ///
+ /// The font used by this element's text.
+ ///
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
+ protected BMFont font;
+ ///
+ /// Specifies how the text in this element is horizontally aligned.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected TextAlignment textAlignment;
+
+ // TODO: Finish implementing
+ ///
+ /// Adds or subtracts space between individual words, measured in pixels.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float wordSpacing;
+ ///
+ /// Adds or subtracts space between individual characters, measured in pixels.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float charSpacing;
+ ///
+ /// The horizontal scaling factor applied to all characters in the text. Values smaller than 1
+ /// will squeeze the text, and values greater than 1 will stretch it.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float stretchX = 1;
+
+ ///
+ /// A horizontal skew to apply to the text, useful for creating faux-italic type. A value of
+ /// 0 represents no skew.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float skew = 0;
+ ///
+ /// The weight of the font when using SDF-based fonts. This is not a true variable-weight
+ /// axis control, but rather an adjustment made to the rendered SDF font, it's useful for
+ /// faux-bold type. A value of 0.5 represents the font's native weight.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float weight = 0.5f;
+
+ // Note that in the future this will store the shaped positions of each glyph to be rendered.
+ ///
+ /// This stores the bounds of the label as measured by the font engine. This is updated automatically
+ /// whenever the UI engine re-measures the element.
+ ///
+ protected Bounds2D measuredBounds;
+
+ public TextBase()
+ {
+ text = string.Empty;
+ size = 14;
+ textColour = new(0, 0, 0, 1);
+ font = Fonts.Default;
+ }
+
+ public TextBase(string? text) : this()
+ {
+ this.text = text;
+ }
+
+ protected internal override Vector2 Measure()
+ {
+ var res = Font.Measure(text, size, 1);
+ measuredBounds = res;
+ return res.Size;
+ }
+
+ protected override Bounds2D ComputeBounds(Bounds2D parent)
+ {
+ var bounds = base.ComputeBounds(parent);
+ // Apply an adjustment to the bounds to correct the vertical centering.
+ switch (VerticalAlignment)
+ {
+ case Alignment.Top:
+ break;
+ case Alignment.Bottom:
+ break;
+ case Alignment.Centre:
+ case Alignment.Stretch:
+ /*float emHeight;
+ //if (font.CharsDict.TryGetValue('M', out var xChar))
+ // emHeight = xChar.size.Y;
+ //else
+ emHeight = font.Size * 1.333f;
+ emHeight *= 0.5f;
+ emHeight = 0;
+ float offset = (font.Base - emHeight) * (size / font.Size);
+ bounds.topLeft.Y -= offset;
+ bounds.bottomRight.Y -= offset;*/
+ float offset = measuredBounds.topLeft.Y;
+ bounds.topLeft.Y -= offset;
+ bounds.bottomRight.Y -= offset;
+
+ break;
+ }
+
+ return bounds;
+ }
+
+ ///
+ /// Draws this text using the given drawing context.
+ ///
+ /// This method exists outside of so that elements derived
+ /// from this class can call the text drawing method whenever they want within their own
+ /// method implementation.
+ ///
+ ///
+ protected void DrawText(IDrawContext ctx)
+ {
+ if (string.IsNullOrEmpty(text))
+ return;
+
+ var fnt = font;
+ var tex = fnt.FontTexture;
+ if (tex == null)
+ return;
+
+ tex.ExecuteDrawCommands(ctx);
+ if (!tex.IsLoaded)
+ {
+ // Can't render yet, the font texture isn't ready, try again next frame.
+ Dirty(DirtyFlag.Content);
+ return;
+ }
+ ctx.DrawText(RenderedBoundsAbsolute, text.AsSpan(), fnt, size, textColour, wordSpacing, charSpacing, skew, weight, stretchX);
+ }
+
+ protected internal override void Draw(IDrawContext ctx)
+ {
+ DrawText(ctx);
+ }
+}
diff --git a/ArgonUI/UIElements/Abstract/TextBlockBase.cs b/ArgonUI/UIElements/Abstract/TextBlockBase.cs
new file mode 100644
index 0000000..16f3a50
--- /dev/null
+++ b/ArgonUI/UIElements/Abstract/TextBlockBase.cs
@@ -0,0 +1,207 @@
+using ArgonUI.Drawing;
+using ArgonUI.SourceGenerator;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace ArgonUI.UIElements.Abstract;
+
+///
+/// The base class for any which draws text on top of a .
+///
+///
+/// With the exception of text wrapping and a background rectangle, this class should have feature parity with .
+///
+[UIClonable]
+public abstract partial class TextBlockBase : RectangleBase
+{
+ ///
+ /// The text represented by this element.
+ ///
+ [Reactive, Dirty(DirtyFlag.Layout)]
+ protected string? text;
+ ///
+ /// The font size of this element's text.
+ ///
+ [Reactive("FontSize"), Dirty(DirtyFlag.Layout), Stylable]
+ protected float size;
+ ///
+ /// The text colour of this element's text.
+ ///
+ [Reactive("TextColour"), Dirty(DirtyFlag.Layout), Stylable]
+ protected Vector4 textColour;
+ ///
+ /// The font used by this element's text.
+ ///
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
+ protected BMFont font;
+ ///
+ /// Specifies how the text in this element is horizontally aligned.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected TextAlignment textAlignment;
+ ///
+ /// Justifies the text of this element to fill it's width.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected bool justify;
+ //protected bool justifyLastLine;
+
+ // TODO: Finish implementing
+ ///
+ /// Adds or subtracts space between individual words, measured in pixels.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float wordSpacing;
+ ///
+ /// Adds or subtracts space between individual characters, measured in pixels.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float charSpacing;
+ ///
+ /// The horizontal scaling factor applied to all characters in the text. Values smaller than 1
+ /// will squeeze the text, and values greater than 1 will stretch it.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float stretchX = 1;
+
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected TextOverflowMode textOverflowMode;
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float lineSpacing;
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float firstLineIndent;
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float indent;
+
+ ///
+ /// A horizontal skew to apply to the text, useful for creating faux-italic type. A value of
+ /// 0 represents no skew.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float skew = 0;
+ ///
+ /// The weight of the font when using SDF-based fonts. This is not a true variable-weight
+ /// axis control, but rather an adjustment made to the rendered SDF font, it's useful for
+ /// faux-bold type. A value of 0.5 represents the font's native weight.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
+ protected float weight = 0.5f;
+
+ // Note that in the future this will store the shaped positions of each glyph to be rendered.
+ ///
+ /// This stores the bounds of the label as measured by the font engine. This is updated automatically
+ /// whenever the UI engine re-measures the element.
+ ///
+ protected Bounds2D measuredBounds;
+
+ public TextBlockBase()
+ {
+ text = string.Empty;
+ size = 14;
+ colour = Vector4.Zero;
+ outlineThickness = 0;
+ textColour = new(0, 0, 0, 1);
+ font = Fonts.Default;
+ }
+
+ public TextBlockBase(string? text) : this()
+ {
+ this.text = text;
+ }
+
+ protected internal override Vector2 Measure()
+ {
+ var res = Font.Measure(text, size, 1);
+ measuredBounds = res;
+ return res.Size;
+ }
+
+ protected override Bounds2D ComputeBounds(Bounds2D parent)
+ {
+ var bounds = base.ComputeBounds(parent);
+ // Apply an adjustment to the bounds to correct the vertical centering.
+ switch (VerticalAlignment)
+ {
+ case Alignment.Top:
+ break;
+ case Alignment.Bottom:
+ break;
+ case Alignment.Centre:
+ case Alignment.Stretch:
+ /*float emHeight;
+ //if (font.CharsDict.TryGetValue('M', out var xChar))
+ // emHeight = xChar.size.Y;
+ //else
+ emHeight = font.Size * 1.333f;
+ emHeight *= 0.5f;
+ emHeight = 0;
+ float offset = (font.Base - emHeight) * (size / font.Size);
+ bounds.topLeft.Y -= offset;
+ bounds.bottomRight.Y -= offset;*/
+ float offset = measuredBounds.topLeft.Y;
+ bounds.topLeft.Y -= offset;
+ bounds.bottomRight.Y -= offset;
+
+ break;
+ }
+
+ return bounds;
+ }
+
+ ///
+ /// Draws this text using the given drawing context.
+ ///
+ /// This method exists outside of so that elements derived
+ /// from this class can call the text drawing method whenever they want within their own
+ /// method implementation.
+ ///
+ ///
+ protected void DrawText(IDrawContext ctx)
+ {
+ if (string.IsNullOrEmpty(text))
+ return;
+
+ var fnt = font;
+ var tex = fnt.FontTexture;
+ if (tex == null)
+ return;
+
+ tex.ExecuteDrawCommands(ctx);
+ if (!tex.IsLoaded)
+ {
+ // Can't render yet, the font texture isn't ready, try again next frame.
+ Dirty(DirtyFlag.Content);
+ return;
+ }
+ ctx.DrawText(RenderedBoundsAbsolute, text.AsSpan(), fnt, size, textColour, wordSpacing, charSpacing, skew, weight, stretchX);
+ }
+
+ protected internal override void Draw(IDrawContext ctx)
+ {
+ DrawRectangle(ctx);
+ DrawText(ctx);
+ }
+}
+
+///
+/// Represents the horizontal alignment of a block of text.
+///
+public enum TextAlignment
+{
+ Left,
+ Centre,
+ Right
+}
+
+///
+/// Determines how text which overflows it's horizontal or vertical bounds should be handled.
+///
+public enum TextOverflowMode
+{
+ Clip,
+ WrapAndClip,
+ Ellipsis,
+ WrapAndEllipsis
+}
diff --git a/ArgonUI/UIElements/UIContainer.cs b/ArgonUI/UIElements/Abstract/UIContainer.cs
similarity index 97%
rename from ArgonUI/UIElements/UIContainer.cs
rename to ArgonUI/UIElements/Abstract/UIContainer.cs
index fa6e056..a43fb3a 100644
--- a/ArgonUI/UIElements/UIContainer.cs
+++ b/ArgonUI/UIElements/Abstract/UIContainer.cs
@@ -1,5 +1,6 @@
using ArgonUI.SourceGenerator;
using ArgonUI.Styling;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -21,18 +22,18 @@ public abstract partial class UIContainer : UIElement
///
/// How much space (in pixels) to leave around each edge of each child element.
///
- [Reactive, CustomAccessibility("public virtual"), Stylable, Dirty(DirtyFlags.Layout)]
+ [Reactive, CustomAccessibility("public virtual"), Stylable, Dirty(DirtyFlag.Layout)]
private Thickness innerPadding;
///
/// Whether child elements which overflow the bounds of this container should be drawn.
///
- [Reactive, CustomAccessibility("public virtual"), Stylable, Dirty(DirtyFlags.Layout)]
+ [Reactive, CustomAccessibility("public virtual"), Stylable, Dirty(DirtyFlag.Layout)]
private bool clipContents;
public UIContainer() : base()
{
- DirtyFlags |= DirtyFlags.AllChild;
+ DirtyFlags |= DirtyFlag.ChildContentAndLayout;
}
///
@@ -104,7 +105,7 @@ public UIContainer() : base()
///
protected internal virtual void BeforeLayoutChildren()
{
- //Dirty(DirtyFlags.Layout);
+ //Dirty(DirtyFlag.Layout);
}
///
@@ -138,7 +139,7 @@ protected internal override Bounds2D Layout(int childIndex)
if (bounds != RenderedBoundsAbsolute)
{
foreach (var child in Children)
- child.Dirty(DirtyFlags.Layout);
+ child.Dirty(DirtyFlag.Layout);
}
return bounds;
diff --git a/ArgonUI/UIElements/Abstract/UIContainerRectangle.cs b/ArgonUI/UIElements/Abstract/UIContainerRectangle.cs
new file mode 100644
index 0000000..4095983
--- /dev/null
+++ b/ArgonUI/UIElements/Abstract/UIContainerRectangle.cs
@@ -0,0 +1,78 @@
+using ArgonUI.Drawing;
+using ArgonUI.SourceGenerator;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace ArgonUI.UIElements.Abstract;
+
+///
+/// Represents the base class for all s which draw a rectangle background.
+///
+/// This should have feature parity with .
+///
+[UIClonable]
+public abstract partial class UIContainerRectangle : UIContainer, IRectangleProps
+{
+ ///
+ /// The colour of this rectangle.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected Vector4 colour;
+ ///
+ /// The outline colour.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected Vector4 outlineColour;
+ ///
+ /// The thickness of the outline in pixels.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected float outlineThickness;
+ ///
+ /// The radius of the corners of this rectangle.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected float rounding;
+ ///
+ /// The texture to fill the rectangle with.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected ArgonTexture? texture;
+ ///
+ /// The gradient to fill the rectangle with.
+ ///
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] protected Gradient? gradientFill;
+
+
+#if DEBUG_LATENCY
+ public bool logLatencyNow;
+#endif
+
+ protected internal override void Draw(IDrawContext ctx)
+ {
+ if (texture != null)
+ {
+ texture.ExecuteDrawCommands(ctx);
+ if (!texture.IsLoaded)
+ {
+ // Can't render yet, the font texture isn't ready, try again next frame.
+ Dirty(DirtyFlag.Content);
+ return;
+ }
+ ctx.DrawTexture(RenderedBoundsAbsolute, texture.TextureHandle!, Rounding);
+ }
+ else if (gradientFill != null)
+ {
+ ctx.DrawGradient(RenderedBoundsAbsolute, gradientFill.ColourTL, gradientFill.ColourTR, gradientFill.ColourBL, gradientFill.ColourBR, Rounding);
+ }
+ else if (Colour.W > 0)
+ {
+ ctx.DrawRect(RenderedBoundsAbsolute, Colour, Rounding);
+ }
+
+ if (outlineThickness > 0 && outlineColour.W > 0)
+ ctx.DrawOutlineRect(RenderedBoundsAbsolute, outlineColour, outlineThickness, rounding);
+#if DEBUG_LATENCY
+ if (logLatencyNow)
+ commands.Add(ctx => ctx.MarkLatencyTimerEnd($"{Colour.Y}"));
+ logLatencyNow = false;
+#endif
+ }
+}
diff --git a/ArgonUI/UIElements/UIElement.cs b/ArgonUI/UIElements/Abstract/UIElement.cs
similarity index 83%
rename from ArgonUI/UIElements/UIElement.cs
rename to ArgonUI/UIElements/Abstract/UIElement.cs
index 035e47b..ad569ce 100644
--- a/ArgonUI/UIElements/UIElement.cs
+++ b/ArgonUI/UIElements/Abstract/UIElement.cs
@@ -3,6 +3,7 @@
using ArgonUI.Input;
using ArgonUI.SourceGenerator;
using ArgonUI.Styling;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -41,7 +42,7 @@ public abstract partial class UIElement : ReactiveObject, IDisposable
///
/// Whether this element and it's children should be drawn. Elements which are not visible will not receive input events, nor will they participate in layout or drawing.
///
- [Reactive, Stylable, Dirty(DirtyFlags.Layout)]
+ [Reactive, Stylable, Dirty(DirtyFlag.Layout)]
private Visibility visible = Visibility.Visible;
///
/// A set of stylable properties to be applied to this UIElement and it's decendants.
@@ -63,53 +64,61 @@ public abstract partial class UIElement : ReactiveObject, IDisposable
///
/// The absolute width of this element. Set to 0 to use automatic sizing.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private int width;
///
/// The absolute height of this element. Set to 0 to use automatic sizing.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private int height;
//public Vector2 Pivot { get => pivot; set => UpdateProperty(ref pivot, value); }
///
/// How this element should be aligned vertically relative to it's parent.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private Alignment verticalAlignment;
///
/// How this element should be aligned horizontally relative to it's parent.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private Alignment horizontalAlignment;
///
/// How much space (in pixels) to leave around each edge of the element relative to the parent. Specified as a vector of (Top, Right, Bottom, Left).
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private Thickness margin;
///
/// Controls which elements are shown on top of each other. Higher z-indexes will be shown on top.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private int zIndex;
///
/// The smallest width to shrink this element down to when using automatic sizing.
+ ///
+ /// Set to a value < 0 to disable the constraint.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private int minWidth = -1;
///
/// The smallest height to shrink this element down to when using automatic sizing.
+ ///
+ /// Set to a value < 0 to disable the constraint.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private int minHeight = -1;
///
/// The largest width to expand this element up to when using automatic sizing.
+ ///
+ /// Set to a value < 0 to disable the constraint.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private int maxWidth = -1;
///
/// The largest height to expand this element up to when using automatic sizing.
+ ///
+ /// Set to a value < 0 to disable the constraint.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private int maxHeight = -1;
///
@@ -132,17 +141,17 @@ public abstract partial class UIElement : ReactiveObject, IDisposable
/// Gets the dirty flags of this element which determine if the element needs to be re-rendered or re-laid out.
///
///
- /// Use the and methods to set the dirty flags.
+ /// Use the and methods to set the dirty flags.
///
- public DirtyFlags DirtyFlags { get => dirtyFlag; protected internal set => dirtyFlag = value; }
+ public DirtyFlag DirtyFlags { get => dirtyFlag; protected internal set => dirtyFlag = value; }
///
/// Gets the width of the element when using automatic sizing.
///
- public int DesiredWidth => desiredSize.x;
+ public float DesiredWidth => desiredSize.X;
///
/// Gets the height of the element when using automatic sizing.
///
- public int DesiredHeight => desiredSize.y;
+ public float DesiredHeight => desiredSize.Y;
///
/// Gets whether the mouse is currently hovering over this element.
///
@@ -179,8 +188,8 @@ public abstract partial class UIElement : ReactiveObject, IDisposable
/// and the previous rendered bounds.
///
internal Bounds2D invalidatedRenderBounds;
- internal VectorInt2 desiredSize;
- private DirtyFlags dirtyFlag = DirtyFlags.ContentAndLayout;
+ internal Vector2 desiredSize;
+ private DirtyFlag dirtyFlag = DirtyFlag.ContentAndLayout;
private bool isHovered;
private bool isPressed;
private bool isFocused;
@@ -304,32 +313,28 @@ public abstract partial class UIElement : ReactiveObject, IDisposable
///
/// Represents the method that handles mouse down/up events.
///
- ///
- /// The mouse button which was pressed/released.
- public delegate void MouseButtonEventHandler(InputManager inputManager, MouseButton button);
+ ///
+ public delegate void MouseButtonEventHandler(MouseButtonInputEventArgs args);
///
/// Represents the method that handles mouse movement events.
///
- ///
- /// The new position of the mouse.
- public delegate void MousePosEventHandler(InputManager inputManager, VectorInt2 pos);
+ ///
+ public delegate void MousePosEventHandler(MousePositionInputEventArgs args);
///
/// Represents the method that handles mouse scroll events.
///
- ///
- /// How much the mouse has scrolled since this event was last invoked.
- public delegate void MouseScrollEventHandler(InputManager inputManager, Vector2 delta);
+ ///
+ public delegate void MouseScrollEventHandler(MouseScrollInputEventArgs args);
///
/// Represents the method that handles keyboard events.
///
- ///
- /// The key that was pressed/released.
- public delegate void KeyEventHandler(InputManager inputManager, KeyCode key);
+ ///
+ public delegate void KeyEventHandler(KeyboardInputEventArgs args);
///
/// Represents the method that handles events from the input manager.
///
- ///
- public delegate void InputEventHandler(InputManager inputManager);
+ ///
+ public delegate void InputEventHandler(InputEventArgs args);
#endregion
public UIElement()
@@ -343,8 +348,8 @@ public UIElement()
///
/// Marks this element as dirty, forcing the UI engine to redraw this element and it's children when it's next dispatched.
///
- /// Which to set.
- public virtual void Dirty(DirtyFlags flags)
+ /// Which to set.
+ public virtual void Dirty(DirtyFlag flags)
{
var prev = dirtyFlag;
var newFlags = prev | flags;
@@ -359,11 +364,11 @@ public virtual void Dirty(DirtyFlags flags)
#endif
// Propagate dirty flags up
- var toPropagate = flags & (DirtyFlags.ChildContent | DirtyFlags.ChildLayout);
- if ((flags & DirtyFlags.Layout) != 0)
- toPropagate |= DirtyFlags.ChildLayout;
- if ((flags & DirtyFlags.Content) != 0)
- toPropagate |= DirtyFlags.ChildContent;
+ var toPropagate = flags & (DirtyFlag.ChildContent | DirtyFlag.ChildLayout);
+ if ((flags & DirtyFlag.Layout) != 0)
+ toPropagate |= DirtyFlag.ChildLayout;
+ if ((flags & DirtyFlag.Content) != 0)
+ toPropagate |= DirtyFlag.ChildContent;
Parent?.Dirty(toPropagate);
}
@@ -371,8 +376,8 @@ public virtual void Dirty(DirtyFlags flags)
///
/// Clears the given dirty flags from the UI element.
///
- /// Which to clear.
- public virtual void ClearDirtyFlag(DirtyFlags flags)
+ /// Which to clear.
+ public virtual void ClearDirtyFlag(DirtyFlag flags)
{
dirtyFlag &= ~flags;
}
@@ -392,6 +397,15 @@ public void Dispose()
style?.Unregister(this);
}
+ ///
+ /// Sets the 's currently (keyboard) focussed element to this .
+ ///
+ public void Focus()
+ {
+ if (window != null)
+ window.InputManager.FocussedElement = this;
+ }
+
///
/// Creates a deep copy of this .
///
@@ -419,7 +433,7 @@ public virtual UIElement Clone(UIElement target)
// I'm hesitant to fully automate parameter cloning (through reflection or source
// generation), as there is some logic to apply to it which could be complex.
// Not sure if we should invalidate dirty flags here or not...
- target.dirtyFlag = DirtyFlags.Layout;
+ target.dirtyFlag = DirtyFlag.Layout;
target.focusable = focusable;
target.height = height;
target.width = width;
@@ -446,9 +460,9 @@ public virtual UIElement Clone(UIElement target)
/// Layout() and Draw() occur from root node to leaves, whereas measure is invoked on the leaves first working it's way up to the root.
///
/// The desired width and height of this element.
- internal protected virtual VectorInt2 Measure()
+ internal protected virtual Vector2 Measure()
{
- return new VectorInt2(width, height);
+ return new Vector2(width, height);
}
// Internal
@@ -461,8 +475,8 @@ protected virtual Bounds2D ComputeBounds(Bounds2D parent)
{
// Apply limits to the width and height
var parentSize = parent.Size;
- var desiredWidth = width > 0 ? width : DesiredWidth;
- var desiredHeight = height > 0 ? height : DesiredHeight;
+ float desiredWidth = width > 0 ? width : DesiredWidth;
+ float desiredHeight = height > 0 ? height : DesiredHeight;
if (minWidth >= 0)
desiredWidth = Math.Max(desiredWidth, minWidth);
@@ -548,79 +562,75 @@ internal protected virtual Bounds2D Layout(int childIndex)
RenderedBounds = new(bounds.topLeft - parentBounds.topLeft, bounds.bottomRight - parentBounds.topLeft);
// Invalidating the layout implies invalidating the content
- Dirty(DirtyFlags.Content);
+ Dirty(DirtyFlag.Content);
return bounds;
}
- internal void InvokeOnMouseDown(InputManager inputManager, MouseButton button)
+ internal void InvokeOnMouseDown(MouseButtonInputEventArgs args)
{
isPressed = true;
OnStylableInputEvent?.Invoke(this, UIElementInputChange.MousePress);
- OnMouseDown?.Invoke(inputManager, button);
+ OnMouseDown?.Invoke(args);
}
- internal void InvokeOnMouseUp(InputManager inputManager, MouseButton button)
+ internal void InvokeOnMouseUp(MouseButtonInputEventArgs args)
{
isPressed = false;
OnStylableInputEvent?.Invoke(this, UIElementInputChange.MousePress);
- OnMouseUp?.Invoke(inputManager, button);
+ OnMouseUp?.Invoke(args);
}
- internal void InvokeOnMouseEnter(InputManager inputManager, VectorInt2 pos)
+ internal void InvokeOnMouseEnter(MousePositionInputEventArgs args)
{
isHovered = true;
OnStylableInputEvent?.Invoke(this, UIElementInputChange.MouseHover);
- OnMouseEnter?.Invoke(inputManager, pos);
-
- Parent?.InvokeOnMouseEnter(inputManager, pos);
+ OnMouseEnter?.Invoke(args);
}
- internal void InvokeOnMouseLeave(InputManager inputManager, VectorInt2 pos)
+ internal void InvokeOnMouseLeave(MousePositionInputEventArgs args)
{
isHovered = false;
OnStylableInputEvent?.Invoke(this, UIElementInputChange.MouseHover);
- OnMouseLeave?.Invoke(inputManager, pos);
-
- Parent?.InvokeOnMouseLeave(inputManager, pos);
+ OnMouseLeave?.Invoke(args);
}
- internal void InvokeOnMouseOver(InputManager inputManager, VectorInt2 pos)
+ internal void InvokeOnMouseOver(MousePositionInputEventArgs args)
{
isHovered = true;
//OnStylableInputEvent?.Invoke(UIElementInputChange.MouseHover);
- OnMouseOver?.Invoke(inputManager, pos);
-
- // TODO: Bubble events up to parents
- // We might consider doing the bubbling in the input manager, and passing a shared
- // reference to the event args object so that subscribers can stop the bubbling
- // of the event.
- Parent?.InvokeOnMouseOver(inputManager, pos);
+ OnMouseOver?.Invoke(args);
+ }
+ internal void InvokeOnDoubleClick(MouseButtonInputEventArgs args)
+ {
+ isPressed = true;
+ OnStylableInputEvent?.Invoke(this, UIElementInputChange.MousePress);
+ OnDoubleClick?.Invoke(args);
}
- internal void InvokeOnDoubleClick(InputManager inputManager, MouseButton button) => OnDoubleClick?.Invoke(inputManager, button);
- internal void InvokeOnMouseWheel(InputManager inputManager, Vector2 delta) => OnMouseWheel?.Invoke(inputManager, delta);
- internal void InvokeOnKeyDown(InputManager inputManager, KeyCode key)
+
+ internal void InvokeOnMouseWheel(MouseScrollInputEventArgs args) => OnMouseWheel?.Invoke(args);
+ internal void InvokeOnKeyDown(KeyboardInputEventArgs args)
{
OnStylableInputEvent?.Invoke(this, UIElementInputChange.KeyPress);
- OnKeyDown?.Invoke(inputManager, key);
+ OnKeyDown?.Invoke(args);
}
- internal void InvokeOnKeyUp(InputManager inputManager, KeyCode key)
+ internal void InvokeOnKeyUp(KeyboardInputEventArgs args)
{
OnStylableInputEvent?.Invoke(this, UIElementInputChange.KeyPress);
- OnKeyUp?.Invoke(inputManager, key);
+ OnKeyUp?.Invoke(args);
}
- internal void InvokeOnDragStart(InputManager inputManager) => OnDragStart?.Invoke(inputManager);
- internal void InvokeOnDrop(InputManager inputManager) => OnDrop?.Invoke(inputManager);
- internal void InvokeOnDragEnter(InputManager inputManager) => OnDragEnter?.Invoke(inputManager);
- internal void InvokeOnDragLeave(InputManager inputManager) => OnDragLeave?.Invoke(inputManager);
- internal void InvokeOnDragOver(InputManager inputManager) => OnDragOver?.Invoke(inputManager);
- internal void InvokeOnFocusGot(InputManager inputManager)
+ internal void InvokeOnDragStart(InputEventArgs args) => OnDragStart?.Invoke(args);
+ internal void InvokeOnDrop(InputEventArgs args) => OnDrop?.Invoke(args);
+ internal void InvokeOnDragEnter(InputEventArgs args) => OnDragEnter?.Invoke(args);
+ internal void InvokeOnDragLeave(InputEventArgs args) => OnDragLeave?.Invoke(args);
+ internal void InvokeOnDragOver(InputEventArgs args) => OnDragOver?.Invoke(args);
+ internal void InvokeOnFocusGot(FocusInputEventArgs args)
{
isFocused = true;
OnStylableInputEvent?.Invoke(this, UIElementInputChange.Focus);
- OnFocusGot?.Invoke(inputManager);
+ OnFocusGot?.Invoke(args);
}
- internal void InvokeOnFocusLost(InputManager inputManager)
+ internal void InvokeOnFocusLost(FocusInputEventArgs args)
{
isFocused = false;
OnStylableInputEvent?.Invoke(this, UIElementInputChange.Focus);
- OnFocusLost?.Invoke(inputManager);
+ OnFocusLost?.Invoke(args);
}
internal void InvokeOnLoaded()
{
diff --git a/ArgonUI/UIElements/UIWindowElement.cs b/ArgonUI/UIElements/Abstract/UIWindowElement.cs
similarity index 92%
rename from ArgonUI/UIElements/UIWindowElement.cs
rename to ArgonUI/UIElements/Abstract/UIWindowElement.cs
index 29e1687..c9f436a 100644
--- a/ArgonUI/UIElements/UIWindowElement.cs
+++ b/ArgonUI/UIElements/Abstract/UIWindowElement.cs
@@ -1,5 +1,6 @@
using ArgonUI.Drawing;
using ArgonUI.SourceGenerator;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -20,7 +21,7 @@ public partial class UIWindowElement : UIContainer
///
/// The background colour of this window.
///
- [Reactive("BGColour"), Dirty(DirtyFlags.Content), Stylable]
+ [Reactive("BGColour"), Dirty(DirtyFlag.Content), Stylable]
private Vector4 bgColour;
//public new UIWindow Window { get; init; }
@@ -41,21 +42,21 @@ internal UIWindowElement(UIWindow window)
{
Width = this.window!.Size.x;
Height = this.window!.Size.y;
- Dirty(DirtyFlags.Layout);
+ Dirty(DirtyFlag.Layout);
};
window.OnResize += () =>
{
Width = this.window!.Size.x;
Height = this.window!.Size.y;
- Dirty(DirtyFlags.Layout);
+ Dirty(DirtyFlag.Layout);
};
}
- public override void Dirty(DirtyFlags flags)
+ public override void Dirty(DirtyFlag flags)
{
base.Dirty(flags);
- if (flags != DirtyFlags.None)
+ if (flags != DirtyFlag.None)
{
Window!.RequestRedraw();
}
@@ -116,11 +117,11 @@ protected internal override Bounds2D Layout(int childIndex)
if (bounds != RenderedBoundsAbsolute)
{
foreach (var child in Children)
- child.Dirty(DirtyFlags.Layout);
+ child.Dirty(DirtyFlag.Layout);
}
// Invalidating the layout implies invalidating the content
- Dirty(DirtyFlags.Content);
+ Dirty(DirtyFlag.Content);
return bounds;
}
diff --git a/ArgonUI/UIElements/Checkbox.cs b/ArgonUI/UIElements/Checkbox.cs
new file mode 100644
index 0000000..a25b761
--- /dev/null
+++ b/ArgonUI/UIElements/Checkbox.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements;
+
+internal class Checkbox
+{
+}
diff --git a/ArgonUI/UIElements/ColourPicker.cs b/ArgonUI/UIElements/ColourPicker.cs
new file mode 100644
index 0000000..be85b9c
--- /dev/null
+++ b/ArgonUI/UIElements/ColourPicker.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements;
+
+internal class ColourPicker
+{
+}
diff --git a/ArgonUI/UIElements/ContentButton.cs b/ArgonUI/UIElements/ContentButton.cs
index 7700dd3..0da8a5a 100644
--- a/ArgonUI/UIElements/ContentButton.cs
+++ b/ArgonUI/UIElements/ContentButton.cs
@@ -1,5 +1,6 @@
using ArgonUI.Drawing;
using ArgonUI.SourceGenerator;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -16,11 +17,11 @@ public partial class ContentButton : ElementPresenterBase
///
/// The colour of this button.
///
- [Reactive, Dirty(DirtyFlags.Content), Stylable] private Vector4 colour;
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] private Vector4 colour;
///
/// The radius of the corners of this button.
///
- [Reactive, Dirty(DirtyFlags.Content), Stylable] private float rounding;
+ [Reactive, Dirty(DirtyFlag.Content), Stylable] private float rounding;
protected internal override void Draw(IDrawContext ctx)
{
diff --git a/ArgonUI/UIElements/DatePicker.cs b/ArgonUI/UIElements/DatePicker.cs
new file mode 100644
index 0000000..f8b9431
--- /dev/null
+++ b/ArgonUI/UIElements/DatePicker.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class DatePicker
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/DirtyFlags.cs b/ArgonUI/UIElements/DirtyFlags.cs
deleted file mode 100644
index 3f8e396..0000000
--- a/ArgonUI/UIElements/DirtyFlags.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace ArgonUI.UIElements;
-
-[Flags]
-public enum DirtyFlags
-{
- None,
- Layout = 1 << 0,
- Content = 1 << 1,
- ChildLayout = 1 << 2,
- ChildContent = 1 << 3,
-
- ContentAndLayout = Layout | Content,
- AllChild = ChildContent | ChildLayout,
- All = ContentAndLayout | AllChild,
-}
diff --git a/ArgonUI/UIElements/DropdownBox.cs b/ArgonUI/UIElements/DropdownBox.cs
new file mode 100644
index 0000000..a0939fc
--- /dev/null
+++ b/ArgonUI/UIElements/DropdownBox.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements;
+
+internal class DropdownBox
+{
+}
diff --git a/ArgonUI/UIElements/FilePicker.cs b/ArgonUI/UIElements/FilePicker.cs
new file mode 100644
index 0000000..ce98ba4
--- /dev/null
+++ b/ArgonUI/UIElements/FilePicker.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class FilePicker
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Grid.cs b/ArgonUI/UIElements/Grid.cs
new file mode 100644
index 0000000..553a0ff
--- /dev/null
+++ b/ArgonUI/UIElements/Grid.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Grid
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Image.cs b/ArgonUI/UIElements/Image.cs
new file mode 100644
index 0000000..48a8ec2
--- /dev/null
+++ b/ArgonUI/UIElements/Image.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Image
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Label.cs b/ArgonUI/UIElements/Label.cs
index 8dadd72..c0484dd 100644
--- a/ArgonUI/UIElements/Label.cs
+++ b/ArgonUI/UIElements/Label.cs
@@ -1,5 +1,6 @@
using ArgonUI.Drawing;
using ArgonUI.SourceGenerator;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,39 +11,13 @@
namespace ArgonUI.UIElements;
[UIClonable]
-public partial class Label : UIElement
+public partial class Label : TextBase
{
- ///
- /// The text represented by this label.
- ///
- [Reactive, Dirty(DirtyFlags.Layout)] protected string? text;
- ///
- /// The font size of this label.
- ///
- [Reactive("FontSize"), Dirty(DirtyFlags.Layout), Stylable] protected float size;
- ///
- /// The text colour of this label.
- ///
- [Reactive("TextColour"), Dirty(DirtyFlags.Layout), Stylable] protected Vector4 colour;
-
- //[UICloneableField]
- ///
- /// The font used by this label.
- ///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
- protected BMFont font;
-
- ///
- /// This stores the bounds of the label as measured by the font engine. This is updated automatically
- /// whenever the UI engine re-measures the element.
- ///
- protected Bounds2D measuredBounds;
-
public Label()
{
text = "Label";
size = 14;
- colour = new(0, 0, 0, 1);
+ textColour = new(0, 0, 0, 1);
font = Fonts.Default;
}
@@ -50,63 +25,4 @@ public Label(string? text) : this()
{
this.text = text;
}
-
- protected internal override VectorInt2 Measure()
- {
- var res = Font.Measure(text, size, 1);
- measuredBounds = res;
- return new(res.Size);
- }
-
- protected override Bounds2D ComputeBounds(Bounds2D parent)
- {
- var bounds = base.ComputeBounds(parent);
- // Apply an adjustment to the bounds to correct the vertical centering.
- switch (VerticalAlignment)
- {
- case Alignment.Top:
- break;
- case Alignment.Bottom:
- break;
- case Alignment.Centre:
- case Alignment.Stretch:
- /*float emHeight;
- //if (font.CharsDict.TryGetValue('M', out var xChar))
- // emHeight = xChar.size.Y;
- //else
- emHeight = font.Size * 1.333f;
- emHeight *= 0.5f;
- emHeight = 0;
- float offset = (font.Base - emHeight) * (size / font.Size);
- bounds.topLeft.Y -= offset;
- bounds.bottomRight.Y -= offset;*/
- float offset = measuredBounds.topLeft.Y;
- bounds.topLeft.Y -= offset;
- bounds.bottomRight.Y -= offset;
-
- break;
- }
-
- return bounds;
- }
-
- protected internal override void Draw(IDrawContext ctx)
- {
- if (string.IsNullOrEmpty(text))
- return;
-
- var fnt = font;
- var tex = fnt.FontTexture;
- if (tex == null)
- return;
-
- tex.ExecuteDrawCommands(ctx);
- if (!tex.IsLoaded)
- {
- // Can't render yet, the font texture isn't ready, try again next frame.
- Dirty(DirtyFlags.Content);
- return;
- }
- ctx.DrawText(RenderedBoundsAbsolute, size, text!, fnt, colour);
- }
}
diff --git a/ArgonUI/UIElements/Modal.cs b/ArgonUI/UIElements/Modal.cs
new file mode 100644
index 0000000..c8e4597
--- /dev/null
+++ b/ArgonUI/UIElements/Modal.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Modal
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/NumberField.cs b/ArgonUI/UIElements/NumberField.cs
new file mode 100644
index 0000000..d7ce417
--- /dev/null
+++ b/ArgonUI/UIElements/NumberField.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements;
+
+internal class NumberField : TextField
+{
+}
diff --git a/ArgonUI/UIElements/OverlayPanel.cs b/ArgonUI/UIElements/OverlayPanel.cs
new file mode 100644
index 0000000..88e9d01
--- /dev/null
+++ b/ArgonUI/UIElements/OverlayPanel.cs
@@ -0,0 +1,10 @@
+using ArgonUI.UIElements.Abstract;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements;
+
+public partial class OverlayPanel : Panel
+{
+}
diff --git a/ArgonUI/UIElements/ProgressBar.cs b/ArgonUI/UIElements/ProgressBar.cs
new file mode 100644
index 0000000..37bf854
--- /dev/null
+++ b/ArgonUI/UIElements/ProgressBar.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class ProgressBar
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/RadioButton.cs b/ArgonUI/UIElements/RadioButton.cs
new file mode 100644
index 0000000..a297e63
--- /dev/null
+++ b/ArgonUI/UIElements/RadioButton.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class RadioButton
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/RangeSlider.cs b/ArgonUI/UIElements/RangeSlider.cs
new file mode 100644
index 0000000..7651590
--- /dev/null
+++ b/ArgonUI/UIElements/RangeSlider.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class RangeSlider
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Rectangle.cs b/ArgonUI/UIElements/Rectangle.cs
index 87bbd10..748c8d5 100644
--- a/ArgonUI/UIElements/Rectangle.cs
+++ b/ArgonUI/UIElements/Rectangle.cs
@@ -1,5 +1,6 @@
using ArgonUI.Drawing;
using ArgonUI.SourceGenerator;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,30 +10,11 @@
namespace ArgonUI.UIElements;
+///
+/// A solid rectangle with an optional outline and rounded corners.
+///
[UIClonable]
-public partial class Rectangle : UIElement
+public partial class Rectangle : RectangleBase
{
- ///
- /// The colour of this rectangle.
- ///
- [Reactive, Dirty(DirtyFlags.Content), Stylable] private Vector4 colour;
- ///
- /// The radius of the corners of this rectangle.
- ///
- [Reactive, Dirty(DirtyFlags.Content), Stylable] private float rounding;
-
-#if DEBUG_LATENCY
- public bool logLatencyNow;
-#endif
-
- protected internal override void Draw(IDrawContext ctx)
- {
- if (Colour.W > 0)
- ctx.DrawRect(RenderedBoundsAbsolute, Colour, Rounding);
-#if DEBUG_LATENCY
- if (logLatencyNow)
- commands.Add(ctx => ctx.MarkLatencyTimerEnd($"{Colour.Y}"));
- logLatencyNow = false;
-#endif
- }
+
}
diff --git a/ArgonUI/UIElements/ScrollPanel.cs b/ArgonUI/UIElements/ScrollPanel.cs
new file mode 100644
index 0000000..6482e47
--- /dev/null
+++ b/ArgonUI/UIElements/ScrollPanel.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class ScrollPanel
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Separator.cs b/ArgonUI/UIElements/Separator.cs
new file mode 100644
index 0000000..fe93ed6
--- /dev/null
+++ b/ArgonUI/UIElements/Separator.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Separator
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Slider.cs b/ArgonUI/UIElements/Slider.cs
index f0e3587..530c47f 100644
--- a/ArgonUI/UIElements/Slider.cs
+++ b/ArgonUI/UIElements/Slider.cs
@@ -1,5 +1,6 @@
using ArgonUI.Drawing;
using ArgonUI.SourceGenerator;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,52 +16,52 @@ public partial class Slider : UIElement
///
/// The colour of this slider.
///
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
private Vector4 colour;
///
/// The rounding radius of the slider's handle.
///
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
+ [Reactive, Dirty(DirtyFlag.Content), Stylable]
private float handleRounding;
///
/// The radius of the handle.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private Vector2 handleSize;
///
/// The thickness of the slider track.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private float trackThickness;
///
/// Whether the slider should slide horizontally, or vertically.
///
- [Reactive, Dirty(DirtyFlags.Layout), Stylable]
+ [Reactive, Dirty(DirtyFlag.Layout), Stylable]
private bool vertical;
///
/// The current value of the slider.
///
- [Reactive, Dirty(DirtyFlags.Content)]
+ [Reactive, Dirty(DirtyFlag.Content)]
private float value;
///
/// The minimum value the slider can represent.
///
- [Reactive, Dirty(DirtyFlags.Content)]
+ [Reactive, Dirty(DirtyFlag.Content)]
private float min;
///
/// The maximum value the slider can represent.
///
- [Reactive, Dirty(DirtyFlags.Content)]
+ [Reactive, Dirty(DirtyFlag.Content)]
private float max;
///
/// The size of steps between values of the slider.
///
- [Reactive, Dirty(DirtyFlags.Content)]
+ [Reactive, Dirty(DirtyFlag.Content)]
private float step;
///
/// An exponent to raise the value of the slider to, useful for creating non-linear sliders.
///
- [Reactive, Dirty(DirtyFlags.Content)]
+ [Reactive, Dirty(DirtyFlag.Content)]
private float power;
protected internal override void Draw(IDrawContext ctx)
diff --git a/ArgonUI/UIElements/Spinner.cs b/ArgonUI/UIElements/Spinner.cs
new file mode 100644
index 0000000..ba99784
--- /dev/null
+++ b/ArgonUI/UIElements/Spinner.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Spinner
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/StackPanel.cs b/ArgonUI/UIElements/StackPanel.cs
index 54155f2..de5dee1 100644
--- a/ArgonUI/UIElements/StackPanel.cs
+++ b/ArgonUI/UIElements/StackPanel.cs
@@ -1,4 +1,5 @@
using ArgonUI.SourceGenerator;
+using ArgonUI.UIElements.Abstract;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,7 +11,7 @@ namespace ArgonUI.UIElements;
[UIClonable]
public partial class StackPanel : Panel
{
- [Reactive, Dirty(DirtyFlags.Layout)]
+ [Reactive, Dirty(DirtyFlag.Layout)]
private Direction direction;
public StackPanel()
@@ -37,34 +38,34 @@ public StackPanel(Direction direction)
protected internal override void BeforeLayoutChildren()
{
- Dirty(DirtyFlags.Layout);
+ Dirty(DirtyFlag.Layout);
}
- protected internal override VectorInt2 Measure()
+ protected internal override Vector2 Measure()
{
var children = Children;
if (children.Count == 0)
return base.Measure();
- VectorInt2 res = VectorInt2.Zero;
- VectorInt2 pad = new((int)(InnerPadding.left + InnerPadding.right), (int)(InnerPadding.top + InnerPadding.bottom));
+ var res = Vector2.Zero;
+ Vector2 pad = InnerPadding.Size;
if (direction == Direction.Vertical)
{
foreach (var child in children)
{
- res.x = Math.Max(res.x, child.desiredSize.x);
- res.y += child.desiredSize.y + pad.y;
+ res.X = Math.Max(res.X, child.desiredSize.X);
+ res.Y += child.desiredSize.Y + pad.Y;
}
- res.x += pad.x;
+ res.X += pad.X;
}
else
{
foreach (var child in children)
{
- res.x += child.desiredSize.x + pad.x;
- res.y = Math.Max(res.y, child.desiredSize.y);
+ res.X += child.desiredSize.X + pad.X;
+ res.Y = Math.Max(res.Y, child.desiredSize.Y);
}
- res.y += pad.y;
+ res.Y += pad.Y;
}
return res;
@@ -78,19 +79,19 @@ protected internal override VectorInt2 Measure()
protected internal override Bounds2D RequestChildBounds(UIElement element, int index)
{
if (index == 0)
- return RenderedBoundsAbsolute.SubtractMargin(InnerPadding);
+ return RenderedBoundsAbsolute.SubtractMargin(InnerPadding).WithSizeNonZero(element.desiredSize);
if (direction == Direction.Vertical)
{
var bounds = RenderedBoundsAbsolute;
bounds.topLeft.Y = Children[index - 1].RenderedBoundsAbsolute.bottomRight.Y;
- return bounds.SubtractMargin(InnerPadding);
+ return bounds.SubtractMargin(InnerPadding).WithSizeNonZero(element.desiredSize);
}
else
{
var bounds = RenderedBoundsAbsolute;
bounds.topLeft.X = Children[index - 1].RenderedBoundsAbsolute.bottomRight.X;
- return bounds.SubtractMargin(InnerPadding);
+ return bounds.SubtractMargin(InnerPadding).WithSizeNonZero(element.desiredSize);
}
}
}
diff --git a/ArgonUI/UIElements/TabPanel.cs b/ArgonUI/UIElements/TabPanel.cs
new file mode 100644
index 0000000..30a272e
--- /dev/null
+++ b/ArgonUI/UIElements/TabPanel.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class TabPanel
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Table.cs b/ArgonUI/UIElements/Table.cs
new file mode 100644
index 0000000..ceda941
--- /dev/null
+++ b/ArgonUI/UIElements/Table.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Table
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/TextBlock.cs b/ArgonUI/UIElements/TextBlock.cs
deleted file mode 100644
index 5e925cb..0000000
--- a/ArgonUI/UIElements/TextBlock.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using ArgonUI.Drawing;
-using ArgonUI.SourceGenerator;
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace ArgonUI.UIElements;
-
-///
-/// Similar to a but with additional features such as text wrapping.
-///
-[UIClonable]
-public partial class TextBlock : Label
-{
- ///
- /// Specifies how the text in this text block is horizontally aligned.
- ///
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected TextAlignment alignment;
- ///
- /// Justifies the contents of this text block to fill it's width.
- ///
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected bool justify;
- //protected bool justifyLastLine;
-
- // TODO: Finish implementing
- ///
- ///
- ///
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected float wordSpacing;
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected float charSpacing;
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected float stretchX;
-
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected float lineSpacing;
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected float firstLineIndent;
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected float indent;
-
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected float skew;
- [Reactive, Dirty(DirtyFlags.Content), Stylable]
- protected float weight;
-
- protected internal override void Draw(IDrawContext ctx)
- {
- if (text == null)
- return;
-
- var fnt = font ?? Fonts.Default;
- fnt.FontTexture?.ExecuteDrawCommands(ctx);
- ctx.DrawText(RenderedBoundsAbsolute, size, text, fnt, colour, wordSpacing, charSpacing, skew, weight);
- }
-}
-
-public enum TextAlignment
-{
- Left,
- Centre,
- Right
-}
diff --git a/ArgonUI/UIElements/TextField.cs b/ArgonUI/UIElements/TextField.cs
new file mode 100644
index 0000000..c080d01
--- /dev/null
+++ b/ArgonUI/UIElements/TextField.cs
@@ -0,0 +1,61 @@
+using ArgonUI.Drawing;
+using ArgonUI.SourceGenerator;
+using ArgonUI.UIElements.Abstract;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace ArgonUI.UIElements;
+
+[UIClonable]
+public partial class TextField : TextBlockBase
+{
+ protected VectorInt2 textSelection;
+
+ public TextField()
+ {
+ text = string.Empty;
+ size = 14;
+ textColour = new(0, 0, 0, 1);
+ font = Fonts.Default;
+
+ //OnKeyDown
+ }
+
+ public TextField(string? text) : this()
+ {
+ this.text = text;
+ }
+
+ ///
+ /// Gets the span of characters in this text field which are currently selected.
+ /// Returns an empty span if the text is , empty, or no
+ /// text is selected.
+ ///
+ public ReadOnlySpan SelectedText
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(text))
+ return default;
+ string t = text!;
+ int len = t.Length;
+ return text.AsSpan(Math.Min(textSelection.x, len), Math.Min(textSelection.y, len));
+ }
+ }
+
+ protected internal override void Draw(IDrawContext ctx)
+ {
+ // Draw rectangle
+ DrawRectangle(ctx);
+
+ // Draw selection
+
+ // Draw text
+ DrawText(ctx);
+
+ // Draw caret
+
+ }
+}
diff --git a/ArgonUI/UIElements/Toggle.cs b/ArgonUI/UIElements/Toggle.cs
new file mode 100644
index 0000000..f8e79be
--- /dev/null
+++ b/ArgonUI/UIElements/Toggle.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Toggle
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Tooltip.cs b/ArgonUI/UIElements/Tooltip.cs
new file mode 100644
index 0000000..78a3496
--- /dev/null
+++ b/ArgonUI/UIElements/Tooltip.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Tooltip
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/TreeView.cs b/ArgonUI/UIElements/TreeView.cs
new file mode 100644
index 0000000..8e4f925
--- /dev/null
+++ b/ArgonUI/UIElements/TreeView.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class TreeView
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Vector2Box.cs b/ArgonUI/UIElements/Vector2Box.cs
new file mode 100644
index 0000000..42f7147
--- /dev/null
+++ b/ArgonUI/UIElements/Vector2Box.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Vector2Box
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Vector3Box.cs b/ArgonUI/UIElements/Vector3Box.cs
new file mode 100644
index 0000000..686b821
--- /dev/null
+++ b/ArgonUI/UIElements/Vector3Box.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Vector3Box
+ {
+ }
+}
diff --git a/ArgonUI/UIElements/Vector4Box.cs b/ArgonUI/UIElements/Vector4Box.cs
new file mode 100644
index 0000000..423b1e7
--- /dev/null
+++ b/ArgonUI/UIElements/Vector4Box.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArgonUI.UIElements
+{
+ internal class Vector4Box
+ {
+ }
+}
diff --git a/ArgonUI/UIRenderer.cs b/ArgonUI/UIRenderer.cs
index 6e356ba..bc244a3 100644
--- a/ArgonUI/UIRenderer.cs
+++ b/ArgonUI/UIRenderer.cs
@@ -1,14 +1,10 @@
-using ArgonUI.UIElements;
-using ArgonUI.Drawing;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using ArgonUI.Drawing;
using ArgonUI.Helpers;
-using System.Runtime.InteropServices;
+using ArgonUI.UIElements;
+using ArgonUI.UIElements.Abstract;
+using System;
+using System.Numerics;
using System.Runtime.CompilerServices;
-using System.Diagnostics;
using System.Threading;
namespace ArgonUI;
@@ -101,7 +97,7 @@ public void RenderFrame()
private static bool MeasureElementRecurse(UIElement element)
{
- if (element is UIContainer container && (element.DirtyFlags & DirtyFlags.ChildLayout) != 0)
+ if (element is UIContainer container && (element.DirtyFlags & DirtyFlag.ChildLayout) != 0)
{
bool changed = false;
for (int i = 0; i < container.Children.Count; i++)
@@ -113,13 +109,13 @@ private static bool MeasureElementRecurse(UIElement element)
container.BeforeLayoutChildren();
}
- if ((element.DirtyFlags & DirtyFlags.Layout) != 0)
+ if ((element.DirtyFlags & DirtyFlag.Layout) != 0)
{
var oldSize = element.desiredSize;
if (element.Visible != Visibility.Hidden)
element.desiredSize = element.Measure();
else
- element.desiredSize = VectorInt2.Zero;
+ element.desiredSize = Vector2.Zero;
if (element.desiredSize != oldSize)
return true;
}
@@ -128,9 +124,9 @@ private static bool MeasureElementRecurse(UIElement element)
private static void LayoutElementRecurse(UIElement element, int childIndex = 0)
{
- if ((element.DirtyFlags & DirtyFlags.Layout) != 0)
+ if ((element.DirtyFlags & DirtyFlag.Layout) != 0)
{
- element.ClearDirtyFlag(DirtyFlags.Layout);
+ element.ClearDirtyFlag(DirtyFlag.Layout);
if (element.Visible != Visibility.Hidden)
{
var bounds = element.Layout(childIndex);
@@ -147,10 +143,10 @@ private static void LayoutElementRecurse(UIElement element, int childIndex = 0)
}
}
- if (element is UIContainer container && (element.DirtyFlags & DirtyFlags.ChildLayout) != 0)
+ if (element is UIContainer container && (element.DirtyFlags & DirtyFlag.ChildLayout) != 0)
{
container.LayoutChildren();
- element.ClearDirtyFlag(DirtyFlags.ChildLayout);
+ element.ClearDirtyFlag(DirtyFlag.ChildLayout);
try
{
for (int i = 0; i < container.Children.Count; i++)
@@ -166,9 +162,9 @@ private static void LayoutElementRecurse(UIElement element, int childIndex = 0)
private void MeasureDrawnElementRecurse(UIElement element)
{
// Expand the draw bounds to include an elements with dirty content
- if ((element.DirtyFlags & DirtyFlags.Content) != 0)
+ if ((element.DirtyFlags & DirtyFlag.Content) != 0)
{
- element.ClearDirtyFlag(DirtyFlags.Content);
+ element.ClearDirtyFlag(DirtyFlag.Content);
// Draw bounds should be computed during layout...
if (!drawBounds.HasValue)
@@ -180,9 +176,9 @@ private void MeasureDrawnElementRecurse(UIElement element)
// drawBounds won't expand any further. We still need to clear any dirty flags though.
ClearContentFlagsRecursive(element);
}
- else if ((element.DirtyFlags & DirtyFlags.ChildContent) != 0 && element is UIContainer container)
+ else if ((element.DirtyFlags & DirtyFlag.ChildContent) != 0 && element is UIContainer container)
{
- element.ClearDirtyFlag(DirtyFlags.ChildContent);
+ element.ClearDirtyFlag(DirtyFlag.ChildContent);
try
{
foreach (UIElement? child in container.Children)
@@ -194,10 +190,10 @@ private void MeasureDrawnElementRecurse(UIElement element)
private static void ClearContentFlagsRecursive(UIElement element)
{
- element.ClearDirtyFlag(DirtyFlags.Content);
- if ((element.DirtyFlags & DirtyFlags.ChildContent) != 0 && element is UIContainer container)
+ element.ClearDirtyFlag(DirtyFlag.Content);
+ if ((element.DirtyFlags & DirtyFlag.ChildContent) != 0 && element is UIContainer container)
{
- element.ClearDirtyFlag(DirtyFlags.ChildContent);
+ element.ClearDirtyFlag(DirtyFlag.ChildContent);
try
{
foreach (UIElement? child in container.Children)
@@ -217,6 +213,10 @@ private void CollectDrawnRecurse(UIElement element)
int index = element.treeDepth + element.ZIndex;
+ // TODO: A lot of UI elements are likely to layer multiple different draw commands. It would be good
+ // if we could expose the layering system to them so that they can take advantage of auto batching
+ // with neighbours. Otherwise we need to layer UI elements, which is a bit heavy weight...
+ // This is notably the case for decoraters like outlines or shadows.
ref var cmdList = ref drawCommands.GetRef(index);
if (Unsafe.IsNullRef(ref cmdList))
{
diff --git a/design_notes.md b/design_notes.md
index 107c40c..4298f00 100644
--- a/design_notes.md
+++ b/design_notes.md
@@ -243,6 +243,42 @@ For each element draw the sublist index it goes into is determined by:
sense to clamp to one above and one below the current min and max index and then re-order when
needed. Or I guess we could use a heap.
+### Drawing Lists V3
+
+The batching allowed by V2 is decent, but has some limitations. Notably, we expect many UI elements
+to be composed of multiple different draw commands (eg: a rect and it's outline). Currently, this
+would break the batching. We need a way of exposing the hierarchical draw lists to components
+which may be able to take advantage of them. Ideally, we would hide this details from implementors
+of custom `Draw()` functions by wrapping `IDrawContext` to automatically do this sorting.
+
+https://skia.googlesource.com/skia/+/refs/heads/main/src/gpu/graphite/DrawList.h#29
+
+Conceptually, each method in `IDrawContext` should translate to a draw command of a given type,
+notably, we mostly care about the different `ShaderFeature` flags involved.
+
+```
+ Top
+ 1 | Rect |
+ 2 | Rect | | Text | | Text | | Text |
+ 3 | Rect | | Rect | | Rect |
+ 4 | Rect |
+ 5 | Clear |
+
+Squashses down into:
+
+ Top
+ 1 | Text | | Text | | Text |
+ 2 | Rect | | Rect | | Rect |
+ 3 | Rect | | Rect | | Rect |
+ 4 | Clear |
+```
+
+Note that the gaps in 3 & 4 can only appear due to a `UIContainer` not pushing any draw commands.
+As such collapsing the space above should be trivial, that being said you generally only want to
+collapse down as far as you can while still leaving command types grouped -> see how Text in
+layer 2 doesn't move down as it would no longer be in a group. Additionally, drawing system should
+split layers such that only one draw type exists in each.
+
# Input
@@ -317,7 +353,7 @@ public partial class Rectangle : UIElement
///
/// The colour of this rectangle.
///
- [Reactive][Dirty(DirtyFlags.Content)]
+ [Reactive][Dirty(DirtyFlag.Content)]
[Stylable(DocString: "This element's colour.")]
private Vector4 Colour;
}
@@ -333,7 +369,7 @@ public partial class Rectangle : UIElement
get => colour; set
{
UpdateProperty(ref colour, value);
- Dirty(DirtyFlags.Content);
+ Dirty(DirtyFlag.Content);
}
}
}