From 5cba1ab7db9e0aa4a746d627bcd1621b3c898427 Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Wed, 19 Nov 2025 20:12:52 +0300 Subject: [PATCH] Give label a proper implementation. --- .../BlurgGLExtension.cs | 31 +--- Dashboard.BlurgText/BlurgTextExtension.cs | 49 ++++++- .../Collections/TypeDictionary.cs | 2 +- .../Drawing/IDeviceContextBase.cs | 10 ++ Dashboard.Common/Drawing/IFontLoader.cs | 4 +- Dashboard.Common/Drawing/IImmediateMode.cs | 4 - Dashboard.Common/Pal/Application.cs | 13 +- Dashboard.OpenGL/Drawing/DeviceContextBase.cs | 58 ++++++++ Dashboard.OpenGL/GLTextureExtension.cs | 1 - Dashboard.OpenTK/PAL2/Pal2Application.cs | 28 +++- Dashboard.OpenTK/PAL2/PhysicalWindow.cs | 54 +------ Dashboard/Controls/ClassSet.cs | 77 ---------- Dashboard/Controls/Container.cs | 137 ++++++++++++++++++ Dashboard/Controls/Control.cs | 48 +++++- Dashboard/Controls/Form.cs | 43 +++++- Dashboard/Controls/Label.cs | 35 ++++- Dashboard/Controls/MessageBox.cs | 11 +- Dashboard/Drawing/Brush.cs | 13 ++ Dashboard/Drawing/Font.cs | 42 ++++++ Dashboard/Drawing/Image.cs | 2 - tests/Dashboard.TestApplication/Program.cs | 133 ++--------------- 21 files changed, 480 insertions(+), 315 deletions(-) delete mode 100644 Dashboard/Controls/ClassSet.cs create mode 100644 Dashboard/Controls/Container.cs create mode 100644 Dashboard/Drawing/Brush.cs create mode 100644 Dashboard/Drawing/Font.cs diff --git a/Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs b/Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs index 46dc373..227cdc1 100644 --- a/Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs +++ b/Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs @@ -41,35 +41,6 @@ namespace Dashboard.BlurgText.OpenGL Dispose(false); } - public BlurgFontProxy InternFont(IFont font) - { - BlurgTextExtension appExtension = Application.ExtensionRequire(); - if (font is FontInfo fontInfo) - { - return (BlurgFontProxy)appExtension.Load(fontInfo); - } - else if (font is BlurgFontProxy blurgFont) - { - if (blurgFont.Owner != Blurg) - { - if (blurgFont.Path == null) - return (BlurgFontProxy)appExtension.Load(new FontInfo(blurgFont.Family, blurgFont.Weight, - blurgFont.Slant, - blurgFont.Stretch)); - else - return (BlurgFontProxy)appExtension.Load(blurgFont.Path); - } - else - { - return blurgFont; - } - } - else - { - throw new Exception("Unsupported font resource."); - } - } - private void UseProgram() { if (_program != 0) @@ -150,7 +121,7 @@ namespace Dashboard.BlurgText.OpenGL if (result.Count == 0) return; - Matrix4x4 view = Matrix4x4.CreateTranslation(position) * Context.ExtensionRequire().Transforms; + Matrix4x4 view = Context.ExtensionRequire().Transforms; List drawCalls = new List(); List vertices = new List(); diff --git a/Dashboard.BlurgText/BlurgTextExtension.cs b/Dashboard.BlurgText/BlurgTextExtension.cs index 0e7532c..724c207 100644 --- a/Dashboard.BlurgText/BlurgTextExtension.cs +++ b/Dashboard.BlurgText/BlurgTextExtension.cs @@ -11,7 +11,7 @@ namespace Dashboard.BlurgText public BlurgDcExtension CreateExtension(BlurgTextExtension appExtension, DeviceContext dc); } - public class BlurgTextExtension(IBlurgDcExtensionFactory dcExtensionFactory) : IApplicationExtension, IFontLoader + public class BlurgTextExtension(IBlurgDcExtensionFactory dcExtensionFactory) : IFontLoader { private readonly Blurg _blurg = new Blurg(GlobalTextureAllocation, GlobalTextureUpdate); @@ -27,6 +27,7 @@ namespace Dashboard.BlurgText { Context = context; context.DeviceContextCreated += OnDeviceContextCreated; + _blurg.EnableSystemFonts(); } void IContextExtensionBase.Require(IContextBase context) => Require((Application)context); @@ -130,7 +131,7 @@ namespace Dashboard.BlurgText } } - public abstract class BlurgDcExtension : IDeviceContextExtension + public abstract class BlurgDcExtension : IDeviceContextExtension, ITextRenderer { public abstract Blurg Blurg { get; } public abstract string DriverName { get; } @@ -151,6 +152,35 @@ namespace Dashboard.BlurgText DrawBlurgResult(result, position); } + protected BlurgFontProxy InternFont(IFont font) + { + BlurgTextExtension appExtension = Application.ExtensionRequire(); + if (font is FontInfo fontInfo) + { + return (BlurgFontProxy)appExtension.Load(fontInfo); + } + else if (font is BlurgFontProxy blurgFont) + { + if (blurgFont.Owner != Blurg) + { + if (blurgFont.Path == null) + return (BlurgFontProxy)appExtension.Load(new FontInfo(blurgFont.Family, blurgFont.Weight, + blurgFont.Slant, + blurgFont.Stretch)); + else + return (BlurgFontProxy)appExtension.Load(blurgFont.Path); + } + else + { + return blurgFont; + } + } + else + { + throw new Exception("Unsupported font resource."); + } + } + public abstract void Dispose(); IContextBase IContextExtensionBase.Context => Context; @@ -161,5 +191,20 @@ namespace Dashboard.BlurgText } void IContextExtensionBase.Require(IContextBase context) => Require((DeviceContext)context); + + public Box2d MeasureText(IFont font, float size, string text) + { + BlurgFontProxy proxy = InternFont(font); + Vector2 sz = Blurg.MeasureString(proxy.Font, size * Context.ExtensionRequire().Scale, text); + return new Box2d(0, 0, sz.X, sz.Y); + } + + public void DrawText(Vector2 position, Vector4 color, float size, IFont font, string text) + { + BlurgFontProxy proxy = InternFont(font); + BlurgResult? result = Blurg.BuildString(proxy.Font, size * Context.ExtensionRequire().Scale, + new BlurgColor((byte)(color.X * 255), (byte)(color.Y * 255), (byte)(color.Z * 255), (byte)(color.W * 255)), text); + DrawBlurgResult(result, new Vector3(position, 0)); + } } } diff --git a/Dashboard.Common/Collections/TypeDictionary.cs b/Dashboard.Common/Collections/TypeDictionary.cs index bc4f077..b846d1b 100644 --- a/Dashboard.Common/Collections/TypeDictionary.cs +++ b/Dashboard.Common/Collections/TypeDictionary.cs @@ -82,7 +82,7 @@ namespace Dashboard.Collections public void Clear() => _objects.Clear(); - public IEnumerator GetEnumerator() => _objects.Values.GetEnumerator(); + public IEnumerator GetEnumerator() => _objects.Values.Distinct().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/Dashboard.Common/Drawing/IDeviceContextBase.cs b/Dashboard.Common/Drawing/IDeviceContextBase.cs index 92ce391..1aa7b1c 100644 --- a/Dashboard.Common/Drawing/IDeviceContextBase.cs +++ b/Dashboard.Common/Drawing/IDeviceContextBase.cs @@ -7,14 +7,24 @@ namespace Dashboard.Drawing public interface IDeviceContextBase : IDeviceContextExtension { RectangleF ClipRegion { get; } + RectangleF ScissorRegion { get; } Matrix4x4 Transforms { get; } + float Scale { get; } + float ScaleOverride { get; set; } void ResetClip(); void PushClip(RectangleF clipRegion); void PopClip(); + void ResetScissor(); + void PushScissor(RectangleF scissorRegion); + void PopScissor(); + void ResetTransforms(); void PushTransforms(in Matrix4x4 matrix); void PopTransforms(); + + void ClearColor(Color color); + void ClearDepth(); } } diff --git a/Dashboard.Common/Drawing/IFontLoader.cs b/Dashboard.Common/Drawing/IFontLoader.cs index 3a54d5c..8326da1 100644 --- a/Dashboard.Common/Drawing/IFontLoader.cs +++ b/Dashboard.Common/Drawing/IFontLoader.cs @@ -1,3 +1,5 @@ +using Dashboard.Pal; + namespace Dashboard.Drawing { public interface IFont : IDisposable @@ -16,7 +18,7 @@ namespace Dashboard.Drawing } } - public interface IFontLoader + public interface IFontLoader : IApplicationExtension { IFont Load(FontInfo info); IFont Load(string path); diff --git a/Dashboard.Common/Drawing/IImmediateMode.cs b/Dashboard.Common/Drawing/IImmediateMode.cs index 0142daf..31a3ad5 100644 --- a/Dashboard.Common/Drawing/IImmediateMode.cs +++ b/Dashboard.Common/Drawing/IImmediateMode.cs @@ -6,10 +6,6 @@ namespace Dashboard.Drawing { public interface IImmediateMode : IDeviceContextExtension { - void ClearColor(Color color); - - - void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color); void Rectangle(Box2d rectangle, float depth, Vector4 color); void Image(Box2d rectangle, Box2d uv, float depth, ITexture texture); diff --git a/Dashboard.Common/Pal/Application.cs b/Dashboard.Common/Pal/Application.cs index bf496b3..ec14659 100644 --- a/Dashboard.Common/Pal/Application.cs +++ b/Dashboard.Common/Pal/Application.cs @@ -16,6 +16,9 @@ namespace Dashboard.Pal public bool IsDisposed { get; private set; } = false; public IContextDebugger? Debugger { get; set; } + protected CancellationToken? CancellationToken { get; private set; } + protected bool Quit { get; set; } = false; + private readonly TypeDictionary _extensions = new TypeDictionary(true); private readonly TypeDictionary> _preloadedExtensions = @@ -58,15 +61,18 @@ namespace Dashboard.Pal Initialize(); } - public void Run() => Run(true, CancellationToken.None); + public void Run() => Run(true, System.Threading.CancellationToken.None); - public void Run(bool wait) => Run(wait, CancellationToken.None); + public void Run(bool wait) => Run(wait, System.Threading.CancellationToken.None); public void Run(bool waitForEvents, CancellationToken token) { + CancellationToken = token; + CancellationToken.Value.Register(() => Quit = true); + InitializeInternal(); - while (!token.IsCancellationRequested) + while (!Quit && !token.IsCancellationRequested) { RunEvents(waitForEvents); } @@ -167,6 +173,7 @@ namespace Dashboard.Pal { if (!isDisposing) return; + Quit = true; foreach (IApplicationExtension extension in _extensions) { extension.Dispose(); diff --git a/Dashboard.OpenGL/Drawing/DeviceContextBase.cs b/Dashboard.OpenGL/Drawing/DeviceContextBase.cs index 9befb08..da2d8f2 100644 --- a/Dashboard.OpenGL/Drawing/DeviceContextBase.cs +++ b/Dashboard.OpenGL/Drawing/DeviceContextBase.cs @@ -2,9 +2,11 @@ using System.Drawing; using System.Numerics; using Dashboard.Drawing; using Dashboard.Pal; +using Dashboard.Windowing; using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.Wgl; using OpenTK.Mathematics; +using ColorBuffer = OpenTK.Graphics.OpenGL.ColorBuffer; namespace Dashboard.OpenGL.Drawing { @@ -12,14 +14,19 @@ namespace Dashboard.OpenGL.Drawing { private readonly Stack _transforms = new Stack(); private readonly Stack _clipRegions = new Stack(); + private readonly Stack _scissorRegions = new Stack(); public DeviceContext Context { get; private set; } = null!; IContextBase IContextExtensionBase.Context => Context; public string DriverName => "Dashboard OpenGL Device Context"; public string DriverVendor => "Dashboard"; public Version DriverVersion => new Version(0, 1); + public RectangleF ClipRegion => _clipRegions.Peek(); + public RectangleF ScissorRegion => _scissorRegions.Peek(); public Matrix4x4 Transforms => _transforms.Peek(); + public float Scale => ScaleOverride > 0 ? ScaleOverride : (Context.Window as IDpiAwareWindow)?.Scale ?? 1; + public float ScaleOverride { get; set; } = -1f; public void Dispose() @@ -32,6 +39,7 @@ namespace Dashboard.OpenGL.Drawing Context = context; ResetClip(); + ResetScissor(); ResetTransforms(); } @@ -64,6 +72,35 @@ namespace Dashboard.OpenGL.Drawing SetClip(ClipRegion); } + public void ResetScissor() + { + GL.Disable(EnableCap.ScissorTest); + _scissorRegions.Clear(); + SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize; + _scissorRegions.Push(new RectangleF(0,0, size.Width, size.Height)); + } + + public void PushScissor(RectangleF scissorRegion) + { + GL.Enable(EnableCap.ScissorTest); + + // scissorRegion = new RectangleF(scissorRegion.X + scissorRegion.X, scissorRegion.Y + scissorRegion.Y, + // Math.Min(ScissorRegion.Right - scissorRegion.X, scissorRegion.Width), + // Math.Min(ScissorRegion.Bottom - scissorRegion.Y, scissorRegion.Height)); + _scissorRegions.Push(scissorRegion); + + SetScissor(scissorRegion); + } + + public void PopScissor() + { + if (_scissorRegions.Count == 1) + GL.Disable(EnableCap.ScissorTest); + + _scissorRegions.Pop(); + SetScissor(ClipRegion); + } + private void SetClip(RectangleF rect) { SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize; @@ -74,6 +111,16 @@ namespace Dashboard.OpenGL.Drawing (int)Math.Round(rect.Height)); } + void SetScissor(RectangleF rect) + { + SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize; + GL.Scissor( + (int)Math.Round(rect.X), + (int)Math.Round(size.Height - rect.Y - rect.Height), + (int)Math.Round(rect.Width), + (int)Math.Round(rect.Height)); + } + public void ResetTransforms() { SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize; @@ -93,5 +140,16 @@ namespace Dashboard.OpenGL.Drawing { _transforms.Pop(); } + + public void ClearColor(Color color) + { + GL.ClearColor(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f); + GL.Clear(ClearBufferMask.ColorBufferBit); + } + + public void ClearDepth() + { + GL.Clear(ClearBufferMask.DepthBufferBit); + } } } diff --git a/Dashboard.OpenGL/GLTextureExtension.cs b/Dashboard.OpenGL/GLTextureExtension.cs index 057e7bd..4e42dba 100644 --- a/Dashboard.OpenGL/GLTextureExtension.cs +++ b/Dashboard.OpenGL/GLTextureExtension.cs @@ -22,7 +22,6 @@ namespace Dashboard.OpenGL public void Dispose() { - throw new NotImplementedException(); } diff --git a/Dashboard.OpenTK/PAL2/Pal2Application.cs b/Dashboard.OpenTK/PAL2/Pal2Application.cs index db7dd1c..5f378c7 100644 --- a/Dashboard.OpenTK/PAL2/Pal2Application.cs +++ b/Dashboard.OpenTK/PAL2/Pal2Application.cs @@ -44,12 +44,27 @@ namespace Dashboard.OpenTK.PAL2 protected override void InitializeInternal() { base.InitializeInternal(); + CancellationToken?.Register(() => + { + TK.Window.PostUserEvent(new ApplicationQuitEventArgs()); + }); EventQueue.EventRaised += OnEventRaised; } + internal void RemoveWindow(PhysicalWindow window) + { + _windows.Remove(window); + } + public override void RunEvents(bool wait) { + if (_windows.Count == 0) + { + Dispose(); + return; + } + TK.Window.ProcessEvents(wait); long tock = Stopwatch.GetTimestamp(); @@ -85,7 +100,7 @@ namespace Dashboard.OpenTK.PAL2 } else { - System.Diagnostics.Debugger.Break(); + // System.Diagnostics.Debugger.Break(); } } @@ -99,6 +114,13 @@ namespace Dashboard.OpenTK.PAL2 switch (type) { + case PlatformEventType.UserMessage: + if (args is ApplicationQuitEventArgs) + { + Quit = true; + return; + } + break; case PlatformEventType.MouseDown: { MouseButtonDownEventArgs down = (MouseButtonDownEventArgs)args; @@ -221,4 +243,8 @@ namespace Dashboard.OpenTK.PAL2 _ => (ScanCode)0, }; } + + internal class ApplicationQuitEventArgs() : EventArgs + { + } } diff --git a/Dashboard.OpenTK/PAL2/PhysicalWindow.cs b/Dashboard.OpenTK/PAL2/PhysicalWindow.cs index f4aaf6c..03b4e5e 100644 --- a/Dashboard.OpenTK/PAL2/PhysicalWindow.cs +++ b/Dashboard.OpenTK/PAL2/PhysicalWindow.cs @@ -58,7 +58,6 @@ namespace Dashboard.OpenTK.PAL2 Application = app; WindowHandle = window; DeviceContext = CreateDeviceContext(app, this, new OpenGLGraphicsApiHints()); - AddWindow(this); } public PhysicalWindow(Application app, WindowHandle window, OpenGLContextHandle context) @@ -66,7 +65,6 @@ namespace Dashboard.OpenTK.PAL2 Application = app; WindowHandle = window; DeviceContext = new GLDeviceContext(app, this, new Pal2GLContext(window, context)); - AddWindow(this); } public PhysicalWindow(Application app, GraphicsApiHints hints) @@ -74,7 +72,6 @@ namespace Dashboard.OpenTK.PAL2 Application = app; WindowHandle = TK.Window.Create(hints); DeviceContext = CreateDeviceContext(app, this, hints); - AddWindow(this); } private static DeviceContext CreateDeviceContext(Application app, PhysicalWindow window, GraphicsApiHints hints) @@ -96,27 +93,18 @@ namespace Dashboard.OpenTK.PAL2 if (IsDisposed) return; IsDisposed = true; - RemoveWindow(this); (DeviceContext as IDisposable)?.Dispose(); + + ((Pal2Application)Application).RemoveWindow(this); TK.Window.Destroy(WindowHandle); } public virtual void SendEvent(object? sender, EventArgs args) { - switch (args) - { - case UiEventArgs ui: - switch (ui.Type) - { - case UiEventType.ControlInvalidateVisual: - break; - } - break; - } - args = TransformEvent(sender, args); + Form?.SendEvent(this, args); EventRaised?.Invoke(this, args); lock (_listeners) @@ -148,42 +136,6 @@ namespace Dashboard.OpenTK.PAL2 } } - private static readonly ConcurrentDictionary _windows = - new ConcurrentDictionary(); - - static PhysicalWindow() - { - EventQueue.EventRaised += EventQueueOnEventRaised; - } - - private static void AddWindow(PhysicalWindow window) - { - _windows.TryAdd(window.WindowHandle, window); - } - - private static void RemoveWindow(PhysicalWindow window) - { - _windows.TryRemove(window.WindowHandle, out _); - } - - private static void EventQueueOnEventRaised(PalHandle? handle, PlatformEventType type, EventArgs args) - { - if (handle is not WindowHandle windowHandle) - return; - - if (!_windows.TryGetValue(windowHandle, out PhysicalWindow? window)) - return; - - switch (type) - { - case PlatformEventType.MouseMove: - case PlatformEventType.Scroll: - case PlatformEventType.MouseUp: - case PlatformEventType.MouseDown: - break; - } - } - public float Dpi => Scale * 96f; public float Scale diff --git a/Dashboard/Controls/ClassSet.cs b/Dashboard/Controls/ClassSet.cs deleted file mode 100644 index 019cc93..0000000 --- a/Dashboard/Controls/ClassSet.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Dashboard.Controls -{ - public enum ClassChangeType - { - Added, - Removed, - } - - public record struct ClassChanged(object? Owner, string ClassName, ClassChangeType Type); - - public class ClassSet : ICollection - { - public int Count => _classes.Count; - public bool IsReadOnly => false; - public object? Owner { get; } - public event EventHandler? ClassChanged; - - private readonly HashSet _classes = new HashSet(); - - public ClassSet(object? owner) - { - Owner = owner; - } - - public IEnumerator GetEnumerator() => _classes.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public void Add(string item) - { - if (_classes.Add(item)) - { - OnClassAdded(item); - } - } - - public void Clear() - { - foreach (string @class in _classes) - OnClassRemoved(@class); - - _classes.Clear(); - } - - public bool Contains(string item) => _classes.Contains(item); - - public void CopyTo(string[] array, int arrayIndex) => _classes.CopyTo(array, arrayIndex); - - public bool Remove(string item) - { - if (_classes.Remove(item)) - { - OnClassRemoved(item); - return true; - } - - return false; - } - - private void OnClassAdded(string @class) - { - ClassChanged?.Invoke(this, new ClassChanged(Owner, @class, ClassChangeType.Added)); - } - - private void OnClassRemoved(string @class) - { - ClassChanged?.Invoke(this, new ClassChanged(Owner, @class, ClassChangeType.Removed)); - } - } -} diff --git a/Dashboard/Controls/Container.cs b/Dashboard/Controls/Container.cs new file mode 100644 index 0000000..14a7161 --- /dev/null +++ b/Dashboard/Controls/Container.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Drawing; +using System.Numerics; +using Dashboard.Drawing; +using Dashboard.Events; +using Dashboard.Pal; + +namespace Dashboard.Controls +{ + public class Container : Control, IList + { + private readonly List _controls = new List(); + + public int Count => _controls.Count; + + public bool IsReadOnly => false; + + public event EventHandler? ChildAdded; + public event EventHandler? ChildRemoved; + + public Control this[int index] + { + get => _controls[index]; + set => _controls[index] = value; + } + + protected virtual void LayoutChildren() + { + // TODO: not position everything using absolute coordinates. + foreach (Control child in _controls) + { + child.ClientArea = new Box2d(child.Position, child.Position + child.Size); + } + } + + public override void OnPaint(DeviceContext dc) + { + base.OnPaint(dc); + + var dcb = dc.ExtensionRequire(); + dcb.PushClip(new RectangleF(ClientArea.Left, ClientArea.Bottom, ClientArea.Size.X, ClientArea.Size.Y)); + // dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Bottom, 0)); + LayoutChildren(); + + foreach (Control child in _controls) + { + if (child.DisplayMode == DisplayMode.None) + continue; + + child.SendEvent(this, new PaintEventArgs(dc)); + } + + dcb.PopClip(); + } + + public IEnumerator GetEnumerator() + { + return _controls.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_controls).GetEnumerator(); + } + + public void Add(Control item) + { + SetParent(this, item); + + _controls.Add(item); + ChildAdded?.Invoke(this, new ContainerChildAddedEventArgs(this, item)); + } + + public void Clear() + { + foreach (Control control in this) + { + ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, control)); + } + _controls.Clear(); + } + + public bool Contains(Control item) + { + return _controls.Contains(item); + } + + public void CopyTo(Control[] array, int arrayIndex) + { + _controls.CopyTo(array, arrayIndex); + } + + public bool Remove(Control item) + { + if (!_controls.Remove(item)) + return false; + + ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, item)); + return true; + } + + public int IndexOf(Control item) + { + return _controls.IndexOf(item); + } + + public void Insert(int index, Control item) + { + SetParent(this, item); + + _controls.Insert(index, item); + ChildAdded?.Invoke(this, new ContainerChildAddedEventArgs(this, item)); + } + + public void RemoveAt(int index) + { + Control child = _controls[index]; + _controls.RemoveAt(index); + ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, child)); + } + } + + public class ContainerChildAddedEventArgs(Container parent, Control child) : EventArgs + { + public Container Parent { get; } = parent; + public Control Child { get; } = child; + } + + public class ContainerChildRemovedEventArgs(Container parent, Control child) : EventArgs + { + public Container Parent { get; } = parent; + public Control Child { get; } = child; + } +} diff --git a/Dashboard/Controls/Control.cs b/Dashboard/Controls/Control.cs index 1d32da4..7eed965 100644 --- a/Dashboard/Controls/Control.cs +++ b/Dashboard/Controls/Control.cs @@ -1,16 +1,40 @@ using System; +using System.Drawing; +using System.Numerics; using Dashboard.Events; using Dashboard.Pal; using Dashboard.Windowing; namespace Dashboard.Controls { + public enum DisplayMode + { + None, + Inline, + Block, + Flex, + Grid, + } + + public enum FlowDirection + { + Row, + Column, + RowReverse, + ColumnReverse, + } + + public enum PositionMode + { + Absolute, + Relative, + } + public class Control : IEventListener, IDisposable { private Form? _owner = null; public string? Id { get; set; } - public ClassSet Classes { get; } public Form Owner { get => _owner ?? throw NoOwnerException; @@ -26,7 +50,20 @@ namespace Dashboard.Controls public virtual Box2d ClientArea { get; set; } public bool IsFocused => _owner?.FocusedControl == this; - public event EventHandler Painting; + // Layout properties for this control. + public Vector2 Size { get; set; } = new Vector2(50, 30); + public Vector2 MinimumSize { get; set; } = new Vector2(-1, -1); + public Vector2 MaximumSize { get; set; } = new Vector2(-1, -1); + public int ZIndex { get; set; } = -1; + public Vector2 Position { get; set; } + public PositionMode PositionMode { get; set; } = PositionMode.Relative; + public DisplayMode DisplayMode { get; set; } = DisplayMode.Inline; + public FlowDirection FlowDirection { get; set; } = FlowDirection.Row; + public int Row { get; set; } + public int Column { get; set; } + public bool HideOverflow { get; set; } = false; + + public event EventHandler? Painting; public event EventHandler? AnimationTick; public event EventHandler? OwnerChanged; public event EventHandler? ParentChanged; @@ -35,11 +72,6 @@ namespace Dashboard.Controls public event EventHandler? Disposing; public event EventHandler? Resized; - public Control() - { - Classes = new ClassSet(this); - } - public virtual void OnPaint(DeviceContext dc) { Painting?.Invoke(this, dc); @@ -93,7 +125,7 @@ namespace Dashboard.Controls OnEventRaised(sender, args); } - internal static void SetParent(Control parent, Control child) + internal static void SetParent(Container parent, Control child) { child.Parent = parent; child.ParentChanged?.Invoke(child, EventArgs.Empty); diff --git a/Dashboard/Controls/Form.cs b/Dashboard/Controls/Form.cs index 53fd726..8a80b91 100644 --- a/Dashboard/Controls/Form.cs +++ b/Dashboard/Controls/Form.cs @@ -1,4 +1,5 @@ using System; +using System.Drawing; using Dashboard.Drawing; using Dashboard.Events; using Dashboard.Pal; @@ -6,13 +7,23 @@ using Dashboard.Windowing; namespace Dashboard.Controls { - public class Form : Control, IForm + public class Form : Container, IForm { + private string? _title = "Untitled Form"; public IWindow Window { get; } public Image? WindowIcon { get; set; } - public string? Title { get; set; } = "Untitled Form"; - public Control? Root { get; set; } = null; + + public string? Title + { + get => _title; + set + { + _title = value; + Window.Title = _title ?? ""; + } + } + public Brush Background { get; set; } = new SolidColorBrush(Color.SlateGray); public Control? FocusedControl { get; private set; } = null; public override Box2d ClientArea @@ -27,6 +38,8 @@ namespace Dashboard.Controls { Window = window; window.Form = this; + + Window.Title = _title; } public void Focus(Control control) @@ -41,7 +54,17 @@ namespace Dashboard.Controls public override void OnPaint(DeviceContext dc) { dc.Begin(); - Root?.SendEvent(this, new PaintEventArgs(dc)); + + var dcb = dc.ExtensionRequire(); + dcb.ResetScissor(); + dcb.ResetTransforms(); + + if (Background is SolidColorBrush solidColorBrush) + dcb.ClearColor(solidColorBrush.Color); + + foreach (Control child in this) + child.SendEvent(this, new PaintEventArgs(dc)); + dc.End(); } @@ -55,5 +78,17 @@ namespace Dashboard.Controls Dispose(); Window.Dispose(); } + + protected override void OnEventRaised(object? sender, EventArgs args) + { + base.OnEventRaised(sender, args); + + switch (args) + { + case WindowCloseEvent close: + OnClosing(close); + break; + } + } } } diff --git a/Dashboard/Controls/Label.cs b/Dashboard/Controls/Label.cs index a0f7a60..7b7a9d3 100644 --- a/Dashboard/Controls/Label.cs +++ b/Dashboard/Controls/Label.cs @@ -14,18 +14,39 @@ namespace Dashboard.Controls public event EventHandler? TextChanged; // protected IBrush TextBrush => throw new NotImplementedException(); - protected IFont Font => throw new NotImplementedException(); + public Font Font { get; set; } = Drawing.Font.Create(new FontInfo("Rec Mono Linear")); + public float TextSize { get; set; } = 12f; + public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black); - protected virtual void OnTextChanged(string oldValue, string newValue) + protected void CalculateSize(DeviceContext dc) { - if (AutoSize) - CalculateSize(); + Box2d box = dc.ExtensionRequire().MeasureText(Font.Base, TextSize, Text); + Size = box.Size; + ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Size); } - protected void CalculateSize() + public override void OnPaint(DeviceContext dc) { - // SizeF sz = Typesetter.MeasureString(Font, Text); - // ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz); + base.OnPaint(dc); + + if (AutoSize) + CalculateSize(dc); + + var dcb = dc.ExtensionRequire(); + if (HideOverflow) + dcb.PushScissor(new RectangleF(ClientArea.Left, ClientArea.Top, ClientArea.Size.X, ClientArea.Size.Y)); + + dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0)); + + var text = dc.ExtensionRequire(); + Color color = (TextBrush as SolidColorBrush)?.Color ?? Color.Black; + Vector4 colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f); + text.DrawText(Vector2.Zero, colorVector, TextSize, Font.Base, Text); + + if (HideOverflow) + dcb.PopScissor(); + + dcb.PopTransforms(); } } } diff --git a/Dashboard/Controls/MessageBox.cs b/Dashboard/Controls/MessageBox.cs index 9e32713..f9fc267 100644 --- a/Dashboard/Controls/MessageBox.cs +++ b/Dashboard/Controls/MessageBox.cs @@ -5,7 +5,6 @@ using System.Collections.ObjectModel; using System.IO; using System.Reflection; using Dashboard.Drawing; -using Dashboard.Pal; using Dashboard.Windowing; namespace Dashboard.Controls @@ -42,14 +41,20 @@ namespace Dashboard.Controls public MessageBoxIcon Icon { get; set; } public Image? CustomImage { get; set; } - public string? Message { get; set; } + + public string? Message + { + get => _label.Text; + set => _label.Text = value ?? String.Empty; + } + public MessageBoxButtons Buttons { get; set; } public ObservableCollection CustomButtons { get; } = new ObservableCollection(); public int Result { get; private set; } public MessageBox(IWindow window) : base(window) { - SetParent(this, _label); + Add(_label); } public static readonly Image s_questionIcon; diff --git a/Dashboard/Drawing/Brush.cs b/Dashboard/Drawing/Brush.cs new file mode 100644 index 0000000..956f77a --- /dev/null +++ b/Dashboard/Drawing/Brush.cs @@ -0,0 +1,13 @@ +using System.Drawing; + +namespace Dashboard.Drawing +{ + public abstract class Brush + { + } + + public class SolidColorBrush(Color color) : Brush + { + public Color Color { get; } = color; + } +} diff --git a/Dashboard/Drawing/Font.cs b/Dashboard/Drawing/Font.cs new file mode 100644 index 0000000..9db77b7 --- /dev/null +++ b/Dashboard/Drawing/Font.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Net.Mime; +using Dashboard.Pal; + +namespace Dashboard.Drawing +{ + public class Font(IFont iFont) : IFont + { + public IFont Base => iFont; + public string Family => iFont.Family; + public FontWeight Weight => iFont.Weight; + public FontSlant Slant => iFont.Slant; + public FontStretch Stretch => iFont.Stretch; + + public void Dispose() + { + iFont.Dispose(); + } + + public static Font Create(Stream stream) + { + IFont iFont = Application.Current.ExtensionRequire().Load(stream); + + return new Font(iFont); + } + + public static Font Create(FontInfo info) + { + IFont iFont = Application.Current.ExtensionRequire().Load(info); + + return new Font(iFont); + } + + public static Font Create(string path) + { + IFont iFont = Application.Current.ExtensionRequire().Load(path); + + return new Font(iFont); + } + } +} diff --git a/Dashboard/Drawing/Image.cs b/Dashboard/Drawing/Image.cs index 50b2d13..528be5b 100644 --- a/Dashboard/Drawing/Image.cs +++ b/Dashboard/Drawing/Image.cs @@ -67,8 +67,6 @@ namespace Dashboard.Drawing { IImageLoader imageLoader = Application.Current.ExtensionRequire(); return new Image(imageLoader.LoadImageData(stream)); - - } } } diff --git a/tests/Dashboard.TestApplication/Program.cs b/tests/Dashboard.TestApplication/Program.cs index bba31d7..0102c73 100644 --- a/tests/Dashboard.TestApplication/Program.cs +++ b/tests/Dashboard.TestApplication/Program.cs @@ -1,6 +1,4 @@ -using System.Drawing; -using System.Text; -using BlurgText; +using BlurgText; using Dashboard.BlurgText; using Dashboard.BlurgText.OpenGL; using Dashboard.Controls; @@ -13,10 +11,7 @@ using Dashboard.StbImage; using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics; using OpenTK.Platform; -using Image = Dashboard.Drawing.Image; -using MouseMoveEventArgs = OpenTK.Platform.MouseMoveEventArgs; using TK = OpenTK.Platform.Toolkit; -using Vector4 = System.Numerics.Vector4; TK.Init(new ToolkitOptions() { @@ -51,24 +46,18 @@ Application app = new Pal2Application() } }; -PhysicalWindow window; CancellationTokenSource source = new CancellationTokenSource(); -// GLEngine engine; -// ContextExecutor executor; -// DimUI dimUI; -Vector2 mousePos = Vector2.Zero; -Random r = new Random(); -List points = new List(); -// IFont font; -StringBuilder builder = new StringBuilder(); app.Initialize(); app.ExtensionRequire(); app.ExtensionLoad(new BlurgTextExtension(new BlurgTextExtensionFactory())); -window = (PhysicalWindow)app.CreatePhysicalWindow(); -window.Title = "DashTerm"; +PhysicalWindow window = (PhysicalWindow)app.CreatePhysicalWindow(); +MessageBox box = MessageBox.Create(window, "Are you sure you want to exit?", "Confirm Exit", MessageBoxIcon.Question, + MessageBoxButtons.YesNo); + +// window.Title = "DashTerm"; TK.Window.SetMinClientSize(window.WindowHandle, 300, 200); TK.Window.SetClientSize(window.WindowHandle, new Vector2i(320, 240)); TK.Window.SetBorderStyle(window.WindowHandle, WindowBorderStyle.ResizableBorder); @@ -79,121 +68,25 @@ GLDeviceContext context = (GLDeviceContext)window.DeviceContext; context.GLContext.MakeCurrent(); context.GLContext.SwapGroup.SwapInterval = 1; -// engine = new GLEngine(); -// engine.Initialize(); -// -// executor = engine.GetExecutor(context); -// -// dimUI = new DimUI(new DimUIConfig() -// { -// Font = new NamedFont("Noto Sans", 9f), -// }); - -EventQueue.EventRaised += (handle, type, eventArgs) => -{ - if (handle != window.WindowHandle) - return; - - switch (type) - { - case PlatformEventType.Close: - source.Cancel(); - break; - case PlatformEventType.MouseMove: - mousePos = ((MouseMoveEventArgs)eventArgs).ClientPosition; - break; - } -}; +GL.Disable(EnableCap.DepthTest); +GL.Enable(EnableCap.Blend); +GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); +GL.ColorMask(true, true, true, true); TK.Window.SetMode(window.WindowHandle, WindowMode.Normal); -// font = Typesetter.LoadFont("Nimbus Mono", 12f); - -foreach (string str in typeof(Image).Assembly.GetManifestResourceNames()) Console.WriteLine(str); BlurgFont font = context.ExtensionRequire().Blurg - .QueryFont("Recursive Mono", FontWeight.Regular, false) ?? throw new Exception("Font not found"); + .QueryFont("Rec Mono Linear", FontWeight.Regular, false) ?? throw new Exception("Font not found"); window.EventRaised += (sender, ea) => { - if (ea is not PaintEventArgs) + if (ea is not PaintEventArgs paint) return; - TK.Window.GetSize(window.WindowHandle, out Vector2i size); - // executor.BeginFrame(); - // - // dimUI.Begin(new Box2d(0, 0, size.X, size.Y), window.DrawQueue); - // dimUI.Text("Hello World!"); - // dimUI.Button("Cancel"); dimUI.SameLine(); - // dimUI.Button("OK"); - // - // dimUI.Input("type me!", builder); - // - // dimUI.BeginMenu(); - // - // if (dimUI.MenuItem("File")) - // { - // dimUI.BeginMenu(); - // dimUI.MenuItem("New Window"); - // dimUI.MenuItem("Preferences"); - // dimUI.MenuItem("Exit"); - // dimUI.EndMenu(); - // } - // - // if (dimUI.MenuItem("Edit")) - // { - // dimUI.BeginMenu(); - // dimUI.MenuItem("Cut"); - // dimUI.MenuItem("Copy"); - // dimUI.MenuItem("Paste"); - // - // if (dimUI.MenuItem("Send Char")) - // { - // dimUI.BeginMenu(); - // dimUI.EndMenu(); - // } - // - // dimUI.EndMenu(); - // } - // - // if (dimUI.MenuItem("View")) - // { - // dimUI.BeginMenu(); - // dimUI.MenuItem("Clear"); - // - // if (dimUI.MenuItem("Set Size")) - // { - // dimUI.BeginMenu(); - // dimUI.MenuItem("24 x 40"); - // dimUI.MenuItem("25 x 40"); - // dimUI.MenuItem("24 x 80"); - // dimUI.MenuItem("25 x 80"); - // dimUI.MenuItem("25 x 120"); - // dimUI.EndMenu(); - // } - // - // dimUI.EndMenu(); - // } - // - // dimUI.Finish(); - - GL.Viewport(0, 0, size.X, size.Y); - GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f); - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - GL.Disable(EnableCap.DepthTest); - GL.Enable(EnableCap.Blend); - GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); - GL.ColorMask(true, true, true, true); + paint.DeviceContext.ExtensionRequire().ScaleOverride = 1.5f; ITexture texture = MessageBox.s_questionIcon.InternTexture(context); - - // executor.Draw(window.DrawQueue, new RectangleF(0, 0, size.X, size.Y), 1.5f /*(window as IDpiAwareWindow)?.Scale ?? 1*/); - // executor.EndFrame(); - context.Begin(); - IImmediateMode imm = context.ExtensionRequire(); - imm.ClearColor(Color.Magenta); - imm.Line(new System.Numerics.Vector2(10, 10), new System.Numerics.Vector2(50, 50), 3, 0, new Vector4(0.2f, 0.2f, 1.0f, 1.0f)); - BlurgDcExtension blurg = context.ExtensionRequire(); blurg.DrawBlurgFormattedText(new BlurgFormattedText("Hello world!", font) { DefaultSize = 64f }, new System.Numerics.Vector3(24, 24, 1));