Rework the library architecture.

This commit is contained in:
H. Utku Maden 2025-11-17 21:59:07 +03:00
parent 66c5eecc26
commit 043060db66
52 changed files with 1277 additions and 511 deletions

View File

@ -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<int> _textures = new List<int>();
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<BlurgTextExtension>();
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<IDeviceContextBase>().Transforms;
List<DrawCall> drawCalls = new List<DrawCall>();
List<Vertex> vertices = new List<Vertex>();
List<ushort> indices = new List<ushort>();
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<int> buffers = stackalloc int[2];
GL.GenBuffers(2, buffers);
GL.BindBuffer(BufferTarget.ArrayBuffer, buffers[0]);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Count * Vertex.Size, (ReadOnlySpan<Vertex>)CollectionsMarshal.AsSpan(vertices), BufferUsage.StaticRead);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, buffers[1]);
GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Count * sizeof(ushort), (ReadOnlySpan<ushort>)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;
}
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Dashboard.BlurgText/Dashboard.BlurgText.csproj"/>
<ProjectReference Include="..\Dashboard.OpenGL\Dashboard.OpenGL.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="text.frag" />
<EmbeddedResource Include="text.vert" />
</ItemGroup>
</Project>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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()
{
}
}
}

View File

@ -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<BlurgDcExtension>(() => 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);
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BlurgText" Version="0.1.0-nightly-19" />
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -1,20 +0,0 @@
namespace Dashboard.Events
{
public class AnimationTickEventArgs : UiEventArgs
{
/// <summary>
/// Animation delta time in seconds.
/// </summary>
public float Delta { get; }
public AnimationTickEventArgs(float delta) : base(UiEventType.AnimationTick)
{
Delta = delta;
}
}
public interface IAnimationTickEvent
{
event EventHandler<AnimationTickEventArgs> AnimationTimerEvent;
}
}

View File

@ -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;
}
}

View File

@ -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<MouseMoveEventArgs> MouseMoved;
event EventHandler<MouseButtonEventArgs> MouseButtonDown;
event EventHandler<MouseButtonEventArgs> MouseButtonUp;
event EventHandler<MouseScrollEventArgs> MouseScroll;
public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 ScrollDelta { get; } = scrollDelta;
}
}

View File

@ -0,0 +1,15 @@
namespace Dashboard.Events
{
public class TickEventArgs : UiEventArgs
{
/// <summary>
/// Animation delta time in seconds.
/// </summary>
public float Delta { get; }
public TickEventArgs(float delta) : base(UiEventType.AnimationTick)
{
Delta = delta;
}
}
}

View File

@ -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.
@ -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; }

View File

@ -0,0 +1,7 @@
namespace Dashboard.Events
{
public class WindowCloseEvent() : UiEventArgs(UiEventType.WindowClose)
{
public bool Cancel { get; set; } = false;
}
}

View File

@ -8,6 +8,7 @@ namespace Dashboard
_400 = 400,
_500 = 500,
_600 = 600,
_700 = 700,
_800 = 800,
_900 = 900,

View File

@ -21,6 +21,8 @@ namespace Dashboard.Pal
private readonly TypeDictionary<IApplicationExtension, Func<IApplicationExtension>> _preloadedExtensions =
new TypeDictionary<IApplicationExtension, Func<IApplicationExtension>>(true);
public event EventHandler<DeviceContext>? 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;
}

View File

@ -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<string, object> _attributes = new Dictionary<string, object>();
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
/// </summary>
public IContextDebugger? Debugger { get; set; }
protected DeviceContext(Application app, IWindow? window)
{
Application = app;
Window = window;
app.OnDeviceContextCreated(this);
}
~DeviceContext()
{
Dispose(false);

View File

@ -11,7 +11,7 @@ namespace Dashboard.Windowing
/// <summary>
/// Interface for classes that implement a window manager.
/// </summary>
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener, IPaintable, IDisposable
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener
{
/// <summary>
/// The physical window that this window manager is associated with.

View File

@ -2,10 +2,13 @@ namespace Dashboard.Windowing
{
public interface IEventListener
{
event EventHandler? EventRaised;
/// <summary>
/// Send an event to this windowing object.
/// </summary>
/// <param name="sender">The object which generated the event.</param>
/// <param name="args">The event arguments sent.</param>
void SendEvent(EventArgs args);
void SendEvent(object? sender, EventArgs args);
}
}

View File

@ -0,0 +1,8 @@
namespace Dashboard.Windowing
{
public interface IForm : IEventListener, IDisposable
{
public IWindow Window { get; }
public string Title { get; }
}
}

View File

@ -1,5 +1,4 @@
using System.Drawing;
using Dashboard.Events;
using Dashboard.Pal;
namespace Dashboard.Windowing
@ -7,12 +6,13 @@ namespace Dashboard.Windowing
/// <summary>
/// Base class of all Dashboard windows.
/// </summary>
public interface IWindow :
IPaintable,
IDisposable,
IAnimationTickEvent,
IMouseEvents
public interface IWindow : IDisposable
{
/// <summary>
/// The application for this window.
/// </summary>
Application Application { get; }
/// <summary>
/// Name of the window.
/// </summary>
@ -27,6 +27,23 @@ namespace Dashboard.Windowing
/// The size of the window that excludes the window extents.
/// </summary>
SizeF ClientSize { get; set; }
IForm? Form { get; set; }
public event EventHandler? EventRaised;
/// <summary>
/// Subscribe to events from this window.
/// </summary>
/// <param name="listener">The event listener instance.</param>
/// <returns>An unsubscription token.</returns>
public void SubcribeEvent(IEventListener listener);
/// <summary>
/// Unsubscribe from events in from this window.
/// </summary>
/// <param name="listener">The event listener to unsubscribe.</param>
public void UnsubscribeEvent(IEventListener listener);
}
/// <summary>

View File

@ -23,4 +23,8 @@
<EmbeddedResource Include="Executors\text.frag" />
</ItemGroup>
<ItemGroup>
<Folder Include="Text\" />
</ItemGroup>
</Project>

View File

@ -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

View File

@ -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<string> Extensions { get; } = new[] { "DB_Text" };
public IContextExecutor Executor { get; private set; }
private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager<BlurgEngine>();
// private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager<BlurgEngine>();
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<TextCommandArgs>();
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)

View File

@ -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)

View File

@ -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<int> _textures = new List<int>();
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);
/// <summary>
/// The global Blurg engine implements the needed methods for command queues to work.
/// </summary>
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;
}
}
}

View File

@ -1,13 +0,0 @@
namespace Dashboard.Drawing.OpenGL.Text
{
public class BlurgFontExtension : IDrawExtension
{
public string Name { get; } = "BLURG_Font";
public IReadOnlyList<IDrawExtension> Requires { get; } = new [] { FontExtension.Instance };
public IReadOnlyList<IDrawCommand> Commands { get; } = new IDrawCommand[] { };
private BlurgFontExtension() {}
public static readonly BlurgFontExtension Instance = new BlurgFontExtension();
}
}

View File

@ -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);
}
}
}

View File

@ -1,52 +0,0 @@
using System.Linq;
namespace Dashboard.Drawing
{
public class FontExtension : DrawExtension
{
private FontExtension() : base("DB_Font", Enumerable.Empty<DrawExtension>())
{
}
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])
{
}
}
}
}

View File

@ -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,

View File

@ -20,7 +20,7 @@ namespace Dashboard.Drawing
IFont LoadFont(Stream stream);
IFont LoadFont(string path);
IFont LoadFont(NamedFont font);
// IFont LoadFont(NamedFont font);
}
/// <summary>
@ -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;
// }
}
}
}

View File

@ -1,7 +1,7 @@
using System.Collections.Concurrent;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL
namespace Dashboard.OpenGL
{
public class ContextCollector : IDisposable
{

View File

@ -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<Matrix4x4> _transforms = new Stack<Matrix4x4>();
private readonly Stack<RectangleF> _clipRegions = new Stack<RectangleF>();
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();
}
}
}

View File

@ -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<IDeviceContextBase>().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<IDeviceContextBase>().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<IDeviceContextBase>().Transforms;
GL.UseProgram(_program);

View File

@ -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<Task> _beforeDrawActions = new ConcurrentQueue<Task>();
private readonly ConcurrentQueue<Task> _afterDrawActions = new ConcurrentQueue<Task>();
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<DeviceContextBase>();
ExtensionPreload<GLTextureExtension>();
ExtensionPreload<ImmediateMode>();
}
@ -132,6 +136,9 @@ namespace Dashboard.Drawing.OpenGL.Pal
base.Begin();
GLContext.MakeCurrent();
IDeviceContextBase dc = ExtensionRequire<IDeviceContextBase>();
dc.ResetClip();
dc.ResetTransforms();
while (_beforeDrawActions.TryDequeue(out Task? action))
{

View File

@ -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<GLDeviceContext>
{
@ -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<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4) where T : unmanaged
void ITexture.Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level, int align) => Write(format, buffer, level, align, null);
public unsafe void Write<T>(PixelFormat format, ReadOnlySpan<T> 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<T>(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)

View File

@ -1,6 +1,6 @@
using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL
namespace Dashboard.OpenGL
{
public static class ShaderUtil
{

View File

@ -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;
}
}
}
}

View File

@ -1,7 +0,0 @@
namespace Dashboard.OpenTK.PAL2
{
public static class OpenTKEventExtensions
{
// public static EventArgs ToDashboardEvent(this EventArgs)
}
}

View File

@ -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<PhysicalWindow> _windows = new List<PhysicalWindow>();
private readonly ConditionalWeakTable<WindowHandle, WindowExtraInfo> _windowHandleWindowMap =
new ConditionalWeakTable<WindowHandle, WindowExtraInfo>();
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,
};
}
}

View File

@ -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<IEventListener> _listeners = new List<IEventListener>();
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<AnimationTickEventArgs>? AnimationTimerEvent;
public event EventHandler<MouseMoveEventArgs>? MouseMoved;
public event EventHandler<MouseButtonEventArgs>? MouseButtonDown;
public event EventHandler<MouseButtonEventArgs>? MouseButtonUp;
public event EventHandler<MouseScrollEventArgs>? 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<WindowHandle, PhysicalWindow> _windows =

View File

@ -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

View File

@ -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<DeviceContext> Painting;
public event EventHandler<TickEventArgs>? 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)

View File

@ -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<WindowCloseEvent>? 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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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<DeviceContext, ITexture> Textures =
new ConditionalWeakTable<DeviceContext, ITexture>();
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<ITextureExtension>();
texture = ext.CreateTexture(TextureType.Texture2D);
texture.SetStorage(Format, Width, Height, 1, 0);
texture.Write<byte>(Format, data);
texture = ext.CreateTexture(Type);
texture.SetStorage(Format, Width, Height, Depth, Levels);
for (int i = 0; i < Levels; i++)
{
texture.Write<byte>(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<byte> data = image.AsSpan<byte>();
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<IImageLoader>();
return new Image(imageLoader.LoadImageData(stream));
}
}
}

View File

@ -8,9 +8,12 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Dashboard.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj" />
<ProjectReference Include="..\..\Dashboard.BlurgText\Dashboard.BlurgText.csproj" />
<ProjectReference Include="..\..\Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj" />
<ProjectReference Include="..\..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
<ProjectReference Include="..\..\Dashboard.ImmediateUI\Dashboard.ImmediateUI.csproj" />
<ProjectReference Include="..\..\Dashboard.OpenTK\Dashboard.OpenTK.csproj" />
<ProjectReference Include="..\..\Dashboard.StbImage\Dashboard.StbImage.csproj" />
</ItemGroup>
</Project>

View File

@ -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<StbImageLoader>();
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<BlurgDcExtension>().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<IImmediateMode>();
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<BlurgDcExtension>();
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();
}
};