diff --git a/Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs b/Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs new file mode 100644 index 0000000..46dc373 --- /dev/null +++ b/Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs @@ -0,0 +1,264 @@ +using System.Numerics; +using System.Reflection; +using System.Runtime.InteropServices; +using BlurgText; +using Dashboard.Drawing; +using Dashboard.OpenGL; +using OpenTK.Graphics.OpenGL; +using OPENGL = OpenTK.Graphics.OpenGL; + +namespace Dashboard.BlurgText.OpenGL +{ + public class BlurgGLExtension : BlurgDcExtension + { + private readonly List _textures = new List(); + private int _program = 0; + private int _transformsLocation = -1; + private int _atlasLocation = -1; + private int _borderWidthLocation = -1; + private int _borderColorLocation = -1; + private int _fillColorLocation = -1; + private int _vertexArray = 0; + + public override Blurg Blurg { get; } + public bool SystemFontsEnabled { get; set; } + public bool IsDisposed { get; private set; } = false; + + public override string DriverName => "BlurgText"; + public override string DriverVendor => "Dashboard and BlurgText"; + public override Version DriverVersion => new Version(1, 0); + + private new GLDeviceContext Context => (GLDeviceContext)base.Context; + + public BlurgGLExtension() + { + Blurg = new Blurg(AllocateTexture, UpdateTexture); + SystemFontsEnabled = Blurg.EnableSystemFonts(); + } + + ~BlurgGLExtension() + { + 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) + { + GL.UseProgram(_program); + return; + } + + Assembly self = typeof(BlurgGLExtension).Assembly; + + using Stream vsource = self.GetManifestResourceStream("Dashboard.BlurgText.OpenGL.text.vert")!; + using Stream fsource = self.GetManifestResourceStream("Dashboard.BlurgText.OpenGL.text.frag")!; + int vs = ShaderUtil.CompileShader(ShaderType.VertexShader, vsource); + int fs = ShaderUtil.CompileShader(ShaderType.FragmentShader, fsource); + _program = ShaderUtil.LinkProgram(vs, fs, [ + "a_v3Position", + "a_v2TexCoords", + ]); + GL.DeleteShader(vs); + GL.DeleteShader(fs); + + _transformsLocation = GL.GetUniformLocation(_program, "m4Transforms"); + _atlasLocation = GL.GetUniformLocation(_program, "txAtlas"); + _borderWidthLocation = GL.GetUniformLocation(_program, "fBorderWidth"); + _borderColorLocation = GL.GetUniformLocation(_program, "v4BorderColor"); + _fillColorLocation = GL.GetUniformLocation(_program, "v4FillColor"); + + GL.UseProgram(_program); + GL.Uniform1i(_atlasLocation, 0); + } + + private void UpdateTexture(IntPtr texture, IntPtr buffer, int x, int y, int width, int height) + { + GL.BindTexture(TextureTarget.Texture2d, (int)texture); + GL.TexSubImage2D(TextureTarget.Texture2d, 0, x, y, width, height, OPENGL.PixelFormat.Rgba, PixelType.UnsignedByte, buffer); + // GL.TexSubImage2D(TextureTarget.Texture2d, 0, x, y, width, height, OPENGL.PixelFormat.Red, PixelType.Byte, buffer); + } + + + private IntPtr AllocateTexture(int width, int height) + { + int texture = GL.GenTexture(); + + GL.BindTexture(TextureTarget.Texture2d, texture); + GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.Rgba, width, height, 0, OPENGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); + // GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.R8, width, height, 0, OPENGL.PixelFormat.Red, PixelType.Byte, IntPtr.Zero); + + GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleR, (int)TextureSwizzle.One); + // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleG, (int)TextureSwizzle.One); + // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleB, (int)TextureSwizzle.One); + // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleA, (int)TextureSwizzle.Red); + + _textures.Add(texture); + + return texture; + } + + protected virtual void Dispose(bool disposing) + { + if (IsDisposed) + return; + IsDisposed = true; + + if (disposing) + { + GC.SuppressFinalize(this); + foreach (int texture in _textures) + { + Context.Collector.DeleteTexture(texture); + } + } + } + + public override void DrawBlurgResult(BlurgResult result, Vector3 position) + { + if (result.Count == 0) + return; + + Matrix4x4 view = Matrix4x4.CreateTranslation(position) * Context.ExtensionRequire().Transforms; + + List drawCalls = new List(); + List vertices = new List(); + List indices = new List(); + int offset = 0; + int count = 0; + + DrawCall prototype = default; + for (int i = 0; i < result.Count; i++) + { + BlurgRect rect = result[i]; + + int texture = (int)rect.UserData; + Vector4 fillColor = new Vector4(rect.Color.R / 255f, rect.Color.G / 255f, rect.Color.B / 255f, + rect.Color.A / 255f); + + if (i == 0) + { + prototype = new DrawCall(0, 0, texture, fillColor); + } + else if (prototype.Texture != texture || prototype.FillColor != fillColor) + { + drawCalls.Add(prototype with { Count = count, Offset = offset }); + prototype = new DrawCall(0, 0, texture, fillColor); + offset += count; + count = 0; + } + + vertices.Add(new Vertex(rect.X, rect.Y, 0, rect.U0, rect.V0)); + vertices.Add(new Vertex(rect.X + rect.Width, rect.Y, 0, rect.U1, rect.V0)); + vertices.Add(new Vertex(rect.X + rect.Width, rect.Y + rect.Height, 0, rect.U1, rect.V1)); + vertices.Add(new Vertex(rect.X, rect.Y + rect.Height, 0, rect.U0, rect.V1)); + + indices.Add((ushort)(vertices.Count - 4)); + indices.Add((ushort)(vertices.Count - 3)); + indices.Add((ushort)(vertices.Count - 2)); + indices.Add((ushort)(vertices.Count - 4)); + indices.Add((ushort)(vertices.Count - 2)); + indices.Add((ushort)(vertices.Count - 1)); + + count += 6; + } + drawCalls.Add(prototype with { Count = count, Offset = offset }); + + if (_vertexArray == 0) + { + _vertexArray = GL.GenVertexArray(); + } + GL.BindVertexArray(_vertexArray); + + Span buffers = stackalloc int[2]; + GL.GenBuffers(2, buffers); + GL.BindBuffer(BufferTarget.ArrayBuffer, buffers[0]); + GL.BufferData(BufferTarget.ArrayBuffer, vertices.Count * Vertex.Size, (ReadOnlySpan)CollectionsMarshal.AsSpan(vertices), BufferUsage.StaticRead); + + GL.BindBuffer(BufferTarget.ElementArrayBuffer, buffers[1]); + GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Count * sizeof(ushort), (ReadOnlySpan)CollectionsMarshal.AsSpan(indices), BufferUsage.StaticRead); + + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, Vertex.Size, Vertex.PositionOffset); + GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex.Size, Vertex.TexCoordOffset); + GL.EnableVertexAttribArray(0); GL.EnableVertexAttribArray(1); + + UseProgram(); + + GL.UniformMatrix4f(_transformsLocation, 1, true, in view); + GL.ActiveTexture(TextureUnit.Texture0); + + foreach (DrawCall call in drawCalls) + { + GL.BindTexture(TextureTarget.Texture2d, call.Texture); + GL.Uniform4f(_fillColorLocation, call.FillColor.X, call.FillColor.Y, call.FillColor.Z, + call.FillColor.W); + GL.DrawElements(PrimitiveType.Triangles, call.Count, DrawElementsType.UnsignedShort, call.Offset); + } + + GL.DeleteBuffers(2, buffers); + } + + public override void Dispose() + { + Dispose(true); + } + + [StructLayout(LayoutKind.Explicit, Size = Size)] + private struct Vertex(Vector3 position, Vector2 texCoord) + { + [FieldOffset(PositionOffset)] + public Vector3 Position = position; + + [FieldOffset(TexCoordOffset)] + public Vector2 TexCoord = texCoord; + + public Vertex(float x, float y, float z, float u, float v) + : this(new Vector3(x, y, z), new Vector2(u, v)) + { + } + + public const int Size = 8 * sizeof(float); + public const int PositionOffset = 0 * sizeof(float); + public const int TexCoordOffset = 4 * sizeof(float); + } + + private struct DrawCall(int offset, int count, int texture, Vector4 fillColor) + { + public int Offset = offset; + public int Count = count; + public int Texture = texture; + public Vector4 FillColor = fillColor; + } + } +} diff --git a/Dashboard.BlurgText.OpenGL/Dashboard.BlurgText.OpenGL.csproj b/Dashboard.BlurgText.OpenGL/Dashboard.BlurgText.OpenGL.csproj new file mode 100644 index 0000000..7ff5950 --- /dev/null +++ b/Dashboard.BlurgText.OpenGL/Dashboard.BlurgText.OpenGL.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + latest + enable + enable + + + + + + + + + + + + + diff --git a/Dashboard.BlurgText.OpenGL/text.frag b/Dashboard.BlurgText.OpenGL/text.frag new file mode 100644 index 0000000..d4fb4e6 --- /dev/null +++ b/Dashboard.BlurgText.OpenGL/text.frag @@ -0,0 +1,21 @@ +#version 140 + +in vec3 v_v3Position; +in vec2 v_v2TexCoords; + +out vec4 f_Color; + +uniform sampler2D txAtlas; +uniform float fBorderWidth; +uniform vec4 v4BorderColor; +uniform vec4 v4FillColor; + +void main() { + // For now just honor the fill color + + vec4 color = texture(txAtlas, v_v2TexCoords) * v4FillColor; + + if (color.a <= 0.01) + discard; + f_Color = color; +} diff --git a/Dashboard.BlurgText.OpenGL/text.vert b/Dashboard.BlurgText.OpenGL/text.vert new file mode 100644 index 0000000..27ae926 --- /dev/null +++ b/Dashboard.BlurgText.OpenGL/text.vert @@ -0,0 +1,18 @@ +#version 140 + +in vec3 a_v3Position; +in vec2 a_v2TexCoords; + +out vec3 v_v3Position; +out vec2 v_v2TexCoords; + +uniform mat4 m4Transforms; + +void main(void) +{ + vec4 position = vec4(a_v3Position, 1) * m4Transforms; + gl_Position = position; + v_v3Position = position.xyz/position.w; + + v_v2TexCoords = a_v2TexCoords; +} diff --git a/Dashboard.BlurgText/BlurgFontProxy.cs b/Dashboard.BlurgText/BlurgFontProxy.cs new file mode 100644 index 0000000..d58575f --- /dev/null +++ b/Dashboard.BlurgText/BlurgFontProxy.cs @@ -0,0 +1,21 @@ +using BlurgText; +using Dashboard.Drawing; + +namespace Dashboard.BlurgText +{ + public class BlurgFontProxy(Blurg owner, BlurgFont font) : IFont + { + public Blurg Owner { get; } = owner; + public BlurgFont Font { get; } = font; + public string Family => Font.FamilyName; + public FontWeight Weight => (FontWeight)Font.Weight.Value; + public FontSlant Slant => Font.Italic ? FontSlant.Italic : FontSlant.Normal; + public FontStretch Stretch => FontStretch.Normal; + + public string? Path { get; init; } + + public void Dispose() + { + } + } +} diff --git a/Dashboard.BlurgText/BlurgTextExtension.cs b/Dashboard.BlurgText/BlurgTextExtension.cs new file mode 100644 index 0000000..0e7532c --- /dev/null +++ b/Dashboard.BlurgText/BlurgTextExtension.cs @@ -0,0 +1,165 @@ +using System.Diagnostics; +using System.Numerics; +using BlurgText; +using Dashboard.Drawing; +using Dashboard.Pal; + +namespace Dashboard.BlurgText +{ + public interface IBlurgDcExtensionFactory + { + public BlurgDcExtension CreateExtension(BlurgTextExtension appExtension, DeviceContext dc); + } + + public class BlurgTextExtension(IBlurgDcExtensionFactory dcExtensionFactory) : IApplicationExtension, IFontLoader + { + private readonly Blurg _blurg = new Blurg(GlobalTextureAllocation, GlobalTextureUpdate); + + public Application Context { get; private set; } = null!; + public string DriverName { get; } = "BlurgText"; + public string DriverVendor { get; } = "Dashbord and BlurgText"; + public Version DriverVersion { get; } = new Version(1, 0); + public IBlurgDcExtensionFactory DcExtensionFactory { get; } = dcExtensionFactory; + IContextBase IContextExtensionBase.Context => Context; + public bool IsDisposed { get; private set; } = false; + + public void Require(Application context) + { + Context = context; + context.DeviceContextCreated += OnDeviceContextCreated; + } + + void IContextExtensionBase.Require(IContextBase context) => Require((Application)context); + + private void RequireDeviceContextExtension(DeviceContext dc) + { + dc.ExtensionPreload(() => DcExtensionFactory.CreateExtension(this, dc)); + } + + private void OnDeviceContextCreated(object? sender, DeviceContext dc) + { + RequireDeviceContextExtension(dc); + } + + public void Dispose() + { + if (IsDisposed) + return; + IsDisposed = true; + + _blurg.Dispose(); + Context.DeviceContextCreated -= OnDeviceContextCreated; + } + + private static void GlobalTextureUpdate(IntPtr userdata, IntPtr buffer, int x, int y, int width, int height) + { + // Report the user error. + Debug.WriteLine("Attempt to create or update a texture from the global BlurgText.", "Dashboard/BlurgEngine"); + } + + private static IntPtr GlobalTextureAllocation(int width, int height) + { + Debug.WriteLine("Attempt to create or update a texture from the global BlurgText.", "Dashboard/BlurgEngine"); + return IntPtr.Zero; + } + + public IFont Load(FontInfo info) + { + BlurgFont? font = _blurg.QueryFont( + info.Family, + info.Weight switch + { + FontWeight._100 => global::BlurgText.FontWeight.Thin, + FontWeight._200 => global::BlurgText.FontWeight.ExtraLight, + FontWeight._300 => global::BlurgText.FontWeight.Light, + FontWeight._500 => global::BlurgText.FontWeight.Medium, + FontWeight._600 => global::BlurgText.FontWeight.SemiBold, + FontWeight._700 => global::BlurgText.FontWeight.Bold, + FontWeight._800 => global::BlurgText.FontWeight.ExtraBold, + FontWeight._900 => global::BlurgText.FontWeight.Black, + _ => global::BlurgText.FontWeight.Regular, + }, + info.Slant switch + { + FontSlant.Oblique or FontSlant.Italic => true, + _ => false, + }); + + if (font == null) + throw new Exception("Font not found."); + + return new BlurgFontProxy(_blurg, font); + } + + public IFont Load(string path) + { + BlurgFont? font = _blurg.AddFontFile(path); + + if (font == null) + throw new Exception("Font not found."); + + return new BlurgFontProxy(_blurg, font); + } + + public IFont Load(Stream stream) + { + string path; + Stream dest; + for (int i = 0;; i++) + { + path = Path.GetTempFileName(); + try + { + dest = File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); + } + catch (IOException ex) + { + if (i < 3) + continue; + else + throw new Exception("Could not open a temporary file for writing the font.", ex); + } + + break; + } + + stream.CopyTo(dest); + dest.Dispose(); + + return Load(path); + } + } + + public abstract class BlurgDcExtension : IDeviceContextExtension + { + public abstract Blurg Blurg { get; } + public abstract string DriverName { get; } + public abstract string DriverVendor { get; } + public abstract Version DriverVersion { get; } + + public Application Application => Context.Application; + public DeviceContext Context { get; private set; } = null!; + + public abstract void DrawBlurgResult(BlurgResult result, Vector3 position); + + public void DrawBlurgFormattedText(BlurgFormattedText text, Vector3 position, float width = 0f) + { + BlurgResult? result = Blurg.BuildFormattedText(text, maxWidth: width); + if (result == null) + throw new Exception("Could not build formatted text result."); + + DrawBlurgResult(result, position); + } + + public abstract void Dispose(); + + IContextBase IContextExtensionBase.Context => Context; + + public void Require(DeviceContext context) + { + Context = context; + } + + void IContextExtensionBase.Require(IContextBase context) => Require((DeviceContext)context); + } +} diff --git a/Dashboard.BlurgText/Dashboard.BlurgText.csproj b/Dashboard.BlurgText/Dashboard.BlurgText.csproj new file mode 100644 index 0000000..b415f40 --- /dev/null +++ b/Dashboard.BlurgText/Dashboard.BlurgText.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + latest + enable + enable + + + + + + + + diff --git a/Dashboard.Common/Drawing/IDeviceContextBase.cs b/Dashboard.Common/Drawing/IDeviceContextBase.cs new file mode 100644 index 0000000..92ce391 --- /dev/null +++ b/Dashboard.Common/Drawing/IDeviceContextBase.cs @@ -0,0 +1,20 @@ +using System.Drawing; +using System.Numerics; +using Dashboard.Pal; + +namespace Dashboard.Drawing +{ + public interface IDeviceContextBase : IDeviceContextExtension + { + RectangleF ClipRegion { get; } + Matrix4x4 Transforms { get; } + + void ResetClip(); + void PushClip(RectangleF clipRegion); + void PopClip(); + + void ResetTransforms(); + void PushTransforms(in Matrix4x4 matrix); + void PopTransforms(); + } +} diff --git a/Dashboard.Common/Drawing/IFontLoader.cs b/Dashboard.Common/Drawing/IFontLoader.cs new file mode 100644 index 0000000..3a54d5c --- /dev/null +++ b/Dashboard.Common/Drawing/IFontLoader.cs @@ -0,0 +1,25 @@ +namespace Dashboard.Drawing +{ + public interface IFont : IDisposable + { + string Family { get; } + FontWeight Weight { get; } + FontSlant Slant { get; } + FontStretch Stretch { get; } + } + + public record struct FontInfo(string Family, FontWeight Weight = FontWeight.Normal, + FontSlant Slant = FontSlant.Normal, FontStretch Stretch = FontStretch.Normal) : IFont + { + public void Dispose() + { + } + } + + public interface IFontLoader + { + IFont Load(FontInfo info); + IFont Load(string path); + IFont Load(Stream stream); + } +} diff --git a/Dashboard.Common/Drawing/IImmediateMode.cs b/Dashboard.Common/Drawing/IImmediateMode.cs index 1db25e3..0142daf 100644 --- a/Dashboard.Common/Drawing/IImmediateMode.cs +++ b/Dashboard.Common/Drawing/IImmediateMode.cs @@ -8,6 +8,8 @@ namespace Dashboard.Drawing { 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/Drawing/ITextRenderer.cs b/Dashboard.Common/Drawing/ITextRenderer.cs new file mode 100644 index 0000000..6e5cf7f --- /dev/null +++ b/Dashboard.Common/Drawing/ITextRenderer.cs @@ -0,0 +1,12 @@ +using System.Numerics; +using Dashboard.Pal; + +namespace Dashboard.Drawing +{ + + public interface ITextRenderer : IDeviceContextExtension + { + Box2d MeasureText(IFont font, float size, string text); + void DrawText(Vector2 position, Vector4 color, float size, IFont font, string text); + } +} diff --git a/Dashboard.Common/Events/AnimationTickEventArgs.cs b/Dashboard.Common/Events/AnimationTickEventArgs.cs deleted file mode 100644 index a9971ac..0000000 --- a/Dashboard.Common/Events/AnimationTickEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -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/KeyboardEvents.cs b/Dashboard.Common/Events/KeyboardEvents.cs new file mode 100644 index 0000000..345881e --- /dev/null +++ b/Dashboard.Common/Events/KeyboardEvents.cs @@ -0,0 +1,47 @@ +namespace Dashboard.Events +{ + [Flags] + public enum ModifierKeys + { + None = 0, + LeftBitPos = 8, + RightBitPos = 16, + + Shift = (1 << 0), + Control = (1 << 1), + Alt = (1 << 2), + Meta = (1 << 3), + + NumLock = (1 << 4), + CapsLock = (1 << 5), + ScrollLock = (1 << 6), + + LeftShift = (Shift << LeftBitPos), + LeftControl = (Control << LeftBitPos), + LeftAlt = (Alt << LeftBitPos), + LeftMeta = (Meta << LeftBitPos), + + RightShift = (Shift << RightBitPos), + RightControl = (Control << RightBitPos), + RightAlt = (Alt << RightBitPos), + RightMeta = (Meta << RightBitPos), + } + + public enum KeyCode + { + // TODO: + } + + public enum ScanCode + { + // TODO: + } + + public class KeyboardButtonEventArgs(KeyCode keyCode, ScanCode scanCode, ModifierKeys modifierKeys, bool up) + : UiEventArgs(up ? UiEventType.KeyUp : UiEventType.KeyDown) + { + public KeyCode KeyCode { get; } = keyCode; + public ScanCode ScanCode { get; } = scanCode; + public ModifierKeys ModifierKeys { get; } = modifierKeys; + } +} diff --git a/Dashboard.Common/Events/MouseEvents.cs b/Dashboard.Common/Events/MouseEvents.cs index 0781c56..277d598 100644 --- a/Dashboard.Common/Events/MouseEvents.cs +++ b/Dashboard.Common/Events/MouseEvents.cs @@ -20,50 +20,24 @@ namespace Dashboard.Events Middle = M3, } - public sealed class MouseMoveEventArgs : UiEventArgs + public sealed class MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) : UiEventArgs(UiEventType.MouseMove) { - public Vector2 ClientPosition { get; } - public Vector2 Delta { get; } - - public MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) - : base(UiEventType.MouseMove) - { - ClientPosition = clientPosition; - Delta = delta; - } + public Vector2 ClientPosition { get; } = clientPosition; + public Vector2 Delta { get; } = delta; } - public sealed class MouseButtonEventArgs : UiEventArgs + public sealed class MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, ModifierKeys modifierKeys, bool up) + : UiEventArgs(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown) { - 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 ModifierKeys ModifierKeys { get; } = modifierKeys; + public Vector2 ClientPosition { get; } = clientPosition; + public MouseButtons Buttons { get; } = buttons; } - public sealed class MouseScrollEventArgs : UiEventArgs + public sealed class MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta) + : UiEventArgs(UiEventType.MouseScroll) { - 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; + public Vector2 ClientPosition { get; } = clientPosition; + public Vector2 ScrollDelta { get; } = scrollDelta; } } diff --git a/Dashboard.Common/Events/TickEventArgs.cs b/Dashboard.Common/Events/TickEventArgs.cs new file mode 100644 index 0000000..5c0efaa --- /dev/null +++ b/Dashboard.Common/Events/TickEventArgs.cs @@ -0,0 +1,15 @@ +namespace Dashboard.Events +{ + public class TickEventArgs : UiEventArgs + { + /// + /// Animation delta time in seconds. + /// + public float Delta { get; } + + public TickEventArgs(float delta) : base(UiEventType.AnimationTick) + { + Delta = delta; + } + } +} diff --git a/Dashboard.Common/Events/UiEventArgs.cs b/Dashboard.Common/Events/UiEventArgs.cs index eb7615b..43eedfc 100644 --- a/Dashboard.Common/Events/UiEventArgs.cs +++ b/Dashboard.Common/Events/UiEventArgs.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Dashboard.Pal; namespace Dashboard.Events { @@ -6,6 +7,7 @@ namespace Dashboard.Events { None, AnimationTick, // Generic timer event. + Paint, // Generic paint event. // Text input related events. KeyDown, // Keyboard key down. @@ -41,7 +43,7 @@ namespace Dashboard.Events ControlFocusGet, // The control acquired focus. ControlFocusLost, // The control lost focus. WindowClose, // The window closed. - + UserRangeStart = 1 << 12, } @@ -57,6 +59,11 @@ namespace Dashboard.Events public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None); } + public class PaintEventArgs(DeviceContext dc) : UiEventArgs(UiEventType.Paint) + { + public DeviceContext DeviceContext { get; } = dc; + } + public class ControlMovedEventArgs : UiEventArgs { public Vector2 OldPosition { get; } @@ -71,6 +78,6 @@ namespace Dashboard.Events public class ControlResizedEventArgs { - + } } diff --git a/Dashboard.Common/Events/WindowEvent.cs b/Dashboard.Common/Events/WindowEvent.cs new file mode 100644 index 0000000..eb0d3a4 --- /dev/null +++ b/Dashboard.Common/Events/WindowEvent.cs @@ -0,0 +1,7 @@ +namespace Dashboard.Events +{ + public class WindowCloseEvent() : UiEventArgs(UiEventType.WindowClose) + { + public bool Cancel { get; set; } = false; + } +} diff --git a/Dashboard.Common/FontProperties.cs b/Dashboard.Common/FontProperties.cs index fac7d37..14847d7 100644 --- a/Dashboard.Common/FontProperties.cs +++ b/Dashboard.Common/FontProperties.cs @@ -8,6 +8,7 @@ namespace Dashboard _400 = 400, _500 = 500, _600 = 600, + _700 = 700, _800 = 800, _900 = 900, diff --git a/Dashboard.Common/Pal/Application.cs b/Dashboard.Common/Pal/Application.cs index 453c3a7..bf496b3 100644 --- a/Dashboard.Common/Pal/Application.cs +++ b/Dashboard.Common/Pal/Application.cs @@ -21,6 +21,8 @@ namespace Dashboard.Pal private readonly TypeDictionary> _preloadedExtensions = new TypeDictionary>(true); + public event EventHandler? DeviceContextCreated; + public Application() { Current = this; @@ -45,6 +47,11 @@ namespace Dashboard.Pal { } + protected internal virtual void OnDeviceContextCreated(DeviceContext dc) + { + DeviceContextCreated?.Invoke(this, dc); + } + public virtual void RunEvents(bool wait) { if (!IsInitialized) @@ -152,6 +159,7 @@ namespace Dashboard.Pal return false; _extensions.Add(instance); + instance.Require(this); return true; } diff --git a/Dashboard.Common/Pal/DeviceContext.cs b/Dashboard.Common/Pal/DeviceContext.cs index acf04eb..cba5131 100644 --- a/Dashboard.Common/Pal/DeviceContext.cs +++ b/Dashboard.Common/Pal/DeviceContext.cs @@ -1,4 +1,5 @@ using Dashboard.Collections; +using Dashboard.Windowing; using BindingFlags = System.Reflection.BindingFlags; namespace Dashboard.Pal @@ -13,6 +14,8 @@ namespace Dashboard.Pal private readonly Dictionary _attributes = new Dictionary(); + public Application Application { get; } + public IWindow? Window { get; } public abstract string DriverName { get; } public abstract string DriverVendor { get; } public abstract Version DriverVersion { get; } @@ -24,6 +27,13 @@ namespace Dashboard.Pal /// public IContextDebugger? Debugger { get; set; } + protected DeviceContext(Application app, IWindow? window) + { + Application = app; + Window = window; + app.OnDeviceContextCreated(this); + } + ~DeviceContext() { Dispose(false); diff --git a/Dashboard.Common/Windowing/ICompositor.cs b/Dashboard.Common/Windowing/ICompositor.cs index 1a61ca1..275602c 100644 --- a/Dashboard.Common/Windowing/ICompositor.cs +++ b/Dashboard.Common/Windowing/ICompositor.cs @@ -11,7 +11,7 @@ namespace Dashboard.Windowing /// /// Interface for classes that implement a window manager. /// - public interface IWindowManager : IEnumerable, IEventListener, IPaintable, IDisposable + public interface IWindowManager : IEnumerable, IEventListener { /// /// The physical window that this window manager is associated with. diff --git a/Dashboard.Common/Windowing/IEventListener.cs b/Dashboard.Common/Windowing/IEventListener.cs index 773fe74..65abbe4 100644 --- a/Dashboard.Common/Windowing/IEventListener.cs +++ b/Dashboard.Common/Windowing/IEventListener.cs @@ -2,10 +2,13 @@ namespace Dashboard.Windowing { public interface IEventListener { + event EventHandler? EventRaised; + /// /// Send an event to this windowing object. /// + /// The object which generated the event. /// The event arguments sent. - void SendEvent(EventArgs args); + void SendEvent(object? sender, EventArgs args); } } diff --git a/Dashboard.Common/Windowing/IForm.cs b/Dashboard.Common/Windowing/IForm.cs new file mode 100644 index 0000000..0e6b950 --- /dev/null +++ b/Dashboard.Common/Windowing/IForm.cs @@ -0,0 +1,8 @@ +namespace Dashboard.Windowing +{ + public interface IForm : IEventListener, IDisposable + { + public IWindow Window { get; } + public string Title { get; } + } +} diff --git a/Dashboard.Common/Windowing/IWindow.cs b/Dashboard.Common/Windowing/IWindow.cs index 55f6f19..e812800 100644 --- a/Dashboard.Common/Windowing/IWindow.cs +++ b/Dashboard.Common/Windowing/IWindow.cs @@ -1,5 +1,4 @@ using System.Drawing; -using Dashboard.Events; using Dashboard.Pal; namespace Dashboard.Windowing @@ -7,12 +6,13 @@ namespace Dashboard.Windowing /// /// Base class of all Dashboard windows. /// - public interface IWindow : - IPaintable, - IDisposable, - IAnimationTickEvent, - IMouseEvents + public interface IWindow : IDisposable { + /// + /// The application for this window. + /// + Application Application { get; } + /// /// Name of the window. /// @@ -27,6 +27,23 @@ namespace Dashboard.Windowing /// The size of the window that excludes the window extents. /// SizeF ClientSize { get; set; } + + IForm? Form { get; set; } + + public event EventHandler? EventRaised; + + /// + /// Subscribe to events from this window. + /// + /// The event listener instance. + /// An unsubscription token. + public void SubcribeEvent(IEventListener listener); + + /// + /// Unsubscribe from events in from this window. + /// + /// The event listener to unsubscribe. + public void UnsubscribeEvent(IEventListener listener); } /// diff --git a/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj b/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj index b521667..9dd7d8d 100644 --- a/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj +++ b/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj @@ -23,4 +23,8 @@ + + + + diff --git a/Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs b/Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs index 6ee7c1a..dd54eb1 100644 --- a/Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs +++ b/Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs @@ -1,6 +1,7 @@ using System.Drawing; using OpenTK.Graphics.OpenGL; using System.Numerics; +using Dashboard.OpenGL; using OTK = OpenTK.Mathematics; namespace Dashboard.Drawing.OpenGL.Executors diff --git a/Dashboard.Drawing.OpenGL/Executors/TextCommandExecutor.cs b/Dashboard.Drawing.OpenGL/Executors/TextCommandExecutor.cs index 20dec00..bcd94d1 100644 --- a/Dashboard.Drawing.OpenGL/Executors/TextCommandExecutor.cs +++ b/Dashboard.Drawing.OpenGL/Executors/TextCommandExecutor.cs @@ -1,7 +1,7 @@ using System.Reflection; using System.Runtime.InteropServices; using BlurgText; -using Dashboard.Drawing.OpenGL.Text; +using Dashboard.OpenGL; using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics; @@ -11,7 +11,7 @@ namespace Dashboard.Drawing.OpenGL.Executors { public IEnumerable Extensions { get; } = new[] { "DB_Text" }; public IContextExecutor Executor { get; private set; } - private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager(); + // private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager(); public bool IsInitialized { get; private set; } private DrawCallRecorder _recorder; @@ -97,7 +97,7 @@ namespace Dashboard.Drawing.OpenGL.Executors private void DrawText(ICommandFrame frame) { TextCommandArgs args = frame.GetParameter(); - DbBlurgFont font = Engine.InternFont(args.Font); + // DbBlurgFont font = Engine.InternFont(args.Font); BlurgColor color; switch (args.TextBrush) @@ -116,15 +116,15 @@ namespace Dashboard.Drawing.OpenGL.Executors break; } - BlurgResult? result = Engine.Blurg.BuildString(font.Font, font.Size, color, args.Text); + //BlurgResult? result = Engine.Blurg.BuildString(font.Font, font.Size, color, args.Text); - if (result == null) - return; - - Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z); - ExecuteBlurgResult(result, position); - - result.Dispose(); + // if (result == null) + // return; + // + // Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z); + // ExecuteBlurgResult(result, position); + // + // result.Dispose(); } private void ExecuteBlurgResult(BlurgResult result, Vector3 position) diff --git a/Dashboard.Drawing.OpenGL/GLEngine.cs b/Dashboard.Drawing.OpenGL/GLEngine.cs index 01e59e3..99641eb 100644 --- a/Dashboard.Drawing.OpenGL/GLEngine.cs +++ b/Dashboard.Drawing.OpenGL/GLEngine.cs @@ -1,4 +1,4 @@ -using Dashboard.Drawing.OpenGL.Text; +// using Dashboard.Drawing.OpenGL.Text; using Dashboard.OpenGL; using OpenTK; using OpenTK.Graphics; @@ -21,7 +21,7 @@ namespace Dashboard.Drawing.OpenGL if (bindingsContext != null) GLLoader.LoadBindings(bindingsContext); - Typesetter.Backend = BlurgEngine.Global; + // Typesetter.Backend = BlurgEngine.Global; } public ContextExecutor GetExecutor(IGLContext glContext) diff --git a/Dashboard.Drawing.OpenGL/Text/BlurgEngine.cs b/Dashboard.Drawing.OpenGL/Text/BlurgEngine.cs deleted file mode 100644 index fc16cd2..0000000 --- a/Dashboard.Drawing.OpenGL/Text/BlurgEngine.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System.Diagnostics; -using System.Drawing; -using System.Numerics; -using BlurgText; -using Dashboard.OpenGL; -using OpenTK.Graphics.OpenGL; -using OPENGL = OpenTK.Graphics.OpenGL; - -namespace Dashboard.Drawing.OpenGL.Text -{ - public class BlurgEngine : IResourceManager, IGLDisposable, ITypeSetter - { - public string Name { get; } = "BlurgEngine"; - public Blurg Blurg { get; } - public bool SystemFontsEnabled { get; } - - private readonly List _textures = new List(); - - public BlurgEngine() : this(false) - { - } - - private BlurgEngine(bool global) - { - if (global) - Blurg = new Blurg(AllocateTextureGlobal, UpdateTextureGlobal); - else - Blurg = new Blurg(AllocateTexture, UpdateTexture); - - SystemFontsEnabled = Blurg.EnableSystemFonts(); - } - - ~BlurgEngine() - { - Dispose(false, true); - } - - public SizeF MeasureString(IFont font, string value) - { - return MeasureStringInternal(InternFont(font), value); - } - - private SizeF MeasureStringInternal(DbBlurgFont font, string value) - { - Vector2 v = Blurg.MeasureString(font.Font, font.Size, value); - return new SizeF(v.X, v.Y); - } - - public IFont LoadFont(Stream stream) - { - string path; - Stream dest; - for (int i = 0;; i++) - { - path = Path.GetTempFileName(); - try - { - dest = File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); - } - catch (IOException ex) - { - if (i < 3) - continue; - else - throw new Exception("Could not open a temporary file for writing the font.", ex); - } - - break; - } - - stream.CopyTo(dest); - dest.Dispose(); - - DbBlurgFont font = (DbBlurgFont)LoadFont(path); - return font; - } - - public IFont LoadFont(string path) - { - BlurgFont? font = Blurg.AddFontFile(path) ?? throw new Exception("Failed to load the font file."); - return new DbBlurgFont(Blurg, font, 12f) { Path = path }; - } - - public IFont LoadFont(NamedFont font) - { - // Ignore the stretch argument. - bool italic = font.Slant != FontSlant.Normal; - BlurgFont? loaded = Blurg.QueryFont(font.Family, new BlurgText.FontWeight((int)font.Weight), italic); - - if (loaded != null) - return new DbBlurgFont(Blurg, loaded, 12f); - else - throw new Exception("Font not found."); - } - - public DbBlurgFont InternFont(IFont font) - { - if (font is NamedFont named) - { - return (DbBlurgFont)LoadFont(named); - } - else if (font is DbBlurgFont dblurg) - { - if (dblurg.Owner != Blurg) - { - if (dblurg.Path == null) - return (DbBlurgFont)LoadFont(new NamedFont(dblurg.Family, dblurg.Size, dblurg.Weight, - dblurg.Slant, - dblurg.Stretch)); - else - return (DbBlurgFont)LoadFont(dblurg.Path); - } - else - { - return dblurg; - } - } - else - { - throw new Exception("Unsupported font resource."); - } - } - - private void UpdateTexture(IntPtr texture, IntPtr buffer, int x, int y, int width, int height) - { - GL.BindTexture(TextureTarget.Texture2d, (int)texture); - GL.TexSubImage2D(TextureTarget.Texture2d, 0, x, y, width, height, OPENGL.PixelFormat.Rgba, PixelType.UnsignedByte, buffer); - // GL.TexSubImage2D(TextureTarget.Texture2d, 0, x, y, width, height, OPENGL.PixelFormat.Red, PixelType.Byte, buffer); - } - - private IntPtr AllocateTexture(int width, int height) - { - int texture = GL.GenTexture(); - - GL.BindTexture(TextureTarget.Texture2d, texture); - GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.Rgba, width, height, 0, OPENGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); - // GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.R8, width, height, 0, OPENGL.PixelFormat.Red, PixelType.Byte, IntPtr.Zero); - - GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); - GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); - // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleR, (int)TextureSwizzle.One); - // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleG, (int)TextureSwizzle.One); - // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleB, (int)TextureSwizzle.One); - // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleA, (int)TextureSwizzle.Red); - - _textures.Add(texture); - - return texture; - } - - private bool _isDisposed = false; - - private void Dispose(bool disposing, bool safeExit) - { - if (_isDisposed) - return; - _isDisposed = true; - - if (disposing) - { - Blurg.Dispose(); - GC.SuppressFinalize(this); - } - - if (safeExit) - { - foreach (int texture in _textures) - ContextCollector.Global.DeleteTexture(texture); - } - } - - public void Dispose() => Dispose(true, true); - - public void Dispose(bool safeExit) => Dispose(true, safeExit); - - /// - /// The global Blurg engine implements the needed methods for command queues to work. - /// - public static BlurgEngine Global { get; } = new BlurgEngine(true); - - private static void UpdateTextureGlobal(IntPtr userdata, IntPtr buffer, int x, int y, int width, int height) - { - // Report the user error. - Debug.WriteLine("Attempt to create or update a texture from the global BlurgEngine.", "Dashboard/BlurgEngine"); - } - - private static IntPtr AllocateTextureGlobal(int width, int height) - { - Debug.WriteLine("Attempt to create or update a texture from the global BlurgEngine.", "Dashboard/BlurgEngine"); - return IntPtr.Zero; - } - } -} diff --git a/Dashboard.Drawing.OpenGL/Text/BlurgFontExtension.cs b/Dashboard.Drawing.OpenGL/Text/BlurgFontExtension.cs deleted file mode 100644 index 8ca8e6d..0000000 --- a/Dashboard.Drawing.OpenGL/Text/BlurgFontExtension.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Dashboard.Drawing.OpenGL.Text -{ - public class BlurgFontExtension : IDrawExtension - { - public string Name { get; } = "BLURG_Font"; - public IReadOnlyList Requires { get; } = new [] { FontExtension.Instance }; - public IReadOnlyList Commands { get; } = new IDrawCommand[] { }; - - private BlurgFontExtension() {} - - public static readonly BlurgFontExtension Instance = new BlurgFontExtension(); - } -} diff --git a/Dashboard.Drawing.OpenGL/Text/DbBlurgFont.cs b/Dashboard.Drawing.OpenGL/Text/DbBlurgFont.cs deleted file mode 100644 index 859b98e..0000000 --- a/Dashboard.Drawing.OpenGL/Text/DbBlurgFont.cs +++ /dev/null @@ -1,30 +0,0 @@ -using BlurgText; - -namespace Dashboard.Drawing.OpenGL.Text -{ - public class DbBlurgFont : IFont - { - public IDrawExtension Kind { get; } = BlurgFontExtension.Instance; - public Blurg Owner { get; } - public BlurgFont Font { get; } - public float Size { get; } - public string Family => Font.FamilyName; - public FontWeight Weight => (FontWeight)Font.Weight.Value; - public FontSlant Slant => Font.Italic ? FontSlant.Italic : FontSlant.Normal; - public FontStretch Stretch => FontStretch.Normal; - - internal string? Path { get; init; } - - public DbBlurgFont(Blurg owner, BlurgFont font, float size) - { - Owner = owner; - Font = font; - Size = size; - } - - public DbBlurgFont WithSize(float size) - { - return new DbBlurgFont(Owner, Font, size); - } - } -} diff --git a/Dashboard.Drawing/Font.cs b/Dashboard.Drawing/Font.cs deleted file mode 100644 index 8739581..0000000 --- a/Dashboard.Drawing/Font.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Linq; - -namespace Dashboard.Drawing -{ - public class FontExtension : DrawExtension - { - private FontExtension() : base("DB_Font", Enumerable.Empty()) - { - } - - public static readonly IDrawExtension Instance = new FontExtension(); - } - - public interface IFont : IDrawResource - { - public string Family { get; } - public float Size { get; } - public FontWeight Weight { get; } - public FontSlant Slant { get; } - public FontStretch Stretch { get; } - } - - public struct NamedFont : IFont - { - public IDrawExtension Kind { get; } = Instance; - - public string Family { get; } - public float Size { get; } - public FontWeight Weight { get; } - public FontSlant Slant { get; } - public FontStretch Stretch { get; } - - public NamedFont(string family, float size, FontWeight weight = FontWeight.Normal, - FontSlant slant = FontSlant.Normal, FontStretch stretch = FontStretch.Normal) - { - Family = family; - Size = size; - Weight = weight; - Slant = slant; - Stretch = Stretch; - } - - private static readonly IDrawExtension Instance = new Extension(); - - private class Extension : DrawExtension - { - public Extension() : base("DB_Font_Named", [FontExtension.Instance]) - { - } - } - } -} diff --git a/Dashboard.Drawing/TextExtension.cs b/Dashboard.Drawing/TextExtension.cs index 1ea0d7d..1c5d831 100644 --- a/Dashboard.Drawing/TextExtension.cs +++ b/Dashboard.Drawing/TextExtension.cs @@ -9,7 +9,7 @@ namespace Dashboard.Drawing { public TextCommand TextCommand { get; } - private TextExtension() : base("DB_Text", new [] { FontExtension.Instance, BrushExtension.Instance }) + private TextExtension() : base("DB_Text", new [] { BrushExtension.Instance }) { TextCommand = new TextCommand(this); } @@ -40,7 +40,7 @@ namespace Dashboard.Drawing header = new Header() { - Font = queue.RequireResource(obj.Font), + // Font = queue.RequireResource(obj.Font), TextBrush = queue.RequireResource(obj.TextBrush), BorderBrush = (obj.BorderBrush != null) ? queue.RequireResource(obj.BorderBrush) : -1, BorderRadius = (obj.BorderBrush != null) ? obj.BorderRadius : 0f, diff --git a/Dashboard.Drawing/Typesetter.cs b/Dashboard.Drawing/Typesetter.cs index 4bcefa1..343813d 100644 --- a/Dashboard.Drawing/Typesetter.cs +++ b/Dashboard.Drawing/Typesetter.cs @@ -20,7 +20,7 @@ namespace Dashboard.Drawing IFont LoadFont(Stream stream); IFont LoadFont(string path); - IFont LoadFont(NamedFont font); + // IFont LoadFont(NamedFont font); } /// @@ -55,15 +55,16 @@ namespace Dashboard.Drawing return Backend.LoadFont(file.FullName); } - public static IFont LoadFont(NamedFont font) - { - return Backend.LoadFont(font); - } + // public static IFont LoadFont(NamedFont font) + // { + // return Backend.LoadFont(font); + // } public static IFont LoadFont(string family, float size, FontWeight weight = FontWeight.Normal, FontSlant slant = FontSlant.Normal, FontStretch stretch = FontStretch.Normal) { - return LoadFont(new NamedFont(family, size, weight, slant, stretch)); + // return LoadFont(new NamedFont(family, size, weight, slant, stretch)); + throw new Exception(); } private class UndefinedTypeSetter : ITypeSetter @@ -94,11 +95,11 @@ namespace Dashboard.Drawing return default; } - public IFont LoadFont(NamedFont font) - { - Except(); - return default; - } + // public IFont LoadFont(NamedFont font) + // { + // Except(); + // return default; + // } } } } diff --git a/Dashboard.Drawing.OpenGL/ContextCollector.cs b/Dashboard.OpenGL/ContextCollector.cs similarity index 99% rename from Dashboard.Drawing.OpenGL/ContextCollector.cs rename to Dashboard.OpenGL/ContextCollector.cs index b5ccde8..6e2a898 100644 --- a/Dashboard.Drawing.OpenGL/ContextCollector.cs +++ b/Dashboard.OpenGL/ContextCollector.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; using OpenTK.Graphics.OpenGL; -namespace Dashboard.Drawing.OpenGL +namespace Dashboard.OpenGL { public class ContextCollector : IDisposable { diff --git a/Dashboard.OpenGL/Drawing/DeviceContextBase.cs b/Dashboard.OpenGL/Drawing/DeviceContextBase.cs new file mode 100644 index 0000000..9befb08 --- /dev/null +++ b/Dashboard.OpenGL/Drawing/DeviceContextBase.cs @@ -0,0 +1,97 @@ +using System.Drawing; +using System.Numerics; +using Dashboard.Drawing; +using Dashboard.Pal; +using OpenTK.Graphics.OpenGL; +using OpenTK.Graphics.Wgl; +using OpenTK.Mathematics; + +namespace Dashboard.OpenGL.Drawing +{ + public class DeviceContextBase : IDeviceContextBase + { + private readonly Stack _transforms = new Stack(); + private readonly Stack _clipRegions = 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 Matrix4x4 Transforms => _transforms.Peek(); + + + public void Dispose() + { + } + + + public void Require(DeviceContext context) + { + Context = context; + + ResetClip(); + ResetTransforms(); + } + + + void IContextExtensionBase.Require(IContextBase context) => Require((DeviceContext)context); + + public void ResetClip() + { + _clipRegions.Clear(); + + SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize; + _clipRegions.Push(new RectangleF(0,0, size.Width, size.Height)); + + SetClip(ClipRegion); + } + + public void PushClip(RectangleF clipRegion) + { + clipRegion = new RectangleF(ClipRegion.X + clipRegion.X, ClipRegion.Y + clipRegion.Y, + Math.Min(ClipRegion.Right - clipRegion.X, clipRegion.Width), + Math.Min(ClipRegion.Bottom - clipRegion.Y, clipRegion.Height)); + _clipRegions.Push(clipRegion); + + SetClip(clipRegion); + } + + public void PopClip() + { + _clipRegions.Pop(); + SetClip(ClipRegion); + } + + private void SetClip(RectangleF rect) + { + SizeF size = ((GLDeviceContext)Context).GLContext.FramebufferSize; + GL.Viewport( + (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; + Matrix4x4 m = Matrix4x4.CreateOrthographicOffCenterLeftHanded(0, size.Width, size.Height, 0, 1, -1); + + _transforms.Clear(); + _transforms.Push(m); + } + + public void PushTransforms(in Matrix4x4 matrix) + { + Matrix4x4 result = matrix * Transforms; + _transforms.Push(result); + } + + public void PopTransforms() + { + _transforms.Pop(); + } + } +} diff --git a/Dashboard.OpenGL/Drawing/ImmediateMode.cs b/Dashboard.OpenGL/Drawing/ImmediateMode.cs index 899a7a5..e32c48d 100644 --- a/Dashboard.OpenGL/Drawing/ImmediateMode.cs +++ b/Dashboard.OpenGL/Drawing/ImmediateMode.cs @@ -2,7 +2,6 @@ using System.Drawing; using System.Numerics; using System.Runtime.InteropServices; using Dashboard.Drawing; -using Dashboard.Drawing.OpenGL.Pal; using Dashboard.Pal; using OpenTK.Graphics.OpenGL; @@ -112,7 +111,7 @@ namespace Dashboard.OpenGL.Drawing GL.EnableVertexAttribArray(_program_acolor); Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize; - Matrix4x4 view = Matrix4x4.CreateOrthographicOffCenter(0, size.Width, 0, size.Height, 1, -1); + Matrix4x4 view = Context.ExtensionRequire().Transforms; GL.UseProgram(_program); @@ -152,7 +151,7 @@ namespace Dashboard.OpenGL.Drawing GL.EnableVertexAttribArray(_program_acolor); Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize; - Matrix4x4 view = Matrix4x4.CreateOrthographicOffCenter(0, size.Width, 0, size.Height, 1, -1); + Matrix4x4 view = Context.ExtensionRequire().Transforms; GL.UseProgram(_program); @@ -191,7 +190,7 @@ namespace Dashboard.OpenGL.Drawing GL.VertexAttribPointer(_program_acolor, 4, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.ColorOffset); GL.EnableVertexAttribArray(_program_acolor); Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize; - Matrix4x4 view = Matrix4x4.CreateOrthographicOffCenter(0, size.Width, 0, size.Height, 1, -1); + Matrix4x4 view = Context.ExtensionRequire().Transforms; GL.UseProgram(_program); diff --git a/Dashboard.OpenGL/Pal/GLDeviceContext.cs b/Dashboard.OpenGL/GLDeviceContext.cs similarity index 91% rename from Dashboard.OpenGL/Pal/GLDeviceContext.cs rename to Dashboard.OpenGL/GLDeviceContext.cs index 385a82e..35e0f09 100644 --- a/Dashboard.OpenGL/Pal/GLDeviceContext.cs +++ b/Dashboard.OpenGL/GLDeviceContext.cs @@ -1,13 +1,14 @@ using System.Collections.Concurrent; using System.Collections.Immutable; -using Dashboard.OpenGL; +using Dashboard.Drawing; using Dashboard.OpenGL.Drawing; using Dashboard.Pal; +using Dashboard.Windowing; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; -namespace Dashboard.Drawing.OpenGL.Pal +namespace Dashboard.OpenGL { internal class GLContextBindingsContext(IGLContext context) : IBindingsContext { @@ -21,6 +22,8 @@ namespace Dashboard.Drawing.OpenGL.Pal { public IGLContext GLContext { get; } + public ContextCollector Collector { get; } = new ContextCollector(); + public override string DriverName => "Dashboard OpenGL Device Context"; public override string DriverVendor => "Dashboard"; public override Version DriverVersion => new Version(0, 1, 0); @@ -36,7 +39,7 @@ namespace Dashboard.Drawing.OpenGL.Pal private readonly ConcurrentQueue _beforeDrawActions = new ConcurrentQueue(); private readonly ConcurrentQueue _afterDrawActions = new ConcurrentQueue(); - public GLDeviceContext(IGLContext context) + public GLDeviceContext(Application app, IWindow? window, IGLContext context) : base(app, window) { GLContext = context; context.MakeCurrent(); @@ -62,6 +65,7 @@ namespace Dashboard.Drawing.OpenGL.Pal Extensions = extensions.ToImmutableHashSet(); + ExtensionPreload(); ExtensionPreload(); ExtensionPreload(); } @@ -132,6 +136,9 @@ namespace Dashboard.Drawing.OpenGL.Pal base.Begin(); GLContext.MakeCurrent(); + IDeviceContextBase dc = ExtensionRequire(); + dc.ResetClip(); + dc.ResetTransforms(); while (_beforeDrawActions.TryDequeue(out Task? action)) { diff --git a/Dashboard.OpenGL/Pal/GLTextureExtension.cs b/Dashboard.OpenGL/GLTextureExtension.cs similarity index 88% rename from Dashboard.OpenGL/Pal/GLTextureExtension.cs rename to Dashboard.OpenGL/GLTextureExtension.cs index 3c542e0..057e7bd 100644 --- a/Dashboard.OpenGL/Pal/GLTextureExtension.cs +++ b/Dashboard.OpenGL/GLTextureExtension.cs @@ -1,9 +1,10 @@ using System.Drawing; +using Dashboard.Drawing; using Dashboard.Pal; using OpenTK.Graphics.OpenGL; using OGL = OpenTK.Graphics.OpenGL; -namespace Dashboard.Drawing.OpenGL.Pal +namespace Dashboard.OpenGL { public class GLTextureExtension : ITextureExtension, IContextExtensionBase { @@ -90,6 +91,11 @@ namespace Dashboard.Drawing.OpenGL.Pal private GLTextureExtension Extension { get; } = extension; private GLDeviceContext Context => Extension.Context; + ~GLTexture() + { + Dispose(false); + } + public void SetStorage(PixelFormat format, int width, int height, int depth, int levels) { if (!Context.IsRenderThread) @@ -151,11 +157,19 @@ namespace Dashboard.Drawing.OpenGL.Pal throw new NotImplementedException(); } - public unsafe void Write(PixelFormat format, ReadOnlySpan buffer, int level = 0, int align = 4) where T : unmanaged + void ITexture.Write(PixelFormat format, ReadOnlySpan buffer, int level, int align) => Write(format, buffer, level, align, null); + + public unsafe void Write(PixelFormat format, ReadOnlySpan buffer, int level = 0, int align = 4, TimeSpan? timeout = null) where T : unmanaged { if (!Context.IsRenderThread) { - throw new NotImplementedException(); + T[] bufferArray = buffer.ToArray(); + Task task = Context.InvokeBeforeDraw(() => Write(format, bufferArray, level, align)); + + if (timeout.HasValue) + task.Wait(timeout.Value); + else + task.Wait(); } Bind(); @@ -217,11 +231,37 @@ namespace Dashboard.Drawing.OpenGL.Pal GL.GenerateMipmap(Target); } - public void Dispose() + private void Dispose(bool disposing) { - throw new NotImplementedException(); + if (IsDisposed) + return; + IsDisposed = true; + + if (disposing) + { + if (Thread.CurrentThread != Context.RendererThread) + { + Context.Collector.DeleteTexture(Handle); + } + else + { + GL.DeleteTexture(Handle); + } + + Handle = 0; + + GC.SuppressFinalize(this); + } + else + { + Context.Collector.DeleteTexture(Handle); + } } + public bool IsDisposed { get; private set; } + + public void Dispose() => Dispose(false); + private void Bind() { if (Handle == 0) diff --git a/Dashboard.Drawing.OpenGL/ShaderUtil.cs b/Dashboard.OpenGL/ShaderUtil.cs similarity index 98% rename from Dashboard.Drawing.OpenGL/ShaderUtil.cs rename to Dashboard.OpenGL/ShaderUtil.cs index a835e15..9705ba5 100644 --- a/Dashboard.Drawing.OpenGL/ShaderUtil.cs +++ b/Dashboard.OpenGL/ShaderUtil.cs @@ -1,6 +1,6 @@ using OpenTK.Graphics.OpenGL; -namespace Dashboard.Drawing.OpenGL +namespace Dashboard.OpenGL { public static class ShaderUtil { diff --git a/Dashboard.OpenTK/PAL2/EventConverter.cs b/Dashboard.OpenTK/PAL2/EventConverter.cs deleted file mode 100644 index d67f7cd..0000000 --- a/Dashboard.OpenTK/PAL2/EventConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -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/OpenTKEventExtensions.cs b/Dashboard.OpenTK/PAL2/OpenTKEventExtensions.cs deleted file mode 100644 index ed1007c..0000000 --- a/Dashboard.OpenTK/PAL2/OpenTKEventExtensions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Dashboard.OpenTK.PAL2 -{ - public static class OpenTKEventExtensions - { - // public static EventArgs ToDashboardEvent(this EventArgs) - } -} diff --git a/Dashboard.OpenTK/PAL2/Pal2Application.cs b/Dashboard.OpenTK/PAL2/Pal2Application.cs index e300b18..db7dd1c 100644 --- a/Dashboard.OpenTK/PAL2/Pal2Application.cs +++ b/Dashboard.OpenTK/PAL2/Pal2Application.cs @@ -1,9 +1,14 @@ -using Dashboard.Drawing.OpenGL.Pal; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using Dashboard.Events; +using Dashboard.OpenGL; using Dashboard.Pal; using Dashboard.Windowing; -using OpenTK.Graphics; using OpenTK.Platform; using TK = OpenTK.Platform.Toolkit; +using OPENTK = OpenTK.Platform; +using DB = Dashboard.Events; namespace Dashboard.OpenTK.PAL2 { @@ -16,10 +21,17 @@ namespace Dashboard.OpenTK.PAL2 private readonly List _windows = new List(); + private readonly ConditionalWeakTable _windowHandleWindowMap = + new ConditionalWeakTable(); + + private long _tick = Stopwatch.GetTimestamp(); + public override IPhysicalWindow CreatePhysicalWindow() { - PhysicalWindow window = new PhysicalWindow(GraphicsApiHints); + PhysicalWindow window = new PhysicalWindow(this, GraphicsApiHints); + _windows.Add(window); + _windowHandleWindowMap.Add(window.WindowHandle, new WindowExtraInfo(window)); return window; } @@ -29,20 +41,184 @@ namespace Dashboard.OpenTK.PAL2 return CreatePhysicalWindow(); } + protected override void InitializeInternal() + { + base.InitializeInternal(); + + EventQueue.EventRaised += OnEventRaised; + } + public override void RunEvents(bool wait) { TK.Window.ProcessEvents(wait); + long tock = Stopwatch.GetTimestamp(); + long elapsed = _tick - tock; + float delta = (float)elapsed / Stopwatch.Frequency; + TickEventArgs tickEvent = new TickEventArgs(delta); + + _tick = tock; + for (int i = 0; i < _windows.Count; i++) { - if (_windows[i].IsDisposed) + PhysicalWindow window = _windows[i]; + + if (window.IsDisposed) { _windows.RemoveAt(i); continue; } - _windows[i].Paint(); + window.SendEvent(this, tickEvent); + window.SendEvent(this, new PaintEventArgs(window.DeviceContext)); + // For now we swap each window individually. + ((GLDeviceContext)window.DeviceContext).GLContext.SwapGroup.Swap(); } } + + private void OnEventRaised(PalHandle? handle, PlatformEventType type, EventArgs args) + { + if (handle is WindowHandle window) + { + OnWindowEventRaised(window, type, args); + return; + } + else + { + System.Diagnostics.Debugger.Break(); + } + } + + private void OnWindowEventRaised(WindowHandle handle, PlatformEventType type, EventArgs args) + { + if (!_windowHandleWindowMap.TryGetValue(handle, out WindowExtraInfo? info)) + { + Debugger?.LogDebug($"Unknown window handle {handle} received from OpenTK."); + return; + } + + switch (type) + { + case PlatformEventType.MouseDown: + { + MouseButtonDownEventArgs down = (MouseButtonDownEventArgs)args; + MouseButtons buttons = (MouseButtons)(1 << (int)down.Button); + ModifierKeys modifierKeys = GetModifierKeys(down.Modifiers); + // TODO: modifier keys + MouseButtonEventArgs down2 = new MouseButtonEventArgs(info.MousePosition, buttons, modifierKeys, false); + info.Window.SendEvent(this, down2); + break; + } + case PlatformEventType.MouseUp: + { + MouseButtonUpEventArgs up = (MouseButtonUpEventArgs)args; + MouseButtons buttons = (MouseButtons)(1 << (int)up.Button); + ModifierKeys modifierKeys = GetModifierKeys(up.Modifiers); + // TODO: modifier keys + MouseButtonEventArgs up2 = new MouseButtonEventArgs(info.MousePosition, buttons, modifierKeys, true); + info.Window.SendEvent(this, up2); + break; + } + case PlatformEventType.MouseMove: + { + OPENTK.MouseMoveEventArgs move = (OPENTK.MouseMoveEventArgs)args; + Vector2 position = new Vector2(move.ClientPosition.X, move.ClientPosition.Y); + DB.MouseMoveEventArgs move2 = new DB.MouseMoveEventArgs(position, position - info.MousePosition); + + info.MousePosition = position; + + info.Window.SendEvent(this, move2); + break; + } + case PlatformEventType.Scroll: + { + ScrollEventArgs scroll = (ScrollEventArgs)args; + Vector2 distance = new Vector2(scroll.Distance.X, scroll.Distance.Y); + Vector2 delta = new Vector2(scroll.Delta.X, scroll.Delta.Y); + MouseScrollEventArgs scroll2 = new MouseScrollEventArgs(distance, delta); + info.Window.SendEvent(this, scroll2); + break; + } + case PlatformEventType.KeyDown: + { + KeyDownEventArgs down = (KeyDownEventArgs)args; + + ModifierKeys modifierKeys = GetModifierKeys(down.Modifiers); + KeyCode keyCode = GetKeyCode(down.Key); + ScanCode scanCode = GetScanCode(down.Scancode); + + KeyboardButtonEventArgs up2 = new KeyboardButtonEventArgs(keyCode, scanCode, modifierKeys, false); + info.Window.SendEvent(this, up2); + break; + } + case PlatformEventType.KeyUp: + { + KeyUpEventArgs up = (KeyUpEventArgs)args; + + ModifierKeys modifierKeys = GetModifierKeys(up.Modifiers); + KeyCode keyCode = GetKeyCode(up.Key); + ScanCode scanCode = GetScanCode(up.Scancode); + + KeyboardButtonEventArgs up2 = new KeyboardButtonEventArgs(keyCode, scanCode, modifierKeys, true); + info.Window.SendEvent(this, up2); + break; + } + case PlatformEventType.Close: + { + info.Window.SendEvent(this, new WindowCloseEvent()); + break; + } + default: + Debugger?.LogDebug($"Unknown event type {type} with \"{args}\"."); + break; + } + } + + private static ModifierKeys GetModifierKeys(KeyModifier modifier) + { + ModifierKeys keys = 0; + + keys |= modifier.HasFlag(KeyModifier.NumLock) ? ModifierKeys.NumLock : 0; + keys |= modifier.HasFlag(KeyModifier.CapsLock) ? ModifierKeys.CapsLock : 0; + keys |= modifier.HasFlag(KeyModifier.ScrollLock) ? ModifierKeys.ScrollLock : 0; + + keys |= modifier.HasFlag(KeyModifier.LeftShift) ? ModifierKeys.LeftShift : 0; + keys |= modifier.HasFlag(KeyModifier.LeftControl) ? ModifierKeys.LeftControl : 0; + keys |= modifier.HasFlag(KeyModifier.LeftAlt) ? ModifierKeys.LeftAlt : 0; + keys |= modifier.HasFlag(KeyModifier.LeftGUI) ? ModifierKeys.LeftMeta : 0; + + keys |= modifier.HasFlag(KeyModifier.RightShift) ? ModifierKeys.RightShift : 0; + keys |= modifier.HasFlag(KeyModifier.RightControl) ? ModifierKeys.RightControl : 0; + keys |= modifier.HasFlag(KeyModifier.RightAlt) ? ModifierKeys.RightAlt : 0; + keys |= modifier.HasFlag(KeyModifier.RightGUI) ? ModifierKeys.RightMeta : 0; + + keys |= modifier.HasFlag(KeyModifier.Shift) ? ModifierKeys.Shift : 0; + keys |= modifier.HasFlag(KeyModifier.Control) ? ModifierKeys.Control : 0; + keys |= modifier.HasFlag(KeyModifier.Alt) ? ModifierKeys.Alt : 0; + keys |= modifier.HasFlag(KeyModifier.GUI) ? ModifierKeys.Meta : 0; + + // C# makes this cast as annoying as possible. + keys |= (ModifierKeys)((((int)keys >> (int)ModifierKeys.RightBitPos) & 0xF) | + (((int)keys >> (int)ModifierKeys.LeftBitPos) & 0xF)); + + return keys; + } + + private record WindowExtraInfo(PhysicalWindow Window) + { + public Vector2 MousePosition { get; set; } = Vector2.Zero; + } + + // TODO: Keycode and scancode tables. + + private static KeyCode GetKeyCode(Key key) => key switch + { + _ => (KeyCode)0, + }; + + private static ScanCode GetScanCode(Scancode scanCode) => scanCode switch + { + _ => (ScanCode)0, + }; } } diff --git a/Dashboard.OpenTK/PAL2/PhysicalWindow.cs b/Dashboard.OpenTK/PAL2/PhysicalWindow.cs index 437bbf6..822528d 100644 --- a/Dashboard.OpenTK/PAL2/PhysicalWindow.cs +++ b/Dashboard.OpenTK/PAL2/PhysicalWindow.cs @@ -1,8 +1,10 @@ using System.Collections.Concurrent; using System.Drawing; +using System.Net; +using System.Runtime.CompilerServices; using Dashboard.Drawing; -using Dashboard.Drawing.OpenGL.Pal; using Dashboard.Events; +using Dashboard.OpenGL; using Dashboard.Pal; using Dashboard.Windowing; using OpenTK.Mathematics; @@ -12,12 +14,15 @@ using TK = OpenTK.Platform.Toolkit; namespace Dashboard.OpenTK.PAL2 { - public class PhysicalWindow : IPhysicalWindow, IDrawQueuePaintable, IEventListener, IDpiAwareWindow + public class PhysicalWindow : IPhysicalWindow, IEventListener, IDpiAwareWindow { - public DrawQueue DrawQueue { get; } = new DrawQueue(); + private readonly List _listeners = new List(); + + public Application Application { get; } public WindowHandle WindowHandle { get; } public DeviceContext DeviceContext { get; } public bool DoubleBuffered => true; // Always true for OpenTK windows. + public IForm? Form { get; set; } = null; public IWindowManager? WindowManager { get; set; } @@ -47,41 +52,40 @@ 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 event EventHandler? EventRaised; - public PhysicalWindow(WindowHandle window) + public PhysicalWindow(Application app, WindowHandle window) { + Application = app; WindowHandle = window; - DeviceContext = CreateDeviceContext(window, new OpenGLGraphicsApiHints()); + DeviceContext = CreateDeviceContext(app, this, new OpenGLGraphicsApiHints()); AddWindow(this); } - public PhysicalWindow(WindowHandle window, OpenGLContextHandle context) + public PhysicalWindow(Application app, WindowHandle window, OpenGLContextHandle context) { + Application = app; WindowHandle = window; - DeviceContext = new GLDeviceContext(new Pal2GLContext(window, context)); + DeviceContext = new GLDeviceContext(app, this, new Pal2GLContext(window, context)); AddWindow(this); } - public PhysicalWindow(GraphicsApiHints hints) + public PhysicalWindow(Application app, GraphicsApiHints hints) { + Application = app; WindowHandle = TK.Window.Create(hints); - DeviceContext = CreateDeviceContext(WindowHandle, hints); + DeviceContext = CreateDeviceContext(app, this, hints); AddWindow(this); } - private static DeviceContext CreateDeviceContext(WindowHandle window, GraphicsApiHints hints) + private static DeviceContext CreateDeviceContext(Application app, PhysicalWindow window, GraphicsApiHints hints) { + WindowHandle handle = window.WindowHandle; switch (hints.Api) { case GraphicsApi.OpenGL: case GraphicsApi.OpenGLES: - return new GLDeviceContext(new Pal2GLContext(window, TK.OpenGL.CreateFromWindow(window))); + return new GLDeviceContext(app, window, new Pal2GLContext(handle, TK.OpenGL.CreateFromWindow(handle))); default: throw new Exception($"Unknown graphics API {hints.Api}."); } @@ -99,31 +103,50 @@ namespace Dashboard.OpenTK.PAL2 TK.Window.Destroy(WindowHandle); } - protected virtual void OnPaint() + public virtual void SendEvent(object? sender, EventArgs args) { - WindowManager?.Paint(); - Painting?.Invoke(this, EventArgs.Empty); + switch (args) + { + case UiEventArgs ui: + switch (ui.Type) + { + case UiEventType.ControlInvalidateVisual: + break; + } + break; + } + + args = TransformEvent(sender, args); + + EventRaised?.Invoke(this, args); + + lock (_listeners) + { + foreach (IEventListener listener in _listeners) + listener.SendEvent(this, args); + } } - public void Paint() + private EventArgs TransformEvent(object? sender, EventArgs args) { - DrawQueue.Clear(); - OnPaint(); + // TODO: future + return args; } - 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) + public void SubcribeEvent(IEventListener listener) { + lock (_listeners) + { + _listeners.Add(listener); + } + } + + public void UnsubscribeEvent(IEventListener listener) + { + lock (_listeners) + { + _listeners.Remove(listener); + } } private static readonly ConcurrentDictionary _windows = diff --git a/Dashboard.sln b/Dashboard.sln index ba0aedf..4e23c84 100644 --- a/Dashboard.sln +++ b/Dashboard.sln @@ -29,6 +29,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenGL", "Dashboa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.StbImage", "Dashboard.StbImage\Dashboard.StbImage.csproj", "{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText.OpenGL", "Dashboard.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj", "{14616F42-663B-4673-8561-5637FAD1B22F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText", "Dashboard.BlurgText\Dashboard.BlurgText.csproj", "{8C68EFB6-B477-48EC-9AAA-31E89883482B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,6 +76,13 @@ Global {85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Release|Any CPU.Build.0 = Release|Any CPU {14616F42-663B-4673-8561-5637FAD1B22F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14616F42-663B-4673-8561-5637FAD1B22F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14616F42-663B-4673-8561-5637FAD1B22F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14616F42-663B-4673-8561-5637FAD1B22F}.Release|Any CPU.Build.0 = Release|Any CPU + {8C68EFB6-B477-48EC-9AAA-31E89883482B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C68EFB6-B477-48EC-9AAA-31E89883482B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C68EFB6-B477-48EC-9AAA-31E89883482B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C68EFB6-B477-48EC-9AAA-31E89883482B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -80,5 +91,7 @@ Global {7C90B90B-DF31-439B-9080-CD805383B014} = {9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40} {7B064228-2629-486E-95C6-BDDD4B4602C4} = {9B62A92D-ABF5-4704-B831-FD075515A82F} {85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7} = {9B62A92D-ABF5-4704-B831-FD075515A82F} + {14616F42-663B-4673-8561-5637FAD1B22F} = {9B62A92D-ABF5-4704-B831-FD075515A82F} + {8C68EFB6-B477-48EC-9AAA-31E89883482B} = {9B62A92D-ABF5-4704-B831-FD075515A82F} EndGlobalSection EndGlobal diff --git a/Dashboard/Controls/Control.cs b/Dashboard/Controls/Control.cs index 56b74ca..54c738d 100644 --- a/Dashboard/Controls/Control.cs +++ b/Dashboard/Controls/Control.cs @@ -1,5 +1,7 @@ using System; using Dashboard.Drawing; +using Dashboard.Events; +using Dashboard.Pal; using Dashboard.Windowing; namespace Dashboard.Controls @@ -25,7 +27,8 @@ namespace Dashboard.Controls public virtual Box2d ClientArea { get; set; } public bool IsFocused => _owner?.FocusedControl == this; - public event EventHandler? Painting; + public event EventHandler Painting; + public event EventHandler? AnimationTick; public event EventHandler? OwnerChanged; public event EventHandler? ParentChanged; public event EventHandler? FocusGained; @@ -38,14 +41,14 @@ namespace Dashboard.Controls Classes = new ClassSet(this); } - public virtual void OnPaint() + public virtual void OnPaint(DeviceContext dc) { - Painting?.Invoke(this, EventArgs.Empty); + Painting?.Invoke(this, dc); } - public void Paint() + public virtual void OnAnimationTick(TickEventArgs tick) { - OnPaint(); + AnimationTick?.Invoke(this, tick); } protected void InvokeDispose(bool disposing) @@ -67,9 +70,28 @@ namespace Dashboard.Controls public void Dispose() => InvokeDispose(true); - public void SendEvent(EventArgs args) + public event EventHandler? EventRaised; + + protected virtual void OnEventRaised(object? sender, EventArgs args) { - throw new NotImplementedException(); + switch (args) + { + case PaintEventArgs paint: + OnPaint(paint.DeviceContext); + break; + } + + EventRaised?.Invoke(this, TransformEvent(sender, args)); + } + + protected virtual EventArgs TransformEvent(object? sender, EventArgs args) + { + return args; + } + + public void SendEvent(object? sender, EventArgs args) + { + OnEventRaised(sender, args); } internal static void SetParent(Control parent, Control child) diff --git a/Dashboard/Controls/Form.cs b/Dashboard/Controls/Form.cs index 06c80ee..53fd726 100644 --- a/Dashboard/Controls/Form.cs +++ b/Dashboard/Controls/Form.cs @@ -1,15 +1,18 @@ using System; using Dashboard.Drawing; +using Dashboard.Events; +using Dashboard.Pal; using Dashboard.Windowing; namespace Dashboard.Controls { - public class Form : Control + public class Form : Control, IForm { public IWindow Window { get; } public Image? WindowIcon { get; set; } public string? Title { get; set; } = "Untitled Form"; + public Control? Root { get; set; } = null; public Control? FocusedControl { get; private set; } = null; public override Box2d ClientArea @@ -18,12 +21,14 @@ namespace Dashboard.Controls set { } } + public event EventHandler? Closing; + public Form(IWindow window) { Window = window; + window.Form = this; } - public void Focus(Control control) { if (FocusedControl != null) @@ -32,5 +37,23 @@ namespace Dashboard.Controls FocusedControl = control; InvokeFocusGained(this, control); } + + public override void OnPaint(DeviceContext dc) + { + dc.Begin(); + Root?.SendEvent(this, new PaintEventArgs(dc)); + dc.End(); + } + + protected virtual void OnClosing(WindowCloseEvent ea) + { + Closing?.Invoke(this, ea); + + if (ea.Cancel) + return; + + Dispose(); + Window.Dispose(); + } } } diff --git a/Dashboard/Controls/Label.cs b/Dashboard/Controls/Label.cs index 1e217bc..93cba80 100644 --- a/Dashboard/Controls/Label.cs +++ b/Dashboard/Controls/Label.cs @@ -2,6 +2,7 @@ using System; using System.Drawing; using System.Numerics; using Dashboard.Drawing; +using Dashboard.Pal; namespace Dashboard.Controls { @@ -26,11 +27,5 @@ namespace Dashboard.Controls 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); - } } } diff --git a/Dashboard/Controls/MessageBox.cs b/Dashboard/Controls/MessageBox.cs index ea2e263..9e32713 100644 --- a/Dashboard/Controls/MessageBox.cs +++ b/Dashboard/Controls/MessageBox.cs @@ -4,7 +4,8 @@ using System.Collections.Immutable; using System.Collections.ObjectModel; using System.IO; using System.Reflection; -using Dashboard.Resources; +using Dashboard.Drawing; +using Dashboard.Pal; using Dashboard.Windowing; namespace Dashboard.Controls @@ -77,11 +78,6 @@ namespace Dashboard.Controls s_errorIcon = Image.Load(str); } - public override void OnPaint() - { - base.OnPaint(); - } - public static MessageBox Create(IWindow window, string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons) { return new MessageBox(window) diff --git a/Dashboard/Resources/Image.cs b/Dashboard/Drawing/Image.cs similarity index 50% rename from Dashboard/Resources/Image.cs rename to Dashboard/Drawing/Image.cs index d06ae22..50b2d13 100644 --- a/Dashboard/Resources/Image.cs +++ b/Dashboard/Drawing/Image.cs @@ -1,23 +1,22 @@ using System; using System.IO; using System.Runtime.CompilerServices; -using Dashboard.Drawing; using Dashboard.Pal; -using ReFuel.Stb; -using ReFuel.Stb.Native; -namespace Dashboard.Resources +namespace Dashboard.Drawing { - public class Image(PixelFormat format, int width, int height, byte[] data, bool premultiplied = false) : IDisposable + public class Image(ImageData data) : IDisposable { protected readonly ConditionalWeakTable Textures = new ConditionalWeakTable(); - protected virtual TextureType TextureType => TextureType.Texture2D; - public PixelFormat Format { get; } = format; - public int Width { get; } = width; - public int Height { get; } = height; - public bool Premultiplied { get; } = premultiplied; + public virtual TextureType Type => data.Type; + public PixelFormat Format { get; } = data.Format; + public int Width { get; } = data.Width; + public int Height { get; } = data.Height; + public int Depth { get; } = data.Depth; + public int Levels { get; } = data.Levels; + public bool Premultiplied { get; } = data.Premultiplied; public bool IsDisposed { get; private set; } = false; @@ -32,9 +31,12 @@ namespace Dashboard.Resources return texture; ITextureExtension ext = dc.ExtensionRequire(); - texture = ext.CreateTexture(TextureType.Texture2D); - texture.SetStorage(Format, Width, Height, 1, 0); - texture.Write(Format, data); + texture = ext.CreateTexture(Type); + texture.SetStorage(Format, Width, Height, Depth, Levels); + for (int i = 0; i < Levels; i++) + { + texture.Write(Format, data.Bitmap.AsSpan()[(int)data.GetLevelOffset(i)..], level: i, align: data.Alignment); + } texture.Premultiplied = Premultiplied; texture.GenerateMipmaps(); Textures.Add(dc, texture); @@ -63,19 +65,10 @@ namespace Dashboard.Resources public static Image Load(Stream stream) { - using StbImage image = StbImage.Load(stream, StbiImageFormat.Rgba); - ReadOnlySpan data = image.AsSpan(); - return new Image( - image.Format switch - { - StbiImageFormat.Grey => PixelFormat.R8I, - StbiImageFormat.Rgb => PixelFormat.Rgb8I, - StbiImageFormat.Rgba => PixelFormat.Rgba8I, - // StbiImageFormat.GreyAlpha) => PixelFormat.Rg8I, - _ => throw new Exception("Unrecognized pixel format"), - }, - image.Width, image.Height, - data.ToArray()); + IImageLoader imageLoader = Application.Current.ExtensionRequire(); + return new Image(imageLoader.LoadImageData(stream)); + + } } } diff --git a/tests/Dashboard.TestApplication/Dashboard.TestApplication.csproj b/tests/Dashboard.TestApplication/Dashboard.TestApplication.csproj index a5f0c4b..019f94e 100644 --- a/tests/Dashboard.TestApplication/Dashboard.TestApplication.csproj +++ b/tests/Dashboard.TestApplication/Dashboard.TestApplication.csproj @@ -8,9 +8,12 @@ + + + diff --git a/tests/Dashboard.TestApplication/Program.cs b/tests/Dashboard.TestApplication/Program.cs index 2dba001..756945e 100644 --- a/tests/Dashboard.TestApplication/Program.cs +++ b/tests/Dashboard.TestApplication/Program.cs @@ -1,8 +1,12 @@ using System.Drawing; using System.Text; +using BlurgText; +using Dashboard.BlurgText; +using Dashboard.BlurgText.OpenGL; using Dashboard.Controls; using Dashboard.Drawing; -using Dashboard.Drawing.OpenGL.Pal; +using Dashboard.Events; +using Dashboard.OpenGL; using Dashboard.OpenTK.PAL2; using Dashboard.Pal; using Dashboard.StbImage; @@ -10,6 +14,7 @@ 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; @@ -61,6 +66,7 @@ StringBuilder builder = new StringBuilder(); app.Initialize(); app.ExtensionRequire(); +app.ExtensionLoad(new BlurgTextExtension(new BlurgTextExtensionFactory())); window = (PhysicalWindow)app.CreatePhysicalWindow(); @@ -106,7 +112,13 @@ TK.Window.SetMode(window.WindowHandle, WindowMode.Normal); foreach (string str in typeof(Image).Assembly.GetManifestResourceNames()) Console.WriteLine(str); -window.Painting += (sender, ea) => { +BlurgFont font = context.ExtensionRequire().Blurg + .QueryFont("Recursive Mono", FontWeight.Regular, false) ?? throw new Exception("Font not found"); + +window.EventRaised += (sender, ea) => { + if (ea is not PaintEventArgs) + return; + TK.Window.GetSize(window.WindowHandle, out Vector2i size); // executor.BeginFrame(); // @@ -178,11 +190,25 @@ window.Painting += (sender, ea) => { // 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)); - context.GLContext.SwapGroup.Swap(); + BlurgDcExtension blurg = context.ExtensionRequire(); + blurg.DrawBlurgFormattedText(new BlurgFormattedText("Hello world!", font) { DefaultSize = 64f }, new System.Numerics.Vector3(24, 24, 1)); + + context.End(); }; app.Run(true, source.Token); + + +class BlurgTextExtensionFactory : IBlurgDcExtensionFactory +{ + public BlurgDcExtension CreateExtension(BlurgTextExtension appExtension, DeviceContext dc) + { + return new BlurgGLExtension(); + } +};