Compare commits
14 Commits
1dcf167022
...
dashboard2
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c3141822a | |||
| e30e50e860 | |||
| 4f67e0fb75 | |||
| 5cba1ab7db | |||
| 9ca309bd52 | |||
| 043060db66 | |||
| 66c5eecc26 | |||
| 9ee6c1180d | |||
| c4fe4841fe | |||
| 2932b3b85e | |||
| 6e8888df48 | |||
| edc85c3f24 | |||
| 50eda46b13 | |||
| c538dbd56b |
235
Dashboard.BlurgText.OpenGL/BlurgGLExtension.cs
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
210
Dashboard.BlurgText/BlurgTextExtension.cs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
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) : 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;
|
||||||
|
_blurg.EnableSystemFonts();
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ITextRenderer
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Dispose();
|
||||||
|
|
||||||
|
IContextBase IContextExtensionBase.Context => Context;
|
||||||
|
|
||||||
|
public void Require(DeviceContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IContextExtensionBase.Require(IContextBase context) => Require((DeviceContext)context);
|
||||||
|
|
||||||
|
public Box2d MeasureText(IFont font, float size, string text)
|
||||||
|
{
|
||||||
|
BlurgFontProxy proxy = InternFont(font);
|
||||||
|
Vector2 sz = Blurg.MeasureString(proxy.Font, size * Context.ExtensionRequire<IDeviceContextBase>().Scale, text);
|
||||||
|
return new Box2d(0, 0, sz.X, sz.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawText(Vector2 position, Vector4 color, float size, IFont font, string text)
|
||||||
|
{
|
||||||
|
BlurgFontProxy proxy = InternFont(font);
|
||||||
|
BlurgResult? result = Blurg.BuildString(proxy.Font, size * Context.ExtensionRequire<IDeviceContextBase>().Scale,
|
||||||
|
new BlurgColor((byte)(color.X * 255), (byte)(color.Y * 255), (byte)(color.Z * 255), (byte)(color.W * 255)), text);
|
||||||
|
DrawBlurgResult(result, new Vector3(position, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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>
|
||||||
157
Dashboard.Common/Collections/TypeDictionary.cs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Dashboard.Collections
|
||||||
|
{
|
||||||
|
public class TypeDictionary<T>(bool hierarchical = false) : IEnumerable<T>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, T> _objects = new Dictionary<int, T>();
|
||||||
|
|
||||||
|
public bool Contains<T2>() where T2 : T => _objects.ContainsKey(TypeAtom<T2>.Id);
|
||||||
|
|
||||||
|
public bool Add<T2>(T2 value) where T2 : T
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<T2>.Atom;
|
||||||
|
|
||||||
|
if (!_objects.TryAdd(atom.Id, value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!hierarchical)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects[ancestor.Id] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T2 Get<T2>() where T2 : T => TryGet(out T2? value) ? value : throw new KeyNotFoundException();
|
||||||
|
|
||||||
|
public void Set<T2>(T2 value) where T2 : T
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<T2>.Atom;
|
||||||
|
_objects[atom.Id] = value;
|
||||||
|
|
||||||
|
if (!hierarchical)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects[ancestor.Id] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove<T2>() where T2 : T => Remove<T>(out _);
|
||||||
|
|
||||||
|
public bool Remove<T2>([NotNullWhen(true)] out T2? value) where T2 : T
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<T2>.Atom;
|
||||||
|
|
||||||
|
if (!_objects.Remove(atom.Id, out T? subValue))
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (T2?)subValue;
|
||||||
|
|
||||||
|
if (hierarchical)
|
||||||
|
{
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects.Remove(ancestor.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet<T2>([NotNullWhen(true)] out T2? value) where T2 : T
|
||||||
|
{
|
||||||
|
if (!_objects.TryGetValue(TypeAtom<T2>.Id, out T? subValue))
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (T2?)subValue;
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => _objects.Clear();
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() => _objects.Values.Distinct().GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeDictionary<TKey, TValue>(bool hierarchical = false) : IEnumerable<KeyValuePair<TypeAtom, TValue>>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, TValue> _objects = new Dictionary<int, TValue>();
|
||||||
|
|
||||||
|
public bool Contains<TKey2>() where TKey2 : TKey => _objects.ContainsKey(TypeAtom<TKey2>.Id);
|
||||||
|
|
||||||
|
public bool Add<TKey2>(TValue value) where TKey2 : TKey
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<TKey2>.Atom;
|
||||||
|
|
||||||
|
if (!_objects.TryAdd(atom.Id, value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!hierarchical)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects[ancestor.Id] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue Get<TKey2>() where TKey2 : TKey => (TValue)_objects[TypeAtom<TKey2>.Id]!;
|
||||||
|
|
||||||
|
public void Set<TKey2>(TValue value) where TKey2 : TKey
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<TKey2>.Atom;
|
||||||
|
_objects[atom.Id] = value;
|
||||||
|
|
||||||
|
if (!hierarchical)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects[ancestor.Id] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove<TKey2>() where TKey2 : TKey => Remove<TKey2>(out _);
|
||||||
|
public bool Remove<TKey2>([MaybeNullWhen(false)] out TValue? value) where TKey2 : TKey
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<TKey2>.Atom;
|
||||||
|
|
||||||
|
if (!_objects.Remove(atom.Id, out value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hierarchical)
|
||||||
|
{
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects.Remove(ancestor.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet<TKey2>([NotNullWhen(true)] out TValue? value) where TKey2 : TKey => _objects.TryGetValue(TypeAtom<TKey2>.Id, out value);
|
||||||
|
|
||||||
|
public void Clear() => _objects.Clear();
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<TypeAtom, TValue>> GetEnumerator() => _objects.Select(x => new KeyValuePair<TypeAtom, TValue>(TypeAtom.Get(x.Key)!, x.Value)).GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Dashboard.Common/Drawing/Brush.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public abstract class Brush
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SolidColorBrush(Color color) : Brush
|
||||||
|
{
|
||||||
|
public Color Color { get; } = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageBrush(Image image) : Brush
|
||||||
|
{
|
||||||
|
public Box2d TextureCoordinates { get; set; } = new Box2d(0, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NinePatchImageBrush(Image image) : Brush
|
||||||
|
{
|
||||||
|
public Box2d CenterCoordinates { get; set; } = new Box2d(0, 0, 1, 1);
|
||||||
|
public Vector4 Extents { get; set; } = Vector4.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Dashboard.Common/Drawing/Font.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public class Font(IFont iFont) : IFont
|
||||||
|
{
|
||||||
|
public IFont Base => iFont;
|
||||||
|
public string Family => iFont.Family;
|
||||||
|
public FontWeight Weight => iFont.Weight;
|
||||||
|
public FontSlant Slant => iFont.Slant;
|
||||||
|
public FontStretch Stretch => iFont.Stretch;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
iFont.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font Create(Stream stream)
|
||||||
|
{
|
||||||
|
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(stream);
|
||||||
|
|
||||||
|
return new Font(iFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font Create(FontInfo info)
|
||||||
|
{
|
||||||
|
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(info);
|
||||||
|
|
||||||
|
return new Font(iFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font Create(string path)
|
||||||
|
{
|
||||||
|
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(path);
|
||||||
|
|
||||||
|
return new Font(iFont);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Dashboard.Common/Drawing/IDeviceContextBase.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public interface IDeviceContextBase : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
Box2d ClipRegion { get; }
|
||||||
|
Box2d ScissorRegion { get; }
|
||||||
|
Matrix4x4 Transforms { get; }
|
||||||
|
float Scale { get; }
|
||||||
|
float ScaleOverride { get; set; }
|
||||||
|
|
||||||
|
void ResetClip();
|
||||||
|
void PushClip(Box2d clipRegion);
|
||||||
|
void PopClip();
|
||||||
|
|
||||||
|
void ResetScissor();
|
||||||
|
void PushScissor(Box2d scissorRegion);
|
||||||
|
void PopScissor();
|
||||||
|
|
||||||
|
void ResetTransforms();
|
||||||
|
void PushTransforms(in Matrix4x4 matrix);
|
||||||
|
void PopTransforms();
|
||||||
|
|
||||||
|
void ClearColor(Color color);
|
||||||
|
void ClearDepth();
|
||||||
|
|
||||||
|
int IncrementZ();
|
||||||
|
int DecrementZ();
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Dashboard.Common/Drawing/IFontLoader.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
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 : IApplicationExtension
|
||||||
|
{
|
||||||
|
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.Layout;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public record struct RectangleDrawInfo(Vector2 Position, ComputedBox Box, Brush Fill, Brush? Border = null);
|
||||||
|
|
||||||
|
public interface IImmediateMode : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color);
|
||||||
|
void Rectangle(Box2d rectangle, float depth, Vector4 color);
|
||||||
|
void Rectangle(in RectangleDrawInfo rectangle);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
84
Dashboard.Common/Drawing/ITextureExtension.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public interface ITextureExtension : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
ITexture CreateTexture(TextureType type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TextureType
|
||||||
|
{
|
||||||
|
Texture1D,
|
||||||
|
Texture2D,
|
||||||
|
Texture2DArray,
|
||||||
|
Texture2DCube,
|
||||||
|
Texture3D,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TextureFilter
|
||||||
|
{
|
||||||
|
Nearest,
|
||||||
|
Linear,
|
||||||
|
NearestMipmapNearest,
|
||||||
|
LinearMipmapNearest,
|
||||||
|
NearestMipmapLinear,
|
||||||
|
LinearMipmapLinear,
|
||||||
|
Anisotropic,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TextureRepeat
|
||||||
|
{
|
||||||
|
Repeat,
|
||||||
|
MirroredRepeat,
|
||||||
|
ClampToEdge,
|
||||||
|
ClampToBorder,
|
||||||
|
MirrorClampToEdge,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CubeMapFace
|
||||||
|
{
|
||||||
|
PositiveX,
|
||||||
|
PositiveY,
|
||||||
|
PositiveZ,
|
||||||
|
NegativeX,
|
||||||
|
NegativeY,
|
||||||
|
NegativeZ,
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITexture : IDisposable
|
||||||
|
{
|
||||||
|
public TextureType Type { get; }
|
||||||
|
public PixelFormat Format { get; }
|
||||||
|
|
||||||
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
public int Depth { get; }
|
||||||
|
public int Levels { get; }
|
||||||
|
|
||||||
|
public bool Premultiplied { get; set; }
|
||||||
|
|
||||||
|
public ColorSwizzle Swizzle { get; set; }
|
||||||
|
|
||||||
|
public TextureFilter MinifyFilter { get; set; }
|
||||||
|
|
||||||
|
public TextureFilter MagnifyFilter { get; set; }
|
||||||
|
|
||||||
|
public Color BorderColor { get; set; }
|
||||||
|
|
||||||
|
public TextureRepeat RepeatS { get; set; }
|
||||||
|
|
||||||
|
public TextureRepeat RepeatT { get; set; }
|
||||||
|
|
||||||
|
public TextureRepeat RepeatR { get; set; }
|
||||||
|
public int Anisotropy { get; set; }
|
||||||
|
|
||||||
|
void SetStorage(PixelFormat format, int width, int height, int depth, int levels);
|
||||||
|
void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged;
|
||||||
|
void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4) where T : unmanaged;
|
||||||
|
void Premultiply();
|
||||||
|
void Unmultiply();
|
||||||
|
void GenerateMipmaps();
|
||||||
|
}
|
||||||
|
}
|
||||||
72
Dashboard.Common/Drawing/Image.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
59
Dashboard.Common/Events/KeyboardEvents.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TextInputEventArgs(string text) : UiEventArgs(UiEventType.TextEdit)
|
||||||
|
{
|
||||||
|
public string Text { get; } = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TextEditEventArgs(string candidate, int cursor, int length) : UiEventArgs(UiEventType.TextEdit)
|
||||||
|
{
|
||||||
|
public string Candidate { get; } = candidate;
|
||||||
|
public int Cursor { get; } = cursor;
|
||||||
|
public int Length { get; } = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,50 +20,24 @@ namespace Dashboard.Events
|
|||||||
Middle = M3,
|
Middle = M3,
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MouseMoveEventArgs : UiEventArgs
|
public sealed class MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) : UiEventArgs(UiEventType.MouseMove)
|
||||||
{
|
{
|
||||||
public Vector2 ClientPosition { get; }
|
public Vector2 ClientPosition { get; } = clientPosition;
|
||||||
public Vector2 Delta { get; }
|
public Vector2 Delta { get; } = delta;
|
||||||
|
|
||||||
public MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta)
|
|
||||||
: base(UiEventType.MouseMove)
|
|
||||||
{
|
|
||||||
ClientPosition = clientPosition;
|
|
||||||
Delta = delta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MouseButtonEventArgs : UiEventArgs
|
public sealed class MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, ModifierKeys modifierKeys, bool up)
|
||||||
|
: UiEventArgs(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
|
||||||
{
|
{
|
||||||
public Vector2 ClientPosition { get; }
|
public ModifierKeys ModifierKeys { get; } = modifierKeys;
|
||||||
public MouseButtons Buttons { get; }
|
public Vector2 ClientPosition { get; } = clientPosition;
|
||||||
|
public MouseButtons Buttons { get; } = buttons;
|
||||||
public MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, bool up)
|
|
||||||
: base(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
|
|
||||||
{
|
|
||||||
ClientPosition = clientPosition;
|
|
||||||
Buttons = buttons;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MouseScrollEventArgs : UiEventArgs
|
public sealed class MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
|
||||||
|
: UiEventArgs(UiEventType.MouseScroll)
|
||||||
{
|
{
|
||||||
public Vector2 ClientPosition { get; }
|
public Vector2 ClientPosition { get; } = clientPosition;
|
||||||
public Vector2 ScrollDelta { get; }
|
public Vector2 ScrollDelta { get; } = scrollDelta;
|
||||||
|
|
||||||
public MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
|
|
||||||
: base(UiEventType.MouseScroll)
|
|
||||||
{
|
|
||||||
ClientPosition = clientPosition;
|
|
||||||
ScrollDelta = scrollDelta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IMouseEvents
|
|
||||||
{
|
|
||||||
event EventHandler<MouseMoveEventArgs> MouseMoved;
|
|
||||||
event EventHandler<MouseButtonEventArgs> MouseButtonDown;
|
|
||||||
event EventHandler<MouseButtonEventArgs> MouseButtonUp;
|
|
||||||
event EventHandler<MouseScrollEventArgs> MouseScroll;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 System.Numerics;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
namespace Dashboard.Events
|
namespace Dashboard.Events
|
||||||
{
|
{
|
||||||
@@ -6,6 +7,7 @@ namespace Dashboard.Events
|
|||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
AnimationTick, // Generic timer event.
|
AnimationTick, // Generic timer event.
|
||||||
|
Paint, // Generic paint event.
|
||||||
|
|
||||||
// Text input related events.
|
// Text input related events.
|
||||||
KeyDown, // Keyboard key down.
|
KeyDown, // Keyboard key down.
|
||||||
@@ -57,6 +59,11 @@ namespace Dashboard.Events
|
|||||||
public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None);
|
public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PaintEventArgs(DeviceContext dc) : UiEventArgs(UiEventType.Paint)
|
||||||
|
{
|
||||||
|
public DeviceContext DeviceContext { get; } = dc;
|
||||||
|
}
|
||||||
|
|
||||||
public class ControlMovedEventArgs : UiEventArgs
|
public class ControlMovedEventArgs : UiEventArgs
|
||||||
{
|
{
|
||||||
public Vector2 OldPosition { get; }
|
public Vector2 OldPosition { get; }
|
||||||
@@ -69,8 +76,7 @@ namespace Dashboard.Events
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ControlResizedEventArgs
|
public class ResizeEventArgs() : UiEventArgs(UiEventType.ControlResized)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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,
|
_400 = 400,
|
||||||
_500 = 500,
|
_500 = 500,
|
||||||
_600 = 600,
|
_600 = 600,
|
||||||
|
_700 = 700,
|
||||||
_800 = 800,
|
_800 = 800,
|
||||||
_900 = 900,
|
_900 = 900,
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,31 @@ namespace Dashboard
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pixel format for images.
|
/// Pixel format for images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
public enum PixelFormat
|
public enum PixelFormat
|
||||||
{
|
{
|
||||||
R8I,
|
None = 0,
|
||||||
Rg8I,
|
|
||||||
Rgb8I,
|
R8I = R | I8,
|
||||||
Rgba8I,
|
Rg8I = Rg | I8,
|
||||||
R16F,
|
Rgb8I = Rgb | I8,
|
||||||
Rg816F,
|
Rgba8I = Rgba | I8,
|
||||||
Rgb16F,
|
R16F = R | F16,
|
||||||
Rgba16F,
|
Rg16F = Rg | F16,
|
||||||
|
Rgb16F = Rgb | F16,
|
||||||
|
Rgba16F = Rgba | F16,
|
||||||
|
|
||||||
|
// Channels
|
||||||
|
R = 0x01,
|
||||||
|
Rg = 0x02,
|
||||||
|
Rgb = 0x03,
|
||||||
|
Rgba = 0x04,
|
||||||
|
A = 0x05,
|
||||||
|
ColorMask = 0x0F,
|
||||||
|
|
||||||
|
I8 = 0x10,
|
||||||
|
F16 = 0x20,
|
||||||
|
TypeMask = 0xF0,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
18
Dashboard.Common/Layout/ILayoutItem.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public interface ILayoutItem : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public LayoutInfo Layout { get; }
|
||||||
|
|
||||||
|
public Vector2 CalculateIntrinsicSize();
|
||||||
|
public Vector2 CalculateSize(Vector2 limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ILayoutContainer : ILayoutItem, IEnumerable<ILayoutItem>
|
||||||
|
{
|
||||||
|
public ContainerLayoutInfo ContainerLayout { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
435
Dashboard.Common/Layout/LayoutBox.cs
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public readonly record struct ComputedBox(Vector4 Margin, Vector4 Padding, Vector4 Border, Vector2 Size)
|
||||||
|
{
|
||||||
|
public float MarginLeft => Margin.X;
|
||||||
|
public float MarginTop => Margin.Y;
|
||||||
|
public float MarginRight => Margin.Z;
|
||||||
|
public float MarginBottom => Margin.W;
|
||||||
|
|
||||||
|
public float PaddingLeft => Padding.X;
|
||||||
|
public float PaddingTop => Padding.Y;
|
||||||
|
public float PaddingRight => Padding.Z;
|
||||||
|
public float PaddingBottom => Padding.W;
|
||||||
|
|
||||||
|
public float BorderLeft => Border.X;
|
||||||
|
public float BorderTop => Border.Y;
|
||||||
|
public float BorderRight => Border.Z;
|
||||||
|
public float BorderBottom => Border.W;
|
||||||
|
|
||||||
|
public float Width => Size.X;
|
||||||
|
public float Height => Size.Y;
|
||||||
|
|
||||||
|
public Vector2 BoundingSize => new Vector2(
|
||||||
|
MarginLeft + BorderLeft + Width + BorderRight + MarginRight,
|
||||||
|
MarginTop + BorderTop + Height + BorderBottom + MarginBottom);
|
||||||
|
|
||||||
|
public Vector2 ContentSize => new Vector2(
|
||||||
|
Width - PaddingLeft - PaddingRight,
|
||||||
|
Height - PaddingTop - PaddingBottom);
|
||||||
|
|
||||||
|
public Vector4 ContentExtents => new Vector4(
|
||||||
|
PaddingLeft,
|
||||||
|
PaddingTop,
|
||||||
|
PaddingLeft + PaddingRight + Width,
|
||||||
|
PaddingTop + PaddingBottom + Height);
|
||||||
|
|
||||||
|
public Vector4 CornerRadii { get; init; } = Vector4.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LayoutBox : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private Vector4 _margin = Vector4.Zero;
|
||||||
|
private Vector4 _padding = Vector4.Zero;
|
||||||
|
private Vector4 _border = Vector4.Zero;
|
||||||
|
private Vector2 _size = Vector2.Zero;
|
||||||
|
private Vector2 _minimumSize = -Vector2.One;
|
||||||
|
private Vector2 _maximumSize = -Vector2.One;
|
||||||
|
private Vector4 _cornerRadii = Vector4.Zero;
|
||||||
|
|
||||||
|
private LayoutUnit _marginLeftUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _marginTopUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _marginRightUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _marginBottomUnit = LayoutUnit.Pixel;
|
||||||
|
|
||||||
|
private LayoutUnit _paddingLeftUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _paddingTopUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _paddingRightUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _paddingBottomUnit = LayoutUnit.Pixel;
|
||||||
|
|
||||||
|
private LayoutUnit _borderLeftUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _borderTopUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _borderRightUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _borderBottomUnit = LayoutUnit.Pixel;
|
||||||
|
|
||||||
|
private LayoutUnit _widthUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _heightUnit = LayoutUnit.Pixel;
|
||||||
|
|
||||||
|
private LayoutUnit _minimumWidthUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _minimumHeightUnit = LayoutUnit.Pixel;
|
||||||
|
|
||||||
|
private LayoutUnit _maximumWidthUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _maximumHeightUnit = LayoutUnit.Pixel;
|
||||||
|
|
||||||
|
private LayoutUnit _cornerRadiusTopLeftUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _cornerRadiusBottomLeftUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _cornerRadiusBottomRightUnit = LayoutUnit.Pixel;
|
||||||
|
private LayoutUnit _cornerRadiusTopRightUnit = LayoutUnit.Pixel;
|
||||||
|
private ComputedBox _computedBox = new ComputedBox();
|
||||||
|
|
||||||
|
public bool UpdateRequired { get; private set; } = true;
|
||||||
|
|
||||||
|
public Vector4 Margin
|
||||||
|
{
|
||||||
|
get => _margin;
|
||||||
|
set => SetField(ref _margin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector4 Padding
|
||||||
|
{
|
||||||
|
get => _padding;
|
||||||
|
set => SetField(ref _padding, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector4 Border
|
||||||
|
{
|
||||||
|
get => _border;
|
||||||
|
set => SetField(ref _border, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 Size
|
||||||
|
{
|
||||||
|
get => _size;
|
||||||
|
set => SetField(ref _size, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 MinimumSize
|
||||||
|
{
|
||||||
|
get => _minimumSize;
|
||||||
|
set => SetField(ref _minimumSize, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 MaximumSize
|
||||||
|
{
|
||||||
|
get => _maximumSize;
|
||||||
|
set => SetField(ref _maximumSize, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector4 CornerRadii
|
||||||
|
{
|
||||||
|
get => _cornerRadii;
|
||||||
|
set => SetField(ref _cornerRadii, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MarginLeft
|
||||||
|
{
|
||||||
|
get => Margin.X;
|
||||||
|
set => Margin = Margin with { X = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MarginTop
|
||||||
|
{
|
||||||
|
get => Margin.Y;
|
||||||
|
set => Margin = Margin with { Y = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MarginRight
|
||||||
|
{
|
||||||
|
get => Margin.Z;
|
||||||
|
set => Margin = Margin with { Z = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MarginBottom
|
||||||
|
{
|
||||||
|
get => Margin.W;
|
||||||
|
set => Margin = Margin with { W = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float PaddingLeft
|
||||||
|
{
|
||||||
|
get => Padding.X;
|
||||||
|
set => Padding = Padding with { X = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float PaddingTop
|
||||||
|
{
|
||||||
|
get => Padding.Y;
|
||||||
|
set => Padding = Padding with { Y = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float PaddingRight
|
||||||
|
{
|
||||||
|
get => Padding.Z;
|
||||||
|
set => Padding = Padding with { Z = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float PaddingBottom
|
||||||
|
{
|
||||||
|
get => Padding.W;
|
||||||
|
set => Padding = Padding with { W = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float BorderLeft
|
||||||
|
{
|
||||||
|
get => Border.X;
|
||||||
|
set => Border = Border with { X = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float BorderTop
|
||||||
|
{
|
||||||
|
get => Border.Y;
|
||||||
|
set => Border = Border with { Y = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float BorderRight
|
||||||
|
{
|
||||||
|
get => Border.Z;
|
||||||
|
set => Border = Border with { Z = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float BorderBottom
|
||||||
|
{
|
||||||
|
get => Border.W;
|
||||||
|
set => Border = Border with { W = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float CornerRadiusTopLeft
|
||||||
|
{
|
||||||
|
get => CornerRadii.X;
|
||||||
|
set => CornerRadii = CornerRadii with { X = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float CornerRadiusBottomLeft
|
||||||
|
{
|
||||||
|
get => CornerRadii.Y;
|
||||||
|
set => CornerRadii = CornerRadii with { Y = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float CornerRadiusBottomRight
|
||||||
|
{
|
||||||
|
get => CornerRadii.Z;
|
||||||
|
set => CornerRadii = CornerRadii with { Z = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float CornerRadiusTopRight
|
||||||
|
{
|
||||||
|
get => CornerRadii.W;
|
||||||
|
set => CornerRadii = CornerRadii with { W = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit MarginLeftUnit
|
||||||
|
{
|
||||||
|
get => _marginLeftUnit;
|
||||||
|
set => SetField(ref _marginLeftUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit MarginTopUnit
|
||||||
|
{
|
||||||
|
get => _marginTopUnit;
|
||||||
|
set => SetField(ref _marginTopUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit MarginRightUnit
|
||||||
|
{
|
||||||
|
get => _marginRightUnit;
|
||||||
|
set => SetField(ref _marginRightUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit MarginBottomUnit
|
||||||
|
{
|
||||||
|
get => _marginBottomUnit;
|
||||||
|
set => SetField(ref _marginBottomUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit PaddingLeftUnit
|
||||||
|
{
|
||||||
|
get => _paddingLeftUnit;
|
||||||
|
set => SetField(ref _paddingLeftUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit PaddingTopUnit
|
||||||
|
{
|
||||||
|
get => _paddingTopUnit;
|
||||||
|
set => SetField(ref _paddingTopUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit PaddingRightUnit
|
||||||
|
{
|
||||||
|
get => _paddingRightUnit;
|
||||||
|
set => SetField(ref _paddingRightUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit PaddingBottomUnit
|
||||||
|
{
|
||||||
|
get => _paddingBottomUnit;
|
||||||
|
set => SetField(ref _paddingBottomUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit BorderLeftUnit
|
||||||
|
{
|
||||||
|
get => _borderLeftUnit;
|
||||||
|
set => SetField(ref _borderLeftUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit BorderTopUnit
|
||||||
|
{
|
||||||
|
get => _borderTopUnit;
|
||||||
|
set => SetField(ref _borderTopUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit BorderRightUnit
|
||||||
|
{
|
||||||
|
get => _borderRightUnit;
|
||||||
|
set => SetField(ref _borderRightUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit BorderBottomUnit
|
||||||
|
{
|
||||||
|
get => _borderBottomUnit;
|
||||||
|
set => SetField(ref _borderBottomUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit WidthUnit
|
||||||
|
{
|
||||||
|
get => _widthUnit;
|
||||||
|
set => SetField(ref _widthUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit HeightUnit
|
||||||
|
{
|
||||||
|
get => _heightUnit;
|
||||||
|
set => SetField(ref _heightUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit MinimumWidthUnit
|
||||||
|
{
|
||||||
|
get => _minimumWidthUnit;
|
||||||
|
set => SetField(ref _minimumWidthUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit MinimumHeightUnit
|
||||||
|
{
|
||||||
|
get => _minimumHeightUnit;
|
||||||
|
set => SetField(ref _minimumHeightUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit MaximumWidthUnit
|
||||||
|
{
|
||||||
|
get => _maximumWidthUnit;
|
||||||
|
set => SetField(ref _maximumWidthUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit MaximumHeightUnit
|
||||||
|
{
|
||||||
|
get => _maximumHeightUnit;
|
||||||
|
set => SetField(ref _maximumHeightUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit CornerRadiusTopLeftUnit
|
||||||
|
{
|
||||||
|
get => _cornerRadiusTopLeftUnit;
|
||||||
|
set => SetField(ref _cornerRadiusTopLeftUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit CornerRadiusBottomLeftUnit
|
||||||
|
{
|
||||||
|
get => _cornerRadiusBottomLeftUnit;
|
||||||
|
set => SetField(ref _cornerRadiusBottomLeftUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit CornerRadiusBottomRightUnit
|
||||||
|
{
|
||||||
|
get => _cornerRadiusBottomRightUnit;
|
||||||
|
set => SetField(ref _cornerRadiusBottomRightUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit CornerRadiusTopRightUnit
|
||||||
|
{
|
||||||
|
get => _cornerRadiusTopRightUnit;
|
||||||
|
set => SetField(ref _cornerRadiusTopRightUnit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComputedBox ComputedBox
|
||||||
|
{
|
||||||
|
get => _computedBox;
|
||||||
|
private set => SetField(ref _computedBox, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public ComputedBox ComputeLayout(Vector2 intrinsic, Vector2 dpi, Vector2 area, Vector2 star)
|
||||||
|
{
|
||||||
|
// TODO: take intrinsic into account.
|
||||||
|
Vector4 margin = Compute(_margin, dpi, area, star, _marginLeftUnit, _marginTopUnit, _marginRightUnit, _marginBottomUnit);
|
||||||
|
Vector4 padding = Compute(_padding, dpi, area, star, _paddingLeftUnit, _paddingTopUnit, _paddingRightUnit, _paddingBottomUnit);
|
||||||
|
Vector4 border = Compute(_border, dpi, area, star, _borderLeftUnit, _borderTopUnit, _borderRightUnit, _borderBottomUnit);
|
||||||
|
|
||||||
|
Vector2 size = Compute(_size, dpi, area, star, _widthUnit, _heightUnit);
|
||||||
|
Vector2 minimumSize = Compute(_minimumSize, dpi, area, star, _minimumWidthUnit, _minimumHeightUnit);
|
||||||
|
Vector2 maximumSize = Compute(_maximumSize, dpi, area, star, _maximumWidthUnit, _maximumHeightUnit);
|
||||||
|
Vector4 cornerRadii = Compute(_cornerRadii, dpi, area, star, _cornerRadiusTopLeftUnit,
|
||||||
|
_cornerRadiusBottomLeftUnit, _cornerRadiusBottomRightUnit, _cornerRadiusTopRightUnit);
|
||||||
|
|
||||||
|
size = Vector2.Clamp(size, minimumSize, maximumSize);
|
||||||
|
|
||||||
|
ComputedBox = new ComputedBox(margin, padding, border, size)
|
||||||
|
{
|
||||||
|
CornerRadii = cornerRadii,
|
||||||
|
};
|
||||||
|
|
||||||
|
UpdateRequired = false;
|
||||||
|
return ComputedBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float Compute(float value, float dpi, float length, float star, LayoutUnit unit)
|
||||||
|
{
|
||||||
|
const float dpiToMm = 0f;
|
||||||
|
const float dpiToPt = 0f;
|
||||||
|
|
||||||
|
return unit switch
|
||||||
|
{
|
||||||
|
LayoutUnit.Pixel => value,
|
||||||
|
LayoutUnit.Millimeter => value * dpi * dpiToMm,
|
||||||
|
LayoutUnit.Percent => value * length,
|
||||||
|
LayoutUnit.Point => value * dpi * dpiToPt,
|
||||||
|
LayoutUnit.Star => value * star,
|
||||||
|
_ => throw new ArgumentException(nameof(unit)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2 Compute(Vector2 value, Vector2 dpi, Vector2 size, Vector2 star, LayoutUnit xUnit, LayoutUnit yUnit)
|
||||||
|
{
|
||||||
|
return new Vector2(
|
||||||
|
Compute(value.X, dpi.X, size.X, star.X, xUnit),
|
||||||
|
Compute(value.Y, dpi.Y, size.Y, star.Y, yUnit));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector4 Compute(Vector4 value, Vector2 dpi, Vector2 size, Vector2 star, LayoutUnit xUnit, LayoutUnit yUnit, LayoutUnit zUnit, LayoutUnit wUnit)
|
||||||
|
{
|
||||||
|
return new Vector4(
|
||||||
|
Compute(value.X, dpi.X, size.X, star.X, xUnit),
|
||||||
|
Compute(value.Y, dpi.Y, size.Y, star.Y, yUnit),
|
||||||
|
Compute(value.Z, dpi.X, size.X, star.X, zUnit),
|
||||||
|
Compute(value.W, dpi.Y, size.Y, star.Y, wUnit));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SetField<T>(ref T field, T value, bool updateRequired = true, [CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||||
|
field = value;
|
||||||
|
UpdateRequired |= updateRequired;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
171
Dashboard.Common/Layout/LayoutInfo.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public enum DisplayMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Inline,
|
||||||
|
Block,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ContainerMode
|
||||||
|
{
|
||||||
|
Basic,
|
||||||
|
Flex,
|
||||||
|
Grid,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FlowDirection
|
||||||
|
{
|
||||||
|
Row,
|
||||||
|
Column,
|
||||||
|
RowReverse,
|
||||||
|
ColumnReverse,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PositionMode
|
||||||
|
{
|
||||||
|
Absolute,
|
||||||
|
Relative,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OverflowMode
|
||||||
|
{
|
||||||
|
Hidden,
|
||||||
|
Overflow,
|
||||||
|
ScrollHorizontal,
|
||||||
|
ScrollVertical,
|
||||||
|
ScrollBoth,
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct TrackInfo(float Width, LayoutUnit Unit)
|
||||||
|
{
|
||||||
|
public static readonly TrackInfo Default = new TrackInfo(0, LayoutUnit.Auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ContainerLayoutInfo : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private ContainerMode _containerMode;
|
||||||
|
private FlowDirection _flowDirection = FlowDirection.Row;
|
||||||
|
|
||||||
|
public ObservableCollection<TrackInfo> Rows { get; } = new ObservableCollection<TrackInfo>() { TrackInfo.Default };
|
||||||
|
|
||||||
|
public ObservableCollection<TrackInfo> Columns { get; } =
|
||||||
|
new ObservableCollection<TrackInfo>() { TrackInfo.Default };
|
||||||
|
|
||||||
|
public ContainerMode ContainerMode
|
||||||
|
{
|
||||||
|
get => _containerMode;
|
||||||
|
set => SetField(ref _containerMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlowDirection FlowDirection
|
||||||
|
{
|
||||||
|
get => _flowDirection;
|
||||||
|
set => SetField(ref _flowDirection, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LayoutInfo : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private DisplayMode _displayMode = DisplayMode.Inline;
|
||||||
|
private PositionMode _positionMode = PositionMode.Relative;
|
||||||
|
private OverflowMode _overflowMode = OverflowMode.Overflow;
|
||||||
|
private int _row = 0;
|
||||||
|
private int _column = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the control display.
|
||||||
|
/// </summary>
|
||||||
|
public DisplayMode DisplayMode
|
||||||
|
{
|
||||||
|
get => _displayMode;
|
||||||
|
set => SetField(ref _displayMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes how the control is positioned.
|
||||||
|
/// </summary>
|
||||||
|
public PositionMode PositionMode
|
||||||
|
{
|
||||||
|
get => _positionMode;
|
||||||
|
set => SetField(ref _positionMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes how overflows are handled.
|
||||||
|
/// </summary>
|
||||||
|
public OverflowMode OverflowMode
|
||||||
|
{
|
||||||
|
get => _overflowMode;
|
||||||
|
set => SetField(ref _overflowMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutBox Box { get; } = new LayoutBox();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The row of the control in a grid container.
|
||||||
|
/// </summary>
|
||||||
|
public int Row
|
||||||
|
{
|
||||||
|
get => _row;
|
||||||
|
set => SetField(ref _row, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The column of the control in a grid container.
|
||||||
|
/// </summary>
|
||||||
|
public int Column
|
||||||
|
{
|
||||||
|
get => _column;
|
||||||
|
set => SetField(ref _column, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public LayoutInfo()
|
||||||
|
{
|
||||||
|
Box.PropertyChanged += BoxOnPropertyChanged;
|
||||||
|
// Rows.CollectionChanged += RowsChanged;
|
||||||
|
// Columns.CollectionChanged += ColumnsChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BoxOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(Box));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
364
Dashboard.Common/Layout/LayoutSolution.cs
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public record struct LayoutItemSolution(ILayoutItem Item, ComputedBox Solution);
|
||||||
|
|
||||||
|
public class LayoutSolution
|
||||||
|
{
|
||||||
|
public ILayoutContainer Container { get; }
|
||||||
|
public IReadOnlyList<LayoutItemSolution> Items { get; }
|
||||||
|
|
||||||
|
private LayoutSolution(ILayoutContainer container, IEnumerable<LayoutItemSolution> itemSolutions)
|
||||||
|
{
|
||||||
|
Container = container;
|
||||||
|
Items = itemSolutions.ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LayoutSolution CalculateLayout<T1>(T1 container, Vector2 limits, int iterations = 3, float absTol = 0.001f, float relTol = 0.01f)
|
||||||
|
where T1 : ILayoutContainer
|
||||||
|
{
|
||||||
|
switch (container.ContainerLayout.ContainerMode)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case ContainerMode.Basic:
|
||||||
|
return SolveForBasicLayout(container, limits, iterations, absTol, relTol);
|
||||||
|
case ContainerMode.Flex:
|
||||||
|
return SolveForFlex(container, limits, iterations, absTol, relTol);
|
||||||
|
case ContainerMode.Grid:
|
||||||
|
return SolveForGrid(container, limits, iterations, absTol, relTol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LayoutSolution SolveForGrid<T1>(T1 container, Vector2 limits, int iterations, float absTol, float relTol) where T1 : ILayoutContainer
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LayoutSolution SolveForFlex<T1>(T1 container, Vector2 limits,int iterations, float absTol, float relTol) where T1 : ILayoutContainer
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LayoutSolution SolveForBasicLayout<T1>(T1 container, Vector2 limits, int iterations, float absTol, float relTol) where T1 : ILayoutContainer
|
||||||
|
{
|
||||||
|
int count = container.Count();
|
||||||
|
LayoutItemSolution[] items = new LayoutItemSolution[count];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
foreach (ILayoutItem item in container)
|
||||||
|
{
|
||||||
|
items[i++] = new LayoutItemSolution(item, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool limitX = limits.X > 0;
|
||||||
|
bool limitY = limits.Y > 0;
|
||||||
|
|
||||||
|
while (iterations-- > 0)
|
||||||
|
{
|
||||||
|
Vector2 pen = Vector2.Zero;
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
foreach (ILayoutItem item in container)
|
||||||
|
{
|
||||||
|
Vector2 size = item.CalculateIntrinsicSize();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LayoutSolution(container, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TrackSolution(TrackInfo Track)
|
||||||
|
{
|
||||||
|
public bool Auto => Track.Unit == LayoutUnit.Auto;
|
||||||
|
|
||||||
|
public bool Absolute => Track.Unit.IsAbsolute();
|
||||||
|
|
||||||
|
public float Requested { get; private set; }
|
||||||
|
public float Value { get; private set; }
|
||||||
|
|
||||||
|
public float Result { get; set; } = 0.0f;
|
||||||
|
public bool IsFrozen { get; set; } = false;
|
||||||
|
|
||||||
|
public void CalculateRequested(float dpi, float rel, float star)
|
||||||
|
{
|
||||||
|
Requested = new Metric(Track.Unit, Track.Width).Compute(dpi, rel, star);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Freeze()
|
||||||
|
{
|
||||||
|
if (IsFrozen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsFrozen = true;
|
||||||
|
Result = Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate float GetItemLength<in T1>(float dpi, float rel, float star, T1 item)
|
||||||
|
where T1 : ILayoutItem;
|
||||||
|
|
||||||
|
private TrackSolution[] SolveForGridTracks<T1, T2, T3>(
|
||||||
|
float limit,
|
||||||
|
T1 tracks,
|
||||||
|
T2 container,
|
||||||
|
int iterations,
|
||||||
|
float absTol,
|
||||||
|
float relTol,
|
||||||
|
Func<int, T3> getItemTrack,
|
||||||
|
GetItemLength<T3> getItemLength)
|
||||||
|
where T1 : IList<TrackInfo>
|
||||||
|
where T2 : ILayoutContainer
|
||||||
|
where T3 : ILayoutItem
|
||||||
|
{
|
||||||
|
int itemCount = container.Count();
|
||||||
|
bool auto = limit < 0;
|
||||||
|
TrackSolution[] solution = new TrackSolution[tracks.Count];
|
||||||
|
|
||||||
|
foreach (TrackSolution track in solution)
|
||||||
|
{
|
||||||
|
if (track.Absolute) {
|
||||||
|
// FIXME: pass DPI here.
|
||||||
|
track.CalculateRequested(96f, limit, 0);
|
||||||
|
track.Freeze();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (iterations-- > 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tracks.Count; i++)
|
||||||
|
{
|
||||||
|
solution[i].Freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
return solution;
|
||||||
|
}
|
||||||
|
|
||||||
|
// private static void GetIntrinsicGridSizes<T1, T2>(Span<float> cols, Span<float> rows, T1 parent, T2 items)
|
||||||
|
// where T1 : ILayoutItem
|
||||||
|
// where T2 : IEnumerable<ILayoutItem>
|
||||||
|
// {
|
||||||
|
// CopyToSpan(rows, parent.Layout.Rows);
|
||||||
|
// CopyToSpan(cols, parent.Layout.Columns);
|
||||||
|
//
|
||||||
|
// foreach (ILayoutItem item in items)
|
||||||
|
// {
|
||||||
|
// int col = Math.Clamp(item.Layout.Column, 0, cols.Length - 1);
|
||||||
|
// int row = Math.Clamp(item.Layout.Row, 0, rows.Length - 1);
|
||||||
|
//
|
||||||
|
// bool autoCols = parent.Layout.Columns[col] < 0;
|
||||||
|
// bool autoRows = parent.Layout.Rows[row] < 0;
|
||||||
|
//
|
||||||
|
// if (!autoRows && !autoCols)
|
||||||
|
// continue;
|
||||||
|
//
|
||||||
|
// Vector2 size = item.CalculateIntrinsicSize();
|
||||||
|
// cols[col] = autoCols ? Math.Max(size.X, cols[col]) : cols[col];
|
||||||
|
// rows[row] = autoRows ? Math.Max(size.Y, rows[row]) : rows[row];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static Vector2 CalculateIntrinsicSize<T1, T2>(T1 parent, T2 items)
|
||||||
|
// where T1 : ILayoutItem
|
||||||
|
// where T2 : IEnumerable<ILayoutItem>
|
||||||
|
// {
|
||||||
|
// // Copy layout details.
|
||||||
|
// Span<float> cols = stackalloc float[parent.Layout.Columns.Count];
|
||||||
|
// Span<float> rows = stackalloc float[parent.Layout.Rows.Count];
|
||||||
|
//
|
||||||
|
// GetIntrinsicGridSizes(cols, rows, parent, items);
|
||||||
|
//
|
||||||
|
// float width = parent.Layout.Margin.X + parent.Layout.Margin.Z + parent.Layout.Padding.X + parent.Layout.Padding.Z + SumSpan<float>(cols);
|
||||||
|
// float height = parent.Layout.Margin.Y + parent.Layout.Margin.W + parent.Layout.Padding.Y + parent.Layout.Padding.W + SumSpan<float>(rows);
|
||||||
|
//
|
||||||
|
// return new Vector2(width, height);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static GridResult Layout<T1, T2>(T1 parent, T2 items, Vector2 limits, int iterations = 3, float abstol = 0.0001f, float reltol = 0.01f)
|
||||||
|
// where T1 : ILayoutItem
|
||||||
|
// where T2 : IEnumerable<ILayoutItem>
|
||||||
|
// {
|
||||||
|
// Vector4 contentSpace = parent.Layout.Margin + parent.Layout.Padding;
|
||||||
|
// Vector2 contentLimits = new Vector2(
|
||||||
|
// limits.X > 0 ? limits.X - contentSpace.X - contentSpace.Z : -1,
|
||||||
|
// limits.Y > 0 ? limits.Y - contentSpace.Y - contentSpace.W : -1);
|
||||||
|
//
|
||||||
|
// // Get rows and columns for now.
|
||||||
|
// Span<Track> cols = stackalloc Track[parent.Layout.Columns.Count];
|
||||||
|
// Span<Track> rows = stackalloc Track[parent.Layout.Rows.Count];
|
||||||
|
//
|
||||||
|
// for (int i = 0; i < cols.Length; i++)
|
||||||
|
// {
|
||||||
|
// cols[i] = new Track(parent.Layout.Columns[i]);
|
||||||
|
//
|
||||||
|
// if (!cols[i].Auto)
|
||||||
|
// {
|
||||||
|
// cols[i].Freeze();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for (int i = 0; i < rows.Length; i++)
|
||||||
|
// {
|
||||||
|
// rows[i] = new Track(parent.Layout.Rows[i]);
|
||||||
|
//
|
||||||
|
// if (!rows[i].Auto)
|
||||||
|
// {
|
||||||
|
// rows[i].Freeze();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// int freeRows = 0;
|
||||||
|
// int freeCols = 0;
|
||||||
|
// while (iterations-- > 0 && ((freeRows = CountFree(rows)) > 0 || (freeCols = CountFree(cols)) > 0))
|
||||||
|
// {
|
||||||
|
// // Calculate the remaining size.
|
||||||
|
// Vector2 remaining = contentLimits;
|
||||||
|
//
|
||||||
|
// for (int i = 0; contentLimits.X > 0 && i < cols.Length; i++)
|
||||||
|
// {
|
||||||
|
// if (cols[i].IsFrozen)
|
||||||
|
// remaining.X -= cols[i].Value;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for (int i = 0; contentLimits.Y > 0 && i < rows.Length; i++)
|
||||||
|
// {
|
||||||
|
// if (rows[i].IsFrozen)
|
||||||
|
// remaining.Y -= rows[i].Value;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Vector2 childLimits = remaining / new Vector2(Math.Max(freeCols, 1), Math.Max(freeRows, 1));
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// // Calculate the size of each free track.
|
||||||
|
// foreach (ILayoutItem child in items)
|
||||||
|
// {
|
||||||
|
// int c = Math.Clamp(child.Layout.Column, 0, cols.Length - 1);
|
||||||
|
// int r = Math.Clamp(child.Layout.Row, 0, rows.Length - 1);
|
||||||
|
//
|
||||||
|
// bool autoRow = rows[r].Auto;
|
||||||
|
// bool autoCol = cols[c].Auto;
|
||||||
|
//
|
||||||
|
// if (!autoRow && !autoCol)
|
||||||
|
// continue;
|
||||||
|
//
|
||||||
|
// Vector2 childSize = child.CalculateSize(childLimits);
|
||||||
|
//
|
||||||
|
// if (autoCol)
|
||||||
|
// cols[c].Value = Math.Max(childLimits.X, childSize.X);
|
||||||
|
//
|
||||||
|
// if (autoRow)
|
||||||
|
// rows[r].Value = Math.Max(childLimits.Y, childSize.Y);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Calculate for errors and decide to freeze them.
|
||||||
|
//
|
||||||
|
// for (int i = 0; limits.X > 0 && i < cols.Length; i++)
|
||||||
|
// {
|
||||||
|
// if (WithinTolerance(cols[i].Value, childLimits.X, abstol, reltol))
|
||||||
|
// {
|
||||||
|
// cols[i].Freeze();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for (int i = 0; limits.Y > 0 && i < rows.Length; i++)
|
||||||
|
// {
|
||||||
|
// if (WithinTolerance(rows[i].Value, childLimits.Y, abstol, reltol))
|
||||||
|
// {
|
||||||
|
// rows[i].Freeze();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Vector2 size = new Vector2(
|
||||||
|
// parent.Layout.Margin.X + parent.Layout.Margin.Z + parent.Layout.Padding.X + parent.Layout.Padding.Z,
|
||||||
|
// parent.Layout.Margin.Y + parent.Layout.Margin.W + parent.Layout.Padding.Y + parent.Layout.Padding.W);
|
||||||
|
//
|
||||||
|
// foreach (ref Track col in cols)
|
||||||
|
// {
|
||||||
|
// col.Freeze();
|
||||||
|
// size.X += col.Result;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// foreach (ref Track row in rows)
|
||||||
|
// {
|
||||||
|
// row.Freeze();
|
||||||
|
// size.Y += row.Result;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (limits.X > 0) size.X = Math.Max(size.X, limits.X);
|
||||||
|
// if (limits.Y > 0) size.Y = Math.Max(size.Y, limits.Y);
|
||||||
|
//
|
||||||
|
// // Temporary solution
|
||||||
|
// return new GridResult(size, cols.ToArray().Select(x => x.Result).ToArray(), rows.ToArray().Select(x => x.Result).ToArray());
|
||||||
|
//
|
||||||
|
// static int CountFree(Span<Track> tracks)
|
||||||
|
// {
|
||||||
|
// int i = 0;
|
||||||
|
// foreach (Track track in tracks)
|
||||||
|
// {
|
||||||
|
// if (!track.IsFrozen)
|
||||||
|
// i++;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return i;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private static void CopyToSpan<T1, T2>(Span<T1> span, T2 items)
|
||||||
|
// where T2 : IEnumerable<T1>
|
||||||
|
// {
|
||||||
|
// using IEnumerator<T1> iterator = items.GetEnumerator();
|
||||||
|
// for (int i = 0; i < span.Length; i++)
|
||||||
|
// {
|
||||||
|
// if (!iterator.MoveNext())
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// span[i] = iterator.Current;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private static T1 SumSpan<T1>(ReadOnlySpan<T1> span)
|
||||||
|
// where T1 : struct, INumber<T1>
|
||||||
|
// {
|
||||||
|
// T1 value = default;
|
||||||
|
//
|
||||||
|
// foreach (T1 item in span)
|
||||||
|
// {
|
||||||
|
// value += item;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return value;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private static bool WithinTolerance<T>(T value, T limit, T abstol, T reltol)
|
||||||
|
// where T : INumber<T>
|
||||||
|
// {
|
||||||
|
// T tol = T.Max(abstol, value * reltol);
|
||||||
|
// return T.Abs(value - limit) < tol;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public record GridResult(Vector2 Size, float[] Columns, float[] Rows)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private record struct Track(float Request)
|
||||||
|
// {
|
||||||
|
// public bool Auto => Request < 0;
|
||||||
|
//
|
||||||
|
// public bool IsFrozen { get; private set; } = false;
|
||||||
|
// public float Value { get; set; } = Request;
|
||||||
|
//
|
||||||
|
// public float Result { get; private set; }
|
||||||
|
//
|
||||||
|
// public void Freeze()
|
||||||
|
// {
|
||||||
|
// Result = Value;
|
||||||
|
// IsFrozen = true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Dashboard.Common/LayoutUnit.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Dashboard
|
||||||
|
{
|
||||||
|
public enum LayoutUnit : short
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Does not specify a unit.
|
||||||
|
/// </summary>
|
||||||
|
Auto,
|
||||||
|
/// <summary>
|
||||||
|
/// The default unit. A size of a single picture element.
|
||||||
|
/// </summary>
|
||||||
|
Pixel = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// 1/72th of an inch traditional in graphics design.
|
||||||
|
/// </summary>
|
||||||
|
Point = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// The universal length unit for small distances.
|
||||||
|
/// </summary>
|
||||||
|
Millimeter = 3,
|
||||||
|
/// <summary>
|
||||||
|
/// An inverse proportional unit with respect to the container size.
|
||||||
|
/// </summary>
|
||||||
|
Star = 4,
|
||||||
|
/// <summary>
|
||||||
|
/// A directly proportional unit with respect to the container size.
|
||||||
|
/// </summary>
|
||||||
|
Percent = 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +1,108 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Dashboard
|
namespace Dashboard
|
||||||
{
|
{
|
||||||
public enum MeasurementUnit
|
public record struct LayoutUnits(LayoutUnit All)
|
||||||
{
|
{
|
||||||
/// <summary>
|
public LayoutUnit XUnit
|
||||||
/// The default unit. A size of a single picture element.
|
{
|
||||||
/// </summary>
|
get => (LayoutUnit)(((int)All & 0xF) >> 0);
|
||||||
Pixel,
|
set => All = (LayoutUnit)(((int)All & ~(0xF << 0)) | ((int)value << 0));
|
||||||
/// <summary>
|
}
|
||||||
/// 1/72th of an inch traditional in graphics design.
|
|
||||||
/// </summary>
|
public LayoutUnit YUnit
|
||||||
Point,
|
{
|
||||||
/// <summary>
|
get => (LayoutUnit)(((int)All & 0xF) >> 4);
|
||||||
/// The universal length unit for small distances.
|
set => All = (LayoutUnit)(((int)All & ~(0xF << 4)) | ((int)value << 4));
|
||||||
/// </summary>
|
}
|
||||||
Millimeter,
|
|
||||||
/// <summary>
|
public LayoutUnit ZUnit
|
||||||
/// An inverse proportional unit with respect to the container size.
|
{
|
||||||
/// </summary>
|
get => (LayoutUnit)(((int)All & 0xF) >> 8);
|
||||||
Star,
|
set => All = (LayoutUnit)(((int)All & ~(0xF << 8)) | ((int)value << 8));
|
||||||
/// <summary>
|
}
|
||||||
/// A directly proportional unit with respect to the container size.
|
|
||||||
/// </summary>
|
public LayoutUnit WUnit
|
||||||
Percent,
|
{
|
||||||
|
get => (LayoutUnit)(((int)All & 0xF) >> 12);
|
||||||
|
set => All = (LayoutUnit)(((int)All & ~(0xF << 12)) | ((int)value << 12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record struct AdvancedMetric(MeasurementUnit Unit, float Value)
|
public record struct Metric(LayoutUnit Units, float Value)
|
||||||
{
|
{
|
||||||
public AdvancedMetric Convert(MeasurementUnit target, float dpi, float rel, int stars)
|
public float Compute(float dpi, float rel, float star)
|
||||||
|
{
|
||||||
|
switch (Units)
|
||||||
|
{
|
||||||
|
case LayoutUnit.Auto:
|
||||||
|
return -1;
|
||||||
|
case LayoutUnit.Millimeter:
|
||||||
|
float mm2Px = dpi / 25.4f;
|
||||||
|
return Value * mm2Px;
|
||||||
|
case LayoutUnit.Pixel:
|
||||||
|
return Value;
|
||||||
|
case LayoutUnit.Point:
|
||||||
|
float pt2Px = 72 / dpi;
|
||||||
|
return Value * pt2Px;
|
||||||
|
case LayoutUnit.Percent:
|
||||||
|
return rel * Value;
|
||||||
|
case LayoutUnit.Star:
|
||||||
|
return star * Value;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unrecognized unit.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct Metric2D(LayoutUnits Units, Vector2 Value)
|
||||||
|
{
|
||||||
|
public float X
|
||||||
|
{
|
||||||
|
get => Value.X;
|
||||||
|
set => Value = new Vector2(value, Value.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit XUnits
|
||||||
|
{
|
||||||
|
get => Units.XUnit;
|
||||||
|
set => Units = Units with { XUnit = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Y
|
||||||
|
{
|
||||||
|
get => Value.Y;
|
||||||
|
set => Value = new Vector2(Value.X, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit YUnits
|
||||||
|
{
|
||||||
|
get => Units.YUnit;
|
||||||
|
set => Units = Units with { YUnit = value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct BoxMetric(LayoutUnit Units, Box2d Value);
|
||||||
|
|
||||||
|
public record struct AdvancedMetric(LayoutUnit Unit, float Value)
|
||||||
|
{
|
||||||
|
public AdvancedMetric Convert(LayoutUnit target, float dpi, float rel, int stars)
|
||||||
{
|
{
|
||||||
if (Unit == target)
|
if (Unit == target)
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
float pixels = Unit switch {
|
float pixels = Unit switch {
|
||||||
MeasurementUnit.Pixel => Value,
|
LayoutUnit.Pixel => Value,
|
||||||
MeasurementUnit.Point => Value * (72f / dpi),
|
LayoutUnit.Point => Value * (72f / dpi),
|
||||||
MeasurementUnit.Millimeter => Value * (28.3464566929f / dpi),
|
LayoutUnit.Millimeter => Value * (28.3464566929f / dpi),
|
||||||
MeasurementUnit.Star => Value * rel / stars,
|
LayoutUnit.Star => Value * rel / stars,
|
||||||
MeasurementUnit.Percent => Value * rel / 100,
|
LayoutUnit.Percent => Value * rel / 100,
|
||||||
_ => throw new Exception(),
|
_ => throw new Exception(),
|
||||||
};
|
};
|
||||||
|
|
||||||
float value = target switch {
|
float value = target switch {
|
||||||
MeasurementUnit.Pixel => pixels,
|
LayoutUnit.Pixel => pixels,
|
||||||
MeasurementUnit.Point => Value * (dpi / 72f),
|
LayoutUnit.Point => Value * (dpi / 72f),
|
||||||
// MeasurementUnit.Millimeter =>
|
// MeasurementUnit.Millimeter =>
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,22 +125,4 @@ namespace Dashboard
|
|||||||
? metric
|
? metric
|
||||||
: throw new Exception($"Could not parse the value '{str}'.");
|
: throw new Exception($"Could not parse the value '{str}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MeasurementExtensions
|
|
||||||
{
|
|
||||||
public static bool IsRelative(this MeasurementUnit unit) => unit switch {
|
|
||||||
MeasurementUnit.Star or MeasurementUnit.Percent => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
public static bool IsAbsolute(this MeasurementUnit unit) => !IsRelative(unit);
|
|
||||||
|
|
||||||
public static string ToShortString(this MeasurementUnit unit) => unit switch {
|
|
||||||
MeasurementUnit.Pixel => "px",
|
|
||||||
MeasurementUnit.Point => "pt",
|
|
||||||
MeasurementUnit.Millimeter => "mm",
|
|
||||||
MeasurementUnit.Star => "*",
|
|
||||||
MeasurementUnit.Percent => "%",
|
|
||||||
_ => throw new Exception("Unknown unit."),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
30
Dashboard.Common/MeasurementExtensions.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Dashboard
|
||||||
|
{
|
||||||
|
public static class MeasurementExtensions
|
||||||
|
{
|
||||||
|
public static bool IsRelative(this LayoutUnit unit) => unit switch {
|
||||||
|
LayoutUnit.Star or LayoutUnit.Percent => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
public static bool IsAbsolute(this LayoutUnit unit) => !IsRelative(unit);
|
||||||
|
|
||||||
|
public static string ToShortString(this LayoutUnit unit) => unit switch {
|
||||||
|
LayoutUnit.Pixel => "px",
|
||||||
|
LayoutUnit.Point => "pt",
|
||||||
|
LayoutUnit.Millimeter => "mm",
|
||||||
|
LayoutUnit.Star => "*",
|
||||||
|
LayoutUnit.Percent => "%",
|
||||||
|
_ => throw new Exception("Unknown unit."),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool WithinTolerance(this float value, float reference, float absTol, float relTol)
|
||||||
|
=> value.CompareTolerance(reference, absTol, relTol) == 0;
|
||||||
|
|
||||||
|
public static int CompareTolerance(this float value, float reference, float absTol, float relTol)
|
||||||
|
{
|
||||||
|
float tolerance = Math.Max(absTol, Math.Abs(reference) * relTol);
|
||||||
|
float difference = value - reference;
|
||||||
|
return difference < -tolerance ? -1 : difference > tolerance ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
202
Dashboard.Common/Pal/Application.cs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
using Dashboard.Collections;
|
||||||
|
using Dashboard.Windowing;
|
||||||
|
using BindingFlags = System.Reflection.BindingFlags;
|
||||||
|
|
||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public abstract class Application : IContextBase<Application, IApplicationExtension>
|
||||||
|
{
|
||||||
|
public abstract string DriverName { get; }
|
||||||
|
public abstract string DriverVendor { get; }
|
||||||
|
public abstract Version DriverVersion { get; }
|
||||||
|
|
||||||
|
public virtual string AppTitle { get; set; } = "Dashboard Application";
|
||||||
|
|
||||||
|
public bool IsInitialized { get; private set; } = false;
|
||||||
|
public bool IsDisposed { get; private set; } = false;
|
||||||
|
public IContextDebugger? Debugger { get; set; }
|
||||||
|
|
||||||
|
protected CancellationToken? CancellationToken { get; private set; }
|
||||||
|
protected bool Quit { get; set; } = false;
|
||||||
|
|
||||||
|
private readonly TypeDictionary<IApplicationExtension> _extensions =
|
||||||
|
new TypeDictionary<IApplicationExtension>(true);
|
||||||
|
private readonly TypeDictionary<IApplicationExtension, Func<IApplicationExtension>> _preloadedExtensions =
|
||||||
|
new TypeDictionary<IApplicationExtension, Func<IApplicationExtension>>(true);
|
||||||
|
|
||||||
|
public event EventHandler<DeviceContext>? DeviceContextCreated;
|
||||||
|
|
||||||
|
public Application()
|
||||||
|
{
|
||||||
|
Current = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Application()
|
||||||
|
{
|
||||||
|
InvokeDispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
if (IsInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsInitialized = true;
|
||||||
|
|
||||||
|
InitializeInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void InitializeInternal()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void OnDeviceContextCreated(DeviceContext dc)
|
||||||
|
{
|
||||||
|
DeviceContextCreated?.Invoke(this, dc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RunEvents(bool wait)
|
||||||
|
{
|
||||||
|
if (!IsInitialized)
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run() => Run(true, System.Threading.CancellationToken.None);
|
||||||
|
|
||||||
|
public void Run(bool wait) => Run(wait, System.Threading.CancellationToken.None);
|
||||||
|
|
||||||
|
public void Run(bool waitForEvents, CancellationToken token)
|
||||||
|
{
|
||||||
|
CancellationToken = token;
|
||||||
|
CancellationToken.Value.Register(() => Quit = true);
|
||||||
|
|
||||||
|
InitializeInternal();
|
||||||
|
|
||||||
|
while (!Quit && !token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
RunEvents(waitForEvents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Window API
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a window. It could be a virtual window, or a physical window.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A window.</returns>
|
||||||
|
public abstract IWindow CreateWindow();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Always creates a physical window.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A physical window.</returns>
|
||||||
|
public abstract IPhysicalWindow CreatePhysicalWindow();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a physical window with a window manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A physical window with the given window manager.</returns>
|
||||||
|
public IPhysicalWindow CreatePhysicalWindow(IWindowManager wm)
|
||||||
|
{
|
||||||
|
IPhysicalWindow window = CreatePhysicalWindow();
|
||||||
|
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 : IApplicationExtension
|
||||||
|
{
|
||||||
|
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 : IApplicationExtension
|
||||||
|
{
|
||||||
|
T? extension = default;
|
||||||
|
|
||||||
|
if (_extensions.TryGet(out extension))
|
||||||
|
return extension;
|
||||||
|
|
||||||
|
lock (_extensions)
|
||||||
|
{
|
||||||
|
if (_extensions.TryGet(out extension))
|
||||||
|
return extension;
|
||||||
|
|
||||||
|
if (_preloadedExtensions.Remove<T>(out Func<IApplicationExtension>? loader))
|
||||||
|
{
|
||||||
|
extension = (T)loader!();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
extension = Activator.CreateInstance<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_extensions.Add(extension);
|
||||||
|
extension.Require(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
Quit = true;
|
||||||
|
foreach (IApplicationExtension extension in _extensions)
|
||||||
|
{
|
||||||
|
extension.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeDispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (IsDisposed) return;
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
Dispose(isDisposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
149
Dashboard.Common/Pal/DeviceContext.cs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
using Dashboard.Collections;
|
||||||
|
using Dashboard.Windowing;
|
||||||
|
using BindingFlags = System.Reflection.BindingFlags;
|
||||||
|
|
||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public abstract class DeviceContext : IContextBase<DeviceContext, IDeviceContextExtension>
|
||||||
|
{
|
||||||
|
private readonly TypeDictionary<IDeviceContextExtension> _extensions =
|
||||||
|
new TypeDictionary<IDeviceContextExtension>(true);
|
||||||
|
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; }
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional debugging object for your pleasure.
|
||||||
|
/// </summary>
|
||||||
|
public IContextDebugger? Debugger { get; set; }
|
||||||
|
|
||||||
|
protected DeviceContext(Application app, IWindow? window)
|
||||||
|
{
|
||||||
|
Application = app;
|
||||||
|
Window = window;
|
||||||
|
app.OnDeviceContextCreated(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~DeviceContext()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Begin() { }
|
||||||
|
|
||||||
|
// public abstract void Paint(object renderbuffer);
|
||||||
|
|
||||||
|
public virtual void End() { }
|
||||||
|
|
||||||
|
public bool IsExtensionAvailable<T>() where T : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
T? extension = default;
|
||||||
|
|
||||||
|
if (_extensions.TryGet(out extension))
|
||||||
|
return extension;
|
||||||
|
|
||||||
|
lock (_extensions)
|
||||||
|
{
|
||||||
|
if (_extensions.TryGet(out extension))
|
||||||
|
return extension;
|
||||||
|
|
||||||
|
if (_preloadedExtensions.Remove<T>(out Func<IDeviceContextExtension>? loader))
|
||||||
|
{
|
||||||
|
extension = (T)loader!();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
extension = Activator.CreateInstance<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_extensions.Add(extension);
|
||||||
|
extension.Require(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
/// <param name="isDisposing">True if disposing, false otherwise.</param>
|
||||||
|
protected virtual void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (!isDisposing) return;
|
||||||
|
|
||||||
|
foreach (IDeviceContextExtension extension in _extensions)
|
||||||
|
{
|
||||||
|
extension.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeDispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (IsDisposed) return;
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
Dispose(isDisposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => InvokeDispose(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Dashboard.Common/Pal/IApplicationExtension.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public interface IApplicationExtension : IContextExtensionBase<Application>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Dashboard.Common/Pal/IContextBase.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about this context interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContextInterfaceInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of this driver.
|
||||||
|
/// </summary>
|
||||||
|
string DriverName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The vendor for this driver.
|
||||||
|
/// </summary>
|
||||||
|
string DriverVendor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of this driver.
|
||||||
|
/// </summary>
|
||||||
|
Version DriverVersion { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base context interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContextBase : IContextInterfaceInfo, IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The debugger for this context.
|
||||||
|
/// </summary>
|
||||||
|
IContextDebugger? Debugger { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base context interface.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TContext">The context type.</typeparam>
|
||||||
|
/// <typeparam name="TExtension">The extension type, if used.</typeparam>
|
||||||
|
public interface IContextBase<TContext, in TExtension> : IContextBase
|
||||||
|
where TContext : IContextBase<TContext, TExtension>
|
||||||
|
where TExtension : IContextExtensionBase<TContext>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Is such an extension available?
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The extension to check.</typeparam>
|
||||||
|
/// <returns>True if the extension is available.</returns>
|
||||||
|
bool IsExtensionAvailable<T>() where T : TExtension;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Preload extensions, to be lazy loaded when required.
|
||||||
|
/// </summary>
|
||||||
|
/// <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>() 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;
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// Base interface for all context extensions.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContextExtensionBase : IContextInterfaceInfo, IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context that loaded this extension.
|
||||||
|
/// </summary>
|
||||||
|
IContextBase Context { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Require this extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that required this extension.</param>
|
||||||
|
void Require(IContextBase context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base interface for all context extensions.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContextExtensionBase<TContext> : IContextExtensionBase
|
||||||
|
where TContext : IContextBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context that loaded this extension.
|
||||||
|
/// </summary>
|
||||||
|
new TContext Context { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Require this extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that required this extension.</param>
|
||||||
|
void Require(TContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Dashboard.Common/Pal/IContextDebugger.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public interface IContextDebugger : IDisposable
|
||||||
|
{
|
||||||
|
void LogDebug(string message);
|
||||||
|
void LogInfo(string message);
|
||||||
|
void LogWarning(string message);
|
||||||
|
void LogError(string message);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Dashboard.Common/Pal/IDeviceContextExtension.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public interface IDeviceContextExtension : IContextExtensionBase<DeviceContext>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ namespace Dashboard.Windowing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for classes that implement a window manager.
|
/// Interface for classes that implement a window manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener, IPaintable, IDisposable
|
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The physical window that this window manager is associated with.
|
/// The physical window that this window manager is associated with.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Dashboard.Windowing
|
namespace Dashboard.Windowing
|
||||||
{
|
{
|
||||||
@@ -15,6 +16,6 @@ namespace Dashboard.Windowing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The size of the window framebuffer in pixels.
|
/// The size of the window framebuffer in pixels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Size FramebufferSize { get; }
|
Vector2 FramebufferSize { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,13 @@ namespace Dashboard.Windowing
|
|||||||
{
|
{
|
||||||
public interface IEventListener
|
public interface IEventListener
|
||||||
{
|
{
|
||||||
|
event EventHandler? EventRaised;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send an event to this windowing object.
|
/// Send an event to this windowing object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="sender">The object which generated the event.</param>
|
||||||
/// <param name="args">The event arguments sent.</param>
|
/// <param name="args">The event arguments sent.</param>
|
||||||
void SendEvent(EventArgs args);
|
void SendEvent(object? sender, EventArgs args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 System.Drawing;
|
||||||
using Dashboard.Events;
|
using Dashboard.Pal;
|
||||||
|
|
||||||
namespace Dashboard.Windowing
|
namespace Dashboard.Windowing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class of all Dashboard windows.
|
/// Base class of all Dashboard windows.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IWindow :
|
public interface IWindow : IDisposable
|
||||||
IPaintable,
|
|
||||||
IDisposable,
|
|
||||||
IAnimationTickEvent,
|
|
||||||
IMouseEvents
|
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The application for this window.
|
||||||
|
/// </summary>
|
||||||
|
Application Application { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Name of the window.
|
/// Name of the window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -26,6 +27,23 @@ namespace Dashboard.Windowing
|
|||||||
/// The size of the window that excludes the window extents.
|
/// The size of the window that excludes the window extents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SizeF ClientSize { get; set; }
|
SizeF ClientSize { get; set; }
|
||||||
|
|
||||||
|
IForm? Form { get; set; }
|
||||||
|
|
||||||
|
public event EventHandler? EventRaised;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to events from this window.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The event listener instance.</param>
|
||||||
|
/// <returns>An unsubscription token.</returns>
|
||||||
|
public void SubcribeEvent(IEventListener listener);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribe from events in from this window.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The event listener to unsubscribe.</param>
|
||||||
|
public void UnsubscribeEvent(IEventListener listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,6 +67,7 @@ namespace Dashboard.Windowing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IVirtualWindow : IWindow, IEventListener
|
public interface IVirtualWindow : IWindow, IEventListener
|
||||||
{
|
{
|
||||||
|
IWindowManager? WindowManager { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -59,7 +78,7 @@ namespace Dashboard.Windowing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The device context for this window.
|
/// The device context for this window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IDeviceContext DeviceContext { get; }
|
DeviceContext DeviceContext { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the window is double buffered.
|
/// True if the window is double buffered.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using Dashboard.Drawing.OpenGL.Executors;
|
using Dashboard.Drawing.OpenGL.Executors;
|
||||||
using Dashboard.OpenGL;
|
using Dashboard.OpenGL;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
namespace Dashboard.Drawing.OpenGL
|
namespace Dashboard.Drawing.OpenGL
|
||||||
{
|
{
|
||||||
@@ -130,10 +131,13 @@ namespace Dashboard.Drawing.OpenGL
|
|||||||
|
|
||||||
public void Draw(DrawQueue drawqueue) => Draw(drawqueue, new RectangleF(new PointF(0f,0f), Context.FramebufferSize));
|
public void Draw(DrawQueue drawqueue) => Draw(drawqueue, new RectangleF(new PointF(0f,0f), Context.FramebufferSize));
|
||||||
|
|
||||||
public virtual void Draw(DrawQueue drawQueue, RectangleF bounds)
|
public virtual void Draw(DrawQueue drawQueue, RectangleF bounds, float scale = 1.0f)
|
||||||
{
|
{
|
||||||
BeginDraw();
|
BeginDraw();
|
||||||
|
|
||||||
|
if (scale != 1.0f)
|
||||||
|
TransformStack.Push(Matrix4.CreateScale(scale, scale, 1));
|
||||||
|
|
||||||
foreach (ICommandFrame frame in drawQueue)
|
foreach (ICommandFrame frame in drawQueue)
|
||||||
{
|
{
|
||||||
if (_executorsMap.TryGetValue(frame.Command.Extension.Name, out ICommandExecutor? executor))
|
if (_executorsMap.TryGetValue(frame.Command.Extension.Name, out ICommandExecutor? executor))
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BlurgText" Version="0.1.0-nightly-19" />
|
<PackageReference Include="BlurgText" Version="0.1.0-nightly-19" />
|
||||||
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre*,5.1)" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -24,4 +23,8 @@
|
|||||||
<EmbeddedResource Include="Executors\text.frag" />
|
<EmbeddedResource Include="Executors\text.frag" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Text\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Dashboard.OpenGL;
|
||||||
using OTK = OpenTK.Mathematics;
|
using OTK = OpenTK.Mathematics;
|
||||||
|
|
||||||
namespace Dashboard.Drawing.OpenGL.Executors
|
namespace Dashboard.Drawing.OpenGL.Executors
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using BlurgText;
|
using BlurgText;
|
||||||
using Dashboard.Drawing.OpenGL.Text;
|
using Dashboard.OpenGL;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ namespace Dashboard.Drawing.OpenGL.Executors
|
|||||||
{
|
{
|
||||||
public IEnumerable<string> Extensions { get; } = new[] { "DB_Text" };
|
public IEnumerable<string> Extensions { get; } = new[] { "DB_Text" };
|
||||||
public IContextExecutor Executor { get; private set; }
|
public IContextExecutor Executor { get; private set; }
|
||||||
private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager<BlurgEngine>();
|
// private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager<BlurgEngine>();
|
||||||
public bool IsInitialized { get; private set; }
|
public bool IsInitialized { get; private set; }
|
||||||
|
|
||||||
private DrawCallRecorder _recorder;
|
private DrawCallRecorder _recorder;
|
||||||
@@ -97,7 +97,7 @@ namespace Dashboard.Drawing.OpenGL.Executors
|
|||||||
private void DrawText(ICommandFrame frame)
|
private void DrawText(ICommandFrame frame)
|
||||||
{
|
{
|
||||||
TextCommandArgs args = frame.GetParameter<TextCommandArgs>();
|
TextCommandArgs args = frame.GetParameter<TextCommandArgs>();
|
||||||
DbBlurgFont font = Engine.InternFont(args.Font);
|
// DbBlurgFont font = Engine.InternFont(args.Font);
|
||||||
|
|
||||||
BlurgColor color;
|
BlurgColor color;
|
||||||
switch (args.TextBrush)
|
switch (args.TextBrush)
|
||||||
@@ -116,15 +116,15 @@ namespace Dashboard.Drawing.OpenGL.Executors
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlurgResult? result = Engine.Blurg.BuildString(font.Font, font.Size, color, args.Text);
|
//BlurgResult? result = Engine.Blurg.BuildString(font.Font, font.Size, color, args.Text);
|
||||||
|
|
||||||
if (result == null)
|
// if (result == null)
|
||||||
return;
|
// return;
|
||||||
|
//
|
||||||
Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z);
|
// Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z);
|
||||||
ExecuteBlurgResult(result, position);
|
// ExecuteBlurgResult(result, position);
|
||||||
|
//
|
||||||
result.Dispose();
|
// result.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteBlurgResult(BlurgResult result, Vector3 position)
|
private void ExecuteBlurgResult(BlurgResult result, Vector3 position)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dashboard.Drawing.OpenGL.Text;
|
// using Dashboard.Drawing.OpenGL.Text;
|
||||||
using Dashboard.OpenGL;
|
using Dashboard.OpenGL;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
@@ -21,7 +21,7 @@ namespace Dashboard.Drawing.OpenGL
|
|||||||
if (bindingsContext != null)
|
if (bindingsContext != null)
|
||||||
GLLoader.LoadBindings(bindingsContext);
|
GLLoader.LoadBindings(bindingsContext);
|
||||||
|
|
||||||
Typesetter.Backend = BlurgEngine.Global;
|
// Typesetter.Backend = BlurgEngine.Global;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContextExecutor GetExecutor(IGLContext glContext)
|
public ContextExecutor GetExecutor(IGLContext glContext)
|
||||||
|
|||||||
@@ -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; }
|
public TextCommand TextCommand { get; }
|
||||||
|
|
||||||
private TextExtension() : base("DB_Text", new [] { FontExtension.Instance, BrushExtension.Instance })
|
private TextExtension() : base("DB_Text", new [] { BrushExtension.Instance })
|
||||||
{
|
{
|
||||||
TextCommand = new TextCommand(this);
|
TextCommand = new TextCommand(this);
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ namespace Dashboard.Drawing
|
|||||||
|
|
||||||
header = new Header()
|
header = new Header()
|
||||||
{
|
{
|
||||||
Font = queue.RequireResource(obj.Font),
|
// Font = queue.RequireResource(obj.Font),
|
||||||
TextBrush = queue.RequireResource(obj.TextBrush),
|
TextBrush = queue.RequireResource(obj.TextBrush),
|
||||||
BorderBrush = (obj.BorderBrush != null) ? queue.RequireResource(obj.BorderBrush) : -1,
|
BorderBrush = (obj.BorderBrush != null) ? queue.RequireResource(obj.BorderBrush) : -1,
|
||||||
BorderRadius = (obj.BorderBrush != null) ? obj.BorderRadius : 0f,
|
BorderRadius = (obj.BorderBrush != null) ? obj.BorderRadius : 0f,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace Dashboard.Drawing
|
|||||||
|
|
||||||
IFont LoadFont(Stream stream);
|
IFont LoadFont(Stream stream);
|
||||||
IFont LoadFont(string path);
|
IFont LoadFont(string path);
|
||||||
IFont LoadFont(NamedFont font);
|
// IFont LoadFont(NamedFont font);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -55,15 +55,16 @@ namespace Dashboard.Drawing
|
|||||||
return Backend.LoadFont(file.FullName);
|
return Backend.LoadFont(file.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IFont LoadFont(NamedFont font)
|
// public static IFont LoadFont(NamedFont font)
|
||||||
{
|
// {
|
||||||
return Backend.LoadFont(font);
|
// return Backend.LoadFont(font);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public static IFont LoadFont(string family, float size, FontWeight weight = FontWeight.Normal,
|
public static IFont LoadFont(string family, float size, FontWeight weight = FontWeight.Normal,
|
||||||
FontSlant slant = FontSlant.Normal, FontStretch stretch = FontStretch.Normal)
|
FontSlant slant = FontSlant.Normal, FontStretch stretch = FontStretch.Normal)
|
||||||
{
|
{
|
||||||
return LoadFont(new NamedFont(family, size, weight, slant, stretch));
|
// return LoadFont(new NamedFont(family, size, weight, slant, stretch));
|
||||||
|
throw new Exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UndefinedTypeSetter : ITypeSetter
|
private class UndefinedTypeSetter : ITypeSetter
|
||||||
@@ -94,11 +95,11 @@ namespace Dashboard.Drawing
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFont LoadFont(NamedFont font)
|
// public IFont LoadFont(NamedFont font)
|
||||||
{
|
// {
|
||||||
Except();
|
// Except();
|
||||||
return default;
|
// return default;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,4 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Dashboard.Drawing.OpenGL
|
namespace Dashboard.OpenGL
|
||||||
{
|
{
|
||||||
public class ContextCollector : IDisposable
|
public class ContextCollector : IDisposable
|
||||||
{
|
{
|
||||||
@@ -4,10 +4,19 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
|
||||||
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Drawing\immediate.frag" />
|
||||||
|
<EmbeddedResource Include="Drawing\immediate.frag" />
|
||||||
|
<None Remove="Drawing\immediate.vert" />
|
||||||
|
<EmbeddedResource Include="Drawing\immediate.vert" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
167
Dashboard.OpenGL/Drawing/DeviceContextBase.cs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
using Dashboard.Windowing;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Graphics.Wgl;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using ColorBuffer = OpenTK.Graphics.OpenGL.ColorBuffer;
|
||||||
|
using Vector2 = System.Numerics.Vector2;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL.Drawing
|
||||||
|
{
|
||||||
|
public class DeviceContextBase : IDeviceContextBase
|
||||||
|
{
|
||||||
|
private readonly Stack<Matrix4x4> _transforms = new Stack<Matrix4x4>();
|
||||||
|
private readonly Stack<Box2d> _clipRegions = new Stack<Box2d>();
|
||||||
|
private readonly Stack<Box2d> _scissorRegions = new Stack<Box2d>();
|
||||||
|
private int _z = 0;
|
||||||
|
|
||||||
|
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 Box2d ClipRegion => _clipRegions.Peek();
|
||||||
|
public Box2d ScissorRegion => _scissorRegions.Peek();
|
||||||
|
public Matrix4x4 Transforms => _transforms.Peek();
|
||||||
|
public float Scale => ScaleOverride > 0 ? ScaleOverride : (Context.Window as IDpiAwareWindow)?.Scale ?? 1;
|
||||||
|
public float ScaleOverride { get; set; } = -1f;
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Require(DeviceContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
|
||||||
|
ResetClip();
|
||||||
|
ResetScissor();
|
||||||
|
ResetTransforms();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IContextExtensionBase.Require(IContextBase context) => Require((DeviceContext)context);
|
||||||
|
|
||||||
|
public void ResetClip()
|
||||||
|
{
|
||||||
|
_clipRegions.Clear();
|
||||||
|
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
_clipRegions.Push(new Box2d(Vector2.Zero, size));
|
||||||
|
|
||||||
|
SetClip(ClipRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushClip(Box2d clipRegion)
|
||||||
|
{
|
||||||
|
clipRegion = new Box2d(ClipRegion.Min.X + clipRegion.Min.X, ClipRegion.Min.Y + clipRegion.Min.Y,
|
||||||
|
Math.Min(ClipRegion.Max.X, ClipRegion.Min.X + clipRegion.Max.X),
|
||||||
|
Math.Min(ClipRegion.Max.Y, ClipRegion.Max.Y + clipRegion.Max.Y));
|
||||||
|
_clipRegions.Push(clipRegion);
|
||||||
|
|
||||||
|
SetClip(clipRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopClip()
|
||||||
|
{
|
||||||
|
_clipRegions.Pop();
|
||||||
|
SetClip(ClipRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetScissor()
|
||||||
|
{
|
||||||
|
GL.Disable(EnableCap.ScissorTest);
|
||||||
|
_scissorRegions.Clear();
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
_scissorRegions.Push(new Box2d(Vector2.Zero, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushScissor(Box2d scissorRegion)
|
||||||
|
{
|
||||||
|
GL.Enable(EnableCap.ScissorTest);
|
||||||
|
|
||||||
|
// scissorRegion = new RectangleF(scissorRegion.X + scissorRegion.X, scissorRegion.Y + scissorRegion.Y,
|
||||||
|
// Math.Min(ScissorRegion.Right - scissorRegion.X, scissorRegion.Width),
|
||||||
|
// Math.Min(ScissorRegion.Bottom - scissorRegion.Y, scissorRegion.Height));
|
||||||
|
_scissorRegions.Push(scissorRegion);
|
||||||
|
|
||||||
|
SetScissor(scissorRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopScissor()
|
||||||
|
{
|
||||||
|
if (_scissorRegions.Count == 1)
|
||||||
|
GL.Disable(EnableCap.ScissorTest);
|
||||||
|
|
||||||
|
_scissorRegions.Pop();
|
||||||
|
SetScissor(ClipRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetClip(Box2d rect)
|
||||||
|
{
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
GL.Viewport(
|
||||||
|
(int)Math.Round(rect.Min.X),
|
||||||
|
(int)Math.Round(size.Y - rect.Min.Y - rect.Size.Y),
|
||||||
|
(int)Math.Round(rect.Size.X),
|
||||||
|
(int)Math.Round(rect.Size.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetScissor(Box2d rect)
|
||||||
|
{
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
GL.Scissor(
|
||||||
|
(int)Math.Round(rect.Min.X),
|
||||||
|
(int)Math.Round(size.Y - rect.Min.Y - rect.Size.Y),
|
||||||
|
(int)Math.Round(rect.Size.X),
|
||||||
|
(int)Math.Round(rect.Size.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetTransforms()
|
||||||
|
{
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
Matrix4x4 m = Matrix4x4.CreateOrthographicOffCenterLeftHanded(0, size.X, size.Y, 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ClearDepth()
|
||||||
|
{
|
||||||
|
GL.Clear(ClearBufferMask.DepthBufferBit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IncrementZ()
|
||||||
|
{
|
||||||
|
return ++_z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DecrementZ()
|
||||||
|
{
|
||||||
|
return --_z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
240
Dashboard.OpenGL/Drawing/ImmediateMode.cs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime;
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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(in RectangleDrawInfo rectangle)
|
||||||
|
{
|
||||||
|
// TODO: implement this better.
|
||||||
|
int z = Context.ExtensionRequire<IDeviceContextBase>().IncrementZ();
|
||||||
|
Color color = (rectangle.Fill as SolidColorBrush)?.Color ?? Color.LightGray;
|
||||||
|
Vector4 colorV = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||||
|
Vector4 margin = rectangle.Box.Margin;
|
||||||
|
Vector4 border = rectangle.Box.Border;
|
||||||
|
Vector2 size = rectangle.Box.Size + new Vector2(border.X + border.Z, border.Y = border.W);
|
||||||
|
Box2d box = Box2d.FromPositionAndSize(rectangle.Position + new Vector2(margin.X + border.X, margin.Y + border.Y), size);
|
||||||
|
|
||||||
|
Rectangle(box, z, colorV);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
169
Dashboard.OpenGL/GLDeviceContext.cs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.OpenGL.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
using Dashboard.Windowing;
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL
|
||||||
|
{
|
||||||
|
internal class GLContextBindingsContext(IGLContext context) : IBindingsContext
|
||||||
|
{
|
||||||
|
public IntPtr GetProcAddress(string procName)
|
||||||
|
{
|
||||||
|
return context.GetProcAddress(procName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GLDeviceContext : DeviceContext
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
public Version GLVersion { get; }
|
||||||
|
public string GLRenderer { get; }
|
||||||
|
public string GLVendor { get; }
|
||||||
|
public ImmutableHashSet<string> Extensions { get; }
|
||||||
|
|
||||||
|
public Thread RendererThread { get; } = Thread.CurrentThread;
|
||||||
|
public bool IsRenderThread => RendererThread == Thread.CurrentThread;
|
||||||
|
|
||||||
|
private readonly ConcurrentQueue<Task> _beforeDrawActions = new ConcurrentQueue<Task>();
|
||||||
|
private readonly ConcurrentQueue<Task> _afterDrawActions = new ConcurrentQueue<Task>();
|
||||||
|
|
||||||
|
public GLDeviceContext(Application app, IWindow? window, IGLContext context) : base(app, window)
|
||||||
|
{
|
||||||
|
GLContext = context;
|
||||||
|
context.MakeCurrent();
|
||||||
|
GLLoader.LoadBindings(new GLContextBindingsContext(context));
|
||||||
|
|
||||||
|
context.Disposed += Dispose;
|
||||||
|
|
||||||
|
GL.GetInteger(GetPName.MajorVersion, out int major);
|
||||||
|
GL.GetInteger(GetPName.MinorVersion, out int minor);
|
||||||
|
GLVersion = new Version(major, minor);
|
||||||
|
|
||||||
|
GLRenderer = GL.GetString(StringName.Renderer) ?? string.Empty;
|
||||||
|
GLVendor = GL.GetString(StringName.Vendor) ?? string.Empty;
|
||||||
|
|
||||||
|
HashSet<string> extensions = new HashSet<string>();
|
||||||
|
GL.GetInteger(GetPName.NumExtensions, out int extensionCount);
|
||||||
|
for (uint i = 0; i < extensionCount; i++)
|
||||||
|
{
|
||||||
|
string? ext = GL.GetStringi(StringName.Extensions, i);
|
||||||
|
if (ext != null)
|
||||||
|
extensions.Add(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
Extensions = extensions.ToImmutableHashSet();
|
||||||
|
|
||||||
|
ExtensionPreload<DeviceContextBase>();
|
||||||
|
ExtensionPreload<GLTextureExtension>();
|
||||||
|
ExtensionPreload<ImmediateMode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsGLExtensionAvailable(string name)
|
||||||
|
{
|
||||||
|
return Extensions.Contains(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AssertGLExtension(string name)
|
||||||
|
{
|
||||||
|
if (IsGLExtensionAvailable(name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
throw new NotSupportedException($"The OpenGL extension \"{name}\" is not supported by this context.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokeBeforeDraw(Task task) => _beforeDrawActions.Enqueue(task);
|
||||||
|
|
||||||
|
public void InvokeAfterDraw(Task task) => _afterDrawActions.Enqueue(task);
|
||||||
|
|
||||||
|
public Task InvokeBeforeDraw(Action action)
|
||||||
|
{
|
||||||
|
Task task = new Task(action);
|
||||||
|
_beforeDrawActions.Enqueue(task);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InvokeAfterDraw(Action action)
|
||||||
|
{
|
||||||
|
Task task = new Task(action);
|
||||||
|
_afterDrawActions.Enqueue(task);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<T> InvokeBeforeDraw<T>(Func<T> function)
|
||||||
|
{
|
||||||
|
Task<T> task = new Task<T>(function);
|
||||||
|
_beforeDrawActions.Enqueue(task);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<T> InvokeAfterDraw<T>(Func<T> function)
|
||||||
|
{
|
||||||
|
Task<T> task = new Task<T>(function);
|
||||||
|
_afterDrawActions.Enqueue(task);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InvokeOnRenderThread(Action action)
|
||||||
|
{
|
||||||
|
if (IsRenderThread)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvokeBeforeDraw(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<T> InvokeOnRenderThread<T>(Func<T> function)
|
||||||
|
{
|
||||||
|
return IsRenderThread ? Task.FromResult(function()) : InvokeBeforeDraw(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Begin()
|
||||||
|
{
|
||||||
|
base.Begin();
|
||||||
|
|
||||||
|
GLContext.MakeCurrent();
|
||||||
|
IDeviceContextBase dc = ExtensionRequire<IDeviceContextBase>();
|
||||||
|
dc.ResetClip();
|
||||||
|
dc.ResetTransforms();
|
||||||
|
|
||||||
|
while (_beforeDrawActions.TryDequeue(out Task? action))
|
||||||
|
{
|
||||||
|
action.RunSynchronously();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void End()
|
||||||
|
{
|
||||||
|
base.End();
|
||||||
|
|
||||||
|
while (_afterDrawActions.TryDequeue(out Task? action))
|
||||||
|
{
|
||||||
|
action.RunSynchronously();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (isDisposing)
|
||||||
|
{
|
||||||
|
GLContext.Disposed -= Dispose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
306
Dashboard.OpenGL/GLTextureExtension.cs
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OGL = OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL
|
||||||
|
{
|
||||||
|
public class GLTextureExtension : ITextureExtension, IContextExtensionBase<GLDeviceContext>
|
||||||
|
{
|
||||||
|
public string DriverName => "Dashboard OpenGL Texture Extension";
|
||||||
|
public string DriverVendor => "Dashboard";
|
||||||
|
public Version DriverVersion => new Version(0, 1, 0);
|
||||||
|
public GLDeviceContext Context { get; private set; } = null!;
|
||||||
|
public bool SupportsArbTextureStorage { get; private set; }
|
||||||
|
public bool SupportsAnisotropy { get; private set; }
|
||||||
|
|
||||||
|
IContextBase IContextExtensionBase.Context => Context;
|
||||||
|
DeviceContext IContextExtensionBase<DeviceContext>.Context => Context;
|
||||||
|
|
||||||
|
private List<GLTexture> _textures = new List<GLTexture>();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Require(GLDeviceContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
|
||||||
|
SupportsArbTextureStorage = Context.DriverVersion >= new Version(4, 2) ||
|
||||||
|
Context.IsGLExtensionAvailable("GL_ARB_texture_storage");
|
||||||
|
SupportsAnisotropy = Context.DriverVersion >= new Version() ||
|
||||||
|
Context.IsGLExtensionAvailable("GL_EXT_texture_filter_anisotropic") ||
|
||||||
|
Context.IsGLExtensionAvailable("GL_ARB_texture_filter_anisotropic");
|
||||||
|
}
|
||||||
|
public void Require(DeviceContext context) => Require((GLDeviceContext)context);
|
||||||
|
public void Require(IContextBase context) => Require((GLDeviceContext)context);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public GLTexture CreateTexture(TextureType type)
|
||||||
|
{
|
||||||
|
GLTexture texture = new GLTexture(this, type);
|
||||||
|
lock (_textures) _textures.Add(texture);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TextureDisposed(GLTexture texture)
|
||||||
|
{
|
||||||
|
lock (_textures) _textures.Remove(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
ITexture ITextureExtension.CreateTexture(TextureType type) => CreateTexture(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GLTexture(GLTextureExtension extension, TextureType type) : ITexture
|
||||||
|
{
|
||||||
|
public int Handle { get; private set; } = 0;
|
||||||
|
public bool IsValid => Handle != 0;
|
||||||
|
|
||||||
|
public TextureType Type { get; } = type;
|
||||||
|
public PixelFormat Format { get; private set; } = PixelFormat.Rgba8I;
|
||||||
|
public ColorSwizzle Swizzle { get; set; } = ColorSwizzle.Default;
|
||||||
|
public TextureFilter MinifyFilter { get; set; } = TextureFilter.Linear;
|
||||||
|
public TextureFilter MagnifyFilter { get; set; } = TextureFilter.Linear;
|
||||||
|
public Color BorderColor { get; set; } = Color.White;
|
||||||
|
public TextureRepeat RepeatS { get; set; } = TextureRepeat.Repeat;
|
||||||
|
public TextureRepeat RepeatT { get; set; } = TextureRepeat.Repeat;
|
||||||
|
public TextureRepeat RepeatR { get; set; } = TextureRepeat.Repeat;
|
||||||
|
public int Anisotropy { get; set; } = 0;
|
||||||
|
|
||||||
|
public int Width { get; private set; } = 0;
|
||||||
|
public int Height { get; private set; } = 0;
|
||||||
|
public int Depth { get; private set; } = 0;
|
||||||
|
public int Levels { get; private set; } = 0;
|
||||||
|
public bool Premultiplied { get; set; } = false;
|
||||||
|
|
||||||
|
private TextureTarget Target { get; } = type switch
|
||||||
|
{
|
||||||
|
TextureType.Texture1D => TextureTarget.Texture1d,
|
||||||
|
TextureType.Texture2D => TextureTarget.Texture2d,
|
||||||
|
TextureType.Texture3D => TextureTarget.Texture3d,
|
||||||
|
TextureType.Texture2DArray => TextureTarget.Texture2dArray,
|
||||||
|
TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
|
||||||
|
_ => throw new NotSupportedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Context.InvokeBeforeDraw(() => SetStorage(format, width, height, depth, levels)).Wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (levels == 0)
|
||||||
|
{
|
||||||
|
levels = Math.Max(Math.ILogB(width), Math.ILogB(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind();
|
||||||
|
SizedInternalFormat glFormat = GetFormat(format);
|
||||||
|
if (Extension.SupportsArbTextureStorage)
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case TextureType.Texture1D:
|
||||||
|
GL.TexStorage1D(Target, levels, glFormat, width);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture2D:
|
||||||
|
GL.TexStorage2D(Target, levels, glFormat, width, height);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture3D:
|
||||||
|
case TextureType.Texture2DArray:
|
||||||
|
case TextureType.Texture2DCube:
|
||||||
|
GL.TexStorage3D(Target, levels, glFormat, width, height, depth);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case TextureType.Texture1D:
|
||||||
|
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.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.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
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
OGL::PixelFormat glFormat = format switch
|
||||||
|
{
|
||||||
|
PixelFormat.R8I or PixelFormat.R16F => OGL.PixelFormat.Red,
|
||||||
|
PixelFormat.Rg8I or PixelFormat.Rg16F => OGL.PixelFormat.Rg,
|
||||||
|
PixelFormat.Rgb8I or PixelFormat.Rgb16F => OGL.PixelFormat.Rgb,
|
||||||
|
PixelFormat.Rgba8I or PixelFormat.Rgba16F => OGL.PixelFormat.Rgba,
|
||||||
|
_ => throw new NotSupportedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
PixelType glType = format switch
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
GL.PixelStorei(PixelStoreParameter.UnpackAlignment, align);
|
||||||
|
fixed (T* ptr = buffer)
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case TextureType.Texture1D:
|
||||||
|
GL.TexSubImage1D(Target, level, 0, Width, glFormat, glType, ptr);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture2D:
|
||||||
|
GL.TexSubImage2D(Target, level, 0, 0, Width, Height, glFormat, glType, ptr);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture2DCube:
|
||||||
|
case TextureType.Texture3D:
|
||||||
|
case TextureType.Texture2DArray:
|
||||||
|
GL.TexSubImage3D(Target, level, 0, 0, 0, Width, Height, Depth, glFormat, glType, ptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Premultiply()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unmultiply()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GenerateMipmaps()
|
||||||
|
{
|
||||||
|
if (!Context.IsRenderThread)
|
||||||
|
{
|
||||||
|
Context.InvokeBeforeDraw(GenerateMipmaps).Wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind();
|
||||||
|
GL.GenerateMipmap(Target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Handle = GL.GenTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.BindTexture(Target, Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SizedInternalFormat GetFormat(PixelFormat format)
|
||||||
|
{
|
||||||
|
return format switch
|
||||||
|
{
|
||||||
|
PixelFormat.R8I => SizedInternalFormat.R8,
|
||||||
|
PixelFormat.R16F => SizedInternalFormat.R16f,
|
||||||
|
PixelFormat.Rg8I => SizedInternalFormat.Rg8,
|
||||||
|
PixelFormat.Rg16F => SizedInternalFormat.Rg16f,
|
||||||
|
PixelFormat.Rgb8I => SizedInternalFormat.Rgb8,
|
||||||
|
PixelFormat.Rgb16F => SizedInternalFormat.Rgb16f,
|
||||||
|
PixelFormat.Rgba8I => SizedInternalFormat.Rgba8,
|
||||||
|
PixelFormat.Rgba16F => SizedInternalFormat.Rgba16f,
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PixelFormat GetFormat(SizedInternalFormat format)
|
||||||
|
{
|
||||||
|
return format switch
|
||||||
|
{
|
||||||
|
SizedInternalFormat.R8 => PixelFormat.R8I,
|
||||||
|
SizedInternalFormat.R16f => PixelFormat.R16F,
|
||||||
|
SizedInternalFormat.Rg8 => PixelFormat.Rg8I,
|
||||||
|
SizedInternalFormat.Rg16f => PixelFormat.Rg16F,
|
||||||
|
SizedInternalFormat.Rgb8 => PixelFormat.Rgb8I,
|
||||||
|
SizedInternalFormat.Rgb16f => PixelFormat.Rgb16F,
|
||||||
|
SizedInternalFormat.Rgba8 => PixelFormat.Rgba8I,
|
||||||
|
SizedInternalFormat.Rgba16f => PixelFormat.Rgba16F,
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Drawing;
|
using System.Numerics;
|
||||||
using Dashboard.Windowing;
|
using Dashboard.Windowing;
|
||||||
|
|
||||||
namespace Dashboard.OpenGL
|
namespace Dashboard.OpenGL
|
||||||
@@ -17,7 +17,7 @@ namespace Dashboard.OpenGL
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The size of the framebuffer in pixels.
|
/// The size of the framebuffer in pixels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Size FramebufferSize { get; }
|
public Vector2 FramebufferSize { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the context is disposed.
|
/// Called when the context is disposed.
|
||||||
@@ -28,5 +28,7 @@ namespace Dashboard.OpenGL
|
|||||||
/// Activate this OpenGL Context.
|
/// Activate this OpenGL Context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void MakeCurrent();
|
void MakeCurrent();
|
||||||
|
|
||||||
|
IntPtr GetProcAddress(string procName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Dashboard.Drawing.OpenGL
|
namespace Dashboard.OpenGL
|
||||||
{
|
{
|
||||||
public static class ShaderUtil
|
public static class ShaderUtil
|
||||||
{
|
{
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
285
Dashboard.OpenTK/PAL2/Pal2Application.cs
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
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();
|
||||||
|
CancellationToken?.Register(() =>
|
||||||
|
{
|
||||||
|
TK.Window.PostUserEvent(new ApplicationQuitEventArgs());
|
||||||
|
});
|
||||||
|
|
||||||
|
EventQueue.EventRaised += OnEventRaised;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveWindow(PhysicalWindow window)
|
||||||
|
{
|
||||||
|
_windows.Remove(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RunEvents(bool wait)
|
||||||
|
{
|
||||||
|
if (_windows.Count == 0)
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.UserMessage:
|
||||||
|
if (args is ApplicationQuitEventArgs)
|
||||||
|
{
|
||||||
|
Quit = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Mouse Events
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyboard & Text Events
|
||||||
|
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.TextInput:
|
||||||
|
{
|
||||||
|
OPENTK.TextInputEventArgs textInput = (OPENTK.TextInputEventArgs)args;
|
||||||
|
DB.TextInputEventArgs textInput2 = new DB.TextInputEventArgs(textInput.Text);
|
||||||
|
info.Window.SendEvent(this, textInput2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PlatformEventType.TextEditing:
|
||||||
|
{
|
||||||
|
TextEditingEventArgs textEditing = (TextEditingEventArgs)args;
|
||||||
|
TextEditEventArgs textEditing2 = new TextEditEventArgs(textEditing.Candidate, textEditing.Cursor, textEditing.Length);
|
||||||
|
info.Window.SendEvent(this, textEditing2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window/Surface related events.
|
||||||
|
case PlatformEventType.Close:
|
||||||
|
{
|
||||||
|
info.Window.SendEvent(this, new WindowCloseEvent());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PlatformEventType.WindowFramebufferResize:
|
||||||
|
{
|
||||||
|
var resize = (WindowFramebufferResizeEventArgs)args;
|
||||||
|
info.Window.SendEvent(this, new ResizeEventArgs());
|
||||||
|
info.Window.SendEvent(this, new PaintEventArgs(info.Window.DeviceContext));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PlatformEventType.WindowResize:
|
||||||
|
{
|
||||||
|
var resize = (WindowResizeEventArgs)args;
|
||||||
|
info.Window.SendEvent(this, new ResizeEventArgs());
|
||||||
|
info.Window.SendEvent(this, new PaintEventArgs(info.Window.DeviceContext));
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ApplicationQuitEventArgs() : EventArgs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using Dashboard.Windowing;
|
|
||||||
using OpenTK.Graphics;
|
|
||||||
using OpenTK.Platform;
|
|
||||||
using TK = OpenTK.Platform.Toolkit;
|
|
||||||
|
|
||||||
namespace Dashboard.OpenTK.PAL2
|
|
||||||
{
|
|
||||||
public class Pal2DashboardBackend : IDashboardBackend
|
|
||||||
{
|
|
||||||
public GraphicsApiHints GraphicsApiHints { get; set; } = new OpenGLGraphicsApiHints();
|
|
||||||
public bool OpenGLBindingsInitialized { get; set; } = false;
|
|
||||||
|
|
||||||
public IPhysicalWindow CreatePhysicalWindow()
|
|
||||||
{
|
|
||||||
PhysicalWindow window = new PhysicalWindow(GraphicsApiHints);
|
|
||||||
|
|
||||||
if (!OpenGLBindingsInitialized)
|
|
||||||
{
|
|
||||||
OpenGLBindingsInitialized = true;
|
|
||||||
GLLoader.LoadBindings(
|
|
||||||
new Pal2BindingsContext(TK.OpenGL,
|
|
||||||
((OpenGLDeviceContext)window.DeviceContext).ContextHandle));
|
|
||||||
}
|
|
||||||
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IWindow CreateWindow()
|
|
||||||
{
|
|
||||||
return CreatePhysicalWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Leave()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunEvents(bool wait)
|
|
||||||
{
|
|
||||||
TK.Window.ProcessEvents(wait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using Dashboard.OpenGL;
|
using Dashboard.OpenGL;
|
||||||
|
using Dashboard.Pal;
|
||||||
using Dashboard.Windowing;
|
using Dashboard.Windowing;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using OpenTK.Platform;
|
using OpenTK.Platform;
|
||||||
@@ -8,31 +9,32 @@ using TK = OpenTK.Platform.Toolkit;
|
|||||||
|
|
||||||
namespace Dashboard.OpenTK.PAL2
|
namespace Dashboard.OpenTK.PAL2
|
||||||
{
|
{
|
||||||
public class OpenGLDeviceContext : IGLContext, IGLDisposable
|
public class Pal2GLContext : IGLContext, IDisposable
|
||||||
{
|
{
|
||||||
public OpenGLContextHandle ContextHandle { get; }
|
public OpenGLContextHandle ContextHandle { get; }
|
||||||
public WindowHandle WindowHandle { get; }
|
public WindowHandle WindowHandle { get; }
|
||||||
|
|
||||||
public ISwapGroup SwapGroup { get; }
|
public ISwapGroup SwapGroup { get; }
|
||||||
|
|
||||||
public int ContextGroup { get; }
|
public int ContextGroup { get; }
|
||||||
|
|
||||||
public Size FramebufferSize
|
public System.Numerics.Vector2 FramebufferSize
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
TK.Window.GetFramebufferSize(WindowHandle, out Vector2i size);
|
TK.Window.GetFramebufferSize(WindowHandle, out Vector2i size);
|
||||||
return new Size(size.X, size.Y);
|
return new System.Numerics.Vector2(size.X, size.Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action? Disposed;
|
public event Action? Disposed;
|
||||||
|
|
||||||
public OpenGLDeviceContext(WindowHandle window, OpenGLContextHandle context, ISwapGroup? group = null)
|
public Pal2GLContext(WindowHandle window, OpenGLContextHandle context)
|
||||||
{
|
{
|
||||||
WindowHandle = window;
|
WindowHandle = window;
|
||||||
ContextHandle = context;
|
ContextHandle = context;
|
||||||
SwapGroup = group ?? new DummySwapGroup(context);
|
SwapGroup = new DummySwapGroup(context);
|
||||||
ContextGroup = GetContextGroup(context);
|
ContextGroup = GetContextGroup(ContextHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MakeCurrent()
|
public void MakeCurrent()
|
||||||
@@ -40,18 +42,18 @@ namespace Dashboard.OpenTK.PAL2
|
|||||||
TK.OpenGL.SetCurrentContext(ContextHandle);
|
TK.OpenGL.SetCurrentContext(ContextHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isDisposed = false;
|
public IntPtr GetProcAddress(string procName)
|
||||||
|
{
|
||||||
|
return TK.OpenGL.GetProcedureAddress(ContextHandle, procName);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose() => Dispose(true);
|
public void Dispose() => Dispose(true);
|
||||||
|
|
||||||
public void Dispose(bool safeExit)
|
protected void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
if (_isDisposed) return;
|
|
||||||
_isDisposed = true;
|
|
||||||
|
|
||||||
if (SwapGroup is IGLDisposable glDisposable)
|
if (SwapGroup is IGLDisposable glDisposable)
|
||||||
{
|
{
|
||||||
glDisposable.Dispose(safeExit);
|
glDisposable.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
else if (SwapGroup is IDisposable disposable)
|
else if (SwapGroup is IDisposable disposable)
|
||||||
{
|
{
|
||||||
@@ -63,6 +65,7 @@ namespace Dashboard.OpenTK.PAL2
|
|||||||
|
|
||||||
private static int _contextGroupId = 0;
|
private static int _contextGroupId = 0;
|
||||||
private static ConcurrentDictionary<OpenGLContextHandle, int> _contextGroupRootContexts = new ConcurrentDictionary<OpenGLContextHandle, int>();
|
private static ConcurrentDictionary<OpenGLContextHandle, int> _contextGroupRootContexts = new ConcurrentDictionary<OpenGLContextHandle, int>();
|
||||||
|
private Size _framebufferSize;
|
||||||
|
|
||||||
private static int GetContextGroup(OpenGLContextHandle handle)
|
private static int GetContextGroup(OpenGLContextHandle handle)
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using Dashboard.Drawing;
|
using System.Net;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Dashboard.Events;
|
using Dashboard.Events;
|
||||||
|
using Dashboard.OpenGL;
|
||||||
|
using Dashboard.Pal;
|
||||||
using Dashboard.Windowing;
|
using Dashboard.Windowing;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using OpenTK.Platform;
|
using OpenTK.Platform;
|
||||||
@@ -10,12 +13,15 @@ using TK = OpenTK.Platform.Toolkit;
|
|||||||
|
|
||||||
namespace Dashboard.OpenTK.PAL2
|
namespace Dashboard.OpenTK.PAL2
|
||||||
{
|
{
|
||||||
public class PhysicalWindow : IPhysicalWindow, IDrawQueuePaintable, IEventListener
|
public class PhysicalWindow : IPhysicalWindow, IEventListener, IDpiAwareWindow
|
||||||
{
|
{
|
||||||
public DrawQueue DrawQueue { get; } = new DrawQueue();
|
private readonly List<IEventListener> _listeners = new List<IEventListener>();
|
||||||
|
|
||||||
|
public Application Application { get; }
|
||||||
public WindowHandle WindowHandle { get; }
|
public WindowHandle WindowHandle { get; }
|
||||||
public IDeviceContext DeviceContext { get; }
|
public DeviceContext DeviceContext { get; }
|
||||||
public bool DoubleBuffered => true; // Always true for OpenTK windows.
|
public bool DoubleBuffered => true; // Always true for OpenTK windows.
|
||||||
|
public IForm? Form { get; set; } = null;
|
||||||
|
|
||||||
public IWindowManager? WindowManager { get; set; }
|
public IWindowManager? WindowManager { get; set; }
|
||||||
|
|
||||||
@@ -45,119 +51,99 @@ namespace Dashboard.OpenTK.PAL2
|
|||||||
set => TK.Window.SetClientSize(WindowHandle, new Vector2i((int)value.Width, (int)value.Height));
|
set => TK.Window.SetClientSize(WindowHandle, new Vector2i((int)value.Width, (int)value.Height));
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler? Painting;
|
public event EventHandler? EventRaised;
|
||||||
public event EventHandler<AnimationTickEventArgs>? AnimationTimerEvent;
|
|
||||||
public event EventHandler<MouseMoveEventArgs>? MouseMoved;
|
|
||||||
public event EventHandler<MouseButtonEventArgs>? MouseButtonDown;
|
|
||||||
public event EventHandler<MouseButtonEventArgs>? MouseButtonUp;
|
|
||||||
public event EventHandler<MouseScrollEventArgs>? MouseScroll;
|
|
||||||
|
|
||||||
public PhysicalWindow(WindowHandle window, IDeviceContext dc)
|
public PhysicalWindow(Application app, WindowHandle window)
|
||||||
{
|
{
|
||||||
|
Application = app;
|
||||||
WindowHandle = window;
|
WindowHandle = window;
|
||||||
DeviceContext = dc;
|
DeviceContext = CreateDeviceContext(app, this, new OpenGLGraphicsApiHints());
|
||||||
AddWindow(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhysicalWindow(WindowHandle window, OpenGLContextHandle context, ISwapGroup? swapGroup = null)
|
public PhysicalWindow(Application app, WindowHandle window, OpenGLContextHandle context)
|
||||||
{
|
{
|
||||||
|
Application = app;
|
||||||
WindowHandle = window;
|
WindowHandle = window;
|
||||||
DeviceContext = new OpenGLDeviceContext(window, context, swapGroup);
|
DeviceContext = new GLDeviceContext(app, this, new Pal2GLContext(window, context));
|
||||||
AddWindow(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhysicalWindow(GraphicsApiHints hints)
|
public PhysicalWindow(Application app, GraphicsApiHints hints)
|
||||||
{
|
{
|
||||||
|
Application = app;
|
||||||
WindowHandle = TK.Window.Create(hints);
|
WindowHandle = TK.Window.Create(hints);
|
||||||
DeviceContext = CreateDeviceContext(WindowHandle, hints);
|
DeviceContext = CreateDeviceContext(app, this, hints);
|
||||||
AddWindow(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IDeviceContext CreateDeviceContext(WindowHandle window, GraphicsApiHints hints)
|
private static DeviceContext CreateDeviceContext(Application app, PhysicalWindow window, GraphicsApiHints hints)
|
||||||
{
|
{
|
||||||
|
WindowHandle handle = window.WindowHandle;
|
||||||
switch (hints.Api)
|
switch (hints.Api)
|
||||||
{
|
{
|
||||||
case GraphicsApi.OpenGL:
|
case GraphicsApi.OpenGL:
|
||||||
case GraphicsApi.OpenGLES:
|
case GraphicsApi.OpenGLES:
|
||||||
OpenGLContextHandle context = TK.OpenGL.CreateFromWindow(window);
|
return new GLDeviceContext(app, window, new Pal2GLContext(handle, TK.OpenGL.CreateFromWindow(handle)));
|
||||||
return new OpenGLDeviceContext(window, context);
|
|
||||||
default:
|
default:
|
||||||
throw new Exception($"Unknown graphics API {hints.Api}.");
|
throw new Exception($"Unknown graphics API {hints.Api}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isDisposed = false;
|
public bool IsDisposed { get; private set; } = false;
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_isDisposed) return;
|
if (IsDisposed) return;
|
||||||
_isDisposed = true;
|
IsDisposed = true;
|
||||||
|
|
||||||
RemoveWindow(this);
|
|
||||||
|
|
||||||
(DeviceContext as IDisposable)?.Dispose();
|
(DeviceContext as IDisposable)?.Dispose();
|
||||||
|
|
||||||
|
((Pal2Application)Application).RemoveWindow(this);
|
||||||
TK.Window.Destroy(WindowHandle);
|
TK.Window.Destroy(WindowHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnPaint()
|
public virtual void SendEvent(object? sender, EventArgs args)
|
||||||
{
|
{
|
||||||
WindowManager?.Paint();
|
args = TransformEvent(sender, args);
|
||||||
Painting?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Paint()
|
Form?.SendEvent(this, args);
|
||||||
{
|
EventRaised?.Invoke(this, args);
|
||||||
DrawQueue.Clear();
|
|
||||||
OnPaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnAnimationTimerEvent(AnimationTickEventArgs ea) =>
|
lock (_listeners)
|
||||||
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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<WindowHandle, PhysicalWindow> _windows =
|
|
||||||
new ConcurrentDictionary<WindowHandle, PhysicalWindow>();
|
|
||||||
|
|
||||||
static PhysicalWindow()
|
|
||||||
{
|
|
||||||
EventQueue.EventRaised += EventQueueOnEventRaised;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddWindow(PhysicalWindow window)
|
|
||||||
{
|
|
||||||
_windows.TryAdd(window.WindowHandle, window);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RemoveWindow(PhysicalWindow window)
|
|
||||||
{
|
|
||||||
_windows.TryRemove(window.WindowHandle, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EventQueueOnEventRaised(PalHandle? handle, PlatformEventType type, EventArgs args)
|
|
||||||
{
|
|
||||||
if (handle is not WindowHandle windowHandle)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_windows.TryGetValue(windowHandle, out PhysicalWindow? window))
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
{
|
||||||
case PlatformEventType.MouseMove:
|
foreach (IEventListener listener in _listeners)
|
||||||
case PlatformEventType.Scroll:
|
listener.SendEvent(this, args);
|
||||||
case PlatformEventType.MouseUp:
|
}
|
||||||
case PlatformEventType.MouseDown:
|
}
|
||||||
break;
|
|
||||||
|
private EventArgs TransformEvent(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
// TODO: future
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SubcribeEvent(IEventListener listener)
|
||||||
|
{
|
||||||
|
lock (_listeners)
|
||||||
|
{
|
||||||
|
_listeners.Add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsubscribeEvent(IEventListener listener)
|
||||||
|
{
|
||||||
|
lock (_listeners)
|
||||||
|
{
|
||||||
|
_listeners.Remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Dpi => Scale * 96f;
|
||||||
|
|
||||||
|
public float Scale
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
TK.Window.GetScaleFactor(WindowHandle, out float x, out float y);
|
||||||
|
return Math.Max(x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.Drawing", "Dashboard.Drawing\Dashboard.Drawing.csproj", "{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}"
|
||||||
@@ -17,16 +15,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Drawing.OpenGL", "Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj", "{454198BA-CB95-41C5-A934-B1C8FDA35A6B}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.ImmediateUI", "Dashboard.ImmediateUI\Dashboard.ImmediateUI.csproj", "{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{9B62A92D-ABF5-4704-B831-FD075515A82F}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{9B62A92D-ABF5-4704-B831-FD075515A82F}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{7B064228-2629-486E-95C6-BDDD4B4602C4}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{7B064228-2629-486E-95C6-BDDD4B4602C4}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenGL", "Dashboard.OpenGL\Dashboard.OpenGL.csproj", "{33EB657C-B53A-41B4-BC3C-F38C09ABA577}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenGL", "Dashboard.OpenGL\Dashboard.OpenGL.csproj", "{33EB657C-B53A-41B4-BC3C-F38C09ABA577}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.StbImage", "Dashboard.StbImage\Dashboard.StbImage.csproj", "{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText.OpenGL", "Dashboard.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj", "{14616F42-663B-4673-8561-5637FAD1B22F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText", "Dashboard.BlurgText\Dashboard.BlurgText.csproj", "{8C68EFB6-B477-48EC-9AAA-31E89883482B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -37,10 +37,6 @@ Global
|
|||||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.Build.0 = Release|Any CPU
|
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{7C90B90B-DF31-439B-9080-CD805383B014}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -49,14 +45,6 @@ Global
|
|||||||
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{3F33197F-0B7B-4CD8-98BD-05D6D5EC76B2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -65,6 +53,18 @@ Global
|
|||||||
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.Build.0 = Release|Any CPU
|
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{14616F42-663B-4673-8561-5637FAD1B22F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{14616F42-663B-4673-8561-5637FAD1B22F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{14616F42-663B-4673-8561-5637FAD1B22F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{14616F42-663B-4673-8561-5637FAD1B22F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -72,5 +72,8 @@ Global
|
|||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{7C90B90B-DF31-439B-9080-CD805383B014} = {9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}
|
{7C90B90B-DF31-439B-9080-CD805383B014} = {9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}
|
||||||
{7B064228-2629-486E-95C6-BDDD4B4602C4} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
{7B064228-2629-486E-95C6-BDDD4B4602C4} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
||||||
|
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
||||||
|
{14616F42-663B-4673-8561-5637FAD1B22F} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
||||||
|
{8C68EFB6-B477-48EC-9AAA-31E89883482B} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using Dashboard.Windowing;
|
|
||||||
|
|
||||||
namespace Dashboard
|
|
||||||
{
|
|
||||||
public class Application : IDisposable
|
|
||||||
{
|
|
||||||
public IDashboardBackend Backend { get; }
|
|
||||||
public IWindowFactory WindowFactory { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; } = "Dashboard Application";
|
|
||||||
|
|
||||||
protected bool IsInitialized { get; private set; } = false;
|
|
||||||
protected bool IsDisposed { get; private set; } = false;
|
|
||||||
|
|
||||||
private readonly List<IPhysicalWindow> _physicalWindows = new List<IPhysicalWindow>();
|
|
||||||
|
|
||||||
public event EventHandler? PreInitializing;
|
|
||||||
public event EventHandler? Initializing;
|
|
||||||
public event EventHandler? PostInitializing;
|
|
||||||
public event EventHandler? Leaving;
|
|
||||||
|
|
||||||
public Application(IDashboardBackend backend)
|
|
||||||
{
|
|
||||||
Backend = backend;
|
|
||||||
WindowFactory = backend;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void PreInitialize()
|
|
||||||
{
|
|
||||||
PreInitializing?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Initialize()
|
|
||||||
{
|
|
||||||
Backend.Initialize();
|
|
||||||
Initializing?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void PostInitialize()
|
|
||||||
{
|
|
||||||
PostInitializing?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeInternal()
|
|
||||||
{
|
|
||||||
if (IsInitialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
IsInitialized = true;
|
|
||||||
|
|
||||||
PreInitialize();
|
|
||||||
Initialize();
|
|
||||||
PostInitialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void RunEvents(bool wait)
|
|
||||||
{
|
|
||||||
if (!IsInitialized)
|
|
||||||
throw new InvalidOperationException("The application is not initialized. Cannot run events at this time.");
|
|
||||||
|
|
||||||
Backend.RunEvents(wait);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Leave()
|
|
||||||
{
|
|
||||||
Backend.Leave();
|
|
||||||
Leaving?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run() => Run(true, CancellationToken.None);
|
|
||||||
public void Run(bool wait) => Run(wait, CancellationToken.None);
|
|
||||||
|
|
||||||
public void Run(bool waitForEvents, CancellationToken token)
|
|
||||||
{
|
|
||||||
InitializeInternal();
|
|
||||||
|
|
||||||
while (!token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
RunEvents(waitForEvents);
|
|
||||||
|
|
||||||
foreach (IPhysicalWindow window in _physicalWindows)
|
|
||||||
{
|
|
||||||
window.Paint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Leave();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IWindowFactory.CreateWindow"/>
|
|
||||||
public IWindow CreateWindow()
|
|
||||||
{
|
|
||||||
IWindow window = WindowFactory.CreateWindow();
|
|
||||||
|
|
||||||
if (window is IPhysicalWindow physical)
|
|
||||||
_physicalWindows.Add(physical);
|
|
||||||
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IWindowFactory.CreatePhysicalWindow"/>
|
|
||||||
public IPhysicalWindow CreatePhysicalWindow()
|
|
||||||
{
|
|
||||||
IPhysicalWindow window = WindowFactory.CreatePhysicalWindow();
|
|
||||||
_physicalWindows.Add(window);
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void InvokeDispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (IsDisposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
IsDisposed = true;
|
|
||||||
|
|
||||||
Dispose(disposing);
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() => InvokeDispose(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
69
Dashboard/Controls/Button.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Layout;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Controls
|
||||||
|
{
|
||||||
|
public class Button : Control
|
||||||
|
{
|
||||||
|
private Vector2 _intrinsicSize = Vector2.Zero;
|
||||||
|
|
||||||
|
public bool AutoSize { get; set; } = true;
|
||||||
|
public string Text { get; set; } = "Click!";
|
||||||
|
|
||||||
|
public Font Font { get; set; } = Font.Create(new FontInfo("Rec Mono Linear"));
|
||||||
|
public float TextSize { get; set; } = 12f;
|
||||||
|
public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black);
|
||||||
|
public Brush ButtonBrush { get; set; } = new SolidColorBrush(Color.DarkSlateGray);
|
||||||
|
|
||||||
|
public Vector2 Padding { get; set; } = new Vector2(4, 4);
|
||||||
|
|
||||||
|
public event EventHandler? Clicked;
|
||||||
|
|
||||||
|
public override Vector2 CalculateIntrinsicSize()
|
||||||
|
{
|
||||||
|
return _intrinsicSize + 2 * Padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CalculateSize(DeviceContext dc)
|
||||||
|
{
|
||||||
|
Box2d box = dc.ExtensionRequire<ITextRenderer>().MeasureText(Font.Base, TextSize, Text);
|
||||||
|
_intrinsicSize = box.Size;
|
||||||
|
// Layout.Size = box.Size;
|
||||||
|
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPaint(DeviceContext dc)
|
||||||
|
{
|
||||||
|
base.OnPaint(dc);
|
||||||
|
|
||||||
|
if (AutoSize)
|
||||||
|
CalculateSize(dc);
|
||||||
|
|
||||||
|
bool hidden = Layout.OverflowMode == OverflowMode.Hidden;
|
||||||
|
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
|
||||||
|
if (hidden)
|
||||||
|
dcb.PushScissor(ClientArea);
|
||||||
|
|
||||||
|
dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0));
|
||||||
|
|
||||||
|
var imm = dc.ExtensionRequire<IImmediateMode>();
|
||||||
|
Color color = (ButtonBrush as SolidColorBrush)?.Color ?? Color.Black;
|
||||||
|
Vector4 colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||||
|
imm.Rectangle(ClientArea, 0, colorVector);
|
||||||
|
|
||||||
|
var text = dc.ExtensionRequire<ITextRenderer>();
|
||||||
|
color = (TextBrush as SolidColorBrush)?.Color ?? Color.Black;
|
||||||
|
colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||||
|
text.DrawText(Vector2.Zero, colorVector, TextSize, Font.Base, Text);
|
||||||
|
|
||||||
|
if (hidden)
|
||||||
|
dcb.PopScissor();
|
||||||
|
|
||||||
|
dcb.PopTransforms();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Dashboard.Controls
|
|
||||||
{
|
|
||||||
public enum ClassChangeType
|
|
||||||
{
|
|
||||||
Added,
|
|
||||||
Removed,
|
|
||||||
}
|
|
||||||
|
|
||||||
public record struct ClassChanged(object? Owner, string ClassName, ClassChangeType Type);
|
|
||||||
|
|
||||||
public class ClassSet : ICollection<string>
|
|
||||||
{
|
|
||||||
public int Count => _classes.Count;
|
|
||||||
public bool IsReadOnly => false;
|
|
||||||
public object? Owner { get; }
|
|
||||||
public event EventHandler<ClassChanged>? ClassChanged;
|
|
||||||
|
|
||||||
private readonly HashSet<string> _classes = new HashSet<string>();
|
|
||||||
|
|
||||||
public ClassSet(object? owner)
|
|
||||||
{
|
|
||||||
Owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<string> GetEnumerator() => _classes.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(string item)
|
|
||||||
{
|
|
||||||
if (_classes.Add(item))
|
|
||||||
{
|
|
||||||
OnClassAdded(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
foreach (string @class in _classes)
|
|
||||||
OnClassRemoved(@class);
|
|
||||||
|
|
||||||
_classes.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(string item) => _classes.Contains(item);
|
|
||||||
|
|
||||||
public void CopyTo(string[] array, int arrayIndex) => _classes.CopyTo(array, arrayIndex);
|
|
||||||
|
|
||||||
public bool Remove(string item)
|
|
||||||
{
|
|
||||||
if (_classes.Remove(item))
|
|
||||||
{
|
|
||||||
OnClassRemoved(item);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClassAdded(string @class)
|
|
||||||
{
|
|
||||||
ClassChanged?.Invoke(this, new ClassChanged(Owner, @class, ClassChangeType.Added));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClassRemoved(string @class)
|
|
||||||
{
|
|
||||||
ClassChanged?.Invoke(this, new ClassChanged(Owner, @class, ClassChangeType.Removed));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
145
Dashboard/Controls/Container.cs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Events;
|
||||||
|
using Dashboard.Layout;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Controls
|
||||||
|
{
|
||||||
|
public class Container : Control, IList<Control>, ILayoutContainer
|
||||||
|
{
|
||||||
|
private readonly List<Control> _controls = new List<Control>();
|
||||||
|
|
||||||
|
public int Count => _controls.Count;
|
||||||
|
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
public ContainerLayoutInfo ContainerLayout { get; } = new ContainerLayoutInfo();
|
||||||
|
|
||||||
|
public event EventHandler<ContainerChildAddedEventArgs>? ChildAdded;
|
||||||
|
public event EventHandler<ContainerChildRemovedEventArgs>? ChildRemoved;
|
||||||
|
|
||||||
|
|
||||||
|
public Control this[int index]
|
||||||
|
{
|
||||||
|
get => _controls[index];
|
||||||
|
set => _controls[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ValidateLayout()
|
||||||
|
{
|
||||||
|
if (!IsLayoutEnabled || IsLayoutValid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// LayoutSolution solution = LayoutSolution.CalculateLayout(this, ClientArea.Size);
|
||||||
|
|
||||||
|
base.ValidateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPaint(DeviceContext dc)
|
||||||
|
{
|
||||||
|
base.OnPaint(dc);
|
||||||
|
|
||||||
|
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
|
||||||
|
dcb.PushClip(ClientArea);
|
||||||
|
ValidateLayout();
|
||||||
|
|
||||||
|
foreach (Control child in _controls)
|
||||||
|
{
|
||||||
|
if (child.Layout.DisplayMode == DisplayMode.None)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
child.SendEvent(this, new PaintEventArgs(dc));
|
||||||
|
}
|
||||||
|
|
||||||
|
dcb.PopClip();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator<ILayoutItem> IEnumerable<ILayoutItem>.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<Control> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _controls.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return ((IEnumerable)_controls).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(Control item)
|
||||||
|
{
|
||||||
|
SetParent(this, item);
|
||||||
|
|
||||||
|
_controls.Add(item);
|
||||||
|
ChildAdded?.Invoke(this, new ContainerChildAddedEventArgs(this, item));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
foreach (Control control in this)
|
||||||
|
{
|
||||||
|
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, control));
|
||||||
|
}
|
||||||
|
_controls.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(Control item)
|
||||||
|
{
|
||||||
|
return _controls.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(Control[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
_controls.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(Control item)
|
||||||
|
{
|
||||||
|
if (!_controls.Remove(item))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, item));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf(Control item)
|
||||||
|
{
|
||||||
|
return _controls.IndexOf(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, Control item)
|
||||||
|
{
|
||||||
|
SetParent(this, item);
|
||||||
|
|
||||||
|
_controls.Insert(index, item);
|
||||||
|
ChildAdded?.Invoke(this, new ContainerChildAddedEventArgs(this, item));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
Control child = _controls[index];
|
||||||
|
_controls.RemoveAt(index);
|
||||||
|
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, child));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ContainerChildAddedEventArgs(Container parent, Control child) : EventArgs
|
||||||
|
{
|
||||||
|
public Container Parent { get; } = parent;
|
||||||
|
public Control Child { get; } = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ContainerChildRemovedEventArgs(Container parent, Control child) : EventArgs
|
||||||
|
{
|
||||||
|
public Container Parent { get; } = parent;
|
||||||
|
public Control Child { get; } = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,72 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
using Dashboard.Drawing;
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Events;
|
||||||
|
using Dashboard.Layout;
|
||||||
|
using Dashboard.Pal;
|
||||||
using Dashboard.Windowing;
|
using Dashboard.Windowing;
|
||||||
|
|
||||||
namespace Dashboard.Controls
|
namespace Dashboard.Controls
|
||||||
{
|
{
|
||||||
public class Control : IEventListener, IDrawQueuePaintable, IDisposable
|
public class Control : IEventListener, ILayoutItem, IDisposable
|
||||||
{
|
{
|
||||||
|
private Form? _owner = null;
|
||||||
|
|
||||||
public string? Id { get; set; }
|
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 Control? Parent { get; private set; } = null;
|
||||||
public bool Disposed { get; private set; }
|
public bool Disposed { get; private set; }
|
||||||
public virtual DrawQueue DrawQueue => Owner?.DrawQueue ?? throw NoOwnerException;
|
|
||||||
public virtual Box2d ClientArea { get; set; }
|
public virtual Box2d ClientArea { get; set; }
|
||||||
|
public bool IsFocused => _owner?.FocusedControl == this;
|
||||||
|
|
||||||
public event EventHandler? Painting;
|
public Brush Background { get; set; } = new SolidColorBrush(Color.Transparent);
|
||||||
|
public Brush BorderBrush { get; set; } = new SolidColorBrush(Color.Black);
|
||||||
|
|
||||||
|
public LayoutInfo Layout { get; } = new LayoutInfo();
|
||||||
|
public bool IsLayoutEnabled { get; private set; } = true;
|
||||||
|
protected bool IsLayoutValid { get; set; } = false;
|
||||||
|
|
||||||
|
public event EventHandler<DeviceContext>? Painting;
|
||||||
|
public event EventHandler<TickEventArgs>? AnimationTick;
|
||||||
public event EventHandler? OwnerChanged;
|
public event EventHandler? OwnerChanged;
|
||||||
public event EventHandler? ParentChanged;
|
public event EventHandler? ParentChanged;
|
||||||
|
public event EventHandler? FocusGained;
|
||||||
|
public event EventHandler? FocusLost;
|
||||||
public event EventHandler? Disposing;
|
public event EventHandler? Disposing;
|
||||||
public event EventHandler? Resized;
|
public event EventHandler? Resized;
|
||||||
|
|
||||||
public Control()
|
public virtual Vector2 CalculateIntrinsicSize()
|
||||||
{
|
{
|
||||||
Classes = new ClassSet(this);
|
return Vector2.Zero;
|
||||||
|
// return Vector2.Max(Vector2.Zero, Vector2.Max(Layout.Size, Layout.MinimumSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnPaint()
|
public Vector2 CalculateSize(Vector2 limits)
|
||||||
{
|
{
|
||||||
Painting?.Invoke(this, EventArgs.Empty);
|
return CalculateIntrinsicSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Paint()
|
public virtual void OnPaint(DeviceContext dc)
|
||||||
{
|
{
|
||||||
OnPaint();
|
Painting?.Invoke(this, dc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnAnimationTick(TickEventArgs tick)
|
||||||
|
{
|
||||||
|
AnimationTick?.Invoke(this, tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void InvokeDispose(bool disposing)
|
protected void InvokeDispose(bool disposing)
|
||||||
@@ -49,20 +83,99 @@ namespace Dashboard.Controls
|
|||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
if (disposing) Disposing?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => InvokeDispose(true);
|
public void Dispose() => InvokeDispose(true);
|
||||||
|
|
||||||
public void SendEvent(EventArgs args)
|
public event EventHandler? EventRaised;
|
||||||
|
|
||||||
|
protected virtual void OnEventRaised(object? sender, EventArgs args)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
switch (args)
|
||||||
|
{
|
||||||
|
case PaintEventArgs paint:
|
||||||
|
OnPaint(paint.DeviceContext);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventRaised?.Invoke(this, TransformEvent(sender, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void SetParent(Control parent, Control child)
|
protected virtual EventArgs TransformEvent(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendEvent(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
OnEventRaised(sender, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void SetParent(Container parent, Control child)
|
||||||
{
|
{
|
||||||
child.Parent = parent;
|
child.Parent = parent;
|
||||||
|
child.ParentChanged?.Invoke(child, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Focus()
|
||||||
|
{
|
||||||
|
(Owner ?? throw NoOwnerException).Focus(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnFocusGained(object sender)
|
||||||
|
{
|
||||||
|
FocusGained?.Invoke(sender, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnFocusLost(object sender)
|
||||||
|
{
|
||||||
|
FocusLost?.Invoke(sender, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void InvokeFocusGained(Form form, Control control)
|
||||||
|
{
|
||||||
|
control.OnFocusGained(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void InvokeFocusLost(Form form, Control control)
|
||||||
|
{
|
||||||
|
control.OnFocusLost(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnResize()
|
||||||
|
{
|
||||||
|
Resized?.Invoke(this, EventArgs.Empty);
|
||||||
|
InvalidateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOwnerChanged(Form value)
|
||||||
|
{
|
||||||
|
OwnerChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvalidateLayout()
|
||||||
|
{
|
||||||
|
IsLayoutValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void ValidateLayout()
|
||||||
|
{
|
||||||
|
IsLayoutValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeLayout()
|
||||||
|
{
|
||||||
|
IsLayoutEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SuspendLayout()
|
||||||
|
{
|
||||||
|
IsLayoutEnabled = false;
|
||||||
|
IsLayoutValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Exception NoOwnerException => new Exception("No form owns this control");
|
protected static Exception NoOwnerException => new Exception("No form owns this control");
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
using Dashboard.Drawing;
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Events;
|
||||||
|
using Dashboard.Pal;
|
||||||
using Dashboard.Windowing;
|
using Dashboard.Windowing;
|
||||||
|
|
||||||
namespace Dashboard.Controls
|
namespace Dashboard.Controls
|
||||||
{
|
{
|
||||||
public class Form : Control
|
public class Form : Container, IForm
|
||||||
{
|
{
|
||||||
|
private string? _title = "Untitled Form";
|
||||||
public IWindow Window { get; }
|
public IWindow Window { get; }
|
||||||
public override DrawQueue DrawQueue { get; }
|
|
||||||
|
public Image? WindowIcon { get; set; }
|
||||||
|
|
||||||
|
public string? Title
|
||||||
|
{
|
||||||
|
get => _title;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_title = value;
|
||||||
|
Window.Title = _title ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Brush Background { get; set; } = new SolidColorBrush(Color.SlateGray);
|
||||||
|
public Control? FocusedControl { get; private set; } = null;
|
||||||
|
|
||||||
public override Box2d ClientArea
|
public override Box2d ClientArea
|
||||||
{
|
{
|
||||||
@@ -14,10 +32,64 @@ namespace Dashboard.Controls
|
|||||||
set { }
|
set { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event EventHandler<WindowCloseEvent>? Closing;
|
||||||
|
|
||||||
public Form(IWindow window)
|
public Form(IWindow window)
|
||||||
{
|
{
|
||||||
Window = window;
|
Window = window;
|
||||||
DrawQueue = (window as IDrawQueuePaintable)?.DrawQueue ?? new DrawQueue();
|
window.Form = this;
|
||||||
|
|
||||||
|
Window.Title = _title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Focus(Control control)
|
||||||
|
{
|
||||||
|
if (FocusedControl != null)
|
||||||
|
InvokeFocusLost(this, FocusedControl);
|
||||||
|
|
||||||
|
FocusedControl = control;
|
||||||
|
InvokeFocusGained(this, control);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPaint(DeviceContext dc)
|
||||||
|
{
|
||||||
|
dc.Begin();
|
||||||
|
|
||||||
|
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
|
||||||
|
dcb.ResetClip();
|
||||||
|
dcb.ResetScissor();
|
||||||
|
dcb.ResetTransforms();
|
||||||
|
|
||||||
|
if (Background is SolidColorBrush solidColorBrush)
|
||||||
|
dcb.ClearColor(solidColorBrush.Color);
|
||||||
|
|
||||||
|
foreach (Control child in this)
|
||||||
|
child.SendEvent(this, new PaintEventArgs(dc));
|
||||||
|
|
||||||
|
dc.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnClosing(WindowCloseEvent ea)
|
||||||
|
{
|
||||||
|
Closing?.Invoke(this, ea);
|
||||||
|
|
||||||
|
if (ea.Cancel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
Window.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEventRaised(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
base.OnEventRaised(sender, args);
|
||||||
|
|
||||||
|
switch (args)
|
||||||
|
{
|
||||||
|
case WindowCloseEvent close:
|
||||||
|
OnClosing(close);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
Dashboard/Controls/ImageBox.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Controls
|
||||||
|
{
|
||||||
|
public class ImageBox : Control
|
||||||
|
{
|
||||||
|
public Image? Image { get; set; }
|
||||||
|
|
||||||
|
public override Vector2 CalculateIntrinsicSize()
|
||||||
|
{
|
||||||
|
return new Vector2(Image?.Width ?? 0, Image?.Height ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPaint(DeviceContext dc)
|
||||||
|
{
|
||||||
|
if (Image == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Layout.Size = CalculateIntrinsicSize();
|
||||||
|
|
||||||
|
// dc.ExtensionRequire<IImmediateMode>().Image(new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size), new Box2d(0, 0, 1, 1), 0, Image.InternTexture(dc));
|
||||||
|
base.OnPaint(dc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,36 +2,61 @@ using System;
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dashboard.Drawing;
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Layout;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
namespace Dashboard.Controls
|
namespace Dashboard.Controls
|
||||||
{
|
{
|
||||||
public class Label : Control
|
public class Label : Control
|
||||||
{
|
{
|
||||||
|
private Vector2 _intrinsicSize = Vector2.Zero;
|
||||||
|
|
||||||
public bool AutoSize { get; set; } = true;
|
public bool AutoSize { get; set; } = true;
|
||||||
public string Text { get; set; } = "";
|
public string Text { get; set; } = "";
|
||||||
|
|
||||||
public event EventHandler? TextChanged;
|
public event EventHandler? TextChanged;
|
||||||
|
|
||||||
protected IBrush TextBrush => throw new NotImplementedException();
|
// protected IBrush TextBrush => throw new NotImplementedException();
|
||||||
protected IFont Font => throw new NotImplementedException();
|
public Font Font { get; set; } = Drawing.Font.Create(new FontInfo("Rec Mono Linear"));
|
||||||
|
public float TextSize { get; set; } = 12f;
|
||||||
|
public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black);
|
||||||
|
|
||||||
protected virtual void OnTextChanged(string oldValue, string newValue)
|
public override Vector2 CalculateIntrinsicSize()
|
||||||
{
|
{
|
||||||
|
return _intrinsicSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CalculateSize(DeviceContext dc)
|
||||||
|
{
|
||||||
|
Box2d box = dc.ExtensionRequire<ITextRenderer>().MeasureText(Font.Base, TextSize, Text);
|
||||||
|
// _intrinsicSize = box.Size;
|
||||||
|
// Layout.Size = box.Size;
|
||||||
|
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPaint(DeviceContext dc)
|
||||||
|
{
|
||||||
|
base.OnPaint(dc);
|
||||||
|
|
||||||
if (AutoSize)
|
if (AutoSize)
|
||||||
CalculateSize();
|
CalculateSize(dc);
|
||||||
}
|
|
||||||
|
|
||||||
protected void CalculateSize()
|
bool hidden = Layout.OverflowMode == OverflowMode.Hidden;
|
||||||
{
|
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
|
||||||
SizeF sz = Typesetter.MeasureString(Font, Text);
|
if (hidden)
|
||||||
ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + (Vector2)sz);
|
dcb.PushScissor(ClientArea);
|
||||||
}
|
|
||||||
|
|
||||||
|
dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0));
|
||||||
|
|
||||||
public override void OnPaint()
|
var text = dc.ExtensionRequire<ITextRenderer>();
|
||||||
{
|
Color color = (TextBrush as SolidColorBrush)?.Color ?? Color.Black;
|
||||||
base.OnPaint();
|
Vector4 colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||||
DrawQueue.Text(new Vector3(ClientArea.Min, 0), TextBrush, Text, Font);
|
text.DrawText(Vector2.Zero, colorVector, TextSize, Font.Base, Text);
|
||||||
|
|
||||||
|
if (hidden)
|
||||||
|
dcb.PopScissor();
|
||||||
|
|
||||||
|
dcb.PopTransforms();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
242
Dashboard/Controls/MessageBox.cs
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
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 MessageBoxIcon _icon;
|
||||||
|
private MessageBoxButtons _buttons;
|
||||||
|
private ImageBox _iconBox = new ImageBox();
|
||||||
|
private Label _label = new Label();
|
||||||
|
private Container _main = new Container();
|
||||||
|
private Container _buttonsContainer = new Container();
|
||||||
|
|
||||||
|
private Image? IconImage
|
||||||
|
{
|
||||||
|
get => _iconBox.Image;
|
||||||
|
set => _iconBox.Image = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageBoxIcon Icon
|
||||||
|
{
|
||||||
|
get => _icon;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
IconImage = value switch
|
||||||
|
{
|
||||||
|
MessageBoxIcon.Question => s_questionIcon,
|
||||||
|
MessageBoxIcon.Info => s_infoIcon,
|
||||||
|
MessageBoxIcon.Warning => s_warningIcon,
|
||||||
|
MessageBoxIcon.Error => s_errorIcon,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
_icon = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image? CustomImage
|
||||||
|
{
|
||||||
|
get => Icon == MessageBoxIcon.Custom ? IconImage : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (IconImage == null)
|
||||||
|
return;
|
||||||
|
Icon = MessageBoxIcon.Custom;
|
||||||
|
IconImage = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? Message
|
||||||
|
{
|
||||||
|
get => _label.Text;
|
||||||
|
set => _label.Text = value ?? String.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageBoxButtons Buttons
|
||||||
|
{
|
||||||
|
get => _buttons;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_buttons = value;
|
||||||
|
UpdateButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ObservableCollection<string> CustomButtons { get; } = new ObservableCollection<string>();
|
||||||
|
public int Result { get; private set; } = -1;
|
||||||
|
|
||||||
|
public MessageBox(IWindow window) : base(window)
|
||||||
|
{
|
||||||
|
// Layout.Rows.Clear();
|
||||||
|
// Layout.Rows.Add(-1);
|
||||||
|
// Layout.Rows.Add(48);
|
||||||
|
//
|
||||||
|
// Add(_main);
|
||||||
|
// _main.Layout.Columns.Clear();
|
||||||
|
// _main.Layout.Columns.Add(48);
|
||||||
|
// _main.Layout.Columns.Add(-1);
|
||||||
|
|
||||||
|
_main.Add(_iconBox);
|
||||||
|
_main.Add(_label);
|
||||||
|
_label.Layout.Column = 1;
|
||||||
|
|
||||||
|
Add(_buttonsContainer);
|
||||||
|
_buttonsContainer.Layout.Row = 1;
|
||||||
|
|
||||||
|
CustomButtons.CollectionChanged += (sender, ea) => UpdateButtons();
|
||||||
|
|
||||||
|
UpdateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateButtons()
|
||||||
|
{
|
||||||
|
foreach (Control button in _buttonsContainer)
|
||||||
|
{
|
||||||
|
Remove(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
IList<string> list = Buttons switch
|
||||||
|
{
|
||||||
|
MessageBoxButtons.Custom => CustomButtons,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
// _buttonsContainer.Clear();
|
||||||
|
// _buttonsContainer.Layout.Columns.Clear();
|
||||||
|
// for (int i = 0; i < list.Count; i++)
|
||||||
|
// {
|
||||||
|
// _buttonsContainer.Layout.Columns.Add(-1);
|
||||||
|
// string str = list[i];
|
||||||
|
//
|
||||||
|
// Button button = new Button() { Text = str };
|
||||||
|
// button.Clicked += (sender, ea) => ButtonClicked(sender, ea, i);
|
||||||
|
// button.Layout.Column = i;
|
||||||
|
// _buttonsContainer.Add(button);
|
||||||
|
// Add(button);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ButtonClicked(object? sender, EventArgs ea, int i)
|
||||||
|
{
|
||||||
|
Result = i;
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
||||||
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
|
<EmbeddedResource Include="Resources\**\*.png"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Dashboard.Windowing;
|
|
||||||
|
|
||||||
namespace Dashboard
|
|
||||||
{
|
|
||||||
public interface IWindowFactory
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a window. It could be a virtual window, or a physical window.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A window.</returns>
|
|
||||||
IWindow CreateWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Always creates a physical window.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A physical window.</returns>
|
|
||||||
IPhysicalWindow CreatePhysicalWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IDashboardBackend : IDisposable, IWindowFactory
|
|
||||||
{
|
|
||||||
void Initialize();
|
|
||||||
|
|
||||||
void RunEvents(bool wait);
|
|
||||||
|
|
||||||
void Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,14 +8,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj" />
|
<ProjectReference Include="..\..\Dashboard.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj" />
|
||||||
<ProjectReference Include="..\..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
|
<ProjectReference Include="..\..\Dashboard.BlurgText\Dashboard.BlurgText.csproj" />
|
||||||
<ProjectReference Include="..\..\Dashboard.ImmediateUI\Dashboard.ImmediateUI.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Dashboard.OpenTK\Dashboard.OpenTK.csproj" />
|
<ProjectReference Include="..\..\Dashboard.OpenTK\Dashboard.OpenTK.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Dashboard.StbImage\Dashboard.StbImage.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="OpenTK" Version="5.0.0-pre.13" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
using Dashboard.Drawing;
|
using Dashboard.BlurgText;
|
||||||
using System.Drawing;
|
using Dashboard.BlurgText.OpenGL;
|
||||||
using System.Text;
|
using Dashboard.Controls;
|
||||||
using Dashboard.Drawing.OpenGL;
|
using Dashboard.Drawing;
|
||||||
using Dashboard.ImmediateUI;
|
using Dashboard.OpenGL;
|
||||||
using Dashboard.OpenTK.PAL2;
|
using Dashboard.OpenTK.PAL2;
|
||||||
using OpenTK.Platform;
|
using Dashboard.Pal;
|
||||||
|
using Dashboard.StbImage;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using Box2d = Dashboard.Box2d;
|
using OpenTK.Platform;
|
||||||
using TK = OpenTK.Platform.Toolkit;
|
using TK = OpenTK.Platform.Toolkit;
|
||||||
using Dashboard;
|
|
||||||
|
|
||||||
TK.Init(new ToolkitOptions()
|
TK.Init(new ToolkitOptions()
|
||||||
{
|
{
|
||||||
@@ -18,10 +18,11 @@ TK.Init(new ToolkitOptions()
|
|||||||
{
|
{
|
||||||
EnableVisualStyles = true,
|
EnableVisualStyles = true,
|
||||||
IsDPIAware = true,
|
IsDPIAware = true,
|
||||||
}
|
},
|
||||||
|
FeatureFlags = ToolkitFlags.EnableOpenGL,
|
||||||
});
|
});
|
||||||
|
|
||||||
Application app = new Application(new Pal2DashboardBackend()
|
Application app = new Pal2Application()
|
||||||
{
|
{
|
||||||
GraphicsApiHints = new OpenGLGraphicsApiHints()
|
GraphicsApiHints = new OpenGLGraphicsApiHints()
|
||||||
{
|
{
|
||||||
@@ -41,136 +42,45 @@ Application app = new Application(new Pal2DashboardBackend()
|
|||||||
|
|
||||||
SupportTransparentFramebufferX11 = true,
|
SupportTransparentFramebufferX11 = true,
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
Vector2 mousePos = Vector2.Zero;
|
|
||||||
Random r = new Random();
|
|
||||||
List<Vector3> points = new List<Vector3>();
|
|
||||||
IFont font;
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
app.PostInitializing += (sender, ea) => {
|
|
||||||
window = (PhysicalWindow)app.CreatePhysicalWindow();
|
|
||||||
|
|
||||||
window.Title = "DashTerm";
|
|
||||||
TK.Window.SetMinClientSize(window.WindowHandle, 300, 200);
|
|
||||||
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;
|
|
||||||
|
|
||||||
context.MakeCurrent();
|
|
||||||
context.SwapGroup.SwapInterval = 1;
|
|
||||||
|
|
||||||
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) =>
|
|
||||||
{
|
|
||||||
if (handle != window.WindowHandle)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case PlatformEventType.Close:
|
|
||||||
source.Cancel();
|
|
||||||
break;
|
|
||||||
case PlatformEventType.MouseMove:
|
|
||||||
mousePos = ((MouseMoveEventArgs)eventArgs).ClientPosition;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
|
|
||||||
font = Typesetter.LoadFont("Nimbus Mono", 12f);
|
|
||||||
|
|
||||||
window.Painting += (sender, ea) => {
|
|
||||||
TK.Window.GetFramebufferSize(context.WindowHandle, out Vector2i framebufferSize);
|
|
||||||
executor.BeginFrame();
|
|
||||||
|
|
||||||
dimUI.Begin(new Box2d(0, 0, framebufferSize.X, framebufferSize.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, framebufferSize.X, framebufferSize.Y);
|
|
||||||
GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f);
|
|
||||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
|
||||||
GL.Disable(EnableCap.DepthTest);
|
|
||||||
GL.Enable(EnableCap.Blend);
|
|
||||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
||||||
GL.ColorMask(true, true, true, true);
|
|
||||||
|
|
||||||
executor.Draw(window.DrawQueue);
|
|
||||||
executor.EndFrame();
|
|
||||||
|
|
||||||
context.SwapGroup.Swap();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CancellationTokenSource source = new CancellationTokenSource();
|
||||||
|
|
||||||
|
app.Initialize();
|
||||||
|
app.ExtensionRequire<StbImageLoader>();
|
||||||
|
app.ExtensionLoad(new BlurgTextExtension(new BlurgTextExtensionFactory()));
|
||||||
|
|
||||||
|
PhysicalWindow window = (PhysicalWindow)app.CreatePhysicalWindow();
|
||||||
|
MessageBox box = MessageBox.Create(window, "Are you sure you want to exit?", "Confirm Exit", MessageBoxIcon.Question,
|
||||||
|
MessageBoxButtons.YesNo);
|
||||||
|
|
||||||
|
// window.Title = "DashTerm";
|
||||||
|
TK.Window.SetMinClientSize(window.WindowHandle, 300, 200);
|
||||||
|
TK.Window.SetClientSize(window.WindowHandle, new Vector2i(320, 240));
|
||||||
|
TK.Window.SetBorderStyle(window.WindowHandle, WindowBorderStyle.ResizableBorder);
|
||||||
|
// TK.Window.SetTransparencyMode(wnd, WindowTransparencyMode.TransparentFramebuffer, 0.1f);
|
||||||
|
|
||||||
|
GLDeviceContext context = (GLDeviceContext)window.DeviceContext;
|
||||||
|
|
||||||
|
context.GLContext.MakeCurrent();
|
||||||
|
context.GLContext.SwapGroup.SwapInterval = 1;
|
||||||
|
|
||||||
|
GL.Disable(EnableCap.DepthTest);
|
||||||
|
GL.Enable(EnableCap.Blend);
|
||||||
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||||
|
GL.ColorMask(true, true, true, true);
|
||||||
|
|
||||||
|
TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
|
||||||
|
|
||||||
|
window.DeviceContext.ExtensionRequire<IDeviceContextBase>().ScaleOverride = 1.5f;
|
||||||
|
|
||||||
app.Run(true, source.Token);
|
app.Run(true, source.Token);
|
||||||
|
|
||||||
|
|
||||||
|
class BlurgTextExtensionFactory : IBlurgDcExtensionFactory
|
||||||
|
{
|
||||||
|
public BlurgDcExtension CreateExtension(BlurgTextExtension appExtension, DeviceContext dc)
|
||||||
|
{
|
||||||
|
return new BlurgGLExtension();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||