Compare commits
6 Commits
6e8888df48
...
9ca309bd52
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ca309bd52 | |||
| 043060db66 | |||
| 66c5eecc26 | |||
| 9ee6c1180d | |||
| c4fe4841fe | |||
| 2932b3b85e |
264
Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Dashboard.BlurgText.OpenGL/Dashboard.BlurgText.OpenGL.csproj
Normal 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>
|
||||
21
Dashboard.BlurgText.OpenGL/text.frag
Normal 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;
|
||||
}
|
||||
18
Dashboard.BlurgText.OpenGL/text.vert
Normal 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;
|
||||
}
|
||||
21
Dashboard.BlurgText/BlurgFontProxy.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
165
Dashboard.BlurgText/BlurgTextExtension.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
15
Dashboard.BlurgText/Dashboard.BlurgText.csproj
Normal 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>
|
||||
20
Dashboard.Common/Drawing/IDeviceContextBase.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
25
Dashboard.Common/Drawing/IFontLoader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
61
Dashboard.Common/Drawing/IImageLoader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
17
Dashboard.Common/Drawing/IImmediateMode.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
12
Dashboard.Common/Drawing/ITextRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using System.Drawing;
|
||||
using Dashboard.Pal;
|
||||
|
||||
namespace Dashboard.Pal
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public interface ITextureExtension : IDeviceContextExtension
|
||||
{
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
47
Dashboard.Common/Events/KeyboardEvents.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -20,50 +20,24 @@ namespace Dashboard.Events
|
||||
Middle = M3,
|
||||
}
|
||||
|
||||
public sealed class MouseMoveEventArgs : UiEventArgs
|
||||
public sealed class MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) : UiEventArgs(UiEventType.MouseMove)
|
||||
{
|
||||
public Vector2 ClientPosition { get; }
|
||||
public Vector2 Delta { get; }
|
||||
|
||||
public MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta)
|
||||
: base(UiEventType.MouseMove)
|
||||
{
|
||||
ClientPosition = clientPosition;
|
||||
Delta = delta;
|
||||
}
|
||||
public Vector2 ClientPosition { get; } = clientPosition;
|
||||
public Vector2 Delta { get; } = delta;
|
||||
}
|
||||
|
||||
public sealed class MouseButtonEventArgs : UiEventArgs
|
||||
public sealed class MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, ModifierKeys modifierKeys, bool up)
|
||||
: UiEventArgs(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
|
||||
{
|
||||
public Vector2 ClientPosition { get; }
|
||||
public MouseButtons Buttons { get; }
|
||||
|
||||
public MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, bool up)
|
||||
: base(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
|
||||
{
|
||||
ClientPosition = clientPosition;
|
||||
Buttons = buttons;
|
||||
}
|
||||
public ModifierKeys ModifierKeys { get; } = modifierKeys;
|
||||
public Vector2 ClientPosition { get; } = clientPosition;
|
||||
public MouseButtons Buttons { get; } = buttons;
|
||||
}
|
||||
|
||||
public sealed class MouseScrollEventArgs : UiEventArgs
|
||||
public sealed class MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
|
||||
: UiEventArgs(UiEventType.MouseScroll)
|
||||
{
|
||||
public Vector2 ClientPosition { get; }
|
||||
public Vector2 ScrollDelta { get; }
|
||||
|
||||
public MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
|
||||
: base(UiEventType.MouseScroll)
|
||||
{
|
||||
ClientPosition = clientPosition;
|
||||
ScrollDelta = scrollDelta;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMouseEvents
|
||||
{
|
||||
event EventHandler<MouseMoveEventArgs> MouseMoved;
|
||||
event EventHandler<MouseButtonEventArgs> MouseButtonDown;
|
||||
event EventHandler<MouseButtonEventArgs> MouseButtonUp;
|
||||
event EventHandler<MouseScrollEventArgs> MouseScroll;
|
||||
public Vector2 ClientPosition { get; } = clientPosition;
|
||||
public Vector2 ScrollDelta { get; } = scrollDelta;
|
||||
}
|
||||
}
|
||||
|
||||
15
Dashboard.Common/Events/TickEventArgs.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Dashboard.Pal;
|
||||
|
||||
namespace Dashboard.Events
|
||||
{
|
||||
@ -6,6 +7,7 @@ namespace Dashboard.Events
|
||||
{
|
||||
None,
|
||||
AnimationTick, // Generic timer event.
|
||||
Paint, // Generic paint event.
|
||||
|
||||
// Text input related events.
|
||||
KeyDown, // Keyboard key down.
|
||||
@ -41,7 +43,7 @@ namespace Dashboard.Events
|
||||
ControlFocusGet, // The control acquired focus.
|
||||
ControlFocusLost, // The control lost focus.
|
||||
WindowClose, // The window closed.
|
||||
|
||||
|
||||
UserRangeStart = 1 << 12,
|
||||
}
|
||||
|
||||
@ -57,6 +59,11 @@ namespace Dashboard.Events
|
||||
public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None);
|
||||
}
|
||||
|
||||
public class PaintEventArgs(DeviceContext dc) : UiEventArgs(UiEventType.Paint)
|
||||
{
|
||||
public DeviceContext DeviceContext { get; } = dc;
|
||||
}
|
||||
|
||||
public class ControlMovedEventArgs : UiEventArgs
|
||||
{
|
||||
public Vector2 OldPosition { get; }
|
||||
@ -71,6 +78,6 @@ namespace Dashboard.Events
|
||||
|
||||
public class ControlResizedEventArgs
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
7
Dashboard.Common/Events/WindowEvent.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Dashboard.Events
|
||||
{
|
||||
public class WindowCloseEvent() : UiEventArgs(UiEventType.WindowClose)
|
||||
{
|
||||
public bool Cancel { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ namespace Dashboard
|
||||
_400 = 400,
|
||||
_500 = 500,
|
||||
_600 = 600,
|
||||
_700 = 700,
|
||||
_800 = 800,
|
||||
_900 = 900,
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
using Dashboard.Collections;
|
||||
using Dashboard.Windowing;
|
||||
using BindingFlags = System.Reflection.BindingFlags;
|
||||
|
||||
namespace Dashboard.Pal
|
||||
{
|
||||
public abstract class AppContext : IContextBase<AppContext, IAppContextExtension>
|
||||
public abstract class Application : IContextBase<Application, IApplicationExtension>
|
||||
{
|
||||
public abstract string DriverName { get; }
|
||||
public abstract string DriverVendor { get; }
|
||||
@ -15,12 +16,19 @@ namespace Dashboard.Pal
|
||||
public bool IsDisposed { get; private set; } = false;
|
||||
public IContextDebugger? Debugger { get; set; }
|
||||
|
||||
private readonly TypeDictionary<IAppContextExtension> _extensions =
|
||||
new TypeDictionary<IAppContextExtension>(true);
|
||||
private readonly TypeDictionary<IAppContextExtension, Func<IAppContextExtension>> _preloadedExtensions =
|
||||
new TypeDictionary<IAppContextExtension, Func<IAppContextExtension>>(true);
|
||||
private readonly TypeDictionary<IApplicationExtension> _extensions =
|
||||
new TypeDictionary<IApplicationExtension>(true);
|
||||
private readonly TypeDictionary<IApplicationExtension, Func<IApplicationExtension>> _preloadedExtensions =
|
||||
new TypeDictionary<IApplicationExtension, Func<IApplicationExtension>>(true);
|
||||
|
||||
~AppContext()
|
||||
public event EventHandler<DeviceContext>? DeviceContextCreated;
|
||||
|
||||
public Application()
|
||||
{
|
||||
Current = this;
|
||||
}
|
||||
|
||||
~Application()
|
||||
{
|
||||
InvokeDispose(false);
|
||||
}
|
||||
@ -39,6 +47,11 @@ namespace Dashboard.Pal
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void OnDeviceContextCreated(DeviceContext dc)
|
||||
{
|
||||
DeviceContextCreated?.Invoke(this, dc);
|
||||
}
|
||||
|
||||
public virtual void RunEvents(bool wait)
|
||||
{
|
||||
if (!IsInitialized)
|
||||
@ -82,19 +95,37 @@ namespace Dashboard.Pal
|
||||
window.WindowManager = wm;
|
||||
return window;
|
||||
}
|
||||
|
||||
public IWindow CreateDialogWindow(IWindow? parent = null)
|
||||
{
|
||||
if (parent is IVirtualWindow virtualWindow)
|
||||
{
|
||||
IWindow? window = virtualWindow.WindowManager?.CreateWindow();
|
||||
|
||||
if (window != null)
|
||||
return window;
|
||||
}
|
||||
|
||||
return CreatePhysicalWindow();
|
||||
}
|
||||
#endregion
|
||||
|
||||
public bool IsExtensionAvailable<T>() where T : IAppContextExtension
|
||||
public bool IsExtensionAvailable<T>() where T : IApplicationExtension
|
||||
{
|
||||
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
|
||||
}
|
||||
|
||||
public bool ExtensionPreload<T>() where T : IAppContextExtension, new()
|
||||
public bool ExtensionPreload<T>(Func<IApplicationExtension> loader) where T : IApplicationExtension
|
||||
{
|
||||
return _preloadedExtensions.Add<T>(loader);
|
||||
}
|
||||
|
||||
public bool ExtensionPreload<T>() where T : IApplicationExtension, new()
|
||||
{
|
||||
return _preloadedExtensions.Add<T>(() => new T());
|
||||
}
|
||||
|
||||
public T ExtensionRequire<T>() where T : IAppContextExtension, new()
|
||||
public T ExtensionRequire<T>() where T : IApplicationExtension
|
||||
{
|
||||
T? extension = default;
|
||||
|
||||
@ -106,13 +137,13 @@ namespace Dashboard.Pal
|
||||
if (_extensions.TryGet(out extension))
|
||||
return extension;
|
||||
|
||||
if (_preloadedExtensions.Remove<T>(out Func<IAppContextExtension>? loader))
|
||||
if (_preloadedExtensions.Remove<T>(out Func<IApplicationExtension>? loader))
|
||||
{
|
||||
extension = (T)loader!();
|
||||
}
|
||||
else
|
||||
{
|
||||
extension = new T();
|
||||
extension = Activator.CreateInstance<T>();
|
||||
}
|
||||
|
||||
_extensions.Add(extension);
|
||||
@ -122,11 +153,21 @@ namespace Dashboard.Pal
|
||||
return extension;
|
||||
}
|
||||
|
||||
public bool ExtensionLoad<T>(T instance) where T : IApplicationExtension
|
||||
{
|
||||
if (_extensions.Contains(instance))
|
||||
return false;
|
||||
|
||||
_extensions.Add(instance);
|
||||
instance.Require(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool isDisposing)
|
||||
{
|
||||
if (!isDisposing) return;
|
||||
|
||||
foreach (IAppContextExtension extension in _extensions)
|
||||
foreach (IApplicationExtension extension in _extensions)
|
||||
{
|
||||
extension.Dispose();
|
||||
}
|
||||
@ -143,5 +184,12 @@ namespace Dashboard.Pal
|
||||
}
|
||||
|
||||
public void Dispose() => InvokeDispose(true);
|
||||
|
||||
[ThreadStatic] private static Application _current;
|
||||
public static Application Current
|
||||
{
|
||||
get => _current ?? throw new InvalidOperationException("There is currently no current application.");
|
||||
set => _current = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
using Dashboard.Collections;
|
||||
using Dashboard.Windowing;
|
||||
using BindingFlags = System.Reflection.BindingFlags;
|
||||
|
||||
namespace Dashboard.Pal
|
||||
{
|
||||
@ -9,6 +11,11 @@ namespace Dashboard.Pal
|
||||
private readonly TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>> _preloadedExtensions =
|
||||
new TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>>(true);
|
||||
|
||||
private readonly Dictionary<string, object> _attributes = new Dictionary<string, object>();
|
||||
|
||||
|
||||
public Application Application { get; }
|
||||
public IWindow? Window { get; }
|
||||
public abstract string DriverName { get; }
|
||||
public abstract string DriverVendor { get; }
|
||||
public abstract Version DriverVersion { get; }
|
||||
@ -20,6 +27,13 @@ namespace Dashboard.Pal
|
||||
/// </summary>
|
||||
public IContextDebugger? Debugger { get; set; }
|
||||
|
||||
protected DeviceContext(Application app, IWindow? window)
|
||||
{
|
||||
Application = app;
|
||||
Window = window;
|
||||
app.OnDeviceContextCreated(this);
|
||||
}
|
||||
|
||||
~DeviceContext()
|
||||
{
|
||||
Dispose(false);
|
||||
@ -36,12 +50,17 @@ namespace Dashboard.Pal
|
||||
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
|
||||
}
|
||||
|
||||
public bool ExtensionPreload<T>(Func<IDeviceContextExtension> loader) where T : IDeviceContextExtension
|
||||
{
|
||||
return _preloadedExtensions.Add<T>(loader);
|
||||
}
|
||||
|
||||
public bool ExtensionPreload<T>() where T : IDeviceContextExtension, new()
|
||||
{
|
||||
return _preloadedExtensions.Add<T>(() => new T());
|
||||
}
|
||||
|
||||
public T ExtensionRequire<T>() where T : IDeviceContextExtension, new()
|
||||
public T ExtensionRequire<T>() where T : IDeviceContextExtension
|
||||
{
|
||||
T? extension = default;
|
||||
|
||||
@ -59,7 +78,7 @@ namespace Dashboard.Pal
|
||||
}
|
||||
else
|
||||
{
|
||||
extension = new T();
|
||||
extension = Activator.CreateInstance<T>();
|
||||
}
|
||||
|
||||
_extensions.Add(extension);
|
||||
@ -69,6 +88,38 @@ namespace Dashboard.Pal
|
||||
return extension;
|
||||
}
|
||||
|
||||
public bool ExtensionLoad<T>(T instance) where T : IDeviceContextExtension
|
||||
{
|
||||
if (_extensions.Contains(instance))
|
||||
return false;
|
||||
|
||||
_extensions.Add(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetAttribute(string name, object? v)
|
||||
{
|
||||
if (v != null)
|
||||
_attributes[name] = v;
|
||||
else
|
||||
_attributes.Remove(name);
|
||||
}
|
||||
|
||||
public void SetAttribute<T>(string name, T v) => SetAttribute(name, (object?)v);
|
||||
|
||||
public object? GetAttribute(string name)
|
||||
{
|
||||
return _attributes.GetValueOrDefault(name);
|
||||
}
|
||||
|
||||
public T? GetAttribute<T>(string name)
|
||||
{
|
||||
object? o = GetAttribute(name);
|
||||
if (o != null)
|
||||
return (T?)o;
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement your dispose in this function.
|
||||
/// </summary>
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
namespace Dashboard.Pal
|
||||
{
|
||||
public interface IAppContextExtension : IContextExtensionBase<AppContext>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
7
Dashboard.Common/Pal/IApplicationExtension.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Dashboard.Pal
|
||||
{
|
||||
public interface IApplicationExtension : IContextExtensionBase<Application>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@ -58,12 +58,31 @@ namespace Dashboard.Pal
|
||||
/// </returns>
|
||||
bool ExtensionPreload<T>() where T : TExtension, new();
|
||||
|
||||
/// <summary>
|
||||
/// Preload extensions, to be lazy loaded when required.
|
||||
/// </summary>
|
||||
/// <param name="loader">The loader delegate.</param>
|
||||
/// <typeparam name="T">The extension to preload.</typeparam>
|
||||
/// <returns>
|
||||
/// True if the extension was added to the preload set. Otherwise, already loaded or another extension
|
||||
/// exists which provides this.
|
||||
/// </returns>
|
||||
bool ExtensionPreload<T>(Func<TExtension> loader) where T : TExtension;
|
||||
|
||||
/// <summary>
|
||||
/// Require an extension.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The extension to require.</typeparam>
|
||||
/// <returns>The extension instance.</returns>
|
||||
T ExtensionRequire<T>() where T : TExtension, new();
|
||||
T ExtensionRequire<T>() where T : TExtension;
|
||||
|
||||
/// <summary>
|
||||
/// Load an extension.
|
||||
/// </summary>
|
||||
/// <param name="instance">The extension instance.</param>
|
||||
/// <typeparam name="T">The extension to require.</typeparam>
|
||||
/// <returns>True if the extension was loaded, false if there was already one.</returns>
|
||||
bool ExtensionLoad<T>(T instance) where T : TExtension;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -11,7 +11,7 @@ namespace Dashboard.Windowing
|
||||
/// <summary>
|
||||
/// Interface for classes that implement a window manager.
|
||||
/// </summary>
|
||||
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener, IPaintable, IDisposable
|
||||
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener
|
||||
{
|
||||
/// <summary>
|
||||
/// The physical window that this window manager is associated with.
|
||||
|
||||
@ -2,10 +2,13 @@ namespace Dashboard.Windowing
|
||||
{
|
||||
public interface IEventListener
|
||||
{
|
||||
event EventHandler? EventRaised;
|
||||
|
||||
/// <summary>
|
||||
/// Send an event to this windowing object.
|
||||
/// </summary>
|
||||
/// <param name="sender">The object which generated the event.</param>
|
||||
/// <param name="args">The event arguments sent.</param>
|
||||
void SendEvent(EventArgs args);
|
||||
void SendEvent(object? sender, EventArgs args);
|
||||
}
|
||||
}
|
||||
|
||||
8
Dashboard.Common/Windowing/IForm.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Dashboard.Windowing
|
||||
{
|
||||
public interface IForm : IEventListener, IDisposable
|
||||
{
|
||||
public IWindow Window { get; }
|
||||
public string Title { get; }
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,18 @@
|
||||
using System.Drawing;
|
||||
using Dashboard.Events;
|
||||
using Dashboard.Pal;
|
||||
|
||||
namespace Dashboard.Windowing
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class of all Dashboard windows.
|
||||
/// </summary>
|
||||
public interface IWindow :
|
||||
IPaintable,
|
||||
IDisposable,
|
||||
IAnimationTickEvent,
|
||||
IMouseEvents
|
||||
public interface IWindow : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The application for this window.
|
||||
/// </summary>
|
||||
Application Application { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the window.
|
||||
/// </summary>
|
||||
@ -26,6 +27,23 @@ namespace Dashboard.Windowing
|
||||
/// The size of the window that excludes the window extents.
|
||||
/// </summary>
|
||||
SizeF ClientSize { get; set; }
|
||||
|
||||
IForm? Form { get; set; }
|
||||
|
||||
public event EventHandler? EventRaised;
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to events from this window.
|
||||
/// </summary>
|
||||
/// <param name="listener">The event listener instance.</param>
|
||||
/// <returns>An unsubscription token.</returns>
|
||||
public void SubcribeEvent(IEventListener listener);
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from events in from this window.
|
||||
/// </summary>
|
||||
/// <param name="listener">The event listener to unsubscribe.</param>
|
||||
public void UnsubscribeEvent(IEventListener listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -49,6 +67,7 @@ namespace Dashboard.Windowing
|
||||
/// </summary>
|
||||
public interface IVirtualWindow : IWindow, IEventListener
|
||||
{
|
||||
IWindowManager? WindowManager { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -59,7 +78,7 @@ namespace Dashboard.Windowing
|
||||
/// <summary>
|
||||
/// The device context for this window.
|
||||
/// </summary>
|
||||
IDeviceContext DeviceContext { get; }
|
||||
DeviceContext DeviceContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the window is double buffered.
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BlurgText" Version="0.1.0-nightly-19" />
|
||||
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -24,4 +23,8 @@
|
||||
<EmbeddedResource Include="Executors\text.frag" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Text\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System.Drawing;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using System.Numerics;
|
||||
using Dashboard.OpenGL;
|
||||
using OTK = OpenTK.Mathematics;
|
||||
|
||||
namespace Dashboard.Drawing.OpenGL.Executors
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using BlurgText;
|
||||
using Dashboard.Drawing.OpenGL.Text;
|
||||
using Dashboard.OpenGL;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
@ -11,7 +11,7 @@ namespace Dashboard.Drawing.OpenGL.Executors
|
||||
{
|
||||
public IEnumerable<string> Extensions { get; } = new[] { "DB_Text" };
|
||||
public IContextExecutor Executor { get; private set; }
|
||||
private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager<BlurgEngine>();
|
||||
// private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager<BlurgEngine>();
|
||||
public bool IsInitialized { get; private set; }
|
||||
|
||||
private DrawCallRecorder _recorder;
|
||||
@ -97,7 +97,7 @@ namespace Dashboard.Drawing.OpenGL.Executors
|
||||
private void DrawText(ICommandFrame frame)
|
||||
{
|
||||
TextCommandArgs args = frame.GetParameter<TextCommandArgs>();
|
||||
DbBlurgFont font = Engine.InternFont(args.Font);
|
||||
// DbBlurgFont font = Engine.InternFont(args.Font);
|
||||
|
||||
BlurgColor color;
|
||||
switch (args.TextBrush)
|
||||
@ -116,15 +116,15 @@ namespace Dashboard.Drawing.OpenGL.Executors
|
||||
break;
|
||||
}
|
||||
|
||||
BlurgResult? result = Engine.Blurg.BuildString(font.Font, font.Size, color, args.Text);
|
||||
//BlurgResult? result = Engine.Blurg.BuildString(font.Font, font.Size, color, args.Text);
|
||||
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z);
|
||||
ExecuteBlurgResult(result, position);
|
||||
|
||||
result.Dispose();
|
||||
// if (result == null)
|
||||
// return;
|
||||
//
|
||||
// Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z);
|
||||
// ExecuteBlurgResult(result, position);
|
||||
//
|
||||
// result.Dispose();
|
||||
}
|
||||
|
||||
private void ExecuteBlurgResult(BlurgResult result, Vector3 position)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using Dashboard.Drawing.OpenGL.Text;
|
||||
// using Dashboard.Drawing.OpenGL.Text;
|
||||
using Dashboard.OpenGL;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
@ -21,7 +21,7 @@ namespace Dashboard.Drawing.OpenGL
|
||||
if (bindingsContext != null)
|
||||
GLLoader.LoadBindings(bindingsContext);
|
||||
|
||||
Typesetter.Backend = BlurgEngine.Global;
|
||||
// Typesetter.Backend = BlurgEngine.Global;
|
||||
}
|
||||
|
||||
public ContextExecutor GetExecutor(IGLContext glContext)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Dashboard.Drawing/DrawBuffer.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@ -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])
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@ namespace Dashboard.Drawing
|
||||
{
|
||||
public TextCommand TextCommand { get; }
|
||||
|
||||
private TextExtension() : base("DB_Text", new [] { FontExtension.Instance, BrushExtension.Instance })
|
||||
private TextExtension() : base("DB_Text", new [] { BrushExtension.Instance })
|
||||
{
|
||||
TextCommand = new TextCommand(this);
|
||||
}
|
||||
@ -40,7 +40,7 @@ namespace Dashboard.Drawing
|
||||
|
||||
header = new Header()
|
||||
{
|
||||
Font = queue.RequireResource(obj.Font),
|
||||
// Font = queue.RequireResource(obj.Font),
|
||||
TextBrush = queue.RequireResource(obj.TextBrush),
|
||||
BorderBrush = (obj.BorderBrush != null) ? queue.RequireResource(obj.BorderBrush) : -1,
|
||||
BorderRadius = (obj.BorderBrush != null) ? obj.BorderRadius : 0f,
|
||||
|
||||
@ -20,7 +20,7 @@ namespace Dashboard.Drawing
|
||||
|
||||
IFont LoadFont(Stream stream);
|
||||
IFont LoadFont(string path);
|
||||
IFont LoadFont(NamedFont font);
|
||||
// IFont LoadFont(NamedFont font);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -55,15 +55,16 @@ namespace Dashboard.Drawing
|
||||
return Backend.LoadFont(file.FullName);
|
||||
}
|
||||
|
||||
public static IFont LoadFont(NamedFont font)
|
||||
{
|
||||
return Backend.LoadFont(font);
|
||||
}
|
||||
// public static IFont LoadFont(NamedFont font)
|
||||
// {
|
||||
// return Backend.LoadFont(font);
|
||||
// }
|
||||
|
||||
public static IFont LoadFont(string family, float size, FontWeight weight = FontWeight.Normal,
|
||||
FontSlant slant = FontSlant.Normal, FontStretch stretch = FontStretch.Normal)
|
||||
{
|
||||
return LoadFont(new NamedFont(family, size, weight, slant, stretch));
|
||||
// return LoadFont(new NamedFont(family, size, weight, slant, stretch));
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
private class UndefinedTypeSetter : ITypeSetter
|
||||
@ -94,11 +95,11 @@ namespace Dashboard.Drawing
|
||||
return default;
|
||||
}
|
||||
|
||||
public IFont LoadFont(NamedFont font)
|
||||
{
|
||||
Except();
|
||||
return default;
|
||||
}
|
||||
// public IFont LoadFont(NamedFont font)
|
||||
// {
|
||||
// Except();
|
||||
// return default;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,4 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public class ContextCollector : IDisposable
|
||||
{
|
||||
@ -4,10 +4,19 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
|
||||
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Drawing\immediate.frag" />
|
||||
<EmbeddedResource Include="Drawing\immediate.frag" />
|
||||
<None Remove="Drawing\immediate.vert" />
|
||||
<EmbeddedResource Include="Drawing\immediate.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
97
Dashboard.OpenGL/Drawing/DeviceContextBase.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
228
Dashboard.OpenGL/Drawing/ImmediateMode.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Dashboard.OpenGL/Drawing/immediate.frag
Normal 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);
|
||||
}
|
||||
18
Dashboard.OpenGL/Drawing/immediate.vert
Normal 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;
|
||||
}
|
||||
@ -1,12 +1,14 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using Dashboard.OpenGL;
|
||||
using Dashboard.Drawing;
|
||||
using Dashboard.OpenGL.Drawing;
|
||||
using Dashboard.Pal;
|
||||
using Dashboard.Windowing;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace Dashboard.Drawing.OpenGL.Pal
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
internal class GLContextBindingsContext(IGLContext context) : IBindingsContext
|
||||
{
|
||||
@ -20,6 +22,8 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
{
|
||||
public IGLContext GLContext { get; }
|
||||
|
||||
public ContextCollector Collector { get; } = new ContextCollector();
|
||||
|
||||
public override string DriverName => "Dashboard OpenGL Device Context";
|
||||
public override string DriverVendor => "Dashboard";
|
||||
public override Version DriverVersion => new Version(0, 1, 0);
|
||||
@ -35,7 +39,7 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
private readonly ConcurrentQueue<Task> _beforeDrawActions = new ConcurrentQueue<Task>();
|
||||
private readonly ConcurrentQueue<Task> _afterDrawActions = new ConcurrentQueue<Task>();
|
||||
|
||||
public GLDeviceContext(IGLContext context)
|
||||
public GLDeviceContext(Application app, IWindow? window, IGLContext context) : base(app, window)
|
||||
{
|
||||
GLContext = context;
|
||||
context.MakeCurrent();
|
||||
@ -61,7 +65,9 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
|
||||
Extensions = extensions.ToImmutableHashSet();
|
||||
|
||||
ExtensionPreload<DeviceContextBase>();
|
||||
ExtensionPreload<GLTextureExtension>();
|
||||
ExtensionPreload<ImmediateMode>();
|
||||
}
|
||||
|
||||
public bool IsGLExtensionAvailable(string name)
|
||||
@ -130,6 +136,9 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
base.Begin();
|
||||
|
||||
GLContext.MakeCurrent();
|
||||
IDeviceContextBase dc = ExtensionRequire<IDeviceContextBase>();
|
||||
dc.ResetClip();
|
||||
dc.ResetTransforms();
|
||||
|
||||
while (_beforeDrawActions.TryDequeue(out Task? action))
|
||||
{
|
||||
@ -1,9 +1,10 @@
|
||||
using System.Drawing;
|
||||
using Dashboard.Drawing;
|
||||
using Dashboard.Pal;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OGL = OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace Dashboard.Drawing.OpenGL.Pal
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public class GLTextureExtension : ITextureExtension, IContextExtensionBase<GLDeviceContext>
|
||||
{
|
||||
@ -90,6 +91,11 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
private GLTextureExtension Extension { get; } = extension;
|
||||
private GLDeviceContext Context => Extension.Context;
|
||||
|
||||
~GLTexture()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void SetStorage(PixelFormat format, int width, int height, int depth, int levels)
|
||||
{
|
||||
if (!Context.IsRenderThread)
|
||||
@ -98,6 +104,11 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
return;
|
||||
}
|
||||
|
||||
if (levels == 0)
|
||||
{
|
||||
levels = Math.Max(Math.ILogB(width), Math.ILogB(height));
|
||||
}
|
||||
|
||||
Bind();
|
||||
SizedInternalFormat glFormat = GetFormat(format);
|
||||
if (Extension.SupportsArbTextureStorage)
|
||||
@ -122,18 +133,23 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
switch (Type)
|
||||
{
|
||||
case TextureType.Texture1D:
|
||||
GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero);
|
||||
GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
break;
|
||||
case TextureType.Texture2D:
|
||||
GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero);
|
||||
GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
break;
|
||||
case TextureType.Texture3D:
|
||||
case TextureType.Texture2DArray:
|
||||
case TextureType.Texture2DCube:
|
||||
GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero);
|
||||
GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Depth = depth;
|
||||
Levels = levels;
|
||||
}
|
||||
|
||||
public void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged
|
||||
@ -141,11 +157,19 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public unsafe void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4) where T : unmanaged
|
||||
void ITexture.Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level, int align) => Write(format, buffer, level, align, null);
|
||||
|
||||
public unsafe void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4, TimeSpan? timeout = null) where T : unmanaged
|
||||
{
|
||||
if (!Context.IsRenderThread)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
T[] bufferArray = buffer.ToArray();
|
||||
Task task = Context.InvokeBeforeDraw(() => Write<T>(format, bufferArray, level, align));
|
||||
|
||||
if (timeout.HasValue)
|
||||
task.Wait(timeout.Value);
|
||||
else
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
Bind();
|
||||
@ -160,7 +184,7 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
|
||||
PixelType glType = format switch
|
||||
{
|
||||
PixelFormat.R8I or PixelFormat.Rg8I or PixelFormat.Rgb8I or PixelFormat.Rgba8I => PixelType.Byte,
|
||||
PixelFormat.R8I or PixelFormat.Rg8I or PixelFormat.Rgb8I or PixelFormat.Rgba8I => PixelType.UnsignedByte,
|
||||
PixelFormat.R16F or PixelFormat.Rg16F or PixelFormat.Rgb16F or PixelFormat.Rgba16F => PixelType.HalfFloat,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
@ -207,11 +231,37 @@ namespace Dashboard.Drawing.OpenGL.Pal
|
||||
GL.GenerateMipmap(Target);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (IsDisposed)
|
||||
return;
|
||||
IsDisposed = true;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (Thread.CurrentThread != Context.RendererThread)
|
||||
{
|
||||
Context.Collector.DeleteTexture(Handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.DeleteTexture(Handle);
|
||||
}
|
||||
|
||||
Handle = 0;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.Collector.DeleteTexture(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public void Dispose() => Dispose(false);
|
||||
|
||||
private void Bind()
|
||||
{
|
||||
if (Handle == 0)
|
||||
@ -1,6 +1,6 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public static class ShaderUtil
|
||||
{
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
namespace Dashboard.OpenTK.PAL2
|
||||
{
|
||||
public static class OpenTKEventExtensions
|
||||
{
|
||||
// public static EventArgs ToDashboardEvent(this EventArgs)
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
224
Dashboard.OpenTK/PAL2/Pal2Application.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Drawing;
|
||||
using Dashboard.OpenGL;
|
||||
using Dashboard.Pal;
|
||||
using Dashboard.Windowing;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Platform;
|
||||
@ -8,7 +9,7 @@ using TK = OpenTK.Platform.Toolkit;
|
||||
|
||||
namespace Dashboard.OpenTK.PAL2
|
||||
{
|
||||
public class OpenGLDeviceContext : IGLContext, IGLDisposable
|
||||
public class Pal2GLContext : IGLContext, IDisposable
|
||||
{
|
||||
public OpenGLContextHandle ContextHandle { get; }
|
||||
public WindowHandle WindowHandle { get; }
|
||||
@ -27,12 +28,12 @@ namespace Dashboard.OpenTK.PAL2
|
||||
|
||||
public event Action? Disposed;
|
||||
|
||||
public OpenGLDeviceContext(WindowHandle window, OpenGLContextHandle context, ISwapGroup? group = null)
|
||||
public Pal2GLContext(WindowHandle window, OpenGLContextHandle context)
|
||||
{
|
||||
WindowHandle = window;
|
||||
ContextHandle = context;
|
||||
SwapGroup = group ?? new DummySwapGroup(context);
|
||||
ContextGroup = GetContextGroup(context);
|
||||
SwapGroup = new DummySwapGroup(context);
|
||||
ContextGroup = GetContextGroup(ContextHandle);
|
||||
}
|
||||
|
||||
public void MakeCurrent()
|
||||
@ -45,18 +46,13 @@ namespace Dashboard.OpenTK.PAL2
|
||||
return TK.OpenGL.GetProcedureAddress(ContextHandle, procName);
|
||||
}
|
||||
|
||||
private bool _isDisposed = false;
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
public void Dispose(bool safeExit)
|
||||
protected void Dispose(bool isDisposing)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
|
||||
if (SwapGroup is IGLDisposable glDisposable)
|
||||
{
|
||||
glDisposable.Dispose(safeExit);
|
||||
glDisposable.Dispose(isDisposing);
|
||||
}
|
||||
else if (SwapGroup is IDisposable disposable)
|
||||
{
|
||||
@ -1,7 +1,10 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Drawing;
|
||||
using Dashboard.Drawing;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dashboard.Events;
|
||||
using Dashboard.OpenGL;
|
||||
using Dashboard.Pal;
|
||||
using Dashboard.Windowing;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Platform;
|
||||
@ -10,12 +13,15 @@ using TK = OpenTK.Platform.Toolkit;
|
||||
|
||||
namespace Dashboard.OpenTK.PAL2
|
||||
{
|
||||
public class PhysicalWindow : IPhysicalWindow, IDrawQueuePaintable, IEventListener, IDpiAwareWindow
|
||||
public class PhysicalWindow : IPhysicalWindow, IEventListener, IDpiAwareWindow
|
||||
{
|
||||
public DrawQueue DrawQueue { get; } = new DrawQueue();
|
||||
private readonly List<IEventListener> _listeners = new List<IEventListener>();
|
||||
|
||||
public Application Application { get; }
|
||||
public WindowHandle WindowHandle { get; }
|
||||
public IDeviceContext DeviceContext { get; }
|
||||
public DeviceContext DeviceContext { get; }
|
||||
public bool DoubleBuffered => true; // Always true for OpenTK windows.
|
||||
public IForm? Form { get; set; } = null;
|
||||
|
||||
public IWindowManager? WindowManager { get; set; }
|
||||
|
||||
@ -45,42 +51,40 @@ namespace Dashboard.OpenTK.PAL2
|
||||
set => TK.Window.SetClientSize(WindowHandle, new Vector2i((int)value.Width, (int)value.Height));
|
||||
}
|
||||
|
||||
public event EventHandler? Painting;
|
||||
public event EventHandler<AnimationTickEventArgs>? AnimationTimerEvent;
|
||||
public event EventHandler<MouseMoveEventArgs>? MouseMoved;
|
||||
public event EventHandler<MouseButtonEventArgs>? MouseButtonDown;
|
||||
public event EventHandler<MouseButtonEventArgs>? MouseButtonUp;
|
||||
public event EventHandler<MouseScrollEventArgs>? MouseScroll;
|
||||
public event EventHandler? EventRaised;
|
||||
|
||||
public PhysicalWindow(WindowHandle window, IDeviceContext dc)
|
||||
public PhysicalWindow(Application app, WindowHandle window)
|
||||
{
|
||||
Application = app;
|
||||
WindowHandle = window;
|
||||
DeviceContext = dc;
|
||||
DeviceContext = CreateDeviceContext(app, this, new OpenGLGraphicsApiHints());
|
||||
AddWindow(this);
|
||||
}
|
||||
|
||||
public PhysicalWindow(WindowHandle window, OpenGLContextHandle context, ISwapGroup? swapGroup = null)
|
||||
public PhysicalWindow(Application app, WindowHandle window, OpenGLContextHandle context)
|
||||
{
|
||||
Application = app;
|
||||
WindowHandle = window;
|
||||
DeviceContext = new OpenGLDeviceContext(window, context, swapGroup);
|
||||
DeviceContext = new GLDeviceContext(app, this, new Pal2GLContext(window, context));
|
||||
AddWindow(this);
|
||||
}
|
||||
|
||||
public PhysicalWindow(GraphicsApiHints hints)
|
||||
public PhysicalWindow(Application app, GraphicsApiHints hints)
|
||||
{
|
||||
Application = app;
|
||||
WindowHandle = TK.Window.Create(hints);
|
||||
DeviceContext = CreateDeviceContext(WindowHandle, hints);
|
||||
DeviceContext = CreateDeviceContext(app, this, hints);
|
||||
AddWindow(this);
|
||||
}
|
||||
|
||||
private static IDeviceContext CreateDeviceContext(WindowHandle window, GraphicsApiHints hints)
|
||||
private static DeviceContext CreateDeviceContext(Application app, PhysicalWindow window, GraphicsApiHints hints)
|
||||
{
|
||||
WindowHandle handle = window.WindowHandle;
|
||||
switch (hints.Api)
|
||||
{
|
||||
case GraphicsApi.OpenGL:
|
||||
case GraphicsApi.OpenGLES:
|
||||
OpenGLContextHandle context = TK.OpenGL.CreateFromWindow(window);
|
||||
return new OpenGLDeviceContext(window, context);
|
||||
return new GLDeviceContext(app, window, new Pal2GLContext(handle, TK.OpenGL.CreateFromWindow(handle)));
|
||||
default:
|
||||
throw new Exception($"Unknown graphics API {hints.Api}.");
|
||||
}
|
||||
@ -98,31 +102,50 @@ namespace Dashboard.OpenTK.PAL2
|
||||
TK.Window.Destroy(WindowHandle);
|
||||
}
|
||||
|
||||
protected virtual void OnPaint()
|
||||
public virtual void SendEvent(object? sender, EventArgs args)
|
||||
{
|
||||
WindowManager?.Paint();
|
||||
Painting?.Invoke(this, EventArgs.Empty);
|
||||
switch (args)
|
||||
{
|
||||
case UiEventArgs ui:
|
||||
switch (ui.Type)
|
||||
{
|
||||
case UiEventType.ControlInvalidateVisual:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
args = TransformEvent(sender, args);
|
||||
|
||||
EventRaised?.Invoke(this, args);
|
||||
|
||||
lock (_listeners)
|
||||
{
|
||||
foreach (IEventListener listener in _listeners)
|
||||
listener.SendEvent(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void Paint()
|
||||
private EventArgs TransformEvent(object? sender, EventArgs args)
|
||||
{
|
||||
DrawQueue.Clear();
|
||||
OnPaint();
|
||||
// TODO: future
|
||||
return args;
|
||||
}
|
||||
|
||||
protected virtual void OnAnimationTimerEvent(AnimationTickEventArgs ea) =>
|
||||
AnimationTimerEvent?.Invoke(this, ea);
|
||||
|
||||
protected virtual void OnMouseMoved(MouseMoveEventArgs ea) => MouseMoved?.Invoke(this, ea);
|
||||
|
||||
protected virtual void OnMouseButtonDown(MouseButtonEventArgs ea) => MouseButtonDown?.Invoke(this, ea);
|
||||
|
||||
protected virtual void OnMouseButtonUp(MouseButtonEventArgs ea) => MouseButtonUp?.Invoke(this, ea);
|
||||
|
||||
protected virtual void OnMouseScroll(MouseScrollEventArgs ea) => MouseScroll?.Invoke(this, ea);
|
||||
|
||||
public void SendEvent(EventArgs args)
|
||||
public void SubcribeEvent(IEventListener listener)
|
||||
{
|
||||
lock (_listeners)
|
||||
{
|
||||
_listeners.Add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnsubscribeEvent(IEventListener listener)
|
||||
{
|
||||
lock (_listeners)
|
||||
{
|
||||
_listeners.Remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<WindowHandle, PhysicalWindow> _windows =
|
||||
|
||||
18
Dashboard.StbImage/Dashboard.StbImage.csproj
Normal 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>
|
||||
47
Dashboard.StbImage/StbImageLoader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,6 @@ VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.Drawing", "Dashboard.Drawing\Dashboard.Drawing.csproj", "{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}"
|
||||
@ -17,16 +15,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication",
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Drawing.OpenGL", "Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj", "{454198BA-CB95-41C5-A934-B1C8FDA35A6B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.ImmediateUI", "Dashboard.ImmediateUI\Dashboard.ImmediateUI.csproj", "{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{9B62A92D-ABF5-4704-B831-FD075515A82F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{7B064228-2629-486E-95C6-BDDD4B4602C4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenGL", "Dashboard.OpenGL\Dashboard.OpenGL.csproj", "{33EB657C-B53A-41B4-BC3C-F38C09ABA577}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.StbImage", "Dashboard.StbImage\Dashboard.StbImage.csproj", "{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText.OpenGL", "Dashboard.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj", "{14616F42-663B-4673-8561-5637FAD1B22F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText", "Dashboard.BlurgText\Dashboard.BlurgText.csproj", "{8C68EFB6-B477-48EC-9AAA-31E89883482B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -37,10 +37,6 @@ Global
|
||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -49,14 +45,6 @@ Global
|
||||
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -65,6 +53,18 @@ Global
|
||||
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{14616F42-663B-4673-8561-5637FAD1B22F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{14616F42-663B-4673-8561-5637FAD1B22F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{14616F42-663B-4673-8561-5637FAD1B22F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{14616F42-663B-4673-8561-5637FAD1B22F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -72,5 +72,8 @@ Global
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014} = {9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}
|
||||
{7B064228-2629-486E-95C6-BDDD4B4602C4} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
||||
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
||||
{14616F42-663B-4673-8561-5637FAD1B22F} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
||||
{8C68EFB6-B477-48EC-9AAA-31E89883482B} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@ -1,22 +1,37 @@
|
||||
using System;
|
||||
using Dashboard.Drawing;
|
||||
using Dashboard.Events;
|
||||
using Dashboard.Pal;
|
||||
using Dashboard.Windowing;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public class Control : IEventListener, IDrawQueuePaintable, IDisposable
|
||||
public class Control : IEventListener, IDisposable
|
||||
{
|
||||
private Form? _owner = null;
|
||||
|
||||
public string? Id { get; set; }
|
||||
public ClassSet Classes { get; }
|
||||
public Form? Owner { get; protected set; } = null;
|
||||
public Form Owner
|
||||
{
|
||||
get => _owner ?? throw NoOwnerException;
|
||||
protected set
|
||||
{
|
||||
_owner = value;
|
||||
OnOwnerChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
public Control? Parent { get; private set; } = null;
|
||||
public bool Disposed { get; private set; }
|
||||
public virtual DrawQueue DrawQueue => Owner?.DrawQueue ?? throw NoOwnerException;
|
||||
public virtual Box2d ClientArea { get; set; }
|
||||
public bool IsFocused => _owner?.FocusedControl == this;
|
||||
|
||||
public event EventHandler? Painting;
|
||||
public event EventHandler<DeviceContext> Painting;
|
||||
public event EventHandler<TickEventArgs>? AnimationTick;
|
||||
public event EventHandler? OwnerChanged;
|
||||
public event EventHandler? ParentChanged;
|
||||
public event EventHandler? FocusGained;
|
||||
public event EventHandler? FocusLost;
|
||||
public event EventHandler? Disposing;
|
||||
public event EventHandler? Resized;
|
||||
|
||||
@ -25,14 +40,14 @@ namespace Dashboard.Controls
|
||||
Classes = new ClassSet(this);
|
||||
}
|
||||
|
||||
public virtual void OnPaint()
|
||||
public virtual void OnPaint(DeviceContext dc)
|
||||
{
|
||||
Painting?.Invoke(this, EventArgs.Empty);
|
||||
Painting?.Invoke(this, dc);
|
||||
}
|
||||
|
||||
public void Paint()
|
||||
public virtual void OnAnimationTick(TickEventArgs tick)
|
||||
{
|
||||
OnPaint();
|
||||
AnimationTick?.Invoke(this, tick);
|
||||
}
|
||||
|
||||
protected void InvokeDispose(bool disposing)
|
||||
@ -49,18 +64,74 @@ namespace Dashboard.Controls
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) Disposing?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void Dispose() => InvokeDispose(true);
|
||||
|
||||
public void SendEvent(EventArgs args)
|
||||
public event EventHandler? EventRaised;
|
||||
|
||||
protected virtual void OnEventRaised(object? sender, EventArgs args)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
switch (args)
|
||||
{
|
||||
case PaintEventArgs paint:
|
||||
OnPaint(paint.DeviceContext);
|
||||
break;
|
||||
}
|
||||
|
||||
EventRaised?.Invoke(this, TransformEvent(sender, args));
|
||||
}
|
||||
|
||||
protected virtual EventArgs TransformEvent(object? sender, EventArgs args)
|
||||
{
|
||||
return args;
|
||||
}
|
||||
|
||||
public void SendEvent(object? sender, EventArgs args)
|
||||
{
|
||||
OnEventRaised(sender, args);
|
||||
}
|
||||
|
||||
internal static void SetParent(Control parent, Control child)
|
||||
{
|
||||
child.Parent = parent;
|
||||
child.ParentChanged?.Invoke(child, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public virtual void Focus()
|
||||
{
|
||||
(Owner ?? throw NoOwnerException).Focus(this);
|
||||
}
|
||||
|
||||
protected virtual void OnFocusGained(object sender)
|
||||
{
|
||||
FocusGained?.Invoke(sender, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnFocusLost(object sender)
|
||||
{
|
||||
FocusLost?.Invoke(sender, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal static void InvokeFocusGained(Form form, Control control)
|
||||
{
|
||||
control.OnFocusGained(form);
|
||||
}
|
||||
|
||||
internal static void InvokeFocusLost(Form form, Control control)
|
||||
{
|
||||
control.OnFocusLost(form);
|
||||
}
|
||||
|
||||
protected virtual void OnResize()
|
||||
{
|
||||
Resized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void OnOwnerChanged(Form value)
|
||||
{
|
||||
OwnerChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected static Exception NoOwnerException => new Exception("No form owns this control");
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
using System;
|
||||
using Dashboard.Drawing;
|
||||
using Dashboard.Events;
|
||||
using Dashboard.Pal;
|
||||
using Dashboard.Windowing;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public class Form : Control
|
||||
public class Form : Control, IForm
|
||||
{
|
||||
public IWindow Window { get; }
|
||||
public override DrawQueue DrawQueue { get; }
|
||||
|
||||
public Image? WindowIcon { get; set; }
|
||||
public string? Title { get; set; } = "Untitled Form";
|
||||
public Control? Root { get; set; } = null;
|
||||
public Control? FocusedControl { get; private set; } = null;
|
||||
|
||||
public override Box2d ClientArea
|
||||
{
|
||||
@ -14,10 +21,39 @@ namespace Dashboard.Controls
|
||||
set { }
|
||||
}
|
||||
|
||||
public event EventHandler<WindowCloseEvent>? Closing;
|
||||
|
||||
public Form(IWindow window)
|
||||
{
|
||||
Window = window;
|
||||
DrawQueue = (window as IDrawQueuePaintable)?.DrawQueue ?? new DrawQueue();
|
||||
window.Form = this;
|
||||
}
|
||||
|
||||
public void Focus(Control control)
|
||||
{
|
||||
if (FocusedControl != null)
|
||||
InvokeFocusLost(this, FocusedControl);
|
||||
|
||||
FocusedControl = control;
|
||||
InvokeFocusGained(this, control);
|
||||
}
|
||||
|
||||
public override void OnPaint(DeviceContext dc)
|
||||
{
|
||||
dc.Begin();
|
||||
Root?.SendEvent(this, new PaintEventArgs(dc));
|
||||
dc.End();
|
||||
}
|
||||
|
||||
protected virtual void OnClosing(WindowCloseEvent ea)
|
||||
{
|
||||
Closing?.Invoke(this, ea);
|
||||
|
||||
if (ea.Cancel)
|
||||
return;
|
||||
|
||||
Dispose();
|
||||
Window.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ using System;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using Dashboard.Drawing;
|
||||
using Dashboard.Pal;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
@ -12,7 +13,7 @@ namespace Dashboard.Controls
|
||||
|
||||
public event EventHandler? TextChanged;
|
||||
|
||||
protected IBrush TextBrush => throw new NotImplementedException();
|
||||
// protected IBrush TextBrush => throw new NotImplementedException();
|
||||
protected IFont Font => throw new NotImplementedException();
|
||||
|
||||
protected virtual void OnTextChanged(string oldValue, string newValue)
|
||||
@ -23,15 +24,8 @@ namespace Dashboard.Controls
|
||||
|
||||
protected void CalculateSize()
|
||||
{
|
||||
SizeF sz = Typesetter.MeasureString(Font, Text);
|
||||
ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz);
|
||||
}
|
||||
|
||||
|
||||
public override void OnPaint()
|
||||
{
|
||||
base.OnPaint();
|
||||
DrawQueue.Text(new Vector3(ClientArea.Min, 0), TextBrush, Text, Font);
|
||||
// SizeF sz = Typesetter.MeasureString(Font, Text);
|
||||
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
134
Dashboard/Controls/MessageBox.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
||||
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
|
||||
<EmbeddedResource Include="Resources\**\*.png"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
74
Dashboard/Drawing/Image.cs
Normal 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));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Dashboard/Resources/error.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
85
Dashboard/Resources/error.svg
Normal 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 |
BIN
Dashboard/Resources/info.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
62
Dashboard/Resources/info.svg
Normal 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 |
BIN
Dashboard/Resources/question.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
66
Dashboard/Resources/question.svg
Normal 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 |
BIN
Dashboard/Resources/warning.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
67
Dashboard/Resources/warning.svg
Normal 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 |
@ -8,9 +8,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj" />
|
||||
<ProjectReference Include="..\..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
|
||||
<ProjectReference Include="..\..\Dashboard.ImmediateUI\Dashboard.ImmediateUI.csproj" />
|
||||
<ProjectReference Include="..\..\Dashboard.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj" />
|
||||
<ProjectReference Include="..\..\Dashboard.BlurgText\Dashboard.BlurgText.csproj" />
|
||||
<ProjectReference Include="..\..\Dashboard.OpenTK\Dashboard.OpenTK.csproj" />
|
||||
<ProjectReference Include="..\..\Dashboard.StbImage\Dashboard.StbImage.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
using Dashboard.Drawing;
|
||||
using System.Drawing;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
using Dashboard.Drawing.OpenGL;
|
||||
using Dashboard.ImmediateUI;
|
||||
using BlurgText;
|
||||
using Dashboard.BlurgText;
|
||||
using Dashboard.BlurgText.OpenGL;
|
||||
using Dashboard.Controls;
|
||||
using Dashboard.Drawing;
|
||||
using Dashboard.Events;
|
||||
using Dashboard.OpenGL;
|
||||
using Dashboard.OpenTK.PAL2;
|
||||
using OpenTK.Platform;
|
||||
using Dashboard.Pal;
|
||||
using Dashboard.StbImage;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
using Box2d = Dashboard.Box2d;
|
||||
using OpenTK.Platform;
|
||||
using Image = Dashboard.Drawing.Image;
|
||||
using MouseMoveEventArgs = OpenTK.Platform.MouseMoveEventArgs;
|
||||
using TK = OpenTK.Platform.Toolkit;
|
||||
using Dashboard;
|
||||
using Dashboard.Windowing;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using AppContext = Dashboard.Pal.AppContext;
|
||||
using Vector4 = System.Numerics.Vector4;
|
||||
|
||||
TK.Init(new ToolkitOptions()
|
||||
{
|
||||
@ -25,7 +29,7 @@ TK.Init(new ToolkitOptions()
|
||||
FeatureFlags = ToolkitFlags.EnableOpenGL,
|
||||
});
|
||||
|
||||
AppContext app = new Pal2AppContext()
|
||||
Application app = new Pal2Application()
|
||||
{
|
||||
GraphicsApiHints = new OpenGLGraphicsApiHints()
|
||||
{
|
||||
@ -48,19 +52,19 @@ AppContext app = new Pal2AppContext()
|
||||
};
|
||||
|
||||
PhysicalWindow window;
|
||||
SolidBrush fg = new SolidBrush(Color.FromArgb(0, 0, 0, 0));
|
||||
SolidBrush bg = new SolidBrush(Color.Black);
|
||||
CancellationTokenSource source = new CancellationTokenSource();
|
||||
GLEngine engine;
|
||||
ContextExecutor executor;
|
||||
DimUI dimUI;
|
||||
// GLEngine engine;
|
||||
// ContextExecutor executor;
|
||||
// DimUI dimUI;
|
||||
Vector2 mousePos = Vector2.Zero;
|
||||
Random r = new Random();
|
||||
List<Vector3> points = new List<Vector3>();
|
||||
IFont font;
|
||||
// IFont font;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
app.Initialize();
|
||||
app.ExtensionRequire<StbImageLoader>();
|
||||
app.ExtensionLoad(new BlurgTextExtension(new BlurgTextExtensionFactory()));
|
||||
|
||||
window = (PhysicalWindow)app.CreatePhysicalWindow();
|
||||
|
||||
@ -70,20 +74,20 @@ TK.Window.SetClientSize(window.WindowHandle, new Vector2i(320, 240));
|
||||
TK.Window.SetBorderStyle(window.WindowHandle, WindowBorderStyle.ResizableBorder);
|
||||
// TK.Window.SetTransparencyMode(wnd, WindowTransparencyMode.TransparentFramebuffer, 0.1f);
|
||||
|
||||
OpenGLDeviceContext context = (OpenGLDeviceContext)window.DeviceContext;
|
||||
GLDeviceContext context = (GLDeviceContext)window.DeviceContext;
|
||||
|
||||
context.MakeCurrent();
|
||||
context.SwapGroup.SwapInterval = 1;
|
||||
context.GLContext.MakeCurrent();
|
||||
context.GLContext.SwapGroup.SwapInterval = 1;
|
||||
|
||||
engine = new GLEngine();
|
||||
engine.Initialize();
|
||||
|
||||
executor = engine.GetExecutor(context);
|
||||
|
||||
dimUI = new DimUI(new DimUIConfig()
|
||||
{
|
||||
Font = new NamedFont("Noto Sans", 9f),
|
||||
});
|
||||
// engine = new GLEngine();
|
||||
// engine.Initialize();
|
||||
//
|
||||
// executor = engine.GetExecutor(context);
|
||||
//
|
||||
// dimUI = new DimUI(new DimUIConfig()
|
||||
// {
|
||||
// Font = new NamedFont("Noto Sans", 9f),
|
||||
// });
|
||||
|
||||
EventQueue.EventRaised += (handle, type, eventArgs) =>
|
||||
{
|
||||
@ -102,66 +106,74 @@ EventQueue.EventRaised += (handle, type, eventArgs) =>
|
||||
};
|
||||
|
||||
TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
|
||||
font = Typesetter.LoadFont("Nimbus Mono", 12f);
|
||||
// font = Typesetter.LoadFont("Nimbus Mono", 12f);
|
||||
|
||||
window.Painting += (sender, ea) => {
|
||||
TK.Window.GetSize(context.WindowHandle, out Vector2i size);
|
||||
executor.BeginFrame();
|
||||
foreach (string str in typeof(Image).Assembly.GetManifestResourceNames()) Console.WriteLine(str);
|
||||
|
||||
dimUI.Begin(new Box2d(0, 0, size.X, size.Y), window.DrawQueue);
|
||||
dimUI.Text("Hello World!");
|
||||
dimUI.Button("Cancel"); dimUI.SameLine();
|
||||
dimUI.Button("OK");
|
||||
BlurgFont font = context.ExtensionRequire<BlurgDcExtension>().Blurg
|
||||
.QueryFont("Recursive Mono", FontWeight.Regular, false) ?? throw new Exception("Font not found");
|
||||
|
||||
dimUI.Input("type me!", builder);
|
||||
window.EventRaised += (sender, ea) => {
|
||||
if (ea is not PaintEventArgs)
|
||||
return;
|
||||
|
||||
dimUI.BeginMenu();
|
||||
|
||||
if (dimUI.MenuItem("File"))
|
||||
{
|
||||
dimUI.BeginMenu();
|
||||
dimUI.MenuItem("New Window");
|
||||
dimUI.MenuItem("Preferences");
|
||||
dimUI.MenuItem("Exit");
|
||||
dimUI.EndMenu();
|
||||
}
|
||||
|
||||
if (dimUI.MenuItem("Edit"))
|
||||
{
|
||||
dimUI.BeginMenu();
|
||||
dimUI.MenuItem("Cut");
|
||||
dimUI.MenuItem("Copy");
|
||||
dimUI.MenuItem("Paste");
|
||||
|
||||
if (dimUI.MenuItem("Send Char"))
|
||||
{
|
||||
dimUI.BeginMenu();
|
||||
dimUI.EndMenu();
|
||||
}
|
||||
|
||||
dimUI.EndMenu();
|
||||
}
|
||||
|
||||
if (dimUI.MenuItem("View"))
|
||||
{
|
||||
dimUI.BeginMenu();
|
||||
dimUI.MenuItem("Clear");
|
||||
|
||||
if (dimUI.MenuItem("Set Size"))
|
||||
{
|
||||
dimUI.BeginMenu();
|
||||
dimUI.MenuItem("24 x 40");
|
||||
dimUI.MenuItem("25 x 40");
|
||||
dimUI.MenuItem("24 x 80");
|
||||
dimUI.MenuItem("25 x 80");
|
||||
dimUI.MenuItem("25 x 120");
|
||||
dimUI.EndMenu();
|
||||
}
|
||||
|
||||
dimUI.EndMenu();
|
||||
}
|
||||
|
||||
dimUI.Finish();
|
||||
TK.Window.GetSize(window.WindowHandle, out Vector2i size);
|
||||
// executor.BeginFrame();
|
||||
//
|
||||
// dimUI.Begin(new Box2d(0, 0, size.X, size.Y), window.DrawQueue);
|
||||
// dimUI.Text("Hello World!");
|
||||
// dimUI.Button("Cancel"); dimUI.SameLine();
|
||||
// dimUI.Button("OK");
|
||||
//
|
||||
// dimUI.Input("type me!", builder);
|
||||
//
|
||||
// dimUI.BeginMenu();
|
||||
//
|
||||
// if (dimUI.MenuItem("File"))
|
||||
// {
|
||||
// dimUI.BeginMenu();
|
||||
// dimUI.MenuItem("New Window");
|
||||
// dimUI.MenuItem("Preferences");
|
||||
// dimUI.MenuItem("Exit");
|
||||
// dimUI.EndMenu();
|
||||
// }
|
||||
//
|
||||
// if (dimUI.MenuItem("Edit"))
|
||||
// {
|
||||
// dimUI.BeginMenu();
|
||||
// dimUI.MenuItem("Cut");
|
||||
// dimUI.MenuItem("Copy");
|
||||
// dimUI.MenuItem("Paste");
|
||||
//
|
||||
// if (dimUI.MenuItem("Send Char"))
|
||||
// {
|
||||
// dimUI.BeginMenu();
|
||||
// dimUI.EndMenu();
|
||||
// }
|
||||
//
|
||||
// dimUI.EndMenu();
|
||||
// }
|
||||
//
|
||||
// if (dimUI.MenuItem("View"))
|
||||
// {
|
||||
// dimUI.BeginMenu();
|
||||
// dimUI.MenuItem("Clear");
|
||||
//
|
||||
// if (dimUI.MenuItem("Set Size"))
|
||||
// {
|
||||
// dimUI.BeginMenu();
|
||||
// dimUI.MenuItem("24 x 40");
|
||||
// dimUI.MenuItem("25 x 40");
|
||||
// dimUI.MenuItem("24 x 80");
|
||||
// dimUI.MenuItem("25 x 80");
|
||||
// dimUI.MenuItem("25 x 120");
|
||||
// dimUI.EndMenu();
|
||||
// }
|
||||
//
|
||||
// dimUI.EndMenu();
|
||||
// }
|
||||
//
|
||||
// dimUI.Finish();
|
||||
|
||||
GL.Viewport(0, 0, size.X, size.Y);
|
||||
GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f);
|
||||
@ -171,10 +183,30 @@ window.Painting += (sender, ea) => {
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
GL.ColorMask(true, true, true, true);
|
||||
|
||||
executor.Draw(window.DrawQueue, new RectangleF(0, 0, size.X, size.Y), 1.5f /*(window as IDpiAwareWindow)?.Scale ?? 1*/);
|
||||
executor.EndFrame();
|
||||
ITexture texture = MessageBox.s_questionIcon.InternTexture(context);
|
||||
|
||||
context.SwapGroup.Swap();
|
||||
// executor.Draw(window.DrawQueue, new RectangleF(0, 0, size.X, size.Y), 1.5f /*(window as IDpiAwareWindow)?.Scale ?? 1*/);
|
||||
// executor.EndFrame();
|
||||
|
||||
context.Begin();
|
||||
|
||||
IImmediateMode imm = context.ExtensionRequire<IImmediateMode>();
|
||||
imm.ClearColor(Color.Magenta);
|
||||
imm.Line(new System.Numerics.Vector2(10, 10), new System.Numerics.Vector2(50, 50), 3, 0, new Vector4(0.2f, 0.2f, 1.0f, 1.0f));
|
||||
|
||||
BlurgDcExtension blurg = context.ExtensionRequire<BlurgDcExtension>();
|
||||
blurg.DrawBlurgFormattedText(new BlurgFormattedText("Hello world!", font) { DefaultSize = 64f }, new System.Numerics.Vector3(24, 24, 1));
|
||||
|
||||
context.End();
|
||||
};
|
||||
|
||||
app.Run(true, source.Token);
|
||||
|
||||
|
||||
class BlurgTextExtensionFactory : IBlurgDcExtensionFactory
|
||||
{
|
||||
public BlurgDcExtension CreateExtension(BlurgTextExtension appExtension, DeviceContext dc)
|
||||
{
|
||||
return new BlurgGLExtension();
|
||||
}
|
||||
};
|
||||
|
||||