Compare commits

...

6 Commits

75 changed files with 2541 additions and 689 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

@ -0,0 +1,61 @@
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public record ImageData(
TextureType Type,
PixelFormat Format,
int Width,
int Height,
byte[] Bitmap)
{
public int Depth { get; init; } = 1;
public int Levels { get; init; } = 1;
public bool Premultiplied { get; init; } = false;
public int Alignment { get; init; } = 4;
public long GetLevelOffset(int level)
{
ArgumentOutOfRangeException.ThrowIfLessThan(level, 0, nameof(level));
ArgumentOutOfRangeException.ThrowIfGreaterThan(level, Levels, nameof(level));
ArgumentOutOfRangeException.ThrowIfGreaterThan(level, Math.ILogB(Math.Max(Width, Height)));
long offset = 0;
long row = Width * Format switch
{
PixelFormat.R8I => 1,
PixelFormat.R16F => 2,
PixelFormat.Rg8I => 2,
PixelFormat.Rg16F => 4,
PixelFormat.Rgb8I => 3,
PixelFormat.Rgb16F => 6,
PixelFormat.Rgba8I => 4,
PixelFormat.Rgba16F => 8,
};
row += Alignment - (row % Alignment);
long plane = row * Height;
long volume = plane * Depth;
for (int i = 0; i < level; i++)
{
if (Depth == 1)
{
offset += plane / (1 << i) / (1 << i);
}
else
{
offset += volume / (1 << i) / (1 << i) / (1 << i);
}
}
return offset;
}
}
public interface IImageLoader : IApplicationExtension
{
public ImageData LoadImageData(Stream stream);
}
}

View File

@ -0,0 +1,17 @@
using System.Drawing;
using System.Numerics;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public interface IImmediateMode : IDeviceContextExtension
{
void ClearColor(Color color);
void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color);
void Rectangle(Box2d rectangle, float depth, Vector4 color);
void Image(Box2d rectangle, Box2d uv, float depth, ITexture texture);
}
}

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,6 +1,7 @@
using System.Drawing;
using Dashboard.Pal;
namespace Dashboard.Pal
namespace Dashboard.Drawing
{
public interface ITextureExtension : IDeviceContextExtension
{

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

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

@ -1,9 +1,10 @@
using Dashboard.Collections;
using Dashboard.Windowing;
using BindingFlags = System.Reflection.BindingFlags;
namespace Dashboard.Pal
{
public abstract class AppContext : IContextBase<AppContext, IAppContextExtension>
public abstract class Application : IContextBase<Application, IApplicationExtension>
{
public abstract string DriverName { get; }
public abstract string DriverVendor { get; }
@ -15,12 +16,19 @@ namespace Dashboard.Pal
public bool IsDisposed { get; private set; } = false;
public IContextDebugger? Debugger { get; set; }
private readonly TypeDictionary<IAppContextExtension> _extensions =
new TypeDictionary<IAppContextExtension>(true);
private readonly TypeDictionary<IAppContextExtension, Func<IAppContextExtension>> _preloadedExtensions =
new TypeDictionary<IAppContextExtension, Func<IAppContextExtension>>(true);
private readonly TypeDictionary<IApplicationExtension> _extensions =
new TypeDictionary<IApplicationExtension>(true);
private readonly TypeDictionary<IApplicationExtension, Func<IApplicationExtension>> _preloadedExtensions =
new TypeDictionary<IApplicationExtension, Func<IApplicationExtension>>(true);
~AppContext()
public event EventHandler<DeviceContext>? DeviceContextCreated;
public Application()
{
Current = this;
}
~Application()
{
InvokeDispose(false);
}
@ -39,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)
@ -82,19 +95,37 @@ namespace Dashboard.Pal
window.WindowManager = wm;
return window;
}
public IWindow CreateDialogWindow(IWindow? parent = null)
{
if (parent is IVirtualWindow virtualWindow)
{
IWindow? window = virtualWindow.WindowManager?.CreateWindow();
if (window != null)
return window;
}
return CreatePhysicalWindow();
}
#endregion
public bool IsExtensionAvailable<T>() where T : IAppContextExtension
public bool IsExtensionAvailable<T>() where T : IApplicationExtension
{
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
}
public bool ExtensionPreload<T>() where T : IAppContextExtension, new()
public bool ExtensionPreload<T>(Func<IApplicationExtension> loader) where T : IApplicationExtension
{
return _preloadedExtensions.Add<T>(loader);
}
public bool ExtensionPreload<T>() where T : IApplicationExtension, new()
{
return _preloadedExtensions.Add<T>(() => new T());
}
public T ExtensionRequire<T>() where T : IAppContextExtension, new()
public T ExtensionRequire<T>() where T : IApplicationExtension
{
T? extension = default;
@ -106,13 +137,13 @@ namespace Dashboard.Pal
if (_extensions.TryGet(out extension))
return extension;
if (_preloadedExtensions.Remove<T>(out Func<IAppContextExtension>? loader))
if (_preloadedExtensions.Remove<T>(out Func<IApplicationExtension>? loader))
{
extension = (T)loader!();
}
else
{
extension = new T();
extension = Activator.CreateInstance<T>();
}
_extensions.Add(extension);
@ -122,11 +153,21 @@ namespace Dashboard.Pal
return extension;
}
public bool ExtensionLoad<T>(T instance) where T : IApplicationExtension
{
if (_extensions.Contains(instance))
return false;
_extensions.Add(instance);
instance.Require(this);
return true;
}
protected virtual void Dispose(bool isDisposing)
{
if (!isDisposing) return;
foreach (IAppContextExtension extension in _extensions)
foreach (IApplicationExtension extension in _extensions)
{
extension.Dispose();
}
@ -143,5 +184,12 @@ namespace Dashboard.Pal
}
public void Dispose() => InvokeDispose(true);
[ThreadStatic] private static Application _current;
public static Application Current
{
get => _current ?? throw new InvalidOperationException("There is currently no current application.");
set => _current = value;
}
}
}

View File

@ -1,4 +1,6 @@
using Dashboard.Collections;
using Dashboard.Windowing;
using BindingFlags = System.Reflection.BindingFlags;
namespace Dashboard.Pal
{
@ -9,6 +11,11 @@ namespace Dashboard.Pal
private readonly TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>> _preloadedExtensions =
new TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>>(true);
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; }
@ -20,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);
@ -36,12 +50,17 @@ namespace Dashboard.Pal
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
}
public bool ExtensionPreload<T>(Func<IDeviceContextExtension> loader) where T : IDeviceContextExtension
{
return _preloadedExtensions.Add<T>(loader);
}
public bool ExtensionPreload<T>() where T : IDeviceContextExtension, new()
{
return _preloadedExtensions.Add<T>(() => new T());
}
public T ExtensionRequire<T>() where T : IDeviceContextExtension, new()
public T ExtensionRequire<T>() where T : IDeviceContextExtension
{
T? extension = default;
@ -59,7 +78,7 @@ namespace Dashboard.Pal
}
else
{
extension = new T();
extension = Activator.CreateInstance<T>();
}
_extensions.Add(extension);
@ -69,6 +88,38 @@ namespace Dashboard.Pal
return extension;
}
public bool ExtensionLoad<T>(T instance) where T : IDeviceContextExtension
{
if (_extensions.Contains(instance))
return false;
_extensions.Add(instance);
return true;
}
public void SetAttribute(string name, object? v)
{
if (v != null)
_attributes[name] = v;
else
_attributes.Remove(name);
}
public void SetAttribute<T>(string name, T v) => SetAttribute(name, (object?)v);
public object? GetAttribute(string name)
{
return _attributes.GetValueOrDefault(name);
}
public T? GetAttribute<T>(string name)
{
object? o = GetAttribute(name);
if (o != null)
return (T?)o;
return default;
}
/// <summary>
/// Implement your dispose in this function.
/// </summary>

View File

@ -1,7 +0,0 @@
namespace Dashboard.Pal
{
public interface IAppContextExtension : IContextExtensionBase<AppContext>
{
}
}

View File

@ -0,0 +1,7 @@
namespace Dashboard.Pal
{
public interface IApplicationExtension : IContextExtensionBase<Application>
{
}
}

View File

@ -58,12 +58,31 @@ namespace Dashboard.Pal
/// </returns>
bool ExtensionPreload<T>() where T : TExtension, new();
/// <summary>
/// Preload extensions, to be lazy loaded when required.
/// </summary>
/// <param name="loader">The loader delegate.</param>
/// <typeparam name="T">The extension to preload.</typeparam>
/// <returns>
/// True if the extension was added to the preload set. Otherwise, already loaded or another extension
/// exists which provides this.
/// </returns>
bool ExtensionPreload<T>(Func<TExtension> loader) where T : TExtension;
/// <summary>
/// Require an extension.
/// </summary>
/// <typeparam name="T">The extension to require.</typeparam>
/// <returns>The extension instance.</returns>
T ExtensionRequire<T>() where T : TExtension, new();
T ExtensionRequire<T>() where T : TExtension;
/// <summary>
/// Load an extension.
/// </summary>
/// <param name="instance">The extension instance.</param>
/// <typeparam name="T">The extension to require.</typeparam>
/// <returns>True if the extension was loaded, false if there was already one.</returns>
bool ExtensionLoad<T>(T instance) where T : TExtension;
}
/// <summary>

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,17 +1,18 @@
using System.Drawing;
using Dashboard.Events;
using Dashboard.Pal;
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>
@ -26,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>
@ -49,6 +67,7 @@ namespace Dashboard.Windowing
/// </summary>
public interface IVirtualWindow : IWindow, IEventListener
{
IWindowManager? WindowManager { get; set; }
}
/// <summary>
@ -59,7 +78,7 @@ namespace Dashboard.Windowing
/// <summary>
/// The device context for this window.
/// </summary>
IDeviceContext DeviceContext { get; }
DeviceContext DeviceContext { get; }
/// <summary>
/// True if the window is double buffered.

View File

@ -9,7 +9,6 @@
<ItemGroup>
<PackageReference Include="BlurgText" Version="0.1.0-nightly-19" />
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
</ItemGroup>
<ItemGroup>
@ -24,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

@ -0,0 +1,26 @@
using System.Numerics;
namespace Dashboard.Drawing
{
public enum DrawPrimitive
{
Point,
Line,
LineStrip,
Triangle,
TriangleFan,
TriangleStrip
}
public record struct DrawVertex(Vector3 Position, Vector3 TextureCoordinate, Vector4 Color);
public record DrawInfo(DrawPrimitive Primitive, int Count)
{
}
public class DrawBuffer
{
}
}

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

@ -6,8 +6,4 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
</ItemGroup>
</Project>

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

@ -4,10 +4,19 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Drawing\immediate.frag" />
<EmbeddedResource Include="Drawing\immediate.frag" />
<None Remove="Drawing\immediate.vert" />
<EmbeddedResource Include="Drawing\immediate.vert" />
</ItemGroup>
</Project>

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

@ -0,0 +1,228 @@
using System.Drawing;
using System.Numerics;
using System.Runtime.InteropServices;
using Dashboard.Drawing;
using Dashboard.Pal;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL.Drawing
{
public class ImmediateMode : IImmediateMode
{
public string DriverName => "Dashboard OpenGL Immediate Mode";
public string DriverVendor => "Dashboard";
public Version DriverVersion { get; } = new Version(1, 0);
public DeviceContext Context { get; private set; } = null!;
private int _program;
private uint _program_apos;
private uint _program_atexcoord;
private uint _program_acolor;
private int _program_transforms;
private int _program_image;
private int _vao;
private int _white;
public void Dispose()
{
}
public void Require(DeviceContext context)
{
Context = context;
_program = GL.CreateProgram();
int vs = GL.CreateShader(ShaderType.VertexShader);
using (StreamReader reader = new StreamReader(GetType().Assembly
.GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.vert")!))
{
GL.ShaderSource(vs, reader.ReadToEnd());
}
GL.CompileShader(vs);
GL.AttachShader(_program, vs);
int fs = GL.CreateShader(ShaderType.FragmentShader);
using (StreamReader reader = new StreamReader(GetType().Assembly
.GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.frag")!))
{
GL.ShaderSource(fs, reader.ReadToEnd());
}
GL.CompileShader(fs);
GL.AttachShader(_program, fs);
GL.LinkProgram(_program);
GL.DeleteShader(vs); GL.DeleteShader(fs);
_program_apos = (uint)GL.GetAttribLocation(_program, "aPos");
_program_atexcoord = (uint)GL.GetAttribLocation(_program, "aTexCoords");
_program_acolor = (uint)GL.GetAttribLocation(_program, "aColor");
_program_transforms = GL.GetUniformLocation(_program, "transforms");
_program_image = GL.GetUniformLocation(_program, "image");
GL.GenTexture(out _white);
GL.BindTexture(TextureTarget.Texture2d, _white);
GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.Rgb, 1, 1, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgb, PixelType.Byte, IntPtr.Zero);
GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleA, (int)All.One);
GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleR, (int)All.One);
GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleG, (int)All.One);
GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleB, (int)All.One);
GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
GL.GenVertexArray(out _vao);
}
public void ClearColor(Color color)
{
GL.ClearColor(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
GL.Clear(ClearBufferMask.ColorBufferBit);
}
public void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color)
{
Vector2 normal = Vector2.Normalize(b - a);
Vector2 tangent = new Vector2(-normal.Y, normal.X) * width;
Span<ImmediateVertex> vertices =
[
new ImmediateVertex(new Vector3(a-tangent, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(b-tangent, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(b+tangent, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(a-tangent, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(b+tangent, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(a+tangent, depth), Vector2.Zero, color),
];
int buffer = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, buffer);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * ImmediateVertex.Size, ref vertices[0], BufferUsage.StreamDraw);
GL.BindVertexArray(_vao);
GL.VertexAttribPointer(_program_apos, 3, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.PosOffset);
GL.EnableVertexAttribArray(_program_apos);
GL.VertexAttribPointer(_program_atexcoord, 2, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.TexCoordsOffset);
GL.EnableVertexAttribArray(_program_atexcoord);
GL.VertexAttribPointer(_program_acolor, 4, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.ColorOffset);
GL.EnableVertexAttribArray(_program_acolor);
Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2d, _white);
GL.UniformMatrix4f(_program_transforms, 1, true, ref view);
GL.Uniform1i(_program_image, 0);
GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
GL.DeleteBuffer(buffer);
}
public void Rectangle(Box2d rectangle, float depth, Vector4 color)
{
Span<ImmediateVertex> vertices =
[
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Min.Y, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Min.Y, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Max.Y, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Min.Y, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Max.Y, depth), Vector2.Zero, color),
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Max.Y, depth), Vector2.Zero, color),
];
int buffer = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, buffer);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * ImmediateVertex.Size, ref vertices[0], BufferUsage.StreamDraw);
GL.BindVertexArray(_vao);
GL.VertexAttribPointer(_program_apos, 3, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.PosOffset);
GL.EnableVertexAttribArray(_program_apos);
GL.VertexAttribPointer(_program_atexcoord, 2, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.TexCoordsOffset);
GL.EnableVertexAttribArray(_program_atexcoord);
GL.VertexAttribPointer(_program_acolor, 4, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.ColorOffset);
GL.EnableVertexAttribArray(_program_acolor);
Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2d, _white);
GL.UniformMatrix4f(_program_transforms, 1, true, ref view);
GL.Uniform1i(_program_image, 0);
GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
GL.DeleteBuffer(buffer);
}
public void Image(Box2d rectangle, Box2d uv, float depth, ITexture texture)
{
Span<ImmediateVertex> vertices =
[
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Min.Y, depth), new Vector2(uv.Min.X, uv.Min.Y), Vector4.One),
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Min.Y, depth), new Vector2(uv.Max.X, uv.Min.Y), Vector4.One),
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Max.Y, depth), new Vector2(uv.Max.X, uv.Max.Y), Vector4.One),
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Min.Y, depth), new Vector2(uv.Min.X, uv.Min.Y), Vector4.One),
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Max.Y, depth), new Vector2(uv.Max.X, uv.Max.Y), Vector4.One),
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Max.Y, depth), new Vector2(uv.Min.X, uv.Max.Y), Vector4.One),
];
int buffer = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, buffer);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * ImmediateVertex.Size, ref vertices[0], BufferUsage.StreamDraw);
GL.BindVertexArray(_vao);
GL.VertexAttribPointer(_program_apos, 3, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.PosOffset);
GL.EnableVertexAttribArray(_program_apos);
GL.VertexAttribPointer(_program_atexcoord, 2, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.TexCoordsOffset);
GL.EnableVertexAttribArray(_program_atexcoord);
GL.VertexAttribPointer(_program_acolor, 4, VertexAttribPointerType.Float, false, ImmediateVertex.Size, ImmediateVertex.ColorOffset);
GL.EnableVertexAttribArray(_program_acolor);
Size size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2d, ((GLTexture)texture).Handle);
GL.UniformMatrix4f(_program_transforms, 1, true, ref view);
GL.Uniform1i(_program_image, 0);
GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
GL.DeleteBuffer(buffer);
}
IContextBase IContextExtensionBase.Context => Context;
void IContextExtensionBase.Require(IContextBase context)
{
Require((DeviceContext)context);
}
[StructLayout(LayoutKind.Explicit, Pack = sizeof(float) * 4, Size = Size)]
private struct ImmediateVertex(Vector3 position, Vector2 texCoords, Vector4 color)
{
[FieldOffset(PosOffset)] public Vector3 Position = position;
[FieldOffset(TexCoordsOffset)] public Vector2 TexCoords = texCoords;
[FieldOffset(ColorOffset)] public Vector4 Color = color;
public const int Size = 16 * sizeof(float);
public const int PosOffset = 0 * sizeof(float);
public const int TexCoordsOffset = 4 * sizeof(float);
public const int ColorOffset = 8 * sizeof(float);
}
}
}

View File

@ -0,0 +1,12 @@
#version 130
uniform sampler2D image;
in vec2 vTexCoords;
in vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor * texture(image, vTexCoords);
}

View File

@ -0,0 +1,18 @@
#version 130
uniform mat4 transforms;
in vec3 aPos;
in vec2 aTexCoords;
in vec4 aColor;
out vec2 vTexCoords;
out vec4 vColor;
void main() {
vec4 position = vec4(aPos, 1.0) * transforms;
gl_Position = position;
vTexCoords = aTexCoords;
vColor = aColor;
}

View File

@ -1,12 +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
{
@ -20,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);
@ -35,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();
@ -61,7 +65,9 @@ namespace Dashboard.Drawing.OpenGL.Pal
Extensions = extensions.ToImmutableHashSet();
ExtensionPreload<DeviceContextBase>();
ExtensionPreload<GLTextureExtension>();
ExtensionPreload<ImmediateMode>();
}
public bool IsGLExtensionAvailable(string name)
@ -130,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)
@ -98,6 +104,11 @@ namespace Dashboard.Drawing.OpenGL.Pal
return;
}
if (levels == 0)
{
levels = Math.Max(Math.ILogB(width), Math.ILogB(height));
}
Bind();
SizedInternalFormat glFormat = GetFormat(format);
if (Extension.SupportsArbTextureStorage)
@ -122,18 +133,23 @@ namespace Dashboard.Drawing.OpenGL.Pal
switch (Type)
{
case TextureType.Texture1D:
GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero);
GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
case TextureType.Texture2D:
GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero);
GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
case TextureType.Texture3D:
case TextureType.Texture2DArray:
case TextureType.Texture2DCube:
GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero);
GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
}
}
Width = width;
Height = height;
Depth = depth;
Levels = levels;
}
public void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged
@ -141,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();
@ -160,7 +184,7 @@ namespace Dashboard.Drawing.OpenGL.Pal
PixelType glType = format switch
{
PixelFormat.R8I or PixelFormat.Rg8I or PixelFormat.Rgb8I or PixelFormat.Rgba8I => PixelType.Byte,
PixelFormat.R8I or PixelFormat.Rg8I or PixelFormat.Rgb8I or PixelFormat.Rgba8I => PixelType.UnsignedByte,
PixelFormat.R16F or PixelFormat.Rg16F or PixelFormat.Rgb16F or PixelFormat.Rgba16F => PixelType.HalfFloat,
_ => throw new NotSupportedException()
};
@ -207,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,56 +0,0 @@
using Dashboard.Windowing;
using OpenTK.Graphics;
using OpenTK.Platform;
using AppContext = Dashboard.Pal.AppContext;
using TK = OpenTK.Platform.Toolkit;
namespace Dashboard.OpenTK.PAL2
{
public class Pal2AppContext : AppContext
{
public override string DriverName => "Dashboard OpenTK PAL2.0 Driver";
public override string DriverVendor => "Dashboard";
public override Version DriverVersion => new Version(0, 1);
public GraphicsApiHints GraphicsApiHints { get; set; } = new OpenGLGraphicsApiHints();
public bool OpenGLBindingsInitialized { get; private set; } = false;
private readonly List<PhysicalWindow> _windows = new List<PhysicalWindow>();
public override IPhysicalWindow CreatePhysicalWindow()
{
PhysicalWindow window = new PhysicalWindow(GraphicsApiHints);
_windows.Add(window);
if (!OpenGLBindingsInitialized)
{
OpenGLBindingsInitialized = true;
GLLoader.LoadBindings(
new Pal2BindingsContext(TK.OpenGL,
((OpenGLDeviceContext)window.DeviceContext).ContextHandle));
}
return window;
}
public override IWindow CreateWindow()
{
return CreatePhysicalWindow();
}
public override void RunEvents(bool wait)
{
TK.Window.ProcessEvents(wait);
for (int i = 0; i < _windows.Count; i++)
{
if (_windows[i].IsDisposed)
{
_windows.RemoveAt(i);
continue;
}
_windows[i].Paint();
}
}
}
}

View File

@ -0,0 +1,224 @@
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using Dashboard.Events;
using Dashboard.OpenGL;
using Dashboard.Pal;
using Dashboard.Windowing;
using OpenTK.Platform;
using TK = OpenTK.Platform.Toolkit;
using OPENTK = OpenTK.Platform;
using DB = Dashboard.Events;
namespace Dashboard.OpenTK.PAL2
{
public class Pal2Application : Application
{
public override string DriverName => "Dashboard OpenTK PAL2.0 Driver";
public override string DriverVendor => "Dashboard";
public override Version DriverVersion => new Version(0, 1);
public GraphicsApiHints GraphicsApiHints { get; set; } = new OpenGLGraphicsApiHints();
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(this, GraphicsApiHints);
_windows.Add(window);
_windowHandleWindowMap.Add(window.WindowHandle, new WindowExtraInfo(window));
return window;
}
public override IWindow CreateWindow()
{
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++)
{
PhysicalWindow window = _windows[i];
if (window.IsDisposed)
{
_windows.RemoveAt(i);
continue;
}
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,6 +1,7 @@
using System.Collections.Concurrent;
using System.Drawing;
using Dashboard.OpenGL;
using Dashboard.Pal;
using Dashboard.Windowing;
using OpenTK.Mathematics;
using OpenTK.Platform;
@ -8,7 +9,7 @@ using TK = OpenTK.Platform.Toolkit;
namespace Dashboard.OpenTK.PAL2
{
public class OpenGLDeviceContext : IGLContext, IGLDisposable
public class Pal2GLContext : IGLContext, IDisposable
{
public OpenGLContextHandle ContextHandle { get; }
public WindowHandle WindowHandle { get; }
@ -27,12 +28,12 @@ namespace Dashboard.OpenTK.PAL2
public event Action? Disposed;
public OpenGLDeviceContext(WindowHandle window, OpenGLContextHandle context, ISwapGroup? group = null)
public Pal2GLContext(WindowHandle window, OpenGLContextHandle context)
{
WindowHandle = window;
ContextHandle = context;
SwapGroup = group ?? new DummySwapGroup(context);
ContextGroup = GetContextGroup(context);
SwapGroup = new DummySwapGroup(context);
ContextGroup = GetContextGroup(ContextHandle);
}
public void MakeCurrent()
@ -45,18 +46,13 @@ namespace Dashboard.OpenTK.PAL2
return TK.OpenGL.GetProcedureAddress(ContextHandle, procName);
}
private bool _isDisposed = false;
public void Dispose() => Dispose(true);
public void Dispose(bool safeExit)
protected void Dispose(bool isDisposing)
{
if (_isDisposed) return;
_isDisposed = true;
if (SwapGroup is IGLDisposable glDisposable)
{
glDisposable.Dispose(safeExit);
glDisposable.Dispose(isDisposing);
}
else if (SwapGroup is IDisposable disposable)
{

View File

@ -1,7 +1,10 @@
using System.Collections.Concurrent;
using System.Drawing;
using Dashboard.Drawing;
using System.Net;
using System.Runtime.CompilerServices;
using Dashboard.Events;
using Dashboard.OpenGL;
using Dashboard.Pal;
using Dashboard.Windowing;
using OpenTK.Mathematics;
using OpenTK.Platform;
@ -10,12 +13,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 IDeviceContext DeviceContext { 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; }
@ -45,42 +51,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, IDeviceContext dc)
public PhysicalWindow(Application app, WindowHandle window)
{
Application = app;
WindowHandle = window;
DeviceContext = dc;
DeviceContext = CreateDeviceContext(app, this, new OpenGLGraphicsApiHints());
AddWindow(this);
}
public PhysicalWindow(WindowHandle window, OpenGLContextHandle context, ISwapGroup? swapGroup = null)
public PhysicalWindow(Application app, WindowHandle window, OpenGLContextHandle context)
{
Application = app;
WindowHandle = window;
DeviceContext = new OpenGLDeviceContext(window, context, swapGroup);
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 IDeviceContext 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:
OpenGLContextHandle context = TK.OpenGL.CreateFromWindow(window);
return new OpenGLDeviceContext(window, context);
return new GLDeviceContext(app, window, new Pal2GLContext(handle, TK.OpenGL.CreateFromWindow(handle)));
default:
throw new Exception($"Unknown graphics API {hints.Api}.");
}
@ -98,31 +102,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

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ReFuel.StbImage" Version="2.1.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,47 @@
using Dashboard.Drawing;
using Dashboard.Pal;
using ReFuel.Stb;
namespace Dashboard.StbImage
{
public class StbImageLoader : IImageLoader
{
public string DriverName { get; } = "Dashboard Stb Image Loader";
public string DriverVendor { get; } = "Dashboard";
public Version DriverVersion { get; } = new Version(1, 0);
public void Dispose()
{
}
IContextBase IContextExtensionBase.Context => Context;
public void Require(Application context)
{
Context = context;
}
public ImageData LoadImageData(Stream stream)
{
using ReFuel.Stb.StbImage image = ReFuel.Stb.StbImage.Load(stream, StbiImageFormat.Rgba);
ReadOnlySpan<byte> data = image.AsSpan<byte>();
return new ImageData(TextureType.Texture2D, image.Format switch
{
StbiImageFormat.GreyAlpha => PixelFormat.Rg8I,
StbiImageFormat.Rgb => PixelFormat.Rgb8I,
StbiImageFormat.Rgba => PixelFormat.Rgba8I,
_ => PixelFormat.R8I,
},
image.Width,
image.Height,
data.ToArray());
}
public Application Context { get; private set; } = null!;
void IContextExtensionBase.Require(IContextBase context)
{
Require((Application)context);
}
}
}

View File

@ -5,8 +5,6 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.Drawing", "Dashboard.Drawing\Dashboard.Drawing.csproj", "{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}"
@ -17,16 +15,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Drawing.OpenGL", "Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj", "{454198BA-CB95-41C5-A934-B1C8FDA35A6B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.ImmediateUI", "Dashboard.ImmediateUI\Dashboard.ImmediateUI.csproj", "{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{9B62A92D-ABF5-4704-B831-FD075515A82F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{7B064228-2629-486E-95C6-BDDD4B4602C4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenGL", "Dashboard.OpenGL\Dashboard.OpenGL.csproj", "{33EB657C-B53A-41B4-BC3C-F38C09ABA577}"
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
@ -37,10 +37,6 @@ Global
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.Build.0 = Release|Any CPU
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Release|Any CPU.Build.0 = Release|Any CPU
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C90B90B-DF31-439B-9080-CD805383B014}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -49,14 +45,6 @@ Global
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.Build.0 = Release|Any CPU
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Release|Any CPU.Build.0 = Release|Any CPU
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Release|Any CPU.Build.0 = Release|Any CPU
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -65,6 +53,18 @@ Global
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Debug|Any CPU.Build.0 = Debug|Any CPU
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.Build.0 = Release|Any CPU
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{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
@ -72,5 +72,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{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,22 +1,37 @@
using System;
using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.Pal;
using Dashboard.Windowing;
namespace Dashboard.Controls
{
public class Control : IEventListener, IDrawQueuePaintable, IDisposable
public class Control : IEventListener, IDisposable
{
private Form? _owner = null;
public string? Id { get; set; }
public ClassSet Classes { get; }
public Form? Owner { get; protected set; } = null;
public Form Owner
{
get => _owner ?? throw NoOwnerException;
protected set
{
_owner = value;
OnOwnerChanged(value);
}
}
public Control? Parent { get; private set; } = null;
public bool Disposed { get; private set; }
public virtual DrawQueue DrawQueue => Owner?.DrawQueue ?? throw NoOwnerException;
public virtual Box2d ClientArea { get; set; }
public 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;
public event EventHandler? FocusLost;
public event EventHandler? Disposing;
public event EventHandler? Resized;
@ -25,14 +40,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)
@ -49,18 +64,74 @@ namespace Dashboard.Controls
protected virtual void Dispose(bool disposing)
{
if (disposing) Disposing?.Invoke(this, EventArgs.Empty);
}
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)
{
child.Parent = parent;
child.ParentChanged?.Invoke(child, EventArgs.Empty);
}
public virtual void Focus()
{
(Owner ?? throw NoOwnerException).Focus(this);
}
protected virtual void OnFocusGained(object sender)
{
FocusGained?.Invoke(sender, EventArgs.Empty);
}
protected virtual void OnFocusLost(object sender)
{
FocusLost?.Invoke(sender, EventArgs.Empty);
}
internal static void InvokeFocusGained(Form form, Control control)
{
control.OnFocusGained(form);
}
internal static void InvokeFocusLost(Form form, Control control)
{
control.OnFocusLost(form);
}
protected virtual void OnResize()
{
Resized?.Invoke(this, EventArgs.Empty);
}
private void OnOwnerChanged(Form value)
{
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
protected static Exception NoOwnerException => new Exception("No form owns this control");

View File

@ -1,12 +1,19 @@
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 override DrawQueue DrawQueue { 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
{
@ -14,10 +21,39 @@ namespace Dashboard.Controls
set { }
}
public event EventHandler<WindowCloseEvent>? Closing;
public Form(IWindow window)
{
Window = window;
DrawQueue = (window as IDrawQueuePaintable)?.DrawQueue ?? new DrawQueue();
window.Form = this;
}
public void Focus(Control control)
{
if (FocusedControl != null)
InvokeFocusLost(this, FocusedControl);
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
{
@ -12,7 +13,7 @@ namespace Dashboard.Controls
public event EventHandler? TextChanged;
protected IBrush TextBrush => throw new NotImplementedException();
// protected IBrush TextBrush => throw new NotImplementedException();
protected IFont Font => throw new NotImplementedException();
protected virtual void OnTextChanged(string oldValue, string newValue)
@ -23,15 +24,8 @@ namespace Dashboard.Controls
protected void CalculateSize()
{
SizeF sz = Typesetter.MeasureString(Font, Text);
ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz);
}
public override void OnPaint()
{
base.OnPaint();
DrawQueue.Text(new Vector3(ClientArea.Min, 0), TextBrush, Text, Font);
// SizeF sz = Typesetter.MeasureString(Font, Text);
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz);
}
}
}
}

View File

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using Dashboard.Drawing;
using Dashboard.Pal;
using Dashboard.Windowing;
namespace Dashboard.Controls
{
public enum MessageBoxIcon
{
Info,
Question,
Warning,
Error,
Custom,
}
public enum MessageBoxButtons
{
AbortRetryIgnore,
CancelRetryContinue,
Ok,
OkCancel,
RetryCancel,
YesNo,
YesNoCancel,
Custom,
}
/// <summary>
/// A simple message box dialog.
/// </summary>
public class MessageBox : Form
{
private Image? _icon;
private Label _label = new Label();
private readonly List<Label> _buttons = new List<Label>();
public MessageBoxIcon Icon { get; set; }
public Image? CustomImage { get; set; }
public string? Message { get; set; }
public MessageBoxButtons Buttons { get; set; }
public ObservableCollection<string> CustomButtons { get; } = new ObservableCollection<string>();
public int Result { get; private set; }
public MessageBox(IWindow window) : base(window)
{
SetParent(this, _label);
}
public static readonly Image s_questionIcon;
public static readonly Image s_infoIcon;
public static readonly Image s_warningIcon;
public static readonly Image s_errorIcon;
private static readonly ImmutableList<string> s_abortRetryContinue = ["Abort", "Retry", "Continue"];
private static readonly ImmutableList<string> s_cancelRetryContinue = ["Cancel", "Retry", "Continue"];
private static readonly ImmutableList<string> s_ok = ["OK"];
private static readonly ImmutableList<string> s_okCancel = ["OK", "Cancel"];
private static readonly ImmutableList<string> s_retryCancel = ["Retry", "Cancel"];
private static readonly ImmutableList<string> s_yesNo = ["Yes", "No"];
private static readonly ImmutableList<string> s_yesNoCancel = ["Yes", "No", "Cancel"];
static MessageBox()
{
Assembly asm = typeof(MessageBox).Assembly;
using (Stream str = asm.GetManifestResourceStream("Dashboard.Resources.question.png")!)
s_questionIcon = Image.Load(str);
using (Stream str = asm.GetManifestResourceStream("Dashboard.Resources.info.png")!)
s_infoIcon = Image.Load(str);
using (Stream str = asm.GetManifestResourceStream("Dashboard.Resources.warning.png")!)
s_warningIcon = Image.Load(str);
using (Stream str = asm.GetManifestResourceStream("Dashboard.Resources.error.png")!)
s_errorIcon = Image.Load(str);
}
public static MessageBox Create(IWindow window, string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons)
{
return new MessageBox(window)
{
Message = message,
Title = title,
Icon = icon,
Buttons = buttons,
};
}
// public static MessageBox Create(IWindow window, string message, string title, Image icon, IEnumerable<string> buttons)
// {
// throw new NotImplementedException();
// }
private static string GetDefaultTitle(MessageBoxIcon icon)
{
return icon switch
{
MessageBoxIcon.Error => "Error",
MessageBoxIcon.Info => "Info",
MessageBoxIcon.Question => "Question",
MessageBoxIcon.Warning => "Warning",
_ => "Message Box",
};
}
private static Image GetDefaultIcon(MessageBoxIcon icon)
{
return icon switch
{
MessageBoxIcon.Error => s_errorIcon,
MessageBoxIcon.Question => s_questionIcon,
MessageBoxIcon.Warning => s_warningIcon,
_ => s_infoIcon,
};
}
private static ImmutableList<string> GetDefaultButtons(MessageBoxButtons buttons)
{
return buttons switch
{
MessageBoxButtons.AbortRetryIgnore => s_abortRetryContinue,
MessageBoxButtons.CancelRetryContinue => s_cancelRetryContinue,
MessageBoxButtons.OkCancel => s_okCancel,
MessageBoxButtons.RetryCancel => s_retryCancel,
MessageBoxButtons.YesNo => s_yesNo,
MessageBoxButtons.YesNoCancel => s_yesNoCancel,
_ => s_ok,
};
}
}
}

View File

@ -8,7 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
<EmbeddedResource Include="Resources\**\*.png"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,74 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public class Image(ImageData data) : IDisposable
{
protected readonly ConditionalWeakTable<DeviceContext, ITexture> Textures =
new ConditionalWeakTable<DeviceContext, ITexture>();
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;
~Image()
{
InvokeDispose(false);
}
public virtual ITexture InternTexture(DeviceContext dc)
{
if (Textures.TryGetValue(dc, out ITexture? texture))
return texture;
ITextureExtension ext = dc.ExtensionRequire<ITextureExtension>();
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);
return texture;
}
private void InvokeDispose(bool disposing)
{
if (IsDisposed)
return;
IsDisposed = true;
Dispose(disposing);
}
protected virtual void Dispose(bool disposing)
{
foreach ((DeviceContext dc, ITexture texture) in Textures)
{
texture.Dispose();
}
}
public void Dispose() => InvokeDispose(true);
public static Image Load(Stream stream)
{
IImageLoader imageLoader = Application.Current.ExtensionRequire<IImageLoader>();
return new Image(imageLoader.LoadImageData(stream));
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="error.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="6.0664802"
inkscape:cx="-5.7694081"
inkscape:cy="25.962336"
inkscape:window-width="2560"
inkscape:window-height="1364"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="true">
<sodipodi:guide
position="6.3500001,12.7"
orientation="-1,0"
id="guide1"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,134,229)" />
<sodipodi:guide
position="3.705891,12.686148"
orientation="-1,0"
id="guide2"
inkscape:label=""
inkscape:locked="false"
inkscape:color="rgb(0,134,229)" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="fill:#ff0000;stroke:#d60000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1"
inkscape:flatsided="true"
sodipodi:sides="8"
sodipodi:cx="6.3499999"
sodipodi:cy="6.3499999"
sodipodi:r1="6.8657174"
sodipodi:r2="8.2966747"
sodipodi:arg1="1.9608592"
sodipodi:arg2="2.3535583"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 3.7393344,12.7 0.01385251,8.9941088 -2.0290497e-7,3.7393344 3.705891,0.01385251 8.9606654,-2.0290497e-7 12.686147,3.705891 12.7,8.9606654 8.9941088,12.686147 Z"
transform="matrix(0.92700733,0,0,0.92700733,0.46350342,0.46350342)" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:9.87777px;line-height:normal;font-family:'Rec Mono Linear';-inkscape-font-specification:'Rec Mono Linear Bold';text-align:center;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;direction:ltr;text-orientation:upright;text-anchor:middle;fill:#ffffff;stroke:none;stroke-width:0.600001"
x="6.3154373"
y="9.0071211"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Rec Mono Linear';-inkscape-font-specification:'Rec Mono Linear Bold';fill:#ffffff;stroke-width:0.6"
x="6.3154373"
y="9.0071211">x</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="info.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="15.987983"
inkscape:cx="14.823634"
inkscape:cy="21.234699"
inkscape:window-width="2560"
inkscape:window-height="1364"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#00b200;fill-opacity:1;stroke:#009900;stroke-width:0.573001;stroke-dasharray:none;stroke-opacity:1"
id="rect1"
width="12.127"
height="12.127"
x="0.28650019"
y="0.28650019"
rx="1.0105833"
ry="1.0105834" />
<text
xml:space="preserve"
style="font-style:italic;font-size:9.87777px;line-height:normal;font-family:'Rec Mono Linear';-inkscape-font-specification:'Rec Mono Linear Italic';text-align:center;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;direction:ltr;text-orientation:upright;text-anchor:middle;fill:#f9f9f9;stroke:none;stroke-width:0.600001"
x="6.3351822"
y="10.172698"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Rec Mono Linear';-inkscape-font-specification:'Rec Mono Linear Bold Italic';fill:#f9f9f9;stroke-width:0.6"
x="6.3351822"
y="10.172698">i</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="question.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="6.4560796"
inkscape:cx="12.701206"
inkscape:cy="14.172688"
inkscape:window-width="2560"
inkscape:window-height="1364"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="true"><sodipodi:guide
position="6.3500001,12.7"
orientation="-1,0"
id="guide1"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,134,229)" /><sodipodi:guide
position="3.705891,12.686148"
orientation="-1,0"
id="guide2"
inkscape:label=""
inkscape:locked="false"
inkscape:color="rgb(0,134,229)" /></sodipodi:namedview><defs
id="defs1" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><circle
style="fill:#0000ff;stroke:#0000cc;stroke-width:0.9271;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path2"
cx="6.3500004"
cy="6.3500004"
r="5.8864961" /><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:9.87777px;line-height:normal;font-family:'Rec Mono Linear';-inkscape-font-specification:'Rec Mono Linear Bold';text-align:center;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;direction:ltr;text-orientation:upright;text-anchor:middle;fill:#ffffff;stroke:none;stroke-width:0.600001"
x="6.2907381"
y="9.8615408"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Rec Mono Linear';-inkscape-font-specification:'Rec Mono Linear Bold';fill:#ffffff;stroke-width:0.6"
x="6.2907381"
y="9.8615408">?</tspan></text></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="warning.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="1"
inkscape:cx="23"
inkscape:cy="24.5"
inkscape:window-width="2560"
inkscape:window-height="1364"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="true">
<sodipodi:guide
position="6.3500001,12.7"
orientation="-1,0"
id="guide1"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,134,229)" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#ffcc00;stroke:#ffbb00;stroke-width:0.9271;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 0.46350557,12.236497 6.3500001,0.46350801 12.236494,12.236497 Z"
id="path1"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:9.87777px;line-height:normal;font-family:'Rec Mono Linear';-inkscape-font-specification:'Rec Mono Linear Bold';text-align:center;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;direction:ltr;text-orientation:upright;text-anchor:middle;fill:#ffcc00;stroke:none;stroke-width:0.600001"
x="6.2858014"
y="10.821102"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="fill:#000000;stroke-width:0.6;-inkscape-font-specification:'Rec Mono Linear Bold';font-family:'Rec Mono Linear';font-weight:bold;font-style:normal;font-stretch:normal;font-variant:normal"
x="6.2858014"
y="10.821102">!</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -8,9 +8,9 @@
</PropertyGroup>
<ItemGroup>
<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.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj" />
<ProjectReference Include="..\..\Dashboard.BlurgText\Dashboard.BlurgText.csproj" />
<ProjectReference Include="..\..\Dashboard.OpenTK\Dashboard.OpenTK.csproj" />
<ProjectReference Include="..\..\Dashboard.StbImage\Dashboard.StbImage.csproj" />
</ItemGroup>
</Project>

View File

@ -1,18 +1,22 @@
using Dashboard.Drawing;
using System.Drawing;
using System.Drawing;
using System.Text;
using Dashboard.Drawing.OpenGL;
using Dashboard.ImmediateUI;
using BlurgText;
using Dashboard.BlurgText;
using Dashboard.BlurgText.OpenGL;
using Dashboard.Controls;
using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.OpenGL;
using Dashboard.OpenTK.PAL2;
using OpenTK.Platform;
using Dashboard.Pal;
using Dashboard.StbImage;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using Box2d = Dashboard.Box2d;
using OpenTK.Platform;
using Image = Dashboard.Drawing.Image;
using MouseMoveEventArgs = OpenTK.Platform.MouseMoveEventArgs;
using TK = OpenTK.Platform.Toolkit;
using Dashboard;
using Dashboard.Windowing;
using OpenTK.Windowing.GraphicsLibraryFramework;
using AppContext = Dashboard.Pal.AppContext;
using Vector4 = System.Numerics.Vector4;
TK.Init(new ToolkitOptions()
{
@ -25,7 +29,7 @@ TK.Init(new ToolkitOptions()
FeatureFlags = ToolkitFlags.EnableOpenGL,
});
AppContext app = new Pal2AppContext()
Application app = new Pal2Application()
{
GraphicsApiHints = new OpenGLGraphicsApiHints()
{
@ -48,19 +52,19 @@ AppContext app = new Pal2AppContext()
};
PhysicalWindow window;
SolidBrush fg = new SolidBrush(Color.FromArgb(0, 0, 0, 0));
SolidBrush bg = new SolidBrush(Color.Black);
CancellationTokenSource source = new CancellationTokenSource();
GLEngine engine;
ContextExecutor executor;
DimUI dimUI;
// GLEngine engine;
// ContextExecutor executor;
// DimUI dimUI;
Vector2 mousePos = Vector2.Zero;
Random r = new Random();
List<Vector3> points = new List<Vector3>();
IFont font;
// IFont font;
StringBuilder builder = new StringBuilder();
app.Initialize();
app.ExtensionRequire<StbImageLoader>();
app.ExtensionLoad(new BlurgTextExtension(new BlurgTextExtensionFactory()));
window = (PhysicalWindow)app.CreatePhysicalWindow();
@ -70,20 +74,20 @@ TK.Window.SetClientSize(window.WindowHandle, new Vector2i(320, 240));
TK.Window.SetBorderStyle(window.WindowHandle, WindowBorderStyle.ResizableBorder);
// TK.Window.SetTransparencyMode(wnd, WindowTransparencyMode.TransparentFramebuffer, 0.1f);
OpenGLDeviceContext context = (OpenGLDeviceContext)window.DeviceContext;
GLDeviceContext context = (GLDeviceContext)window.DeviceContext;
context.MakeCurrent();
context.SwapGroup.SwapInterval = 1;
context.GLContext.MakeCurrent();
context.GLContext.SwapGroup.SwapInterval = 1;
engine = new GLEngine();
engine.Initialize();
executor = engine.GetExecutor(context);
dimUI = new DimUI(new DimUIConfig()
{
Font = new NamedFont("Noto Sans", 9f),
});
// engine = new GLEngine();
// engine.Initialize();
//
// executor = engine.GetExecutor(context);
//
// dimUI = new DimUI(new DimUIConfig()
// {
// Font = new NamedFont("Noto Sans", 9f),
// });
EventQueue.EventRaised += (handle, type, eventArgs) =>
{
@ -102,66 +106,74 @@ EventQueue.EventRaised += (handle, type, eventArgs) =>
};
TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
font = Typesetter.LoadFont("Nimbus Mono", 12f);
// font = Typesetter.LoadFont("Nimbus Mono", 12f);
window.Painting += (sender, ea) => {
TK.Window.GetSize(context.WindowHandle, out Vector2i size);
executor.BeginFrame();
foreach (string str in typeof(Image).Assembly.GetManifestResourceNames()) Console.WriteLine(str);
dimUI.Begin(new Box2d(0, 0, size.X, size.Y), window.DrawQueue);
dimUI.Text("Hello World!");
dimUI.Button("Cancel"); dimUI.SameLine();
dimUI.Button("OK");
BlurgFont font = context.ExtensionRequire<BlurgDcExtension>().Blurg
.QueryFont("Recursive Mono", FontWeight.Regular, false) ?? throw new Exception("Font not found");
dimUI.Input("type me!", builder);
window.EventRaised += (sender, ea) => {
if (ea is not PaintEventArgs)
return;
dimUI.BeginMenu();
if (dimUI.MenuItem("File"))
{
dimUI.BeginMenu();
dimUI.MenuItem("New Window");
dimUI.MenuItem("Preferences");
dimUI.MenuItem("Exit");
dimUI.EndMenu();
}
if (dimUI.MenuItem("Edit"))
{
dimUI.BeginMenu();
dimUI.MenuItem("Cut");
dimUI.MenuItem("Copy");
dimUI.MenuItem("Paste");
if (dimUI.MenuItem("Send Char"))
{
dimUI.BeginMenu();
dimUI.EndMenu();
}
dimUI.EndMenu();
}
if (dimUI.MenuItem("View"))
{
dimUI.BeginMenu();
dimUI.MenuItem("Clear");
if (dimUI.MenuItem("Set Size"))
{
dimUI.BeginMenu();
dimUI.MenuItem("24 x 40");
dimUI.MenuItem("25 x 40");
dimUI.MenuItem("24 x 80");
dimUI.MenuItem("25 x 80");
dimUI.MenuItem("25 x 120");
dimUI.EndMenu();
}
dimUI.EndMenu();
}
dimUI.Finish();
TK.Window.GetSize(window.WindowHandle, out Vector2i size);
// executor.BeginFrame();
//
// dimUI.Begin(new Box2d(0, 0, size.X, size.Y), window.DrawQueue);
// dimUI.Text("Hello World!");
// dimUI.Button("Cancel"); dimUI.SameLine();
// dimUI.Button("OK");
//
// dimUI.Input("type me!", builder);
//
// dimUI.BeginMenu();
//
// if (dimUI.MenuItem("File"))
// {
// dimUI.BeginMenu();
// dimUI.MenuItem("New Window");
// dimUI.MenuItem("Preferences");
// dimUI.MenuItem("Exit");
// dimUI.EndMenu();
// }
//
// if (dimUI.MenuItem("Edit"))
// {
// dimUI.BeginMenu();
// dimUI.MenuItem("Cut");
// dimUI.MenuItem("Copy");
// dimUI.MenuItem("Paste");
//
// if (dimUI.MenuItem("Send Char"))
// {
// dimUI.BeginMenu();
// dimUI.EndMenu();
// }
//
// dimUI.EndMenu();
// }
//
// if (dimUI.MenuItem("View"))
// {
// dimUI.BeginMenu();
// dimUI.MenuItem("Clear");
//
// if (dimUI.MenuItem("Set Size"))
// {
// dimUI.BeginMenu();
// dimUI.MenuItem("24 x 40");
// dimUI.MenuItem("25 x 40");
// dimUI.MenuItem("24 x 80");
// dimUI.MenuItem("25 x 80");
// dimUI.MenuItem("25 x 120");
// dimUI.EndMenu();
// }
//
// dimUI.EndMenu();
// }
//
// dimUI.Finish();
GL.Viewport(0, 0, size.X, size.Y);
GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f);
@ -171,10 +183,30 @@ window.Painting += (sender, ea) => {
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.ColorMask(true, true, true, true);
executor.Draw(window.DrawQueue, new RectangleF(0, 0, size.X, size.Y), 1.5f /*(window as IDpiAwareWindow)?.Scale ?? 1*/);
executor.EndFrame();
ITexture texture = MessageBox.s_questionIcon.InternTexture(context);
context.SwapGroup.Swap();
// 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));
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();
}
};