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 System.Drawing;
using Dashboard.Pal;
namespace Dashboard.Pal namespace Dashboard.Drawing
{ {
public interface ITextureExtension : IDeviceContextExtension 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, Middle = M3,
} }
public sealed class MouseMoveEventArgs : UiEventArgs public sealed class MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) : UiEventArgs(UiEventType.MouseMove)
{ {
public Vector2 ClientPosition { get; } public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 Delta { get; } public Vector2 Delta { get; } = delta;
public MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta)
: base(UiEventType.MouseMove)
{
ClientPosition = clientPosition;
Delta = 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 ModifierKeys ModifierKeys { get; } = modifierKeys;
public MouseButtons Buttons { get; } public Vector2 ClientPosition { get; } = clientPosition;
public MouseButtons Buttons { get; } = buttons;
public MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, bool up)
: base(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
{
ClientPosition = clientPosition;
Buttons = buttons;
}
} }
public sealed class MouseScrollEventArgs : UiEventArgs public sealed class MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
: UiEventArgs(UiEventType.MouseScroll)
{ {
public Vector2 ClientPosition { get; } public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 ScrollDelta { get; } public Vector2 ScrollDelta { get; } = scrollDelta;
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;
} }
} }

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 System.Numerics;
using Dashboard.Pal;
namespace Dashboard.Events namespace Dashboard.Events
{ {
@ -6,6 +7,7 @@ namespace Dashboard.Events
{ {
None, None,
AnimationTick, // Generic timer event. AnimationTick, // Generic timer event.
Paint, // Generic paint event.
// Text input related events. // Text input related events.
KeyDown, // Keyboard key down. KeyDown, // Keyboard key down.
@ -57,6 +59,11 @@ namespace Dashboard.Events
public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None); 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 class ControlMovedEventArgs : UiEventArgs
{ {
public Vector2 OldPosition { get; } public Vector2 OldPosition { get; }

View File

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

View File

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

View File

@ -1,9 +1,10 @@
using Dashboard.Collections; using Dashboard.Collections;
using Dashboard.Windowing; using Dashboard.Windowing;
using BindingFlags = System.Reflection.BindingFlags;
namespace Dashboard.Pal 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 DriverName { get; }
public abstract string DriverVendor { get; } public abstract string DriverVendor { get; }
@ -15,12 +16,19 @@ namespace Dashboard.Pal
public bool IsDisposed { get; private set; } = false; public bool IsDisposed { get; private set; } = false;
public IContextDebugger? Debugger { get; set; } public IContextDebugger? Debugger { get; set; }
private readonly TypeDictionary<IAppContextExtension> _extensions = private readonly TypeDictionary<IApplicationExtension> _extensions =
new TypeDictionary<IAppContextExtension>(true); new TypeDictionary<IApplicationExtension>(true);
private readonly TypeDictionary<IAppContextExtension, Func<IAppContextExtension>> _preloadedExtensions = private readonly TypeDictionary<IApplicationExtension, Func<IApplicationExtension>> _preloadedExtensions =
new TypeDictionary<IAppContextExtension, Func<IAppContextExtension>>(true); new TypeDictionary<IApplicationExtension, Func<IApplicationExtension>>(true);
~AppContext() public event EventHandler<DeviceContext>? DeviceContextCreated;
public Application()
{
Current = this;
}
~Application()
{ {
InvokeDispose(false); 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) public virtual void RunEvents(bool wait)
{ {
if (!IsInitialized) if (!IsInitialized)
@ -82,19 +95,37 @@ namespace Dashboard.Pal
window.WindowManager = wm; window.WindowManager = wm;
return window; 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 #endregion
public bool IsExtensionAvailable<T>() where T : IAppContextExtension public bool IsExtensionAvailable<T>() where T : IApplicationExtension
{ {
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>(); 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()); return _preloadedExtensions.Add<T>(() => new T());
} }
public T ExtensionRequire<T>() where T : IAppContextExtension, new() public T ExtensionRequire<T>() where T : IApplicationExtension
{ {
T? extension = default; T? extension = default;
@ -106,13 +137,13 @@ namespace Dashboard.Pal
if (_extensions.TryGet(out extension)) if (_extensions.TryGet(out extension))
return extension; return extension;
if (_preloadedExtensions.Remove<T>(out Func<IAppContextExtension>? loader)) if (_preloadedExtensions.Remove<T>(out Func<IApplicationExtension>? loader))
{ {
extension = (T)loader!(); extension = (T)loader!();
} }
else else
{ {
extension = new T(); extension = Activator.CreateInstance<T>();
} }
_extensions.Add(extension); _extensions.Add(extension);
@ -122,11 +153,21 @@ namespace Dashboard.Pal
return extension; 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) protected virtual void Dispose(bool isDisposing)
{ {
if (!isDisposing) return; if (!isDisposing) return;
foreach (IAppContextExtension extension in _extensions) foreach (IApplicationExtension extension in _extensions)
{ {
extension.Dispose(); extension.Dispose();
} }
@ -143,5 +184,12 @@ namespace Dashboard.Pal
} }
public void Dispose() => InvokeDispose(true); 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.Collections;
using Dashboard.Windowing;
using BindingFlags = System.Reflection.BindingFlags;
namespace Dashboard.Pal namespace Dashboard.Pal
{ {
@ -9,6 +11,11 @@ namespace Dashboard.Pal
private readonly TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>> _preloadedExtensions = private readonly TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>> _preloadedExtensions =
new TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>>(true); 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 DriverName { get; }
public abstract string DriverVendor { get; } public abstract string DriverVendor { get; }
public abstract Version DriverVersion { get; } public abstract Version DriverVersion { get; }
@ -20,6 +27,13 @@ namespace Dashboard.Pal
/// </summary> /// </summary>
public IContextDebugger? Debugger { get; set; } public IContextDebugger? Debugger { get; set; }
protected DeviceContext(Application app, IWindow? window)
{
Application = app;
Window = window;
app.OnDeviceContextCreated(this);
}
~DeviceContext() ~DeviceContext()
{ {
Dispose(false); Dispose(false);
@ -36,12 +50,17 @@ namespace Dashboard.Pal
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>(); 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() public bool ExtensionPreload<T>() where T : IDeviceContextExtension, new()
{ {
return _preloadedExtensions.Add<T>(() => new T()); return _preloadedExtensions.Add<T>(() => new T());
} }
public T ExtensionRequire<T>() where T : IDeviceContextExtension, new() public T ExtensionRequire<T>() where T : IDeviceContextExtension
{ {
T? extension = default; T? extension = default;
@ -59,7 +78,7 @@ namespace Dashboard.Pal
} }
else else
{ {
extension = new T(); extension = Activator.CreateInstance<T>();
} }
_extensions.Add(extension); _extensions.Add(extension);
@ -69,6 +88,38 @@ namespace Dashboard.Pal
return extension; 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> /// <summary>
/// Implement your dispose in this function. /// Implement your dispose in this function.
/// </summary> /// </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> /// </returns>
bool ExtensionPreload<T>() where T : TExtension, new(); 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> /// <summary>
/// Require an extension. /// Require an extension.
/// </summary> /// </summary>
/// <typeparam name="T">The extension to require.</typeparam> /// <typeparam name="T">The extension to require.</typeparam>
/// <returns>The extension instance.</returns> /// <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> /// <summary>

View File

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

View File

@ -2,10 +2,13 @@ namespace Dashboard.Windowing
{ {
public interface IEventListener public interface IEventListener
{ {
event EventHandler? EventRaised;
/// <summary> /// <summary>
/// Send an event to this windowing object. /// Send an event to this windowing object.
/// </summary> /// </summary>
/// <param name="sender">The object which generated the event.</param>
/// <param name="args">The event arguments sent.</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 System.Drawing;
using Dashboard.Events; using Dashboard.Pal;
namespace Dashboard.Windowing namespace Dashboard.Windowing
{ {
/// <summary> /// <summary>
/// Base class of all Dashboard windows. /// Base class of all Dashboard windows.
/// </summary> /// </summary>
public interface IWindow : public interface IWindow : IDisposable
IPaintable,
IDisposable,
IAnimationTickEvent,
IMouseEvents
{ {
/// <summary>
/// The application for this window.
/// </summary>
Application Application { get; }
/// <summary> /// <summary>
/// Name of the window. /// Name of the window.
/// </summary> /// </summary>
@ -26,6 +27,23 @@ namespace Dashboard.Windowing
/// The size of the window that excludes the window extents. /// The size of the window that excludes the window extents.
/// </summary> /// </summary>
SizeF ClientSize { get; set; } 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> /// <summary>
@ -49,6 +67,7 @@ namespace Dashboard.Windowing
/// </summary> /// </summary>
public interface IVirtualWindow : IWindow, IEventListener public interface IVirtualWindow : IWindow, IEventListener
{ {
IWindowManager? WindowManager { get; set; }
} }
/// <summary> /// <summary>
@ -59,7 +78,7 @@ namespace Dashboard.Windowing
/// <summary> /// <summary>
/// The device context for this window. /// The device context for this window.
/// </summary> /// </summary>
IDeviceContext DeviceContext { get; } DeviceContext DeviceContext { get; }
/// <summary> /// <summary>
/// True if the window is double buffered. /// True if the window is double buffered.

View File

@ -9,7 +9,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BlurgText" Version="0.1.0-nightly-19" /> <PackageReference Include="BlurgText" Version="0.1.0-nightly-19" />
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -24,4 +23,8 @@
<EmbeddedResource Include="Executors\text.frag" /> <EmbeddedResource Include="Executors\text.frag" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Text\" />
</ItemGroup>
</Project> </Project>

View File

@ -1,6 +1,7 @@
using System.Drawing; using System.Drawing;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using System.Numerics; using System.Numerics;
using Dashboard.OpenGL;
using OTK = OpenTK.Mathematics; using OTK = OpenTK.Mathematics;
namespace Dashboard.Drawing.OpenGL.Executors namespace Dashboard.Drawing.OpenGL.Executors

View File

@ -1,7 +1,7 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using BlurgText; using BlurgText;
using Dashboard.Drawing.OpenGL.Text; using Dashboard.OpenGL;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics; using OpenTK.Mathematics;
@ -11,7 +11,7 @@ namespace Dashboard.Drawing.OpenGL.Executors
{ {
public IEnumerable<string> Extensions { get; } = new[] { "DB_Text" }; public IEnumerable<string> Extensions { get; } = new[] { "DB_Text" };
public IContextExecutor Executor { get; private set; } 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; } public bool IsInitialized { get; private set; }
private DrawCallRecorder _recorder; private DrawCallRecorder _recorder;
@ -97,7 +97,7 @@ namespace Dashboard.Drawing.OpenGL.Executors
private void DrawText(ICommandFrame frame) private void DrawText(ICommandFrame frame)
{ {
TextCommandArgs args = frame.GetParameter<TextCommandArgs>(); TextCommandArgs args = frame.GetParameter<TextCommandArgs>();
DbBlurgFont font = Engine.InternFont(args.Font); // DbBlurgFont font = Engine.InternFont(args.Font);
BlurgColor color; BlurgColor color;
switch (args.TextBrush) switch (args.TextBrush)
@ -116,15 +116,15 @@ namespace Dashboard.Drawing.OpenGL.Executors
break; 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) // if (result == null)
return; // return;
//
Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z); // Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z);
ExecuteBlurgResult(result, position); // ExecuteBlurgResult(result, position);
//
result.Dispose(); // result.Dispose();
} }
private void ExecuteBlurgResult(BlurgResult result, Vector3 position) 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 Dashboard.OpenGL;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -21,7 +21,7 @@ namespace Dashboard.Drawing.OpenGL
if (bindingsContext != null) if (bindingsContext != null)
GLLoader.LoadBindings(bindingsContext); GLLoader.LoadBindings(bindingsContext);
Typesetter.Backend = BlurgEngine.Global; // Typesetter.Backend = BlurgEngine.Global;
} }
public ContextExecutor GetExecutor(IGLContext glContext) 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; } 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); TextCommand = new TextCommand(this);
} }
@ -40,7 +40,7 @@ namespace Dashboard.Drawing
header = new Header() header = new Header()
{ {
Font = queue.RequireResource(obj.Font), // Font = queue.RequireResource(obj.Font),
TextBrush = queue.RequireResource(obj.TextBrush), TextBrush = queue.RequireResource(obj.TextBrush),
BorderBrush = (obj.BorderBrush != null) ? queue.RequireResource(obj.BorderBrush) : -1, BorderBrush = (obj.BorderBrush != null) ? queue.RequireResource(obj.BorderBrush) : -1,
BorderRadius = (obj.BorderBrush != null) ? obj.BorderRadius : 0f, BorderRadius = (obj.BorderBrush != null) ? obj.BorderRadius : 0f,

View File

@ -20,7 +20,7 @@ namespace Dashboard.Drawing
IFont LoadFont(Stream stream); IFont LoadFont(Stream stream);
IFont LoadFont(string path); IFont LoadFont(string path);
IFont LoadFont(NamedFont font); // IFont LoadFont(NamedFont font);
} }
/// <summary> /// <summary>
@ -55,15 +55,16 @@ namespace Dashboard.Drawing
return Backend.LoadFont(file.FullName); return Backend.LoadFont(file.FullName);
} }
public static IFont LoadFont(NamedFont font) // public static IFont LoadFont(NamedFont font)
{ // {
return Backend.LoadFont(font); // return Backend.LoadFont(font);
} // }
public static IFont LoadFont(string family, float size, FontWeight weight = FontWeight.Normal, public static IFont LoadFont(string family, float size, FontWeight weight = FontWeight.Normal,
FontSlant slant = FontSlant.Normal, FontStretch stretch = FontStretch.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 private class UndefinedTypeSetter : ITypeSetter
@ -94,11 +95,11 @@ namespace Dashboard.Drawing
return default; return default;
} }
public IFont LoadFont(NamedFont font) // public IFont LoadFont(NamedFont font)
{ // {
Except(); // Except();
return default; // return default;
} // }
} }
} }
} }

View File

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

View File

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

View File

@ -4,10 +4,19 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" /> <ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="Drawing\immediate.frag" />
<EmbeddedResource Include="Drawing\immediate.frag" />
<None Remove="Drawing\immediate.vert" />
<EmbeddedResource Include="Drawing\immediate.vert" />
</ItemGroup>
</Project> </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.Concurrent;
using System.Collections.Immutable; using System.Collections.Immutable;
using Dashboard.OpenGL; using Dashboard.Drawing;
using Dashboard.OpenGL.Drawing;
using Dashboard.Pal; using Dashboard.Pal;
using Dashboard.Windowing;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL.Pal namespace Dashboard.OpenGL
{ {
internal class GLContextBindingsContext(IGLContext context) : IBindingsContext internal class GLContextBindingsContext(IGLContext context) : IBindingsContext
{ {
@ -20,6 +22,8 @@ namespace Dashboard.Drawing.OpenGL.Pal
{ {
public IGLContext GLContext { get; } public IGLContext GLContext { get; }
public ContextCollector Collector { get; } = new ContextCollector();
public override string DriverName => "Dashboard OpenGL Device Context"; public override string DriverName => "Dashboard OpenGL Device Context";
public override string DriverVendor => "Dashboard"; public override string DriverVendor => "Dashboard";
public override Version DriverVersion => new Version(0, 1, 0); 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> _beforeDrawActions = new ConcurrentQueue<Task>();
private readonly ConcurrentQueue<Task> _afterDrawActions = 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; GLContext = context;
context.MakeCurrent(); context.MakeCurrent();
@ -61,7 +65,9 @@ namespace Dashboard.Drawing.OpenGL.Pal
Extensions = extensions.ToImmutableHashSet(); Extensions = extensions.ToImmutableHashSet();
ExtensionPreload<DeviceContextBase>();
ExtensionPreload<GLTextureExtension>(); ExtensionPreload<GLTextureExtension>();
ExtensionPreload<ImmediateMode>();
} }
public bool IsGLExtensionAvailable(string name) public bool IsGLExtensionAvailable(string name)
@ -130,6 +136,9 @@ namespace Dashboard.Drawing.OpenGL.Pal
base.Begin(); base.Begin();
GLContext.MakeCurrent(); GLContext.MakeCurrent();
IDeviceContextBase dc = ExtensionRequire<IDeviceContextBase>();
dc.ResetClip();
dc.ResetTransforms();
while (_beforeDrawActions.TryDequeue(out Task? action)) while (_beforeDrawActions.TryDequeue(out Task? action))
{ {

View File

@ -1,9 +1,10 @@
using System.Drawing; using System.Drawing;
using Dashboard.Drawing;
using Dashboard.Pal; using Dashboard.Pal;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using OGL = OpenTK.Graphics.OpenGL; using OGL = OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL.Pal namespace Dashboard.OpenGL
{ {
public class GLTextureExtension : ITextureExtension, IContextExtensionBase<GLDeviceContext> public class GLTextureExtension : ITextureExtension, IContextExtensionBase<GLDeviceContext>
{ {
@ -90,6 +91,11 @@ namespace Dashboard.Drawing.OpenGL.Pal
private GLTextureExtension Extension { get; } = extension; private GLTextureExtension Extension { get; } = extension;
private GLDeviceContext Context => Extension.Context; private GLDeviceContext Context => Extension.Context;
~GLTexture()
{
Dispose(false);
}
public void SetStorage(PixelFormat format, int width, int height, int depth, int levels) public void SetStorage(PixelFormat format, int width, int height, int depth, int levels)
{ {
if (!Context.IsRenderThread) if (!Context.IsRenderThread)
@ -98,6 +104,11 @@ namespace Dashboard.Drawing.OpenGL.Pal
return; return;
} }
if (levels == 0)
{
levels = Math.Max(Math.ILogB(width), Math.ILogB(height));
}
Bind(); Bind();
SizedInternalFormat glFormat = GetFormat(format); SizedInternalFormat glFormat = GetFormat(format);
if (Extension.SupportsArbTextureStorage) if (Extension.SupportsArbTextureStorage)
@ -122,18 +133,23 @@ namespace Dashboard.Drawing.OpenGL.Pal
switch (Type) switch (Type)
{ {
case TextureType.Texture1D: 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; break;
case TextureType.Texture2D: 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; break;
case TextureType.Texture3D: case TextureType.Texture3D:
case TextureType.Texture2DArray: case TextureType.Texture2DArray:
case TextureType.Texture2DCube: 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; 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 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(); 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) 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(); Bind();
@ -160,7 +184,7 @@ namespace Dashboard.Drawing.OpenGL.Pal
PixelType glType = format switch 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, PixelFormat.R16F or PixelFormat.Rg16F or PixelFormat.Rgb16F or PixelFormat.Rgba16F => PixelType.HalfFloat,
_ => throw new NotSupportedException() _ => throw new NotSupportedException()
}; };
@ -207,11 +231,37 @@ namespace Dashboard.Drawing.OpenGL.Pal
GL.GenerateMipmap(Target); 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() private void Bind()
{ {
if (Handle == 0) if (Handle == 0)

View File

@ -1,6 +1,6 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL namespace Dashboard.OpenGL
{ {
public static class ShaderUtil 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.Collections.Concurrent;
using System.Drawing; using System.Drawing;
using Dashboard.OpenGL; using Dashboard.OpenGL;
using Dashboard.Pal;
using Dashboard.Windowing; using Dashboard.Windowing;
using OpenTK.Mathematics; using OpenTK.Mathematics;
using OpenTK.Platform; using OpenTK.Platform;
@ -8,7 +9,7 @@ using TK = OpenTK.Platform.Toolkit;
namespace Dashboard.OpenTK.PAL2 namespace Dashboard.OpenTK.PAL2
{ {
public class OpenGLDeviceContext : IGLContext, IGLDisposable public class Pal2GLContext : IGLContext, IDisposable
{ {
public OpenGLContextHandle ContextHandle { get; } public OpenGLContextHandle ContextHandle { get; }
public WindowHandle WindowHandle { get; } public WindowHandle WindowHandle { get; }
@ -27,12 +28,12 @@ namespace Dashboard.OpenTK.PAL2
public event Action? Disposed; public event Action? Disposed;
public OpenGLDeviceContext(WindowHandle window, OpenGLContextHandle context, ISwapGroup? group = null) public Pal2GLContext(WindowHandle window, OpenGLContextHandle context)
{ {
WindowHandle = window; WindowHandle = window;
ContextHandle = context; ContextHandle = context;
SwapGroup = group ?? new DummySwapGroup(context); SwapGroup = new DummySwapGroup(context);
ContextGroup = GetContextGroup(context); ContextGroup = GetContextGroup(ContextHandle);
} }
public void MakeCurrent() public void MakeCurrent()
@ -45,18 +46,13 @@ namespace Dashboard.OpenTK.PAL2
return TK.OpenGL.GetProcedureAddress(ContextHandle, procName); return TK.OpenGL.GetProcedureAddress(ContextHandle, procName);
} }
private bool _isDisposed = false;
public void Dispose() => Dispose(true); 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) if (SwapGroup is IGLDisposable glDisposable)
{ {
glDisposable.Dispose(safeExit); glDisposable.Dispose(isDisposing);
} }
else if (SwapGroup is IDisposable disposable) else if (SwapGroup is IDisposable disposable)
{ {

View File

@ -1,7 +1,10 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Drawing; using System.Drawing;
using Dashboard.Drawing; using System.Net;
using System.Runtime.CompilerServices;
using Dashboard.Events; using Dashboard.Events;
using Dashboard.OpenGL;
using Dashboard.Pal;
using Dashboard.Windowing; using Dashboard.Windowing;
using OpenTK.Mathematics; using OpenTK.Mathematics;
using OpenTK.Platform; using OpenTK.Platform;
@ -10,12 +13,15 @@ using TK = OpenTK.Platform.Toolkit;
namespace Dashboard.OpenTK.PAL2 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 WindowHandle WindowHandle { get; }
public IDeviceContext DeviceContext { get; } public DeviceContext DeviceContext { get; }
public bool DoubleBuffered => true; // Always true for OpenTK windows. public bool DoubleBuffered => true; // Always true for OpenTK windows.
public IForm? Form { get; set; } = null;
public IWindowManager? WindowManager { get; set; } 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)); set => TK.Window.SetClientSize(WindowHandle, new Vector2i((int)value.Width, (int)value.Height));
} }
public event EventHandler? Painting; public event EventHandler? EventRaised;
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 PhysicalWindow(WindowHandle window, IDeviceContext dc) public PhysicalWindow(Application app, WindowHandle window)
{ {
Application = app;
WindowHandle = window; WindowHandle = window;
DeviceContext = dc; DeviceContext = CreateDeviceContext(app, this, new OpenGLGraphicsApiHints());
AddWindow(this); AddWindow(this);
} }
public PhysicalWindow(WindowHandle window, OpenGLContextHandle context, ISwapGroup? swapGroup = null) public PhysicalWindow(Application app, WindowHandle window, OpenGLContextHandle context)
{ {
Application = app;
WindowHandle = window; WindowHandle = window;
DeviceContext = new OpenGLDeviceContext(window, context, swapGroup); DeviceContext = new GLDeviceContext(app, this, new Pal2GLContext(window, context));
AddWindow(this); AddWindow(this);
} }
public PhysicalWindow(GraphicsApiHints hints) public PhysicalWindow(Application app, GraphicsApiHints hints)
{ {
Application = app;
WindowHandle = TK.Window.Create(hints); WindowHandle = TK.Window.Create(hints);
DeviceContext = CreateDeviceContext(WindowHandle, hints); DeviceContext = CreateDeviceContext(app, this, hints);
AddWindow(this); 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) switch (hints.Api)
{ {
case GraphicsApi.OpenGL: case GraphicsApi.OpenGL:
case GraphicsApi.OpenGLES: case GraphicsApi.OpenGLES:
OpenGLContextHandle context = TK.OpenGL.CreateFromWindow(window); return new GLDeviceContext(app, window, new Pal2GLContext(handle, TK.OpenGL.CreateFromWindow(handle)));
return new OpenGLDeviceContext(window, context);
default: default:
throw new Exception($"Unknown graphics API {hints.Api}."); throw new Exception($"Unknown graphics API {hints.Api}.");
} }
@ -98,31 +102,50 @@ namespace Dashboard.OpenTK.PAL2
TK.Window.Destroy(WindowHandle); TK.Window.Destroy(WindowHandle);
} }
protected virtual void OnPaint() public virtual void SendEvent(object? sender, EventArgs args)
{ {
WindowManager?.Paint(); switch (args)
Painting?.Invoke(this, EventArgs.Empty); {
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(); // TODO: future
OnPaint(); return args;
} }
protected virtual void OnAnimationTimerEvent(AnimationTickEventArgs ea) => public void SubcribeEvent(IEventListener listener)
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)
{ {
lock (_listeners)
{
_listeners.Add(listener);
}
}
public void UnsubscribeEvent(IEventListener listener)
{
lock (_listeners)
{
_listeners.Remove(listener);
}
} }
private static readonly ConcurrentDictionary<WindowHandle, PhysicalWindow> _windows = 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 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}" 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{9B62A92D-ABF5-4704-B831-FD075515A82F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{7B064228-2629-486E-95C6-BDDD4B4602C4}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{7B064228-2629-486E-95C6-BDDD4B4602C4}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenGL", "Dashboard.OpenGL\Dashboard.OpenGL.csproj", "{33EB657C-B53A-41B4-BC3C-F38C09ABA577}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenGL", "Dashboard.OpenGL\Dashboard.OpenGL.csproj", "{33EB657C-B53A-41B4-BC3C-F38C09ABA577}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.Build.0 = 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 {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}.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.ActiveCfg = Release|Any CPU
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.Build.0 = 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 {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}.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.ActiveCfg = Release|Any CPU
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -72,5 +72,8 @@ Global
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{7C90B90B-DF31-439B-9080-CD805383B014} = {9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40} {7C90B90B-DF31-439B-9080-CD805383B014} = {9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}
{7B064228-2629-486E-95C6-BDDD4B4602C4} = {9B62A92D-ABF5-4704-B831-FD075515A82F} {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 EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,22 +1,37 @@
using System; using System;
using Dashboard.Drawing; using Dashboard.Events;
using Dashboard.Pal;
using Dashboard.Windowing; using Dashboard.Windowing;
namespace Dashboard.Controls namespace Dashboard.Controls
{ {
public class Control : IEventListener, IDrawQueuePaintable, IDisposable public class Control : IEventListener, IDisposable
{ {
private Form? _owner = null;
public string? Id { get; set; } public string? Id { get; set; }
public ClassSet Classes { get; } 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 Control? Parent { get; private set; } = null;
public bool Disposed { get; private set; } public bool Disposed { get; private set; }
public virtual DrawQueue DrawQueue => Owner?.DrawQueue ?? throw NoOwnerException;
public virtual Box2d ClientArea { get; set; } 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? OwnerChanged;
public event EventHandler? ParentChanged; public event EventHandler? ParentChanged;
public event EventHandler? FocusGained;
public event EventHandler? FocusLost;
public event EventHandler? Disposing; public event EventHandler? Disposing;
public event EventHandler? Resized; public event EventHandler? Resized;
@ -25,14 +40,14 @@ namespace Dashboard.Controls
Classes = new ClassSet(this); 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) protected void InvokeDispose(bool disposing)
@ -49,18 +64,74 @@ namespace Dashboard.Controls
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) Disposing?.Invoke(this, EventArgs.Empty);
} }
public void Dispose() => InvokeDispose(true); 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) internal static void SetParent(Control parent, Control child)
{ {
child.Parent = parent; 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"); protected static Exception NoOwnerException => new Exception("No form owns this control");

View File

@ -1,12 +1,19 @@
using System;
using Dashboard.Drawing; using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.Pal;
using Dashboard.Windowing; using Dashboard.Windowing;
namespace Dashboard.Controls namespace Dashboard.Controls
{ {
public class Form : Control public class Form : Control, IForm
{ {
public IWindow Window { get; } 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 public override Box2d ClientArea
{ {
@ -14,10 +21,39 @@ namespace Dashboard.Controls
set { } set { }
} }
public event EventHandler<WindowCloseEvent>? Closing;
public Form(IWindow window) public Form(IWindow window)
{ {
Window = 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.Drawing;
using System.Numerics; using System.Numerics;
using Dashboard.Drawing; using Dashboard.Drawing;
using Dashboard.Pal;
namespace Dashboard.Controls namespace Dashboard.Controls
{ {
@ -12,7 +13,7 @@ namespace Dashboard.Controls
public event EventHandler? TextChanged; public event EventHandler? TextChanged;
protected IBrush TextBrush => throw new NotImplementedException(); // protected IBrush TextBrush => throw new NotImplementedException();
protected IFont Font => throw new NotImplementedException(); protected IFont Font => throw new NotImplementedException();
protected virtual void OnTextChanged(string oldValue, string newValue) protected virtual void OnTextChanged(string oldValue, string newValue)
@ -23,15 +24,8 @@ namespace Dashboard.Controls
protected void CalculateSize() protected void CalculateSize()
{ {
SizeF sz = Typesetter.MeasureString(Font, Text); // SizeF sz = Typesetter.MeasureString(Font, Text);
ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz); // ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz);
}
public override void OnPaint()
{
base.OnPaint();
DrawQueue.Text(new Vector3(ClientArea.Min, 0), TextBrush, Text, Font);
} }
} }
} }

View File

@ -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> <ItemGroup>
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" /> <ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" /> <EmbeddedResource Include="Resources\**\*.png"/>
</ItemGroup> </ItemGroup>
</Project> </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> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj" /> <ProjectReference Include="..\..\Dashboard.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj" />
<ProjectReference Include="..\..\Dashboard.Drawing\Dashboard.Drawing.csproj" /> <ProjectReference Include="..\..\Dashboard.BlurgText\Dashboard.BlurgText.csproj" />
<ProjectReference Include="..\..\Dashboard.ImmediateUI\Dashboard.ImmediateUI.csproj" />
<ProjectReference Include="..\..\Dashboard.OpenTK\Dashboard.OpenTK.csproj" /> <ProjectReference Include="..\..\Dashboard.OpenTK\Dashboard.OpenTK.csproj" />
<ProjectReference Include="..\..\Dashboard.StbImage\Dashboard.StbImage.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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