diff --git a/Dashboard.Common/Events/AnimationTickEventArgs.cs b/Dashboard.Common/Events/AnimationTickEventArgs.cs new file mode 100644 index 0000000..a9971ac --- /dev/null +++ b/Dashboard.Common/Events/AnimationTickEventArgs.cs @@ -0,0 +1,20 @@ +namespace Dashboard.Events +{ + public class AnimationTickEventArgs : UiEventArgs + { + /// + /// Animation delta time in seconds. + /// + public float Delta { get; } + + public AnimationTickEventArgs(float delta) : base(UiEventType.AnimationTick) + { + Delta = delta; + } + } + + public interface IAnimationTickEvent + { + event EventHandler AnimationTimerEvent; + } +} diff --git a/Dashboard.Common/Events/MouseEvents.cs b/Dashboard.Common/Events/MouseEvents.cs new file mode 100644 index 0000000..0781c56 --- /dev/null +++ b/Dashboard.Common/Events/MouseEvents.cs @@ -0,0 +1,69 @@ +using System.Drawing; +using System.Numerics; + +namespace Dashboard.Events +{ + [Flags] + public enum MouseButtons + { + M1 = 1 << 0, + M2 = 1 << 1, + M3 = 1 << 2, + M4 = 1 << 3, + M5 = 1 << 4, + M6 = 1 << 5, + M7 = 1 << 6, + M8 = 1 << 7, + + Left = M1, + Right = M2, + Middle = M3, + } + + public sealed class MouseMoveEventArgs : UiEventArgs + { + public Vector2 ClientPosition { get; } + public Vector2 Delta { get; } + + public MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) + : base(UiEventType.MouseMove) + { + ClientPosition = clientPosition; + Delta = delta; + } + } + + public sealed class MouseButtonEventArgs : UiEventArgs + { + public Vector2 ClientPosition { get; } + public MouseButtons Buttons { get; } + + public MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, bool up) + : base(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown) + { + ClientPosition = clientPosition; + Buttons = buttons; + } + } + + public sealed class MouseScrollEventArgs : UiEventArgs + { + public Vector2 ClientPosition { get; } + public Vector2 ScrollDelta { get; } + + public MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta) + : base(UiEventType.MouseScroll) + { + ClientPosition = clientPosition; + ScrollDelta = scrollDelta; + } + } + + public interface IMouseEvents + { + event EventHandler MouseMoved; + event EventHandler MouseButtonDown; + event EventHandler MouseButtonUp; + event EventHandler MouseScroll; + } +} diff --git a/Dashboard.Common/Events/UiEventArgs.cs b/Dashboard.Common/Events/UiEventArgs.cs new file mode 100644 index 0000000..eb7615b --- /dev/null +++ b/Dashboard.Common/Events/UiEventArgs.cs @@ -0,0 +1,76 @@ +using System.Numerics; + +namespace Dashboard.Events +{ + public enum UiEventType + { + None, + AnimationTick, // Generic timer event. + + // Text input related events. + KeyDown, // Keyboard key down. + KeyUp, // Keyboard key up. + TextInput, // Non-IME text event. + TextEdit, // IME text event. + TextCandidates, // IME text candidate list. + TextLanguage, // Keyboard language changed event. + + // Mouse & touch related events + MouseButtonDown, // Mouse button down. + MouseButtonUp, // Mouse button up. + MouseMove, // Mouse moved. + MouseScroll, // Mouse scrolled. + + // Reserved event names + StylusEnter, // The stylus has entered the hover region. + StylusLeave, // The stylus has left the hover region. + StylusMove, // The stylus has moved. + StylusDown, // The stylus is touching. + StylusUp, // The stylus is no longer touching. + StylusButtonUp, // Stylus button up. + StylusButtonDown, // Stylus button down. + StylusAxes, // Extra stylus axes data. + + // Window & Control Events + ControlInvalidateVisual, // Force rendering the control again. + ControlStateChanged, // Control state changed. + ControlMoved, // Control moved. + ControlResized, // Control resized. + ControlEnter, // The pointing device entered the control. + ControlLeave, // The pointing device left the control. + ControlFocusGet, // The control acquired focus. + ControlFocusLost, // The control lost focus. + WindowClose, // The window closed. + + UserRangeStart = 1 << 12, + } + + public class UiEventArgs : EventArgs + { + public UiEventType Type { get; } + + public UiEventArgs(UiEventType type) + { + Type = type; + } + + public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None); + } + + public class ControlMovedEventArgs : UiEventArgs + { + public Vector2 OldPosition { get; } + public Vector2 NewPosition { get; } + + public ControlMovedEventArgs(Vector2 oldPosition, Vector2 newPosition) : base(UiEventType.ControlMoved) + { + OldPosition = oldPosition; + NewPosition = newPosition; + } + } + + public class ControlResizedEventArgs + { + + } +} diff --git a/Dashboard.Common/Windowing/ICompositor.cs b/Dashboard.Common/Windowing/ICompositor.cs new file mode 100644 index 0000000..1a61ca1 --- /dev/null +++ b/Dashboard.Common/Windowing/ICompositor.cs @@ -0,0 +1,43 @@ +namespace Dashboard.Windowing +{ + /// + /// Interface for a class that composites multiple windows together. + /// + public interface ICompositor : IDisposable + { + void Composite(IPhysicalWindow window, IEnumerable windows); + } + + /// + /// Interface for classes that implement a window manager. + /// + public interface IWindowManager : IEnumerable, IEventListener, IPaintable, IDisposable + { + /// + /// The physical window that this window manager is associated with. + /// + IPhysicalWindow PhysicalWindow { get; } + + /// + /// The compositor that will composite all virtual windows. + /// + ICompositor Compositor { get; set; } + + /// + /// The window that is currently focused. + /// + IVirtualWindow? FocusedWindow { get; } + + /// + /// Create a virtual window. + /// + /// Virtual window handle. + IVirtualWindow CreateWindow(); + + /// + /// Focus a virtual window, if it is owned by this window manager. + /// + /// The window to focus. + void Focus(IVirtualWindow window); + } +} diff --git a/Dashboard.Common/Windowing/IDeviceContext.cs b/Dashboard.Common/Windowing/IDeviceContext.cs new file mode 100644 index 0000000..64d7e1c --- /dev/null +++ b/Dashboard.Common/Windowing/IDeviceContext.cs @@ -0,0 +1,20 @@ +using System.Drawing; + +namespace Dashboard.Windowing +{ + /// + /// Generic interface for the rendering system present in a + /// + public interface IDeviceContext + { + /// + /// The swap group for this device context. + /// + ISwapGroup SwapGroup { get; } + + /// + /// The size of the window framebuffer in pixels. + /// + Size FramebufferSize { get; } + } +} \ No newline at end of file diff --git a/Dashboard.Common/Windowing/IEventListener.cs b/Dashboard.Common/Windowing/IEventListener.cs new file mode 100644 index 0000000..773fe74 --- /dev/null +++ b/Dashboard.Common/Windowing/IEventListener.cs @@ -0,0 +1,11 @@ +namespace Dashboard.Windowing +{ + public interface IEventListener + { + /// + /// Send an event to this windowing object. + /// + /// The event arguments sent. + void SendEvent(EventArgs args); + } +} diff --git a/Dashboard.Common/Windowing/IPaintable.cs b/Dashboard.Common/Windowing/IPaintable.cs new file mode 100644 index 0000000..ba676bb --- /dev/null +++ b/Dashboard.Common/Windowing/IPaintable.cs @@ -0,0 +1,12 @@ +namespace Dashboard.Windowing +{ + public interface IPaintable + { + event EventHandler Painting; + + /// + /// Paint this paintable object. + /// + void Paint(); + } +} diff --git a/Dashboard.Common/Windowing/ISwapGroup.cs b/Dashboard.Common/Windowing/ISwapGroup.cs new file mode 100644 index 0000000..b4f4e3a --- /dev/null +++ b/Dashboard.Common/Windowing/ISwapGroup.cs @@ -0,0 +1,18 @@ +namespace Dashboard.Windowing +{ + /// + /// Interface that is used to swap the buffers of windows + /// + public interface ISwapGroup + { + /// + /// The swap interval for this swap group. + /// + public int SwapInterval { get; set; } + + /// + /// Swap buffers. + /// + void Swap(); + } +} \ No newline at end of file diff --git a/Dashboard.Common/IWindow.cs b/Dashboard.Common/Windowing/IWindow.cs similarity index 58% rename from Dashboard.Common/IWindow.cs rename to Dashboard.Common/Windowing/IWindow.cs index 565c88d..9b56ce2 100644 --- a/Dashboard.Common/IWindow.cs +++ b/Dashboard.Common/Windowing/IWindow.cs @@ -1,11 +1,16 @@ using System.Drawing; +using Dashboard.Events; -namespace Dashboard +namespace Dashboard.Windowing { /// /// Base class of all Dashboard windows. /// - public interface IWindow : IDisposable + public interface IWindow : + IPaintable, + IDisposable, + IAnimationTickEvent, + IMouseEvents { /// /// Name of the window. @@ -42,46 +47,8 @@ namespace Dashboard /// /// An object that represents a window in a virtual space, usually another window or a rendering system. /// - public interface IVirtualWindow : IWindow + public interface IVirtualWindow : IWindow, IEventListener { - /// - /// Send an event to this window. - /// - /// The event arguments - /// TODO: - void SendEvent(EventArgs args); - } - - /// - /// Generic interface for the rendering system present in a - /// - public interface IDeviceContext - { - /// - /// The swap group for this device context. - /// - ISwapGroup SwapGroup { get; } - - /// - /// The size of the window framebuffer in pixels. - /// - Size FramebufferSize { get; } - } - - /// - /// Interface that is used to swap the buffers of - /// - public interface ISwapGroup - { - /// - /// The swap interval for this swap group. - /// - public int SwapInterval { get; set; } - - /// - /// Swap buffers. - /// - void Swap(); } /// @@ -98,5 +65,10 @@ namespace Dashboard /// True if the window is double buffered. /// public bool DoubleBuffered { get; } + + /// + /// The window manager for this physical window. + /// + public IWindowManager? WindowManager { get; set; } } } diff --git a/Dashboard.Drawing/IDrawQueuePaintable.cs b/Dashboard.Drawing/IDrawQueuePaintable.cs new file mode 100644 index 0000000..a719b7d --- /dev/null +++ b/Dashboard.Drawing/IDrawQueuePaintable.cs @@ -0,0 +1,9 @@ +using Dashboard.Windowing; + +namespace Dashboard.Drawing +{ + public interface IDrawQueuePaintable : IPaintable + { + DrawQueue DrawQueue { get; } + } +} diff --git a/Dashboard.OpenGL/IGLContext.cs b/Dashboard.OpenGL/IGLContext.cs index 072ba62..00f4605 100644 --- a/Dashboard.OpenGL/IGLContext.cs +++ b/Dashboard.OpenGL/IGLContext.cs @@ -1,4 +1,5 @@ using System.Drawing; +using Dashboard.Windowing; namespace Dashboard.OpenGL { diff --git a/Dashboard.OpenTK/PAL2/EventConverter.cs b/Dashboard.OpenTK/PAL2/EventConverter.cs new file mode 100644 index 0000000..d67f7cd --- /dev/null +++ b/Dashboard.OpenTK/PAL2/EventConverter.cs @@ -0,0 +1,18 @@ +using Dashboard.Events; +using OpenTK.Platform; + +namespace Dashboard.OpenTK.PAL2 +{ + public static class EventConverter + { + public static UiEventArgs? Convert(PlatformEventType type, EventArgs ea) + { + switch (type) + { + case PlatformEventType.KeyDown: + default: + return null; + } + } + } +} diff --git a/Dashboard.OpenTK/PAL2/OpenGLDeviceContext.cs b/Dashboard.OpenTK/PAL2/OpenGLDeviceContext.cs index c279092..6d20797 100644 --- a/Dashboard.OpenTK/PAL2/OpenGLDeviceContext.cs +++ b/Dashboard.OpenTK/PAL2/OpenGLDeviceContext.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Drawing; using Dashboard.OpenGL; +using Dashboard.Windowing; using OpenTK.Mathematics; using OpenTK.Platform; using TK = OpenTK.Platform.Toolkit; diff --git a/Dashboard.OpenTK/PAL2/OpenTKEventExtensions.cs b/Dashboard.OpenTK/PAL2/OpenTKEventExtensions.cs new file mode 100644 index 0000000..ed1007c --- /dev/null +++ b/Dashboard.OpenTK/PAL2/OpenTKEventExtensions.cs @@ -0,0 +1,7 @@ +namespace Dashboard.OpenTK.PAL2 +{ + public static class OpenTKEventExtensions + { + // public static EventArgs ToDashboardEvent(this EventArgs) + } +} diff --git a/Dashboard.OpenTK/PAL2/Pal2DashboardBackend.cs b/Dashboard.OpenTK/PAL2/Pal2DashboardBackend.cs new file mode 100644 index 0000000..61b13b4 --- /dev/null +++ b/Dashboard.OpenTK/PAL2/Pal2DashboardBackend.cs @@ -0,0 +1,50 @@ +using Dashboard.Windowing; +using OpenTK.Graphics; +using OpenTK.Platform; +using TK = OpenTK.Platform.Toolkit; + +namespace Dashboard.OpenTK.PAL2 +{ + public class Pal2DashboardBackend : IDashboardBackend + { + public GraphicsApiHints GraphicsApiHints { get; set; } = new OpenGLGraphicsApiHints(); + public bool OpenGLBindingsInitialized { get; set; } = false; + + public IPhysicalWindow CreatePhysicalWindow() + { + PhysicalWindow window = new PhysicalWindow(GraphicsApiHints); + + if (!OpenGLBindingsInitialized) + { + OpenGLBindingsInitialized = true; + GLLoader.LoadBindings( + new Pal2BindingsContext(TK.OpenGL, + ((OpenGLDeviceContext)window.DeviceContext).ContextHandle)); + } + + return window; + } + + public virtual IWindow CreateWindow() + { + return CreatePhysicalWindow(); + } + + public void Dispose() + { + } + + public void Initialize() + { + } + + public void Leave() + { + } + + public void RunEvents(bool wait) + { + TK.Window.ProcessEvents(wait); + } + } +} \ No newline at end of file diff --git a/Dashboard.OpenTK/PAL2/PhysicalWindow.cs b/Dashboard.OpenTK/PAL2/PhysicalWindow.cs index 8c0943a..569f63f 100644 --- a/Dashboard.OpenTK/PAL2/PhysicalWindow.cs +++ b/Dashboard.OpenTK/PAL2/PhysicalWindow.cs @@ -1,16 +1,24 @@ -using System.Drawing; +using System.Collections.Concurrent; +using System.Drawing; +using Dashboard.Drawing; +using Dashboard.Events; +using Dashboard.Windowing; using OpenTK.Mathematics; using OpenTK.Platform; +using MouseMoveEventArgs = Dashboard.Events.MouseMoveEventArgs; using TK = OpenTK.Platform.Toolkit; namespace Dashboard.OpenTK.PAL2 { - public class PhysicalWindow : IPhysicalWindow + public class PhysicalWindow : IPhysicalWindow, IDrawQueuePaintable, IEventListener { + public DrawQueue DrawQueue { get; } = new DrawQueue(); public WindowHandle WindowHandle { get; } public IDeviceContext DeviceContext { get; } public bool DoubleBuffered => true; // Always true for OpenTK windows. + public IWindowManager? WindowManager { get; set; } + public string Title { get => TK.Window.GetTitle(WindowHandle); @@ -37,22 +45,32 @@ namespace Dashboard.OpenTK.PAL2 set => TK.Window.SetClientSize(WindowHandle, new Vector2i((int)value.Width, (int)value.Height)); } + public event EventHandler? Painting; + public event EventHandler? AnimationTimerEvent; + public event EventHandler? MouseMoved; + public event EventHandler? MouseButtonDown; + public event EventHandler? MouseButtonUp; + public event EventHandler? MouseScroll; + public PhysicalWindow(WindowHandle window, IDeviceContext dc) { WindowHandle = window; DeviceContext = dc; + AddWindow(this); } public PhysicalWindow(WindowHandle window, OpenGLContextHandle context, ISwapGroup? swapGroup = null) { WindowHandle = window; DeviceContext = new OpenGLDeviceContext(window, context, swapGroup); + AddWindow(this); } public PhysicalWindow(GraphicsApiHints hints) { WindowHandle = TK.Window.Create(hints); DeviceContext = CreateDeviceContext(WindowHandle, hints); + AddWindow(this); } private static IDeviceContext CreateDeviceContext(WindowHandle window, GraphicsApiHints hints) @@ -74,8 +92,73 @@ namespace Dashboard.OpenTK.PAL2 if (_isDisposed) return; _isDisposed = true; + RemoveWindow(this); + (DeviceContext as IDisposable)?.Dispose(); TK.Window.Destroy(WindowHandle); } + + protected virtual void OnPaint() + { + WindowManager?.Paint(); + Painting?.Invoke(this, EventArgs.Empty); + } + + public void Paint() + { + DrawQueue.Clear(); + OnPaint(); + } + + protected virtual void OnAnimationTimerEvent(AnimationTickEventArgs ea) => + AnimationTimerEvent?.Invoke(this, ea); + + protected virtual void OnMouseMoved(MouseMoveEventArgs ea) => MouseMoved?.Invoke(this, ea); + + protected virtual void OnMouseButtonDown(MouseButtonEventArgs ea) => MouseButtonDown?.Invoke(this, ea); + + protected virtual void OnMouseButtonUp(MouseButtonEventArgs ea) => MouseButtonUp?.Invoke(this, ea); + + protected virtual void OnMouseScroll(MouseScrollEventArgs ea) => MouseScroll?.Invoke(this, ea); + + public void SendEvent(EventArgs args) + { + } + + 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; + } + } } } diff --git a/Dashboard/Application.cs b/Dashboard/Application.cs new file mode 100644 index 0000000..c9c1bd0 --- /dev/null +++ b/Dashboard/Application.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Dashboard.Windowing; + +namespace Dashboard +{ + public class Application : IDisposable + { + public IDashboardBackend Backend { get; } + public IWindowFactory WindowFactory { get; set; } + + public string Name { get; } = "Dashboard Application"; + + protected bool IsInitialized { get; private set; } = false; + protected bool IsDisposed { get; private set; } = false; + + private readonly List _physicalWindows = new List(); + + public event EventHandler? PreInitializing; + public event EventHandler? Initializing; + public event EventHandler? PostInitializing; + public event EventHandler? Leaving; + + public Application(IDashboardBackend backend) + { + Backend = backend; + WindowFactory = backend; + } + + protected virtual void PreInitialize() + { + PreInitializing?.Invoke(this, EventArgs.Empty); + } + + public virtual void Initialize() + { + Backend.Initialize(); + Initializing?.Invoke(this, EventArgs.Empty); + } + + protected virtual void PostInitialize() + { + PostInitializing?.Invoke(this, EventArgs.Empty); + } + + private void InitializeInternal() + { + if (IsInitialized) + return; + + IsInitialized = true; + + PreInitialize(); + Initialize(); + PostInitialize(); + } + + public virtual void RunEvents(bool wait) + { + if (!IsInitialized) + throw new InvalidOperationException("The application is not initialized. Cannot run events at this time."); + + Backend.RunEvents(wait); + } + + public virtual void Leave() + { + Backend.Leave(); + } + + public void Run() => Run(true, CancellationToken.None); + public void Run(bool wait) => Run(wait, CancellationToken.None); + + public void Run(bool waitForEvents, CancellationToken token) + { + InitializeInternal(); + + while (!token.IsCancellationRequested) + { + RunEvents(waitForEvents); + + foreach (IPhysicalWindow window in _physicalWindows) + { + window.Paint(); + } + } + + Leave(); + } + + /// + public IWindow CreateWindow() + { + IWindow window = WindowFactory.CreateWindow(); + + if (window is IPhysicalWindow physical) + _physicalWindows.Add(physical); + + return window; + } + + /// + public IPhysicalWindow CreatePhysicalWindow() + { + IPhysicalWindow window = WindowFactory.CreatePhysicalWindow(); + _physicalWindows.Add(window); + return window; + } + + protected virtual void Dispose(bool disposing) + { + } + + protected void InvokeDispose(bool disposing) + { + if (IsDisposed) + return; + + IsDisposed = true; + + Dispose(disposing); + + if (disposing) + GC.SuppressFinalize(this); + } + + public void Dispose() => InvokeDispose(true); + } +} \ No newline at end of file diff --git a/Dashboard/Controls/ClassSet.cs b/Dashboard/Controls/ClassSet.cs new file mode 100644 index 0000000..019cc93 --- /dev/null +++ b/Dashboard/Controls/ClassSet.cs @@ -0,0 +1,77 @@ +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/Control.cs b/Dashboard/Controls/Control.cs new file mode 100644 index 0000000..72c1a78 --- /dev/null +++ b/Dashboard/Controls/Control.cs @@ -0,0 +1,68 @@ +using System; +using Dashboard.Drawing; +using Dashboard.Windowing; + +namespace Dashboard.Controls +{ + public class Control : IEventListener, IDrawQueuePaintable, IDisposable + { + public string? Id { get; set; } + public ClassSet Classes { get; } + public Form? Owner { get; protected set; } = null; + public Control? Parent { get; private set; } = null; + public bool Disposed { get; private set; } + public virtual DrawQueue DrawQueue => Owner?.DrawQueue ?? throw NoOwnerException; + public virtual Box2d ClientArea { get; set; } + + public event EventHandler? Painting; + public event EventHandler? OwnerChanged; + public event EventHandler? ParentChanged; + public event EventHandler? Disposing; + public event EventHandler? Resized; + + public Control() + { + Classes = new ClassSet(this); + } + + public virtual void OnPaint() + { + Painting?.Invoke(this, EventArgs.Empty); + } + + public void Paint() + { + OnPaint(); + } + + protected void InvokeDispose(bool disposing) + { + if (Disposed) + return; + Disposed = true; + + Dispose(disposing); + + if (disposing) + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + + public void Dispose() => InvokeDispose(true); + + public void SendEvent(EventArgs args) + { + throw new NotImplementedException(); + } + + internal static void SetParent(Control parent, Control child) + { + child.Parent = parent; + } + + protected static Exception NoOwnerException => new Exception("No form owns this control"); + } +} diff --git a/Dashboard/Controls/Form.cs b/Dashboard/Controls/Form.cs new file mode 100644 index 0000000..cc39426 --- /dev/null +++ b/Dashboard/Controls/Form.cs @@ -0,0 +1,23 @@ +using Dashboard.Drawing; +using Dashboard.Windowing; + +namespace Dashboard.Controls +{ + public class Form : Control + { + public IWindow Window { get; } + public override DrawQueue DrawQueue { get; } + + public override Box2d ClientArea + { + get => new Box2d(0, 0, Window.ClientSize.Width, Window.ClientSize.Height); + set { } + } + + public Form(IWindow window) + { + Window = window; + DrawQueue = (window as IDrawQueuePaintable)?.DrawQueue ?? new DrawQueue(); + } + } +} diff --git a/Dashboard/Controls/Label.cs b/Dashboard/Controls/Label.cs new file mode 100644 index 0000000..f03339c --- /dev/null +++ b/Dashboard/Controls/Label.cs @@ -0,0 +1,37 @@ +using System; +using System.Drawing; +using System.Numerics; +using Dashboard.Drawing; + +namespace Dashboard.Controls +{ + public class Label : Control + { + public bool AutoSize { get; set; } = true; + public string Text { get; set; } = ""; + + public event EventHandler? TextChanged; + + protected IBrush TextBrush => throw new NotImplementedException(); + protected IFont Font => throw new NotImplementedException(); + + protected virtual void OnTextChanged(string oldValue, string newValue) + { + if (AutoSize) + CalculateSize(); + } + + protected void CalculateSize() + { + SizeF sz = Typesetter.MeasureString(Font, Text); + ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz); + } + + + public override void OnPaint() + { + base.OnPaint(); + DrawQueue.Text(new Vector3(ClientArea.Min, 0), TextBrush, Text, Font); + } + } +} \ No newline at end of file diff --git a/Dashboard/IDashboardBackend.cs b/Dashboard/IDashboardBackend.cs new file mode 100644 index 0000000..f56ecb4 --- /dev/null +++ b/Dashboard/IDashboardBackend.cs @@ -0,0 +1,29 @@ +using System; +using Dashboard.Windowing; + +namespace Dashboard +{ + public interface IWindowFactory + { + /// + /// Creates a window. It could be a virtual window, or a physical window. + /// + /// A window. + IWindow CreateWindow(); + + /// + /// Always creates a physical window. + /// + /// A physical window. + IPhysicalWindow CreatePhysicalWindow(); + } + + public interface IDashboardBackend : IDisposable, IWindowFactory + { + void Initialize(); + + void RunEvents(bool wait); + + void Leave(); + } +} \ No newline at end of file diff --git a/tests/Dashboard.TestApplication/Program.cs b/tests/Dashboard.TestApplication/Program.cs index a63bd3e..17a80ea 100644 --- a/tests/Dashboard.TestApplication/Program.cs +++ b/tests/Dashboard.TestApplication/Program.cs @@ -10,6 +10,7 @@ using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics; using Box2d = Dashboard.Box2d; using TK = OpenTK.Platform.Toolkit; +using Dashboard; TK.Init(new ToolkitOptions() { @@ -21,149 +22,156 @@ TK.Init(new ToolkitOptions() } }); -PhysicalWindow window = new PhysicalWindow(new OpenGLGraphicsApiHints() -{ - Version = new Version(3, 2), - ForwardCompatibleFlag = true, - DebugFlag = true, - Profile = OpenGLProfile.Core, - sRGBFramebuffer = true, +Application app = new Application(new Pal2DashboardBackend() +{ + GraphicsApiHints = new OpenGLGraphicsApiHints() + { + Version = new Version(3, 2), + ForwardCompatibleFlag = true, + DebugFlag = true, + Profile = OpenGLProfile.Core, + sRGBFramebuffer = true, - SwapMethod = ContextSwapMethod.Undefined, + SwapMethod = ContextSwapMethod.Undefined, - RedColorBits = 8, - GreenColorBits = 8, - BlueColorBits = 8, - AlphaColorBits = 8, - Multisamples = 0, + RedColorBits = 8, + GreenColorBits = 8, + BlueColorBits = 8, + AlphaColorBits = 8, + Multisamples = 0, - SupportTransparentFramebufferX11 = true, + SupportTransparentFramebufferX11 = true, + } }); -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); -// TK.Window.SetTransparencyMode(wnd, WindowTransparencyMode.TransparentFramebuffer, 0.1f); - -OpenGLDeviceContext context = (OpenGLDeviceContext)window.DeviceContext; - -context.MakeCurrent(); -context.SwapGroup.SwapInterval = 1; - -GLLoader.LoadBindings(new Pal2BindingsContext(TK.OpenGL, context.ContextHandle)); - -DrawQueue queue = new DrawQueue(); +PhysicalWindow window; SolidBrush fg = new SolidBrush(Color.FromArgb(0, 0, 0, 0)); SolidBrush bg = new SolidBrush(Color.Black); -bool shouldExit = false; - -GLEngine engine = new GLEngine(); -engine.Initialize(); - -ContextExecutor executor = engine.GetExecutor(context); -DimUI dimUI = new DimUI(new DimUIConfig() -{ - Font = new NamedFont("Noto Sans", 9f), -}); - +CancellationTokenSource source = new CancellationTokenSource(); +GLEngine engine; +ContextExecutor executor; +DimUI dimUI; Vector2 mousePos = Vector2.Zero; Random r = new Random(); -EventQueue.EventRaised += (handle, type, eventArgs) => -{ - if (handle != window.WindowHandle) - return; - - switch (type) - { - case PlatformEventType.Close: - shouldExit = true; - break; - case PlatformEventType.MouseMove: - mousePos = ((MouseMoveEventArgs)eventArgs).ClientPosition; - break; - } -}; - -TK.Window.SetMode(window.WindowHandle, WindowMode.Normal); - List points = new List(); - -IFont font = Typesetter.LoadFont("Nimbus Mono", 12f); +IFont font; StringBuilder builder = new StringBuilder(); -while (!shouldExit) -{ - TK.Window.ProcessEvents(true); - TK.Window.GetFramebufferSize(context.WindowHandle, out Vector2i framebufferSize); - executor.BeginFrame(); +app.PostInitializing += (sender, ea) => { + window = (PhysicalWindow)app.CreatePhysicalWindow(); - dimUI.Begin(new Box2d(0, 0, framebufferSize.X, framebufferSize.Y), queue); - dimUI.Text("Hello World!"); - dimUI.Button("Cancel"); dimUI.SameLine(); - dimUI.Button("OK"); + 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); + // TK.Window.SetTransparencyMode(wnd, WindowTransparencyMode.TransparentFramebuffer, 0.1f); - dimUI.Input("type me!", builder); + OpenGLDeviceContext context = (OpenGLDeviceContext)window.DeviceContext; - dimUI.BeginMenu(); + context.MakeCurrent(); + context.SwapGroup.SwapInterval = 1; - if (dimUI.MenuItem("File")) + engine = new GLEngine(); + engine.Initialize(); + + executor = engine.GetExecutor(context); + + dimUI = new DimUI(new DimUIConfig() { - dimUI.BeginMenu(); - dimUI.MenuItem("New Window"); - dimUI.MenuItem("Preferences"); - dimUI.MenuItem("Exit"); - dimUI.EndMenu(); - } + Font = new NamedFont("Noto Sans", 9f), + }); - if (dimUI.MenuItem("Edit")) + EventQueue.EventRaised += (handle, type, eventArgs) => { - dimUI.BeginMenu(); - dimUI.MenuItem("Cut"); - dimUI.MenuItem("Copy"); - dimUI.MenuItem("Paste"); + if (handle != window.WindowHandle) + return; - if (dimUI.MenuItem("Send Char")) + switch (type) { - dimUI.BeginMenu(); - dimUI.EndMenu(); + case PlatformEventType.Close: + source.Cancel(); + break; + case PlatformEventType.MouseMove: + mousePos = ((MouseMoveEventArgs)eventArgs).ClientPosition; + break; } + }; - dimUI.EndMenu(); - } + TK.Window.SetMode(window.WindowHandle, WindowMode.Normal); + font = Typesetter.LoadFont("Nimbus Mono", 12f); - if (dimUI.MenuItem("View")) - { - dimUI.BeginMenu(); - dimUI.MenuItem("Clear"); + window.Painting += (sender, ea) => { + TK.Window.GetFramebufferSize(context.WindowHandle, out Vector2i framebufferSize); + executor.BeginFrame(); + + dimUI.Begin(new Box2d(0, 0, framebufferSize.X, framebufferSize.Y), window.DrawQueue); + dimUI.Text("Hello World!"); + dimUI.Button("Cancel"); dimUI.SameLine(); + dimUI.Button("OK"); + + dimUI.Input("type me!", builder); - 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(); - } + if (dimUI.MenuItem("File")) + { + dimUI.BeginMenu(); + dimUI.MenuItem("New Window"); + dimUI.MenuItem("Preferences"); + dimUI.MenuItem("Exit"); + dimUI.EndMenu(); + } - dimUI.Finish(); + if (dimUI.MenuItem("Edit")) + { + dimUI.BeginMenu(); + dimUI.MenuItem("Cut"); + dimUI.MenuItem("Copy"); + dimUI.MenuItem("Paste"); - GL.Viewport(0, 0, framebufferSize.X, framebufferSize.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); + if (dimUI.MenuItem("Send Char")) + { + dimUI.BeginMenu(); + dimUI.EndMenu(); + } - executor.Draw(queue); - executor.EndFrame(); - queue.Clear(); + dimUI.EndMenu(); + } - context.SwapGroup.Swap(); -} + 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, framebufferSize.X, framebufferSize.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); + + executor.Draw(window.DrawQueue); + executor.EndFrame(); + + context.SwapGroup.Swap(); + }; +}; + +app.Run(true, source.Token);