80 Commits

Author SHA1 Message Date
themixedupstuff 362165f911 Push a fix for the upstream issue in ReFuel. 2026-08-24 22:01:20 +03:00
themixedupstuff 008882cb81 Move framework projects to their own folders. 2026-08-24 21:49:12 +03:00
themixedupstuff a6dbba19b4 Remove archaic code from the working tree. 2026-08-24 21:46:18 +03:00
themixedupstuff 6665a023c8 Refactor ImmediateMode rendering logic to use DrawImmediate. 2026-08-23 22:04:46 +03:00
themixedupstuff 3b20c53088 Refactor rendering interfaces and implementations
- Refactored DirectRendering class to support new IDirectRendering interface and manage textures directly.
- Enhanced ImmediateMode to utilize ImmediateVertex struct for immediate rendering calls.
- Updated Image class to use IDirectRendering for texture creation.
- Modified test application to demonstrate immediate mode rendering with new structures.
- Removed obsolete code related to previous rendering abstractions.
2026-08-23 21:44:05 +03:00
themixedupstuff bda8e173c9 Use new syntax to remove compile time warning from Application.Current. 2026-08-23 20:56:19 +03:00
themixedupstuff 1b5e13eb10 Add new SVG resources for dashboard components. 2026-08-23 17:42:43 +03:00
themixedupstuff 97f8a29f19 Refactor DrawCall to use VertexSpecification and update related rendering logic 2026-08-20 20:54:01 +03:00
themixedupstuff abdb78eae7 Move to blurgtext stable. 2026-08-11 10:12:11 +03:00
themixedupstuff 99a853f35d Use th new API in the demo. 2026-08-11 10:10:01 +03:00
themixedupstuff 3d0f15af45 Don't set the depth state twice. 2026-08-11 10:07:36 +03:00
themixedupstuff 01d7f27dc7 Fix the use of glCreateXXX (4.2) instead of glGenXXX (1.0). 2026-08-11 10:07:11 +03:00
themixedupstuff 7bf7b6c53c Add a configure blurg method to the extension factory interface. 2026-08-11 10:06:35 +03:00
themixedupstuff fe96499512 Implement the first version of the direct rendering API. 2026-08-02 16:44:17 +03:00
themixedupstuff 18cccf1e41 WIP direct rendering backend. 2026-08-01 17:38:59 +03:00
themixedupstuff 019ac6f4ae Add Begin() and End() functions to each extension for pre/post frame tasks. 2026-08-01 17:38:27 +03:00
themixedupstuff 449c398f05 Move modifier and scan code tables to an ext class. 2026-08-01 15:52:26 +03:00
themixedupstuff 1457e32095 Add a few more scan codes. 2026-06-07 15:33:29 +03:00
themixedupstuff 9dc6175f8b Fix typo in SubscribeEvent method. 2026-06-04 21:56:38 +03:00
themixedupstuff faaadbf5b1 Merge branch 'dashboard2' of https://git.mixedup.dev/ReFuel/Dashboard into HEAD 2026-05-17 21:46:46 +03:00
themixedupstuff 764b2bff8b Update to the latest OpenTK and BlurgText. 2026-05-17 21:45:58 +03:00
themixedupstuff aaa79d1878 Add Image member to image brushes. 2026-03-21 20:05:06 +03:00
themixedupstuff f9e374db50 Fix double initialization bug. 2026-03-21 20:04:50 +03:00
themixedupstuff 5bcc2d8214 Write partial scan code translation table. 2026-02-07 15:40:54 +03:00
themixedupstuff 804a3979fd Write out Win32 based Scancode table. 2026-02-07 15:40:38 +03:00
themixedupstuff d299084db5 Fix deafult font structure causing issues when passed to InternFont(). 2026-02-07 15:40:24 +03:00
themixedupstuff d20d929515 Call base method in Form class. 2026-02-02 23:16:17 +03:00
themixedupstuff 3b9fa5c9fb Implement a very botched input scancode mapping. 2026-02-02 23:16:05 +03:00
themixedupstuff 027ebe8dbd Bubble some OpenGL elements up to the DC impl. 2026-02-02 22:02:32 +03:00
themixedupstuff 36dbcac8d9 Make Pal2Application handle Toolkit.Init() instead. 2026-02-01 00:01:08 +03:00
themixedupstuff 7c3141822a Add image brushes as a construct, as well as move some resource types to Common assembly. 2026-01-05 22:34:34 +03:00
themixedupstuff e30e50e860 Unfinished work I want to document in the Git history. 2026-01-05 22:26:34 +03:00
themixedupstuff 4f67e0fb75 Create basic controls without much regards to layout rules. 2025-11-21 23:04:37 +03:00
themixedupstuff 5cba1ab7db Give label a proper implementation. 2025-11-19 20:12:52 +03:00
themixedupstuff 9ca309bd52 Remove all old project files. 2025-11-17 22:00:58 +03:00
themixedupstuff 043060db66 Rework the library architecture. 2025-11-17 21:59:07 +03:00
themixedupstuff 66c5eecc26 Move StbImage to its own assembly. 2025-11-14 22:31:54 +03:00
themixedupstuff 9ee6c1180d Add new functions to extension system. 2025-11-14 22:28:21 +03:00
themixedupstuff c4fe4841fe Work on creating a simple message box dialog. 2025-11-13 21:35:01 +03:00
themixedupstuff 2932b3b85e Implement new immediate mode. 2025-11-12 21:24:25 +03:00
themixedupstuff 6e8888df48 Add CreatePhysicalWindow(). 2025-11-09 12:00:21 +03:00
themixedupstuff edc85c3f24 Create a new context system for dashboard. 2025-08-05 23:50:29 +03:00
themixedupstuff 50eda46b13 Fix dependency versions. 2025-07-31 20:59:44 +03:00
themixedupstuff c538dbd56b Implement type dictionaries, with hierarchical sets and unsets. 2025-07-25 23:51:14 +03:00
themixedupstuff 1dcf167022 Implement ReFuel style type atoms and type collections. 2025-07-24 23:16:24 +03:00
themixedupstuff 2690c5bec0 Commit WIP changes. 2025-04-29 23:20:54 +03:00
themixedupstuff 1c3c730e82 Some additional work on dashboard architecture. 2025-02-11 23:29:26 +03:00
themixedupstuff 2c957a0c1a Create some high level classes. 2025-02-02 21:58:49 +03:00
themixedupstuff 49257574f4 Add a few demo controls. 2025-01-25 15:39:18 +03:00
themixedupstuff 1cd005f827 Create the first immediate mode UI base. 2025-01-25 12:34:37 +03:00
themixedupstuff ef860f39bf Patch in BlurgText upstream as well. 2025-01-23 22:00:58 +03:00
themixedupstuff b1aadae0f2 Add initial version of text rendering. 2025-01-22 22:42:43 +03:00
themixedupstuff afa6ed4f4c Add new draw call recorder class. 2025-01-20 23:14:30 +03:00
themixedupstuff 57620f5d5c Fix missing variable command parameter data in DrawQueue. 2025-01-20 23:14:15 +03:00
themixedupstuff 78f06a2359 Fix null reference exception in TextCommand. 2025-01-20 23:13:56 +03:00
themixedupstuff 87ab64f727 Add IDpiAwareGLContext. 2025-01-19 16:48:15 +03:00
themixedupstuff 95cc1648b2 Implement basic commands. 2025-01-18 23:30:56 +03:00
themixedupstuff 5014d218e2 Work on resource classes and generic interfaces. 2025-01-18 20:43:42 +03:00
themixedupstuff 8ce1329dfc Make all commands be parallel to the XY plane because i can't be bothered to fix them all. 2025-01-17 21:34:15 +03:00
themixedupstuff cde0fe2901 Added new mathematical types and enums related to drawing. 2025-01-17 21:32:52 +03:00
themixedupstuff 1a52f17990 Add missing properties. 2025-01-15 20:42:09 +03:00
themixedupstuff b841dbe6c2 Begin implementing the GL engine. 2025-01-14 20:34:47 +03:00
themixedupstuff bb5b87417f Add clone to gradients. 2024-12-25 22:58:23 +03:00
themixedupstuff 165801800e Add basic text support. 2024-12-24 23:20:43 +03:00
themixedupstuff cf7cf9a77a Fix root namespaces. 2024-12-24 22:30:19 +03:00
themixedupstuff 3dff7438b3 Add gradient brushes. 2024-12-24 22:10:31 +03:00
themixedupstuff 6ad11812e2 Add hash function to Gradient. 2024-12-24 22:10:24 +03:00
themixedupstuff f8dea00021 Move basic types to Dashboard.Common. 2024-12-24 22:00:16 +03:00
themixedupstuff 7485ec5eaa Add Gradient. 2024-12-24 21:58:51 +03:00
themixedupstuff 6acaf0a7d5 Rename SolidBrush.cs to Brush.cs 2024-12-24 21:58:43 +03:00
themixedupstuff b5fda1ce3e Create ImageProperties.cs. 2024-12-22 23:27:48 +03:00
themixedupstuff 1067d3e188 Implement command iterator. 2024-12-14 20:44:27 +03:00
themixedupstuff 1b6bf09f95 Move types to Dashboard.Common. 2024-12-14 19:57:23 +03:00
themixedupstuff 509bad221f Add new commands. 2024-12-14 19:46:56 +03:00
themixedupstuff 9ab2edffee Change the IDrawCommand interface. 2024-12-14 19:46:47 +03:00
themixedupstuff 1a1e761326 Add custom hashcode to SolidBrush. 2024-12-14 19:43:50 +03:00
themixedupstuff 34720970ff Make bounds a Box3d. 2024-12-14 16:27:40 +03:00
themixedupstuff c706e16db1 Create Box3d structure. 2024-12-14 16:27:13 +03:00
themixedupstuff 8bc2685206 First code commit 2024-12-13 21:57:07 +03:00
themixedupstuff fb76258e41 Initial commit of the Dashboard rewrite. 2024-12-11 20:56:39 +03:00
194 changed files with 11176 additions and 9701 deletions
@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BlurgText" Version="0.1.0-nightly-4" />
</ItemGroup>
</Project>
+15
View File
@@ -0,0 +1,15 @@
namespace Dashboard
{
[Flags]
public enum Anchor
{
Auto = 0,
Right = (1 << 0),
Left = (1 << 1),
HCenter = Left | Right,
Top = (1 << 2),
Bottom = (1 << 3),
VCenter = Top | Bottom,
Middle = HCenter | VCenter,
}
}
+65
View File
@@ -0,0 +1,65 @@
using System.ComponentModel.DataAnnotations;
using System.Drawing;
using System.Numerics;
namespace Dashboard
{
public readonly record struct Box2d(Vector2 Min, Vector2 Max)
{
public float Left => Min.X;
public float Right => Max.X;
public float Top => Min.Y;
public float Bottom => Max.Y;
public Vector2 Size => Max - Min;
public Vector2 Center => (Min + Max) * 0.5f;
public Box2d(RectangleF rectangle)
: this(new Vector2(rectangle.Left, rectangle.Top), new Vector2(rectangle.Right, rectangle.Bottom))
{
}
public Box2d(float x0, float y0, float x1, float y1)
: this(new Vector2(x0, y0), new Vector2(x1, y1))
{
}
public static Box2d FromPositionAndSize(Vector2 position, Vector2 size, Origin anchor = Origin.Center)
{
Vector2 half = size * 0.5f;
switch (anchor)
{
case Origin.Center:
return new Box2d(position - half, position + half);
case Origin.TopLeft:
return new Box2d(position, position + size);
default:
throw new NotImplementedException();
}
}
public static Box2d Union(Box2d left, Box2d right)
{
Vector2 min = Vector2.Min(left.Min, right.Min);
Vector2 max = Vector2.Max(left.Max, right.Max);
return new Box2d(min, max);
}
public static Box2d Intersect(Box2d left, Box2d right)
{
Vector2 min = Vector2.Max(left.Min, right.Min);
Vector2 max = Vector2.Min(left.Max, right.Max);
return new Box2d(min, max);
}
public static explicit operator RectangleF(Box2d box2d)
{
return new RectangleF((PointF)box2d.Center, (SizeF)box2d.Size);
}
public static explicit operator Box2d(RectangleF rectangle)
{
return new Box2d(rectangle);
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.Drawing;
using System.Numerics;
namespace Dashboard
{
public readonly record struct Box3d(Vector3 Min, Vector3 Max)
{
public float Left => Min.X;
public float Right => Max.X;
public float Top => Min.Y;
public float Bottom => Max.Y;
public float Far => Min.Z;
public float Near => Max.Z;
public Vector3 Size => Max - Min;
public Vector3 Center => Min + Size * 0.5f;
public static Box3d Union(Box3d left, Box3d right)
{
Vector3 min = Vector3.Min(left.Min, right.Min);
Vector3 max = Vector3.Max(left.Max, right.Max);
return new Box3d(min, max);
}
public static Box3d Union(Box3d box, Box2d bounds, float depth)
{
Vector3 min = Vector3.Min(box.Min, new Vector3(bounds.Left, bounds.Top, depth));
Vector3 max = Vector3.Max(box.Max, new Vector3(bounds.Right, bounds.Bottom, depth));
return new Box3d(min, max);
}
public static Box3d Intersect(Box3d left, Box3d right)
{
Vector3 min = Vector3.Max(left.Min, right.Min);
Vector3 max = Vector3.Min(left.Max, right.Max);
return new Box3d(min, max);
}
public static Box3d Intersect(Box3d box, Box2d bounds, float depth)
{
Vector3 min = Vector3.Max(box.Min, new Vector3(bounds.Min, depth));
Vector3 max = Vector3.Min(box.Max, new Vector3(bounds.Max, depth));
return new Box3d(min, max);
}
}
}
+95
View File
@@ -0,0 +1,95 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
namespace Dashboard.Collections
{
/// <summary>
/// Helper class for better type access performance.
/// </summary>
public record TypeAtom
{
public Type Type { get; }
public int Id { get; }
public ImmutableHashSet<TypeAtom> Ancestors { get; }
// Makes it so TypeAtom doesn't get pissed at me
private protected TypeAtom()
{
throw new NotSupportedException();
}
private TypeAtom(Type type)
{
Type = type;
HashSet<TypeAtom> ancestors = new HashSet<TypeAtom>();
FindAncestors(Type, ancestors);
ancestors.Add(this);
Ancestors = ancestors.ToImmutableHashSet();
lock (s_lockObject)
{
Id = s_counter++;
s_atoms.Add(Id, this);
s_types.Add(Type, this);
}
}
private static readonly object s_lockObject = new object();
private static readonly Dictionary<int, TypeAtom> s_atoms = new Dictionary<int, TypeAtom>();
private static readonly Dictionary<Type, TypeAtom> s_types = new Dictionary<Type, TypeAtom>();
private static int s_counter = 0;
public static TypeAtom? Get(int id) => s_atoms.GetValueOrDefault(id);
public static TypeAtom Get(Type type)
{
if (s_types.TryGetValue(type, out TypeAtom? id))
return id;
// Type is not registered, try to acquire lock.
lock (s_lockObject)
{
// Maybe somebody else registered this type whilst acquiring the lock.
if (s_types.TryGetValue(type, out id))
return id;
// Register the type if applicable and leave.
return new TypeAtom(type);
}
}
private static void FindAncestors(Type type, HashSet<TypeAtom> destination)
{
// Traverse the object tree for all possible aliases.
if (type.BaseType != null)
{
foreach (TypeAtom ancestor in Get(type.BaseType).Ancestors)
{
destination.Add(ancestor);
}
}
foreach (Type trait in type.GetInterfaces())
{
TypeAtom atom = Get(trait);
destination.Add(atom);
}
}
}
/// <summary>
/// Helper class for better type access performance.
/// </summary>
public sealed record TypeAtom<T> : TypeAtom
{
public static TypeAtom Atom { get; } = Get(typeof(T));
public new static int Id => Atom.Id;
public new static Type Type => Atom.Type;
public new static ImmutableHashSet<TypeAtom> Ancestors => Atom.Ancestors;
private TypeAtom() { }
}
}
@@ -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();
}
}
@@ -0,0 +1,44 @@
using System.Collections;
namespace Dashboard.Collections
{
public class TypeHashSet(bool hierarchical = false) : IEnumerable<TypeAtom>
{
private readonly HashSet<int> _set = new HashSet<int>();
public bool Contains<T>() => _set.Contains(TypeAtom<T>.Id);
public bool Set<T>()
{
if (!_set.Add(TypeAtom<T>.Id))
return false;
if (hierarchical)
foreach (TypeAtom ancestor in TypeAtom<T>.Ancestors)
{
_set.Add(ancestor.Id);
}
return true;
}
public bool Reset<T>()
{
if (!_set.Remove(TypeAtom<T>.Id))
return false;
if (hierarchical)
foreach (TypeAtom ancestor in TypeAtom<T>.Ancestors)
{
_set.Remove(ancestor.Id);
}
return true;
}
public void Clear() => _set.Clear();
public IEnumerator<TypeAtom> GetEnumerator() => _set.Select(x => TypeAtom.Get(x)!).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
+10
View File
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Dashboard</RootNamespace>
</PropertyGroup>
</Project>
+27
View File
@@ -0,0 +1,27 @@
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 Image Image { get; set; } = image;
public Box2d TextureCoordinates { get; set; } = new Box2d(0, 0, 1, 1);
}
public class NinePatchImageBrush(Image image) : Brush
{
public Image Image { get; set; } = image;
public Box2d CenterCoordinates { get; set; } = new Box2d(0, 0, 1, 1);
public Vector4 Extents { get; set; } = Vector4.Zero;
}
}
+242
View File
@@ -0,0 +1,242 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Numerics;
namespace Dashboard.Drawing
{
public enum MeshPrimitive
{
Point,
Line,
Triangle,
TriangleFan,
TriangleStrip,
}
public enum WindingOrder
{
Clockwise,
Counterclockwise,
}
public enum FaceCulling
{
None,
Front,
Back,
Both,
}
public enum VertexAttributeType
{
UnsignedByte,
UnsignedShort,
UnsignedInt,
Byte,
Short,
Int,
Half,
Float,
Double,
}
public enum IndexType
{
UnsignedShort,
UnsignedInt,
UnsignedLong,
Short,
Int,
}
public enum BlendFunction
{
One,
Zero,
SourceAlpha,
OneMinusSourceAlpha,
DestinationAlpha,
OneMinusDestinationAlpha,
SourceColor,
OneMinusSourceColor,
DestinationColor,
OneMinusDestinationColor,
ConstantColor,
OneMinusConstantColor,
ConstantAlpha,
OneMinusConstantAlpha,
}
public enum BlendEquation
{
Add,
Subtract,
ReverseSubtract,
Min,
Max,
}
public enum TestFunction
{
Never,
LessThan,
LessThanOrEqual,
Equal,
NotEqual,
GreaterThanOrEqual,
Greater,
Always,
}
public enum StencilOperation
{
Keep,
Zero,
Replace,
Increment,
UncheckedIncrement,
Decrement,
UncheckedDecrement,
Invert,
}
public enum UniformType
{
U1,
U2,
U3,
U4,
I1,
I2,
I3,
I4,
F1,
F2,
F3,
F4,
Mat2,
Mat3,
Mat4,
Buffer,
}
public readonly record struct DepthMode(bool Test, bool Mask, TestFunction Function)
{
public static readonly DepthMode Off = new DepthMode(false, false, TestFunction.Always);
public static readonly DepthMode WriteOnly = new DepthMode(false, true, TestFunction.LessThan);
public static readonly DepthMode Equal = new DepthMode(true, false, TestFunction.Equal);
public static readonly DepthMode Enabled = new DepthMode(true, true, TestFunction.LessThan);
}
public readonly record struct BlendFactor(BlendFunction Source, BlendFunction Destination)
{
public static readonly BlendFactor Default =
new BlendFactor(BlendFunction.SourceAlpha, BlendFunction.OneMinusSourceAlpha);
}
public readonly record struct BlendMode(bool Enabled, BlendFactor Color, BlendFactor Alpha)
{
[MemberNotNullWhen(false, nameof(Unified), nameof(UnifiedEquation))]
public bool IsSeparate => Color != Alpha;
public BlendFactor? Unified => IsSeparate ? null : Color;
public BlendEquation? UnifiedEquation => IsSeparate ? null : ColorEquation;
public BlendEquation ColorEquation { get; init; } = BlendEquation.Add;
public BlendEquation AlphaEquation { get; init; } = BlendEquation.Add;
public Color Constant { get; init; } = System.Drawing.Color.Black;
public BlendMode(BlendFunction source, BlendFunction destination)
: this(true, new BlendFactor(source, destination), new BlendFactor(source, destination))
{
}
public static readonly BlendMode Off = new BlendMode(false, BlendFactor.Default, BlendFactor.Default);
public static readonly BlendMode Normal =
new BlendMode(true, BlendFactor.Default, BlendFactor.Default);
}
public readonly record struct StencilMode(bool Enabled, TestFunction Function)
{
public int Reference { get; init; } = 0;
public int Mask { get; init; } = ~0;
public StencilOperation Pass { get; init; } = StencilOperation.Keep;
public StencilOperation DepthFail { get; init; } = StencilOperation.Keep;
public StencilOperation Fail { get; init; } = StencilOperation.Keep;
public static readonly StencilMode Off = new StencilMode(false, TestFunction.Always);
}
public record struct VertexAttribute(
int Location,
int Components,
VertexAttributeType Type,
long Offset,
long Stride)
{
public int Divisor { get; init; } = 1;
}
public record VertexSpecification(
ImmutableList<VertexAttribute> Attributes,
ImmutableList<IBuffer?> VertexBuffers
)
{
public IBuffer? ElementBuffer { get; init; } = null;
}
public record struct UniformDescriptor(int Location, UniformType Type, long Offset, long Size);
public struct PipelineState()
{
public Box2d ViewportRegion { get; init; } = new Box2d(Vector2.Zero, Vector2.PositiveInfinity);
public Box2d ScissorRegion { get; init; } = new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity);
public WindingOrder FrontFace { get; init; } = WindingOrder.Counterclockwise;
public FaceCulling CullMode { get; init; } = FaceCulling.Back;
public BlendMode BlendMode { get; init; } = BlendMode.Normal;
public DepthMode DepthMode { get; init; } = DepthMode.Enabled;
public StencilMode StencilMode { get; init; } = StencilMode.Off;
public bool RedMask { get; init; } = true;
public bool GreenMask { get; init; } = true;
public bool BlueMask { get; init; } = true;
public bool AlphaMask { get; init; } = true;
public float PointSize { get; init; }= 1.0f;
public float LineWidth { get; init; }= 1.0f;
}
public struct DrawCall(MeshPrimitive primitive, VertexSpecification vertexSpec, int first, int count)
{
public IShader? ShaderPipeline { get; init; }
public PipelineState PipelineState { get; init; } = new PipelineState();
public VertexSpecification VertexSpecification { get; init; } = vertexSpec;
public MeshPrimitive Primitive { get; init; } = primitive;
public int First { get; init; } = first;
public int Count { get; init; } = count;
public int BaseVertex { get; init; } = 0;
public long Offset { get; init; } = 0;
public bool EnableRestart { get; init; } = false;
public long RestartIndex { get; init; } = -1;
public Box2d ViewportRegion => PipelineState.ViewportRegion;
public Box2d ScissorRegion => PipelineState.ScissorRegion;
public WindingOrder FrontFace => PipelineState.FrontFace;
public FaceCulling CullMode => PipelineState.CullMode;
public BlendMode BlendMode => PipelineState.BlendMode;
public DepthMode DepthMode => PipelineState.DepthMode;
public StencilMode StencilMode => PipelineState.StencilMode;
public bool RedMask => PipelineState.RedMask;
public bool GreenMask => PipelineState.GreenMask;
public bool BlueMask => PipelineState.BlueMask;
public bool AlphaMask => PipelineState.AlphaMask;
public float PointSize => PipelineState.PointSize;
public float LineWidth => PipelineState.LineWidth;
public List<ITexture?> Textures { get; init; } = [];
public List<UniformDescriptor> Uniforms { get; init; } = [];
public IndexType IndexType { get; init; } = IndexType.UnsignedShort;
public ReadOnlyMemory<byte> UniformData { get; init; } = ReadOnlyMemory<byte>.Empty;
public IBuffer? UniformBuffer { get; init; }
}
}
+42
View 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);
}
}
}
+43
View File
@@ -0,0 +1,43 @@
namespace Dashboard.Drawing
{
public enum BufferAccessPattern
{
/// <summary>
/// No hint given. Not recommended.
/// </summary>
DontCare,
/// <summary>
/// For buffers which have a long lifetime, but the contents are initialized once.
/// </summary>
Static,
/// <summary>
/// For buffers which have a long lifetime, but the contents can be updated every frame.
/// </summary>
Stream,
/// <summary>
/// For buffers which have a short lifetime, used for streaming resources to the graphics hardware.
/// </summary>
Upload,
/// <summary>
/// For buffers which have a short lifetime, used for streaming resources from the graphics hardware.
/// </summary>
Download,
}
public interface IBuffer : IDisposable
{
public BufferAccessPattern AccessPattern { get; }
public long Size { get; }
void Reallocate(long newSize);
Span<T> Map<T>(long offset, int count = -1) where T : unmanaged;
void Unmap();
void Read<T>(long offset, Span<T> span) where T : unmanaged;
void Write<T>(long offset, ReadOnlySpan<T> span) where T : unmanaged;
}
}
@@ -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();
}
}
@@ -0,0 +1,50 @@
using Dashboard.Pal;
namespace Dashboard.Drawing
{
/// <summary>
/// This extension provides direct access to the backend rendering pipeline without writing with a higher level
/// abstraction than the rendering backend.
/// </summary>
public interface IDirectRendering : IDeviceContextExtension
{
/// <summary>
/// Create a shader program.
/// </summary>
/// <typeparam name="T">Type of the shader create info object.</typeparam>
/// <param name="createInfo">An object which describes how the shader is created.</param>
/// <returns>A shader program object.</returns>
/// <seealso cref="SpirvShaderCreateInfo"/>
/// <seealso cref="GlslShaderCreateInfo"/>
/// <seealso cref="GlslComputeShaderCreateInfo"/>
IShader CreateShader<T>(T createInfo) where T : IShaderCreateInfo;
/// <summary>
/// Create a texture object.
/// </summary>
/// <param name="type">Type of texture to create.</param>
/// <returns>An empty texture object.</returns>
ITexture CreateTexture(TextureType type);
/// <summary>
/// Create a buffer object.
/// </summary>
/// <param name="pattern">Buffer access pattern hint.</param>
/// <param name="size">Initial capacity of the buffer in bytes.</param>
/// <returns>An empty buffer object.</returns>
IBuffer CreateBuffer(BufferAccessPattern pattern, long size);
/// <summary>
/// Enqueue a draw call.
/// </summary>
/// <param name="drawCall">The draw call to enqueue.</param>
void Draw(DrawCall drawCall);
}
[Obsolete("Use IDirectRendering instead.")]
public interface ITextureExtension : IDeviceContextExtension
{
/// <inheritdoc cref="IDirectRendering.CreateTexture(TextureType)"/>
ITexture CreateTexture(TextureType type);
}
}
+27
View 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
View File
@@ -0,0 +1,61 @@
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public record ImageData(
TextureType Type,
PixelFormat Format,
int Width,
int Height,
byte[] Bitmap)
{
public int Depth { get; init; } = 1;
public int Levels { get; init; } = 1;
public bool Premultiplied { get; init; } = false;
public int Alignment { get; init; } = 4;
public long GetLevelOffset(int level)
{
ArgumentOutOfRangeException.ThrowIfLessThan(level, 0, nameof(level));
ArgumentOutOfRangeException.ThrowIfGreaterThan(level, Levels, nameof(level));
ArgumentOutOfRangeException.ThrowIfGreaterThan(level, Math.ILogB(Math.Max(Width, Height)));
long offset = 0;
long row = Width * Format switch
{
PixelFormat.R8I => 1,
PixelFormat.R16F => 2,
PixelFormat.Rg8I => 2,
PixelFormat.Rg16F => 4,
PixelFormat.Rgb8I => 3,
PixelFormat.Rgb16F => 6,
PixelFormat.Rgba8I => 4,
PixelFormat.Rgba16F => 8,
};
row += Alignment - (row % Alignment);
long plane = row * Height;
long volume = plane * Depth;
for (int i = 0; i < level; i++)
{
if (Depth == 1)
{
offset += plane / (1 << i) / (1 << i);
}
else
{
offset += volume / (1 << i) / (1 << i) / (1 << i);
}
}
return offset;
}
}
public interface IImageLoader : IApplicationExtension
{
public ImageData LoadImageData(Stream stream);
}
}
@@ -0,0 +1,68 @@
using System.Data;
using System.Drawing;
using System.Numerics;
using System.Runtime.InteropServices;
using Dashboard.Layout;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public record struct RectangleDrawInfo(Vector2 Position, ComputedBox Box, Brush Fill, Brush? Border = null);
[StructLayout(LayoutKind.Explicit, Size = Size)]
public struct ImmediateVertex()
{
[field: FieldOffset(PosOffset)]
public Vector3 Position { get; init; }= Vector3.Zero;
[field: FieldOffset(TexCoordsOffset)]
public Vector2 TexCoords { get; init; } = Vector2.Zero;
[field: FieldOffset(ColorOffset)]
public Vector4 Color { get; init; } = Vector4.One;
public ImmediateVertex(Vector3 position, Vector2 texCoords, Vector4 color) : this()
{
Position = position;
TexCoords = texCoords;
Color = color;
}
public ImmediateVertex(Vector3 position, Vector2 texCoords, Color color)
: this(
position,
texCoords,
new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f))
{
}
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);
}
public struct ImmediateDrawCall(MeshPrimitive primitive, ReadOnlyMemory<ImmediateVertex> vertices)
{
public MeshPrimitive Primitive { get; init; } = primitive;
public ReadOnlyMemory<ImmediateVertex> Vertices { get; init; } = vertices;
public PipelineState PipelineState { get; init; } = new PipelineState();
public Matrix4x4 Transforms { get; init; } = Matrix4x4.Identity;
public ITexture? Texture { get; init; } = null;
}
public interface IImmediateMode : IDeviceContextExtension
{
/// <summary>
/// Enqueue a draw with the immediate mode shader pipeline.
/// </summary>
/// <param name="call">Information about this draw call.</param>
void DrawImmediate(ImmediateDrawCall call);
// TODO: move these to their own extension?
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);
}
}
+21
View File
@@ -0,0 +1,21 @@
using System.Collections.Immutable;
namespace Dashboard.Drawing
{
/// <summary>
/// Base interface for creating shader pipelines.
/// </summary>
public interface IShaderCreateInfo
{
}
public readonly record struct ShaderMappingProperty(string Name, int Index);
public interface IShader : IDisposable
{
public ImmutableList<ShaderMappingProperty> Attributes { get; }
public ImmutableList<ShaderMappingProperty> Uniforms { get; }
public ImmutableList<ShaderMappingProperty> Blocks { get; }
public ImmutableList<ShaderMappingProperty> Textures { get; }
}
}
+12
View File
@@ -0,0 +1,12 @@
using System.Numerics;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public interface ITextRenderer : IDeviceContextExtension
{
Box2d MeasureText(IFont font, float size, string text);
void DrawText(Vector2 position, Vector4 color, float size, IFont font, string text);
}
}
+78
View File
@@ -0,0 +1,78 @@
using System.Drawing;
namespace Dashboard.Drawing
{
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
View 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;
IDirectRendering ext = dc.ExtensionRequire<IDirectRendering>();
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));
}
}
}
+39
View File
@@ -0,0 +1,39 @@
namespace Dashboard.Drawing
{
public record GlslShaderCreateInfo() : IShaderCreateInfo
{
public required string VertexShader { get; init; }
public string? GeometryShader { get; init; } = null;
public string? TesselationControl { get; init; } = null;
public string? TesselationEvaluation { get; init; } = null;
public string? FragmentShader { get; init; } = null;
}
public record GlslComputeShaderCreateInfo(string Source) : IShaderCreateInfo
{
}
public record SpirvShaderCreateInfo(byte[] Binary) : IShaderCreateInfo
{
public int Format { get; init; } = Spirv;
public SpirvShaderCreateInfo(Stream stream) : this(GetBinary(stream)) { }
private static byte[] GetBinary(Stream stream)
{
//FIXME: this might actually be copying twice.
if (stream is MemoryStream ms)
{
return ms.ToArray();
}
else
{
using MemoryStream memory = new MemoryStream();
stream.CopyTo(memory);
return memory.ToArray();
}
}
private const int Spirv = 38225;
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace Dashboard.Drawing
{
public enum ShaderStage
{
Vertex,
Geometry,
TesselationEvaluation,
TesselationControl,
Fragment,
Compute,
}
public abstract class ShaderBuilder(string language)
{
public string Language { get; } = language;
}
}
+224
View File
@@ -0,0 +1,224 @@
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: Keycode table. Keycodes are subject to change upstream. SeeAlso: Extensions.cs@KeyCode ToDashboard(this Key key)
}
public enum ScanCode
{
// This scan code table is based on the Win32 scan code table.
// See https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
Error = 0xFF,
SystemPowerDown = 0xE05E,
SystemSleep = 0xE05F,
SystemWakeUp = 0xE063,
A = 0x1E,
B = 0x30,
C = 0x2E,
D = 0x20,
E = 0x12,
F = 0x21,
G = 0x22,
H = 0x23,
I = 0x17,
J = 0x24,
K = 0x25,
L = 0x26,
M = 0x32,
N = 0x31,
O = 0x18,
P = 0x19,
Q = 0x10,
R = 0x13,
S = 0x1F,
T = 0x14,
U = 0x16,
V = 0x2F,
W = 0x11,
X = 0x2D,
Y = 0x15,
Z = 0x2C,
D1 = 0x02,
D2 = 0x03,
D3 = 0x04,
D4 = 0x05,
D5 = 0x06,
D6 = 0x07,
D7 = 0x08,
D8 = 0x09,
D9 = 0x0A,
D0 = 0x0B,
Return = 0x1C,
Esc = 0x01,
Delete = 0x0E,
Tab = 0x0F,
Space = 0x39,
Dash = 0x0C,
Equals = 0x0D,
LBracket = 0x1A,
RBracket = 0x1B,
Backslash = 0x2B,
Semicolon = 0x27,
Apostrophe = 0x28,
Grave = 0x29,
Comma = 0x33,
Period = 0x34,
ForwardSlash = 0x35,
CapsLock = 0x3A,
F1 = 0x3B,
F2 = 0x3C,
F3 = 0x3D,
F4 = 0x3E,
F5 = 0x3F,
F6 = 0x40,
F7 = 0x41,
F8 = 0x42,
F9 = 0x43,
F10 = 0x44,
F11 = 0x45,
F12 = 0x46,
PrintScr = 0xE037,
ScrollLock = 0x46,
Pause = 0xE046,
Insert = 0xE052,
Home = 0xE047,
PageUp = 0xE049,
Backspace = 0xE053,
End = 0xE04F,
PageDown = 0xE051,
RightArrow = 0xE04D,
LeftArrow = 0xE04B,
DownArrow = 0xE050,
UpArrow = 0xE048,
NumLock = 0xE045,
NumDiv = 0xE035,
NumMul = 0x37,
NumSub = 0x4A,
NumAdd = 0x4E,
NumEnter = 0xE01C,
Num1 = 0x4F,
Num2 = 0x50,
Num3 = 0x51,
Num4 = 0x4B,
Num5 = 0x4C,
Num6 = 0x4D,
Num7 = 0x47,
Num8 = 0x48,
Num9 = 0x49,
Num0 = 0x52,
NumDecimal = 0x53,
NumBackslash = 0x56,
NumEquals = 0x59,
Application = 0xE05D,
F13 = 0x64,
F14 = 0x65,
F15 = 0x66,
F16 = 0x67,
F17 = 0x68,
F18 = 0x69,
F19 = 0x6A,
F20 = 0x6B,
F21 = 0x6C,
F22 = 0x6D,
F23 = 0x6E,
F24 = 0x76,
NumComma = 0x7E,
International1 = 0x73,
International2 = 0x70,
International3 = 0x7D,
International4 = 0x79,
International5 = 0x7B,
International6 = 0x5C,
International7,
International8,
International9,
Lang1 = 0x72,
Lang2 = 0x71,
Lang3 = 0x78,
Lang4 = 0x77,
Lang5,
Lang6,
Lang7,
Lang8,
Lang9,
LCtrl = 0x1D,
LShift = 0x2A,
LWin = 0xE05B,
LAlt = 0x38,
RAlt = 0xE038,
RWin = 0xE05C,
RCtrl = 0xE01D,
RShift = 0x36,
NextTrack = 0xE019,
PrevTrack = 0xE010,
Stop = 0xE024,
PlayPause = 0xE022,
VolUp = 0xE030,
VolDown = 0xE029,
Configuration = 0xE06D,
Email = 0xE06C,
Calculator = 0xE021,
Browser = 0xE06B,
Search = 0xE065,
HomePage = 0xE032,
Back = 0xE06A,
Forward = 0xE69,
Halt = 0xE068,
Refresh = 0xE67,
Bookmarks = 0xE066,
NonUsSlashBar = 0xFF01,
Mute,
}
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;
}
}
+43
View File
@@ -0,0 +1,43 @@
using System.Drawing;
using System.Numerics;
namespace Dashboard.Events
{
[Flags]
public enum MouseButtons
{
M1 = 1 << 0,
M2 = 1 << 1,
M3 = 1 << 2,
M4 = 1 << 3,
M5 = 1 << 4,
M6 = 1 << 5,
M7 = 1 << 6,
M8 = 1 << 7,
Left = M1,
Right = M2,
Middle = M3,
}
public sealed class MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) : UiEventArgs(UiEventType.MouseMove)
{
public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 Delta { get; } = delta;
}
public sealed class MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, ModifierKeys modifierKeys, bool up)
: UiEventArgs(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
{
public ModifierKeys ModifierKeys { get; } = modifierKeys;
public Vector2 ClientPosition { get; } = clientPosition;
public MouseButtons Buttons { get; } = buttons;
}
public sealed class MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
: UiEventArgs(UiEventType.MouseScroll)
{
public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 ScrollDelta { get; } = scrollDelta;
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace Dashboard.Events
{
public class TickEventArgs : UiEventArgs
{
/// <summary>
/// Animation delta time in seconds.
/// </summary>
public float Delta { get; }
public TickEventArgs(float delta) : base(UiEventType.AnimationTick)
{
Delta = delta;
}
}
}
+82
View File
@@ -0,0 +1,82 @@
using System.Numerics;
using Dashboard.Pal;
namespace Dashboard.Events
{
public enum UiEventType
{
None,
AnimationTick, // Generic timer event.
Paint, // Generic paint event.
// Text input related events.
KeyDown, // Keyboard key down.
KeyUp, // Keyboard key up.
TextInput, // Non-IME text event.
TextEdit, // IME text event.
TextCandidates, // IME text candidate list.
TextLanguage, // Keyboard language changed event.
// Mouse & touch related events
MouseButtonDown, // Mouse button down.
MouseButtonUp, // Mouse button up.
MouseMove, // Mouse moved.
MouseScroll, // Mouse scrolled.
// Reserved event names
StylusEnter, // The stylus has entered the hover region.
StylusLeave, // The stylus has left the hover region.
StylusMove, // The stylus has moved.
StylusDown, // The stylus is touching.
StylusUp, // The stylus is no longer touching.
StylusButtonUp, // Stylus button up.
StylusButtonDown, // Stylus button down.
StylusAxes, // Extra stylus axes data.
// Window & Control Events
ControlInvalidateVisual, // Force rendering the control again.
ControlStateChanged, // Control state changed.
ControlMoved, // Control moved.
ControlResized, // Control resized.
ControlEnter, // The pointing device entered the control.
ControlLeave, // The pointing device left the control.
ControlFocusGet, // The control acquired focus.
ControlFocusLost, // The control lost focus.
WindowClose, // The window closed.
UserRangeStart = 1 << 12,
}
public class UiEventArgs : EventArgs
{
public UiEventType Type { get; }
public UiEventArgs(UiEventType type)
{
Type = type;
}
public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None);
}
public class PaintEventArgs(DeviceContext dc) : UiEventArgs(UiEventType.Paint)
{
public DeviceContext DeviceContext { get; } = dc;
}
public class ControlMovedEventArgs : UiEventArgs
{
public Vector2 OldPosition { get; }
public Vector2 NewPosition { get; }
public ControlMovedEventArgs(Vector2 oldPosition, Vector2 newPosition) : base(UiEventType.ControlMoved)
{
OldPosition = oldPosition;
NewPosition = newPosition;
}
}
public class ResizeEventArgs() : UiEventArgs(UiEventType.ControlResized)
{
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace Dashboard.Events
{
public class WindowCloseEvent() : UiEventArgs(UiEventType.WindowClose)
{
public bool Cancel { get; set; } = false;
}
}
+41
View File
@@ -0,0 +1,41 @@
namespace Dashboard
{
public enum FontWeight
{
_100 = 100,
_200 = 200,
_300 = 300,
_400 = 400,
_500 = 500,
_600 = 600,
_700 = 700,
_800 = 800,
_900 = 900,
Thin = _100,
Normal = _400,
Bold = _600,
Heavy = _900,
}
public enum FontSlant
{
Normal,
Italic,
Oblique,
}
public enum FontStretch
{
UltraCondensed = 500,
ExtraCondensed = 625,
Condensed = 750,
SemiCondensed = 875,
Normal = 1000,
SemiExpanded = 1125,
Expanded = 1250,
ExtraExpanded = 1500,
UltraExpanded = 2000,
}
}
+227
View File
@@ -0,0 +1,227 @@
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Numerics;
namespace Dashboard
{
/// <summary>
/// Enumeration of the kinds of gradients available.
/// </summary>
public enum GradientType
{
/// <summary>
/// A gradient which transitions over a set axis.
/// </summary>
Axial,
/// <summary>
/// A gradient which transitions along elliptical curves.
/// </summary>
Radial,
}
/// <summary>
/// A single gradient stop.
/// </summary>
/// <param name="Position">The position of the gradient stop. Must be [0,1].</param>
/// <param name="Color">The color value for the stop.</param>
public record struct GradientStop(float Position, Color Color);
/// <summary>
/// Represents a linear gradient.
/// </summary>
public struct Gradient : ICollection<GradientStop>, ICloneable, IEquatable<Gradient>
{
private readonly List<GradientStop> _stops = new List<GradientStop>();
/// <summary>
/// Gradient type.
/// </summary>
public GradientType Type { get; set; } = GradientType.Axial;
/// <summary>
/// First gradient control point.
/// </summary>
public Vector2 C0 { get; set; } = Vector2.Zero;
/// <summary>
/// Second gradient control point.
/// </summary>
public Vector2 C1 { get; set; } = Vector2.One;
/// <summary>
/// Number of stops in a gradient.
/// </summary>
public int Count => _stops.Count;
public bool IsReadOnly => false;
/// <summary>
/// Get a gradient control point.
/// </summary>
/// <param name="index">The index to get the control point for.</param>
public GradientStop this[int index]
{
get => _stops[index];
set
{
RemoveAt(index);
Add(value);
}
}
public Gradient()
{
}
public Gradient(Color a, Color b)
{
Add(new GradientStop(0, a));
Add(new GradientStop(1, b));
}
public Gradient(IEnumerable<GradientStop> stops)
{
_stops.AddRange(stops);
if (_stops.Any(x => x.Position < 0 || x.Position > 1))
throw new Exception("Gradient stop positions must be in the range [0, 1].");
_stops.Sort((a, b) => a.Position.CompareTo(b.Position));
}
public Color GetColor(float position)
{
if (Count == 0)
return Color.Black;
else if (Count == 1)
return _stops[0].Color;
int pivot = _stops.FindIndex(x => x.Position < position);
GradientStop left, right;
if (pivot == -1)
{
left = right = _stops[^1];
}
else if (pivot == 0)
{
left = right = _stops[0];
}
else
{
left = _stops[pivot-1];
right = _stops[pivot];
}
float weight = (position - left.Position) / (right.Position - left.Position);
Vector4 lcolor = new Vector4(left.Color.R, left.Color.G, left.Color.B, left.Color.A) * (1-weight);
Vector4 rcolor = new Vector4(right.Color.R, right.Color.G, right.Color.B, right.Color.A) * weight;
Vector4 color = lcolor + rcolor;
return Color.FromArgb((byte)color.W, (byte)color.X, (byte)color.Y, (byte)color.Z);
}
public Gradient Clone()
{
Gradient gradient = new Gradient()
{
Type = Type,
C0 = C0,
C1 = C1,
};
foreach (GradientStop stop in _stops)
{
gradient.Add(stop);
}
return gradient;
}
object ICloneable.Clone()
{
return Clone();
}
public IEnumerator<GradientStop> GetEnumerator()
{
return _stops.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_stops).GetEnumerator();
}
public void Add(GradientStop item)
{
if (item.Position < 0 || item.Position > 1)
throw new Exception("Gradient stop positions must be in the range [0, 1].");
int index = _stops.FindIndex(x => x.Position > item.Position);
if (index == -1)
index = _stops.Count;
_stops.Insert(index, item);
}
public void Clear()
{
_stops.Clear();
}
public bool Contains(GradientStop item)
{
return _stops.Contains(item);
}
public void CopyTo(GradientStop[] array, int arrayIndex)
{
_stops.CopyTo(array, arrayIndex);
}
public bool Remove(GradientStop item)
{
return _stops.Remove(item);
}
public void RemoveAt(int index)
{
_stops.RemoveAt(index);
}
public override int GetHashCode()
{
HashCode code = new HashCode();
code.Add(Count);
foreach (GradientStop item in this)
code.Add(item.GetHashCode());
return code.ToHashCode();
}
public bool Equals(Gradient other)
{
return
Type == other.Type &&
C0 == other.C0 &&
C1 == other.C1 &&
_stops.Equals(other._stops);
}
public override bool Equals(object? obj)
{
return obj is Gradient other && Equals(other);
}
public static bool operator ==(Gradient left, Gradient right)
{
return left.Equals(right);
}
public static bool operator !=(Gradient left, Gradient right)
{
return !left.Equals(right);
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using System.Collections;
using System.Collections.Generic;
namespace Dashboard
{
public class HashList<T> : IReadOnlyList<T>
where T : notnull
{
private readonly List<T> _list = new List<T>();
private readonly Dictionary<T, int> _map = new Dictionary<T, int>();
public T this[int index] => _list[index];
public int Count => _list.Count;
public int Intern(T value)
{
if (_map.TryGetValue(value, out int index))
return index;
index = Count;
_list.Add(value);
_map.Add(value, index);
return index;
}
public void Clear()
{
_list.Clear();
_map.Clear();
}
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
}
}
+211
View File
@@ -0,0 +1,211 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Dashboard
{
/// <summary>
/// Pixel format for images.
/// </summary>
[Flags]
public enum PixelFormat
{
None = 0,
R8I = R | I8,
Rg8I = Rg | I8,
Rgb8I = Rgb | I8,
Rgba8I = Rgba | I8,
R16F = R | F16,
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>
/// Color channels for images.
/// </summary>
public enum ColorChannel
{
/// <summary>
/// The zero channel. Used for swizzle masks.
/// </summary>
Zero = 0,
/// <summary>
/// The one channel. Used for swizzle masks.
/// </summary>
One = 1,
/// <summary>
/// An invalid swizzle mask.
/// </summary>
Unknown = 2,
/// <summary>
/// The red channel.
/// </summary>
Red = 4,
/// <summary>
/// The green channel.
/// </summary>
Green = 5,
/// <summary>
/// The blue channel.
/// </summary>
Blue = 6,
/// <summary>
/// The alpha channel.
/// </summary>
Alpha = 7,
}
/// <summary>
/// Defines a image swizzle mask.
/// </summary>
public struct ColorSwizzle : IEquatable<ColorSwizzle>
{
public short Mask;
private const int MASK = 7;
private const int RBIT = 0;
private const int GBIT = 3;
private const int BBIT = 6;
private const int ABIT = 9;
/// <summary>
/// Swizzle the red channel.
/// </summary>
public ColorChannel R
{
get => (ColorChannel)((Mask >> RBIT) & MASK);
set => Mask = (short)(((int)value << RBIT) | (Mask & ~(MASK << RBIT)));
}
/// <summary>
/// Swizzle the green channel.
/// </summary>
public ColorChannel G
{
get => (ColorChannel)((Mask >> GBIT) & MASK);
set => Mask = (short)(((int)value << GBIT) | (Mask & ~(MASK << GBIT)));
}
/// <summary>
/// Swizzle the blue channel.
/// </summary>
public ColorChannel B
{
get => (ColorChannel)((Mask >> BBIT) & MASK);
set => Mask = (short)(((int)value << BBIT) | (Mask & ~(MASK << BBIT)));
}
/// <summary>
/// Swizzle the alpha channel.
/// </summary>
public ColorChannel A
{
get => (ColorChannel)((Mask >> ABIT) & MASK);
set => Mask = (short)(((int)value << ABIT) | (Mask & ~(MASK << ABIT)));
}
public ColorSwizzle(short mask)
{
Mask = mask;
}
public ColorSwizzle(ColorChannel r, ColorChannel g, ColorChannel b, ColorChannel a)
{
Mask = (short)(((int)r << RBIT) | ((int)g << GBIT) | ((int)b << BBIT) | ((int)a << ABIT));
}
public override string ToString()
{
return $"{GetChannelChar(R)}{GetChannelChar(G)}{GetChannelChar(B)}{GetChannelChar(A)}";
char GetChannelChar(ColorChannel channel) => channel switch
{
ColorChannel.Zero => '0',
ColorChannel.Red => 'R',
ColorChannel.Green => 'G',
ColorChannel.Blue => 'B',
ColorChannel.Alpha => 'A',
ColorChannel.One => '1',
_ => '?',
};
}
public override int GetHashCode()
{
return Mask.GetHashCode();
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is ColorSwizzle other && Equals(other);
}
public bool Equals(ColorSwizzle other)
{
return Mask == other.Mask;
}
public static readonly ColorSwizzle Default = Parse("RGBA");
public static readonly ColorSwizzle White = Parse("1111");
public static readonly ColorSwizzle Black = Parse("0001");
public static readonly ColorSwizzle Transparent = Parse("0000");
public static readonly ColorSwizzle RedToGrayscale = Parse("RRR1");
public static readonly ColorSwizzle RedToWhiteAlpha = Parse("111A");
public static bool TryParse(ReadOnlySpan<char> str, out ColorSwizzle value)
{
if (str.Length < 4)
{
value = default;
return false;
}
ColorChannel r = GetChannelFromChar(str[0]);
ColorChannel g = GetChannelFromChar(str[1]);
ColorChannel b = GetChannelFromChar(str[2]);
ColorChannel a = GetChannelFromChar(str[3]);
value = new ColorSwizzle(r, g, b, a);
return true;
ColorChannel GetChannelFromChar(char chr) => chr switch
{
'0' => ColorChannel.Zero,
'R' => ColorChannel.Red,
'G' => ColorChannel.Green,
'B' => ColorChannel.Blue,
'A' => ColorChannel.Alpha,
'1' => ColorChannel.One,
_ => ColorChannel.Unknown,
};
}
public static ColorSwizzle Parse(ReadOnlySpan<char> str) =>
TryParse(str, out ColorSwizzle value) ? value : throw new FormatException(nameof(str));
public static bool operator ==(ColorSwizzle left, ColorSwizzle right) =>
left.Mask == right.Mask;
public static bool operator !=(ColorSwizzle left, ColorSwizzle right) =>
left.Mask != right.Mask;
}
}
+18
View 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
View 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
View 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
View 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
View 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,
}
}
+23
View File
@@ -0,0 +1,23 @@
namespace Dashboard
{
public enum BorderKind
{
Inset = -1,
Center = 0,
Outset = 1,
}
public enum CapType
{
None,
Circular,
Rectangular,
}
public enum CuspType
{
None,
Circular,
Rectangular,
}
}
+128
View File
@@ -0,0 +1,128 @@
using System.Numerics;
namespace Dashboard
{
public record struct LayoutUnits(LayoutUnit All)
{
public LayoutUnit XUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 0);
set => All = (LayoutUnit)(((int)All & ~(0xF << 0)) | ((int)value << 0));
}
public LayoutUnit YUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 4);
set => All = (LayoutUnit)(((int)All & ~(0xF << 4)) | ((int)value << 4));
}
public LayoutUnit ZUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 8);
set => All = (LayoutUnit)(((int)All & ~(0xF << 8)) | ((int)value << 8));
}
public LayoutUnit WUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 12);
set => All = (LayoutUnit)(((int)All & ~(0xF << 12)) | ((int)value << 12));
}
}
public record struct Metric(LayoutUnit Units, float Value)
{
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)
return this;
float pixels = Unit switch {
LayoutUnit.Pixel => Value,
LayoutUnit.Point => Value * (72f / dpi),
LayoutUnit.Millimeter => Value * (28.3464566929f / dpi),
LayoutUnit.Star => Value * rel / stars,
LayoutUnit.Percent => Value * rel / 100,
_ => throw new Exception(),
};
float value = target switch {
LayoutUnit.Pixel => pixels,
LayoutUnit.Point => Value * (dpi / 72f),
// MeasurementUnit.Millimeter =>
};
return new AdvancedMetric(target, value);
}
public override string ToString()
{
return $"{Value} {Unit.ToShortString()}";
}
public static bool TryParse(ReadOnlySpan<char> str, out AdvancedMetric metric)
{
metric = default;
return false;
}
public static AdvancedMetric Parse(ReadOnlySpan<char> str) =>
TryParse(str, out AdvancedMetric metric)
? metric
: throw new Exception($"Could not parse the value '{str}'.");
}
}
+30
View 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;
}
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace Dashboard
{
public enum Origin
{
Center = 0,
Left = (1 << 0),
Top = (1 << 1),
Right = (1 << 2),
Bottom = (1 << 3),
TopLeft = Top | Left,
BottomLeft = Bottom | Left,
BottomRight = Bottom | Right,
TopRight = Top | Right,
}
}
+202
View 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);
Initialize();
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);
[field: ThreadStatic]
public static Application Current
{
get => field ?? throw new InvalidOperationException("There is currently no current application.");
set;
}
}
}
+164
View File
@@ -0,0 +1,164 @@
using System.Numerics;
using Dashboard.Collections;
using Dashboard.Windowing;
namespace Dashboard.Pal
{
public abstract class DeviceContext : IContextBase<DeviceContext, IDeviceContextExtension>, IDeviceContext
{
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 abstract ISwapGroup SwapGroup { get; }
public abstract Vector2 FramebufferSize { get; }
public virtual bool DoubleBuffered { get; } = false;
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()
{
foreach (IDeviceContextExtension extension in _extensions)
{
extension.Begin();
}
}
// public abstract void Paint(object renderbuffer);
public virtual void End()
{
foreach (IDeviceContextExtension extension in _extensions)
{
extension.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);
}
}
@@ -0,0 +1,7 @@
namespace Dashboard.Pal
{
public interface IApplicationExtension : IContextExtensionBase<Application>
{
}
}
+122
View 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
View 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);
}
}
@@ -0,0 +1,8 @@
namespace Dashboard.Pal
{
public interface IDeviceContextExtension : IContextExtensionBase<DeviceContext>
{
void Begin() {}
void End() {}
}
}
+43
View File
@@ -0,0 +1,43 @@
namespace Dashboard.Windowing
{
/// <summary>
/// Interface for a class that composites multiple windows together.
/// </summary>
public interface ICompositor : IDisposable
{
void Composite(IPhysicalWindow window, IEnumerable<IVirtualWindow> windows);
}
/// <summary>
/// Interface for classes that implement a window manager.
/// </summary>
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener
{
/// <summary>
/// The physical window that this window manager is associated with.
/// </summary>
IPhysicalWindow PhysicalWindow { get; }
/// <summary>
/// The compositor that will composite all virtual windows.
/// </summary>
ICompositor Compositor { get; set; }
/// <summary>
/// The window that is currently focused.
/// </summary>
IVirtualWindow? FocusedWindow { get; }
/// <summary>
/// Create a virtual window.
/// </summary>
/// <returns>Virtual window handle.</returns>
IVirtualWindow CreateWindow();
/// <summary>
/// Focus a virtual window, if it is owned by this window manager.
/// </summary>
/// <param name="window">The window to focus.</param>
void Focus(IVirtualWindow window);
}
}
@@ -0,0 +1,21 @@
using System.Drawing;
using System.Numerics;
namespace Dashboard.Windowing
{
/// <summary>
/// Generic interface for the rendering system present in a <see cref="IPhysicalWindow"/>
/// </summary>
public interface IDeviceContext
{
/// <summary>
/// The swap group for this device context.
/// </summary>
ISwapGroup SwapGroup { get; }
/// <summary>
/// The size of the window framebuffer in pixels.
/// </summary>
Vector2 FramebufferSize { get; }
}
}
@@ -0,0 +1,14 @@
namespace Dashboard.Windowing
{
public interface IEventListener
{
event EventHandler? EventRaised;
/// <summary>
/// Send an event to this windowing object.
/// </summary>
/// <param name="sender">The object which generated the event.</param>
/// <param name="args">The event arguments sent.</param>
void SendEvent(object? sender, EventArgs args);
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Dashboard.Windowing
{
public interface IForm : IEventListener, IDisposable
{
public IWindow Window { get; }
public string Title { get; }
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace Dashboard.Windowing
{
public interface IPaintable
{
event EventHandler Painting;
/// <summary>
/// Paint this paintable object.
/// </summary>
void Paint();
}
}
+18
View File
@@ -0,0 +1,18 @@
namespace Dashboard.Windowing
{
/// <summary>
/// Interface that is used to swap the buffers of windows
/// </summary>
public interface ISwapGroup
{
/// <summary>
/// The swap interval for this swap group.
/// </summary>
public int SwapInterval { get; set; }
/// <summary>
/// Swap buffers.
/// </summary>
void Swap();
}
}
+93
View File
@@ -0,0 +1,93 @@
using System.Drawing;
using Dashboard.Pal;
namespace Dashboard.Windowing
{
/// <summary>
/// Base class of all Dashboard windows.
/// </summary>
public interface IWindow : IDisposable
{
/// <summary>
/// The application for this window.
/// </summary>
Application Application { get; }
/// <summary>
/// Name of the window.
/// </summary>
string Title { get; set; }
/// <summary>
/// The size of the window that includes the window extents.
/// </summary>
SizeF OuterSize { get; set; }
/// <summary>
/// The size of the window that excludes the window extents.
/// </summary>
SizeF ClientSize { get; set; }
IForm? Form { get; set; }
public event EventHandler? EventRaised;
/// <summary>
/// Subscribe to events from this window.
/// </summary>
/// <param name="listener">The event listener instance.</param>
/// <returns>An unsubscription token.</returns>
public void SubscribeEvent(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>
/// Base class for all Dashboard windows that are DPI-aware.
/// </summary>
public interface IDpiAwareWindow : IWindow
{
/// <summary>
/// DPI of the window.
/// </summary>
float Dpi { get; }
/// <summary>
/// Scale of the window.
/// </summary>
float Scale { get; }
}
/// <summary>
/// An object that represents a window in a virtual space, usually another window or a rendering system.
/// </summary>
public interface IVirtualWindow : IWindow, IEventListener
{
IWindowManager? WindowManager { get; set; }
}
/// <summary>
/// An object that represents a native operating system window.
/// </summary>
public interface IPhysicalWindow : IWindow
{
/// <summary>
/// The device context for this window.
/// </summary>
DeviceContext DeviceContext { get; }
/// <summary>
/// True if the window is double buffered.
/// </summary>
public bool DoubleBuffered { get; }
/// <summary>
/// The window manager for this physical window.
/// </summary>
public IWindowManager? WindowManager { get; set; }
}
}
@@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LanguageVersion>7.3</LanguageVersion>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ReFuel.FreeType" Version="0.1.0-rc.5" />
<PackageReference Include="ReFuel.StbImage" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard\Dashboard.csproj" />
</ItemGroup>
</Project>
@@ -1,13 +0,0 @@
namespace Dashboard.Media.Defaults
{
internal static class EnvironmentVariables
{
public const string SerifFont = "QUIK_SERIF_FONT";
public const string SansFont = "QUIK_SANS_FONT";
public const string MonospaceFont = "QUIK_MONOSPACE_FONT";
public const string CursiveFont = "QUIK_CURSIVE_FONT";
public const string FantasyFont = "QUIK_FANTASY_FONT";
public const string FallbackFontDatabase = "QUIK_FALLBACK_FONT_DB";
}
}
-16
View File
@@ -1,16 +0,0 @@
using System;
using ReFuel.FreeType;
namespace Dashboard.Media.Defaults
{
public static class FTProvider
{
private static FTLibrary _ft;
public static FTLibrary Ft => _ft;
static FTProvider()
{
FT.InitFreeType(out _ft);
}
}
}
@@ -1,269 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.Json;
using ReFuel.FreeType;
using Dashboard.Media.Font;
using Dashboard.PAL;
using Dashboard.Media.Defaults.Linux;
namespace Dashboard.Media.Defaults.Fallback
{
public class FallbackFontDatabase : IFontDataBase
{
private readonly string DbPath =
Environment.GetEnvironmentVariable(EnvironmentVariables.FallbackFontDatabase) ??
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "QUIK/fontdb.json");
private Dictionary<FontFace, FileInfo> FilesMap { get; } = new Dictionary<FontFace, FileInfo>();
private Dictionary<string, List<FontFace>> ByFamily { get; } = new Dictionary<string, List<FontFace>>();
private Dictionary<SystemFontFamily, FontFace> SystemFonts { get; } = new Dictionary<SystemFontFamily, FontFace>();
private List<FontFace> All { get; } = new List<FontFace>();
IEnumerable<FontFace> IFontDataBase.All => this.All;
public FallbackFontDatabase(bool rebuild = false)
{
// Load existing database if desired.
List<DbEntry> database;
if(!rebuild)
{
database = LoadDatabase();
}
else
{
database = new List<DbEntry>();
}
VerifyDatabase(database);
FlushDatabase(database);
database.ForEach(x => AddFont(x.Face, new FileInfo(x.FilePath)));
(FontFace, FileInfo) serif = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SerifFont, LinuxFonts.DefaultSerifFamilies, this);
SystemFonts[SystemFontFamily.Serif] = serif.Item1;
(FontFace, FileInfo) sans = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SansFont, LinuxFonts.DefaultSansFamilies, this);
SystemFonts[SystemFontFamily.Sans] = sans.Item1;
(FontFace, FileInfo) mono = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.MonospaceFont, LinuxFonts.DefaultMonospaceFamilies, this);
SystemFonts[SystemFontFamily.Monospace] = mono.Item1;
(FontFace, FileInfo) cursive = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.CursiveFont, LinuxFonts.DefaultCursiveFamilies, this);
SystemFonts[SystemFontFamily.Cursive] = cursive.Item1;
(FontFace, FileInfo) fantasy = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.FantasyFont, LinuxFonts.DefaultFantasyFamilies, this);
SystemFonts[SystemFontFamily.Fantasy] = fantasy.Item1;
}
public FileInfo FontFileInfo(FontFace face)
{
if (FilesMap.TryGetValue(face, out FileInfo info))
return info;
else
return null;
}
public Stream Open(FontFace face)
{
return FontFileInfo(face)?.OpenRead() ?? throw new FileNotFoundException();
}
public IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All)
{
// A bit scuffed and LINQ heavy but it should work.
IEnumerable<FontFace> candidates;
if (criteria.HasFlag(FontMatchCriteria.Family))
{
List<FontFace> siblings;
if (!ByFamily.TryGetValue(prototype.Family, out siblings))
{
return Enumerable.Empty<FontFace>();
}
candidates = siblings;
}
else
{
candidates = All;
}
return
candidates
.Where(x =>
implies(criteria.HasFlag(FontMatchCriteria.Slant), prototype.Slant == x.Slant) ||
implies(criteria.HasFlag(FontMatchCriteria.Weight), prototype.Weight == x.Weight) ||
implies(criteria.HasFlag(FontMatchCriteria.Stretch), prototype.Stretch == x.Stretch)
)
.OrderByDescending(x =>
(prototype.Slant == x.Slant ? 1 : 0) +
(prototype.Weight == x.Weight ? 1 : 0) +
(prototype.Stretch == x.Stretch ? 1 : 0) +
confidence(prototype.Family, x.Family) * 3
);
// a => b = a'+b
static bool implies(bool a, bool b)
{
return !a || b;
}
static int confidence(string target, string testee)
{
int i;
for (i = 0; i < target.Length && i < testee.Length && target[i] == testee[i]; i++);
return i;
}
}
public FontFace GetSystemFontFace(SystemFontFamily family)
{
return SystemFonts[family];
}
private void AddFont(FontFace face, FileInfo file)
{
if (!All.Contains(face))
All.Add(face);
FilesMap.TryAdd(face, file);
if (!ByFamily.TryGetValue(face.Family, out List<FontFace> siblings))
{
siblings = new List<FontFace>();
ByFamily.Add(face.Family, siblings);
}
if (!siblings.Contains(face))
siblings.Add(face);
}
private List<DbEntry> LoadDatabase()
{
FileInfo info = new FileInfo(DbPath);
if (!info.Exists)
return new List<DbEntry>();
using Stream str = info.OpenRead();
try
{
return JsonSerializer.Deserialize<List<DbEntry>>(str);
}
catch
{
return new List<DbEntry>();
}
}
private void VerifyDatabase(List<DbEntry> db)
{
// Very slow way to do this but how many fonts could a system have on average?
Dictionary<string, DbEntry> entires = new Dictionary<string, DbEntry>();
foreach (DbEntry entry in db)
{
FileInfo info = new FileInfo(entry.FilePath);
// Reprocess fonts that appear like this.
if (!info.Exists) continue;
else if (info.LastWriteTime > entry.AccessTime) continue;
}
string fontpath = null;
try
{
fontpath = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
if (string.IsNullOrEmpty(fontpath))
throw new Exception();
}
catch
{
foreach (string path in FontPaths)
{
if (Directory.Exists(path))
{
fontpath = path;
break;
}
}
// rip
if (string.IsNullOrEmpty(fontpath))
return;
}
SearchPathForFonts(entires, fontpath);
db.Clear();
db.AddRange(entires.Values);
}
private static void SearchPathForFonts(Dictionary<string, DbEntry> entries, string path)
{
DirectoryInfo dir = new DirectoryInfo(path);
foreach (FileInfo file in dir.EnumerateFiles())
{
SearchFileForFonts(entries, file);
}
foreach (DirectoryInfo directory in dir.EnumerateDirectories())
{
SearchPathForFonts(entries, directory.FullName);
}
}
private static void SearchFileForFonts(Dictionary<string, DbEntry> entries, FileInfo file)
{
if (entries.ContainsKey(file.FullName))
return;
if (FT.NewFace(FTProvider.Ft, file.FullName, 0, out FTFace face) != FTError.None)
return;
FontFace facename = FontFace.Parse(face.FamilyName, face.StyleName);
DbEntry entry = new DbEntry(facename, file.FullName);
entries.Add(file.FullName, entry);
FT.DoneFace(face);
}
private void FlushDatabase(List<DbEntry> db)
{
FileInfo info = new FileInfo(DbPath);
Directory.CreateDirectory(Path.GetDirectoryName(DbPath));
using Stream str = info.Open(FileMode.Create);
JsonSerializer.Serialize(str, db);
}
private static readonly string[] FontPaths = new string[] {
"/usr/share/fonts",
};
[JsonSerializable(typeof(DbEntry))]
private class DbEntry
{
[JsonIgnore] public FontFace Face => new FontFace(Family, Slant, Weight, Stretch);
public string Family { get; set; }
public FontSlant Slant { get; set; }
public FontWeight Weight { get; set; }
public FontStretch Stretch { get; set; }
public string FilePath { get; set; }
public DateTime AccessTime { get; set; }
public DbEntry() {}
public DbEntry(FontFace face, string path)
{
Family = face.Family;
Slant = face.Slant;
Weight = face.Weight;
Stretch = face.Stretch;
FilePath = path;
AccessTime = DateTime.Now;
}
}
}
}
@@ -1,93 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using ReFuel.FreeType;
using Dashboard.Media.Defaults.Fallback;
using Dashboard.Media.Defaults.Linux;
using Dashboard.Media.Font;
using Dashboard.PAL;
namespace Dashboard.Media.Defaults
{
public static class FontDataBaseProvider
{
public static IFontDataBase Instance { get; }
static FontDataBaseProvider()
{
try
{
// TODO: add as other operating systems are supported.
if (OperatingSystem.IsLinux())
{
Instance = new FontConfigFontDatabase();
}
else
{
Instance = new FallbackFontDatabase();
}
}
catch (Exception ex)
{
throw new NotSupportedException("Could not load a suitable font database implementation.", ex);
}
}
public static (FontFace, FileInfo) ResolveSystemFont(string envVar, string defaults, IFontDataBase db)
{
StringBuilder builder = new StringBuilder();
string user = Environment.GetEnvironmentVariable(envVar);
if (user != null)
{
builder.Append(user);
builder.Append(':');
}
builder.Append(defaults);
string[] list = builder.ToString().Split(':');
foreach (string item in list)
{
if (File.Exists(item))
{
// Process file.
if (FT.NewFace(FTProvider.Ft, item, 0, out FTFace ftface) != FTError.None)
{
continue;
}
FontFace face = FontFace.Parse(ftface.FamilyName, ftface.StyleName);
FT.DoneFace(ftface);
return (face, new FileInfo(item));
}
else
{
IEnumerable<FontFace> faces = db.Search(
new FontFace(item, FontSlant.Normal, FontWeight.Normal, FontStretch.Normal),
FontMatchCriteria.Family);
if (faces.Any())
{
FontFace face = faces.First();
return (face, db.FontFileInfo(face));
}
}
}
try
{
FontFace face = db.GetSystemFontFace(SystemFontFamily.Sans);
return (face, db.FontFileInfo(face));
}
catch (Exception ex)
{
throw new NotImplementedException("No fallback font yet.", ex);
}
}
}
}
@@ -1,24 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Dashboard.PAL;
namespace Dashboard.Media.Defaults
{
public class FreeTypeFontFactory : IFontFactory
{
public bool TryOpen(Stream stream, [NotNullWhen(true)] out QFont font)
{
try
{
font = new QFontFreeType(stream);
return true;
}
catch
{
font = null;
return false;
}
}
}
}
@@ -1,230 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Text;
using Dashboard;
namespace Dashboard.Media.Defaults
{
public static unsafe class FontConfig
{
private const string fontconfig = "fontconfig";
public static bool Exists { get; }
public static IntPtr FAMILY { get; } = Marshal.StringToHGlobalAnsi("family");
public static IntPtr STYLE { get; } = Marshal.StringToHGlobalAnsi("style");
public static IntPtr FILE { get; } = Marshal.StringToHGlobalAnsi("file");
public static IntPtr WEIGHT { get; } = Marshal.StringToHGlobalAnsi("weight");
public static IntPtr SLANT { get; } = Marshal.StringToHGlobalAnsi("slant");
static FontConfig()
{
try
{
if (FcInitLoadConfigAndFonts() == null)
{
Exists = false;
}
Exists = true;
}
catch
{
Exists = false;
}
}
[DllImport(fontconfig, EntryPoint = "FcInitLoadConfigAndFonts")]
public static extern FcConfig* FcInitLoadConfigAndFonts();
[DllImport(fontconfig, EntryPoint = "FcConfigGetCurrent")]
public static extern FcConfig ConfigGetCurrent();
[DllImport(fontconfig, EntryPoint = "FcPatternCreate")]
public static extern FcPattern PatternCreate();
[DllImport(fontconfig, EntryPoint = "FcPatternCreate")]
public static extern bool FcPatternAdd(FcPattern pattern, IntPtr what, FcValue value, bool append);
[DllImport(fontconfig, EntryPoint = "FcObjectSetBuild", CallingConvention = CallingConvention.Cdecl)]
public static extern FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4, IntPtr i5, IntPtr i6);
public static FcObjectSet ObjectSetBuild(IntPtr i1)
{
return ObjectSetBuild(i1, IntPtr.Zero);
}
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2)
{
return ObjectSetBuild(i1, i2, IntPtr.Zero);
}
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3)
{
return ObjectSetBuild(i1, i2, i3, IntPtr.Zero);
}
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4)
{
return ObjectSetBuild(i1, i2, i3, i4, IntPtr.Zero);
}
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4, IntPtr i5)
{
return ObjectSetBuild(i1, i2, i3, i4, i5, IntPtr.Zero);
}
[DllImport(fontconfig, EntryPoint = "FcFontList")]
public static extern FcFontSet FontList(FcConfig config, FcPattern pattern, FcObjectSet objectSet);
[DllImport(fontconfig, EntryPoint = "FcNameUnparse")]
public static extern IntPtr NameUnparse(FcPattern pat);
public static string NameUnparseStr(FcPattern pat) => Marshal.PtrToStringAnsi(NameUnparse(pat));
[DllImport(fontconfig, EntryPoint = "FcPatternGetString")]
public static extern FcResult PatternGetString(FcPattern p, IntPtr what, int n, out IntPtr val);
public static FcResult PatternGetString(FcPattern p, IntPtr what, out string str)
{
FcResult i = PatternGetString(p, what, 0, out IntPtr ptr);
if (i == FcResult.Match)
{
str = Marshal.PtrToStringAnsi(ptr);
}
else
{
str = null;
}
Marshal.FreeHGlobal(ptr);
return i;
}
[DllImport(fontconfig, EntryPoint = "FcPatternGet")]
public static extern FcResult PatternGet(FcPattern p, IntPtr what, int id, out FcValue value);
[DllImport(fontconfig, EntryPoint = "FcFontSetDestroy")]
public static extern void FontSetDestroy(FcFontSet fs);
[DllImport(fontconfig, EntryPoint = "FcObjectSetDestroy")]
public static extern void ObjectSetDestroy (FcObjectSet os);
[DllImport(fontconfig, EntryPoint = "FcConfigDestroy")]
public static extern void ConfigDestroy (FcConfig cfg);
[DllImport(fontconfig, EntryPoint = "FcPatternDestroy")]
public static extern void PatternDestroy (FcPattern os);
#region Range
[DllImport(fontconfig, EntryPoint = "FcRangeCreateDouble")]
public static extern IntPtr RangeCreateDouble(double begin, double end);
[DllImport(fontconfig, EntryPoint = "FcRangeCreateInteger")]
public static extern IntPtr RangeCreateInteger (int begin, int end);
[DllImport(fontconfig, EntryPoint = "FcRangeDestroy")]
public static extern void RangeDestroy(IntPtr range);
[DllImport(fontconfig, EntryPoint = "FcRangeCopy")]
public static extern IntPtr RangeCopy (IntPtr range);
[DllImport(fontconfig, EntryPoint = "FcRangeGetDouble")]
public static extern bool RangeGetDouble(IntPtr range, out double start, out double end);
#endregion
}
public enum FcResult
{
Match,
NoMatch,
TypeMismatch,
NoId,
OutOfMemory
}
public struct FcConfig
{
public readonly IntPtr Handle;
}
public struct FcPattern
{
public readonly IntPtr Handle;
}
public unsafe struct FcObjectSet
{
public readonly IntPtr Handle;
private Accessor* AsPtr => (Accessor*)Handle;
public int NObject => AsPtr->nobject;
public int SObject => AsPtr->sobject;
#pragma warning disable CS0649 // Will always have default value.
private struct Accessor
{
public int nobject;
public int sobject;
public byte** objects;
}
#pragma warning restore CS0649
}
public unsafe struct FcFontSet
{
public readonly IntPtr Handle;
private Accessor* AsPtr => (Accessor*)Handle;
public int NFont => AsPtr->nfont;
public int SFont => AsPtr->sfont;
public FcPattern this[int i]
{
get
{
if (i < 0 || i >= NFont)
throw new IndexOutOfRangeException();
return AsPtr->fonts[i];
}
}
#pragma warning disable CS0649 // Will always have default value.
private struct Accessor
{
public int nfont;
public int sfont;
public FcPattern* fonts;
}
#pragma warning restore CS0649
}
public enum FcType
{
Unknown = -1,
Void,
Integer,
Double,
String,
Bool,
Matrix,
CharSet,
FTFace,
LangSet,
Range
}
[StructLayout(LayoutKind.Explicit)]
public readonly struct FcValue
{
[FieldOffset(0)] public readonly FcType Type;
[FieldOffset(sizeof(FcType))] public readonly IntPtr Pointer;
[FieldOffset(sizeof(FcType))] public readonly int Int;
[FieldOffset(sizeof(FcType))] public readonly double Double;
}
}
@@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using ReFuel.FreeType;
using Dashboard.Media.Font;
using Dashboard.PAL;
namespace Dashboard.Media.Defaults.Linux
{
/// <summary>
/// Font database for Linux libfontconfig systems.
/// </summary>
public class FontConfigFontDatabase : IFontDataBase
{
private Dictionary<FontFace, FileInfo> FilesMap { get; } = new Dictionary<FontFace, FileInfo>();
private Dictionary<string, List<FontFace>> ByFamily { get; } = new Dictionary<string, List<FontFace>>();
private Dictionary<SystemFontFamily, FontFace> SystemFonts { get; } = new Dictionary<SystemFontFamily, FontFace>();
private List<FontFace> All { get; } = new List<FontFace>();
IEnumerable<FontFace> IFontDataBase.All => this.All;
public FontConfigFontDatabase()
{
if (!FontConfig.Exists)
{
throw new NotSupportedException("This host doesn't have fontconfig installed.");
}
FcConfig config = FontConfig.ConfigGetCurrent();
FcPattern pattern = FontConfig.PatternCreate();
FcObjectSet os = FontConfig.ObjectSetBuild(FontConfig.FAMILY, FontConfig.STYLE, FontConfig.FILE);
FcFontSet fs = FontConfig.FontList(config, pattern, os);
for (int i = 0; i < fs.NFont; i++)
{
FcPattern current = fs[i];
if (
FontConfig.PatternGetString(current, FontConfig.FAMILY, 0, out IntPtr pFamily) != FcResult.Match ||
FontConfig.PatternGetString(current, FontConfig.STYLE, 0, out IntPtr pStyle) != FcResult.Match)
{
continue;
}
string family = Marshal.PtrToStringUTF8(pFamily);
string style = Marshal.PtrToStringUTF8(pStyle);
FontFace face = FontFace.Parse(family, style);
FontConfig.PatternGetString(current, FontConfig.FILE, 0, out IntPtr pFile);
string file = Marshal.PtrToStringAnsi(pFile);
AddFont(face, new FileInfo(file));
}
FontConfig.FontSetDestroy(fs);
FontConfig.ObjectSetDestroy(os);
FontConfig.PatternDestroy(pattern);
(FontFace, FileInfo) serif = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SerifFont, LinuxFonts.DefaultSerifFamilies, this);
SystemFonts[SystemFontFamily.Serif] = serif.Item1;
(FontFace, FileInfo) sans = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SansFont, LinuxFonts.DefaultSansFamilies, this);
SystemFonts[SystemFontFamily.Sans] = sans.Item1;
(FontFace, FileInfo) mono = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.MonospaceFont, LinuxFonts.DefaultMonospaceFamilies, this);
SystemFonts[SystemFontFamily.Monospace] = mono.Item1;
(FontFace, FileInfo) cursive = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.CursiveFont, LinuxFonts.DefaultCursiveFamilies, this);
SystemFonts[SystemFontFamily.Cursive] = cursive.Item1;
(FontFace, FileInfo) fantasy = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.FantasyFont, LinuxFonts.DefaultFantasyFamilies, this);
SystemFonts[SystemFontFamily.Fantasy] = fantasy.Item1;
AddFont(serif.Item1, serif.Item2);
AddFont(sans.Item1, sans.Item2);
AddFont(mono.Item1, mono.Item2);
AddFont(cursive.Item1, cursive.Item2);
AddFont(fantasy.Item1, fantasy.Item2);
}
private void AddFont(FontFace face, FileInfo file)
{
if (!All.Contains(face))
All.Add(face);
FilesMap.TryAdd(face, file);
if (!ByFamily.TryGetValue(face.Family, out List<FontFace> siblings))
{
siblings = new List<FontFace>();
ByFamily.Add(face.Family, siblings);
}
if (!siblings.Contains(face))
siblings.Add(face);
}
public IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All)
{
// A bit scuffed and LINQ heavy but it should work.
IEnumerable<FontFace> candidates;
if (criteria.HasFlag(FontMatchCriteria.Family))
{
List<FontFace> siblings;
if (!ByFamily.TryGetValue(prototype.Family, out siblings))
{
return Enumerable.Empty<FontFace>();
}
candidates = siblings;
}
else
{
candidates = All;
}
return
candidates
.Where(x =>
implies(criteria.HasFlag(FontMatchCriteria.Slant), prototype.Slant == x.Slant) ||
implies(criteria.HasFlag(FontMatchCriteria.Weight), prototype.Weight == x.Weight) ||
implies(criteria.HasFlag(FontMatchCriteria.Stretch), prototype.Stretch == x.Stretch)
)
.OrderByDescending(x =>
(prototype.Slant == x.Slant ? 1 : 0) +
(prototype.Weight == x.Weight ? 1 : 0) +
(prototype.Stretch == x.Stretch ? 1 : 0) +
confidence(prototype.Family, x.Family) * 3
);
// a => b = a'+b
static bool implies(bool a, bool b)
{
return !a || b;
}
static int confidence(string target, string testee)
{
int i;
for (i = 0; i < target.Length && i < testee.Length && target[i] == testee[i]; i++);
return i;
}
}
public FileInfo FontFileInfo(FontFace face)
{
if (FilesMap.TryGetValue(face, out FileInfo info))
return info;
else
return null;
}
public Stream Open(FontFace face)
{
return FontFileInfo(face)?.OpenRead() ?? throw new FileNotFoundException();
}
public FontFace GetSystemFontFace(SystemFontFamily family)
{
return SystemFonts[family];
}
}
}
@@ -1,11 +0,0 @@
namespace Dashboard.Media.Defaults.Linux
{
internal static class LinuxFonts
{
public const string DefaultSerifFamilies = "Noto Serif:Nimbus Roman:Liberation Serif:FreeSerif:Times:Times New Roman";
public const string DefaultSansFamilies = "Noto Sans:Nimbus Sans:Droid Sans:Liberation Sans:FreeSans:Helvetica Neue:Helvetica:Arial";
public const string DefaultMonospaceFamilies = "Noto Mono:Nimbus Mono PS:Liberation Mono:DejaVu Mono:FreeMono:Lucida Console:Consolas:Courier:Courier New";
public const string DefaultCursiveFamilies = "";
public const string DefaultFantasyFamilies = "";
}
}
-83
View File
@@ -1,83 +0,0 @@
using System;
using System.Buffers;
using System.IO;
using ReFuel.FreeType;
using Dashboard.Media.Color;
using Dashboard.Media.Font;
namespace Dashboard.Media.Defaults
{
public class QFontFreeType : QFont
{
private MemoryStream ms;
private FTFace face;
public override FontFace Face => throw new NotImplementedException();
public QFontFreeType(Stream stream)
{
ms = new MemoryStream();
stream.CopyTo(ms);
FTError e = FT.NewMemoryFace(Ft, ms.GetBuffer(), ms.Length, 0, out face);
if (e != FTError.None)
{
throw new Exception("Could not load font face from stream.");
}
}
public override bool HasRune(int rune)
{
return FT.GetCharIndex(face, (ulong)rune) != 0;
}
protected override QImage Render(out QGlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options)
{
FT.SetCharSize(face, 0, (long)Math.Round(64*size), 0, (uint)Math.Round(options.Resolution));
uint index = FT.GetCharIndex(face, (ulong)codepoint);
FT.LoadGlyph(face, index, FTLoadFlags.Default);
ref readonly FTGlyphMetrics ftmetrics = ref face.Glyph.Metrics;
metrics = new QGlyphMetrics(codepoint,
new QVec2(ftmetrics.Width/64f, ftmetrics.Height/64f),
new QVec2(ftmetrics.HorizontalBearingX/64f, ftmetrics.HorizontalBearingY/64f),
new QVec2(ftmetrics.VerticalBearingX/64f, ftmetrics.VerticalBearingY/64f),
new QVec2(ftmetrics.HorizontalAdvance/64f, ftmetrics.VerticalAdvance/64f)
);
FT.RenderGlyph(face.Glyph, options.Sdf ? FTRenderMode.Sdf : FTRenderMode.Normal);
ref readonly FTBitmap bitmap = ref face.Glyph.Bitmap;
if (bitmap.Width == 0 || bitmap.Pitch == 0 || bitmap.Buffer == IntPtr.Zero)
{
return null;
}
QImageBuffer image = new QImageBuffer(QImageFormat.AlphaU8, (int)bitmap.Width, (int)bitmap.Rows);
image.LockBits2d(out QImageLock lk, QImageLockOptions.Default);
unsafe
{
Buffer.MemoryCopy((void*)bitmap.Buffer, (void*)lk.ImagePtr, lk.Width * lk.Height, bitmap.Width * bitmap.Rows);
}
image.UnlockBits();
return image;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
ms.Dispose();
}
FT.DoneFace(face);
}
private static FTLibrary Ft => FTProvider.Ft;
}
}
-98
View File
@@ -1,98 +0,0 @@
using System;
using System.IO;
using Dashboard.Media.Color;
using ReFuel.Stb;
namespace Dashboard.Media.Defaults
{
public unsafe class QImageStbi : QImage
{
private readonly StbImage image;
private QImageBuffer buffer;
private bool isSdf = false;
public override int Width => image.Width;
public override int Height => image.Height;
public override int Depth => 1;
public override bool IsSdf => isSdf;
public override QImageFormat InternalFormat => Stb2QImageFormat(image.Format);
public QImageStbi(Stream source)
{
// According to the stbi documentation, only a specific type of PNG
// files are premultiplied out of the box (iPhone PNG). Take the
// precision loss L and move on.
StbImage.FlipVerticallyOnLoad = true;
StbImage.UnpremultiplyOnLoad = true;
image = StbImage.Load(source);
}
public static QImageFormat Stb2QImageFormat(StbiImageFormat src)
{
switch (src)
{
case StbiImageFormat.Grey: return QImageFormat.RedU8;
case StbiImageFormat.Rgb: return QImageFormat.RgbU8;
case StbiImageFormat.Rgba: return QImageFormat.RgbaU8;
case StbiImageFormat.GreyAlpha: return QImageFormat.RaU8;
default: return QImageFormat.Undefined;
}
}
public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
{
if (options.MipLevel > 0) throw new Exception("This image has no mip levels.");
buffer?.Dispose();
buffer = new QImageBuffer(options.Format, Width, Height);
buffer.LockBits2d(out QImageLock dst, QImageLockOptions.Default);
byte *srcPtr = (byte*)image.ImagePointer;
QImageLock src = new QImageLock(InternalFormat, Width, Height, 1, (IntPtr)srcPtr);
FormatConvert.Convert(dst, src);
if (options.Premultiply)
{
FormatConvert.Premultiply(dst);
}
imageLock = dst;
}
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
{
LockBits2d(out imageLock, options);
}
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
{
if (depth != 1) throw new ArgumentOutOfRangeException(nameof(depth));
LockBits2d(out imageLock, options);
}
public override void UnlockBits()
{
buffer.UnlockBits();
}
public void SdfHint(bool value = true)
{
isSdf = value;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
buffer?.Dispose();
image.Dispose();
}
}
}
}
-155
View File
@@ -1,155 +0,0 @@
using System;
using System.Buffers;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using Dashboard.Media.Font;
// WebRequest is obsolete but runs on .NET framework.
#pragma warning disable SYSLIB0014
namespace Dashboard.Media.Defaults
{
public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontFace>
{
public bool AllowRemoteTransfers { get; set; } = false;
private readonly ArrayPool<byte> ByteArrays = ArrayPool<byte>.Create();
public IDisposable GetMedia(object key, MediaHint hint)
{
Type t = key.GetType();
/**/ if (t == typeof(string))
{
return GetMedia((string)key, hint);
}
else if (t == typeof(Uri))
{
return GetMedia((Uri)key, hint);
}
else if (t == typeof(FileInfo))
{
return GetMedia((FileInfo)key, hint);
}
else if (t == typeof(FontFace))
{
return GetMedia((FontFace)key, hint);
}
else
{
return null;
}
}
public IDisposable GetMedia(Uri uri, MediaHint hint)
{
throw new NotImplementedException();
}
public IDisposable GetMedia(string str, MediaHint hint)
{
throw new NotImplementedException();
}
public IDisposable GetMedia(FileInfo file, MediaHint hint)
{
throw new NotImplementedException();
}
public IDisposable GetMedia(FontFace key, MediaHint hint)
{
throw new NotImplementedException();
}
public Stream OpenResource(FileInfo file)
{
if (file.Exists)
{
return file.Open(FileMode.Open);
}
else
{
return null;
}
}
public Stream OpenResource(Uri uri)
{
switch (uri.Scheme)
{
case "http":
case "https":
if (!AllowRemoteTransfers) return null;
try
{
WebRequest request = HttpWebRequest.Create(uri);
WebResponse response = request.GetResponse();
MemoryStream stream = new MemoryStream();
response.GetResponseStream().CopyTo(stream);
response.Close();
stream.Position = 0;
return stream;
}
catch
{
return null;
}
case "file":
return OpenResource(new FileInfo(uri.AbsolutePath));
default:
return null;
}
}
public Stream OpenResource(string key)
{
if (File.Exists(key))
{
return File.Open(key, FileMode.Open);
}
else if (Uri.TryCreate(key, UriKind.RelativeOrAbsolute, out Uri uri))
{
return OpenResource(uri);
}
else
{
return null;
}
}
MediaHint InferMedia(Stream str, MediaHint hint)
{
if (hint != MediaHint.None)
{
return hint;
}
byte[] array = ByteArrays.Rent(4);
str.Read(array, 0, 4);
str.Position = 0;
foreach (var(type, seq) in MediaTypes)
{
if (seq.SequenceEqual(array))
return hint;
}
return MediaHint.None;
}
private readonly (MediaHint, byte[])[] MediaTypes = new (MediaHint, byte[])[] {
(MediaHint.Image, new byte[] { 0x42, 0x4d }), /* .bmp `BM` */
(MediaHint.Image, new byte[] { 0x47, 0x49, 0x46, 0x38 }), /* .gif `GIF8` */
(MediaHint.Image, new byte[] { 0xff, 0xd8, 0xff, 0xe0 }), /* .jpg (JFIF) */
(MediaHint.Image, new byte[] { 0xff, 0xd8, 0xff, 0xe1 }), /* .jpg (EXIF) */
(MediaHint.Image, new byte[] { 0x89, 0x50, 0x4e, 0x47 }), /* .png `.PNG `*/
(MediaHint.Image, new byte[] { 0x4d, 0x4d, 0x00, 0x2a }), /* .tif (motorola) */
(MediaHint.Image, new byte[] { 0x49, 0x49, 0x2a, 0x00 }), /* .tif (intel) */
(MediaHint.Font, new byte[] { 0x00, 0x01, 0x00, 0x00 }), /* .ttf */
(MediaHint.Font, new byte[] { 0x4F, 0x54, 0x54, 0x4F }), /* .otf */
};
}
}
@@ -1,151 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
#if false
namespace Quik.Media.Defaults.Win32
{
public class EnumerateFonts
{
private const byte DEFAULT_CHARSET = 1;
public static void Enumerate(FontFace font)
{
/* It's windows, just borrow the desktop window. */
IntPtr hdc = GetDC(GetDesktopWindow());
List<(LogFontA, TextMetricA)> list = new List<(LogFontA, TextMetricA)>();
LogFontA font2 = new LogFontA()
{
//FaceName = font.Family,
Weight = ((font.Style & FontStyle.Bold) != 0) ? FontWeight.Bold : FontWeight.Regular,
Italic = (font.Style & FontStyle.Italic) != 0,
CharSet = DEFAULT_CHARSET
};
Console.WriteLine(font2.FaceName);
EnumFontFamiliesExProc proc = (in LogFontA font, in TextMetricA metric, int type, IntPtr lparam) =>
{
list.Add((font, metric));
return 0;
};
EnumFontFamiliesExA(hdc, font2, proc, IntPtr.Zero, 0);
}
private const string gdi32 = "Gdi32.dll";
private const string user32 = "User32.dll";
[DllImport(gdi32)]
private static extern int EnumFontFamiliesExA(
IntPtr hdc,
in LogFontA font,
[MarshalAs(UnmanagedType.FunctionPtr)] EnumFontFamiliesExProc proc,
IntPtr lparam,
int flags /* Should be zero. */);
[DllImport(user32)]
private static extern IntPtr /* HWND */ GetDesktopWindow();
[DllImport(user32)]
private static extern IntPtr /* HDC */ GetDC(IntPtr hwnd);
private delegate int EnumFontFamiliesExProc(in LogFontA font, in TextMetricA metric, int fontType, IntPtr lParam);
private struct LogFontA
{
public long Height;
public long Width;
public long Escapement;
public long Orientation;
public FontWeight Weight;
[MarshalAs(UnmanagedType.U1)]
public bool Italic;
[MarshalAs(UnmanagedType.U1)]
public bool Underline;
[MarshalAs(UnmanagedType.U1)]
public bool StrikeOut;
public byte CharSet;
public byte OutPrecision;
public byte ClipPrecision;
public byte PitchAndFamily;
private unsafe fixed byte aFaceName[32];
public unsafe string FaceName
{
get
{
fixed (byte* str = aFaceName)
{
int len = 0;
for (; str[len] != 0 && len < 32; len++) ;
return Encoding.UTF8.GetString(str, len);
}
}
set
{
fixed (byte *str = aFaceName)
{
Span<byte> span = new Span<byte>(str, 32);
Encoding.UTF8.GetBytes(value, span);
span[31] = 0;
}
}
}
}
private struct TextMetricA
{
public long Height;
public long Ascent;
public long Descent;
public long InternalLeading;
public long ExternalLeading;
public long AveCharWidth;
public long MaxCharWidth;
public long Weight;
public long Overhang;
public long DigitizedAspectX;
public long DigitizedAspectY;
public byte FirstChar;
public byte LastChar;
public byte DefaultChar;
public byte BreakChar;
public byte Italic;
public byte Underlined;
public byte StruckOut;
public byte PitchAndFamily;
public byte CharSet;
}
private enum FontWeight : long
{
DontCare = 0,
Thin = 100,
ExtraLight = 200,
UltraLight = 200,
Light = 300,
Normal = 400,
Regular = 400,
Medium = 500,
Semibold = 600,
Demibold = 600,
Bold = 700,
Extrabold = 800,
Ultrabold = 800,
Heavy = 900,
Black = 900
}
}
}
#endif
+77
View File
@@ -0,0 +1,77 @@
using System.Collections.Concurrent;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public class ContextCollector : IDisposable
{
private readonly ConcurrentQueue<GLObject> _disposedObjects = new ConcurrentQueue<GLObject>();
public void Dispose()
{
while (_disposedObjects.TryDequeue(out GLObject obj))
{
obj.Dispose();
}
}
void DeleteObject(ObjectIdentifier identifier, int handle) => _disposedObjects.Enqueue(new GLObject(identifier, handle));
public void DeleteTexture(int texture) => DeleteObject(ObjectIdentifier.Texture, texture);
public void DeleteBufffer(int buffer) => DeleteObject(ObjectIdentifier.Buffer, buffer);
public void DeleteFramebuffer(int framebuffer) => DeleteObject(ObjectIdentifier.Framebuffer, framebuffer);
public void DeleteRenderBuffer(int renderbuffer) => DeleteObject(ObjectIdentifier.Renderbuffer, renderbuffer);
public void DeleteSampler(int sampler) => DeleteObject(ObjectIdentifier.Sampler, sampler);
public void DeleteShader(int shader) => DeleteObject(ObjectIdentifier.Shader, shader);
public void DeleteProgram(int program) => DeleteObject(ObjectIdentifier.Program, program);
public void DeleteVertexArray(int vertexArray) => DeleteObject(ObjectIdentifier.VertexArray, vertexArray);
public void DeleteQuery(int query) => DeleteObject(ObjectIdentifier.Query, query);
public void DeleteProgramPipeline(int programPipeline) => DeleteObject(ObjectIdentifier.ProgramPipeline, programPipeline);
public void DeleteTransformFeedback(int transformFeedback) => DeleteObject(ObjectIdentifier.TransformFeedback, transformFeedback);
private readonly record struct GLObject(ObjectIdentifier Type, int Handle)
{
public void Dispose()
{
switch (Type)
{
case ObjectIdentifier.Texture:
GL.DeleteTexture(Handle);
break;
case ObjectIdentifier.Buffer:
GL.DeleteBuffer(Handle);
break;
case ObjectIdentifier.Framebuffer:
GL.DeleteFramebuffer(Handle);
break;
case ObjectIdentifier.Renderbuffer:
GL.DeleteRenderbuffer(Handle);
break;
case ObjectIdentifier.Sampler:
GL.DeleteSampler(Handle);
break;
case ObjectIdentifier.Shader:
GL.DeleteShader(Handle);
break;
case ObjectIdentifier.VertexArray:
GL.DeleteVertexArray(Handle);
break;
case ObjectIdentifier.Program:
GL.DeleteProgram(Handle);
break;
case ObjectIdentifier.Query:
GL.DeleteQuery(Handle);
break;
case ObjectIdentifier.ProgramPipeline:
GL.DeleteProgramPipeline(Handle);
break;
case ObjectIdentifier.TransformFeedback:
GL.DeleteTransformFeedback(Handle);
break;
}
}
}
public static readonly ContextCollector Global = new ContextCollector();
}
}
+22
View File
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Drawing\immediate.frag" />
<EmbeddedResource Include="Drawing\immediate.frag" />
<None Remove="Drawing\immediate.vert" />
<EmbeddedResource Include="Drawing\immediate.vert" />
</ItemGroup>
</Project>
@@ -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;
}
}
}
+113
View File
@@ -0,0 +1,113 @@
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Pal;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL.Drawing
{
public class DirectRendering : IDirectRendering, ITextureExtension
{
public GLDeviceContext Context { get; private set; } = null!;
public string DriverName { get; } = "Dashboard OpenGL";
public string DriverVendor { get; } = "Dashboard";
public Version DriverVersion { get; } = new Version(0, 1);
IContextBase IContextExtensionBase.Context => Context;
DeviceContext IContextExtensionBase<DeviceContext>.Context => Context;
public bool SupportsArbTextureStorage { get; private set; }
public bool SupportsAnisotropy { get; private set; }
private int _vao = -1;
private List<GLTexture> _textures = new List<GLTexture>();
public void Dispose()
{
GC.SuppressFinalize(this);
}
public void Require(DeviceContext context)
{
Context = (GLDeviceContext)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(IContextBase context) => Require((DeviceContext)context);
public void Begin()
{
if (_vao == -1)
{
GL.GenVertexArray(out _vao);
}
}
public void End()
{
}
public IShader CreateShader<T>(T createInfo) where T : IShaderCreateInfo
{
int program = createInfo switch
{
GlslShaderCreateInfo glsl => glsl.CreateProgram(),
GlslComputeShaderCreateInfo compute => compute.CreateProgram(),
SpirvShaderCreateInfo spirv => spirv.CreateProgram(),
_ => throw new Exception($"Unsupported shader pipeline type {createInfo.GetType()}."),
};
return new GLShader((GLDeviceContext)Context, program);
}
public IBuffer CreateBuffer(BufferAccessPattern pattern, long size)
{
return GLBuffer.Create((GLDeviceContext)Context, pattern, size);
}
public void Draw(DrawCall drawCall)
{
Vector2 size = Context.FramebufferSize;
drawCall.SetAll(size, _vao, false);
if (drawCall.VertexSpecification.ElementBuffer is null)
{
GL.DrawArrays(drawCall.Primitive.OpenGL, drawCall.First, drawCall.Count);
}
else if (drawCall.BaseVertex == 0)
{
GL.DrawElements(drawCall.Primitive.OpenGL, drawCall.Count, drawCall.IndexType.OpenGL, (nint)drawCall.Offset);
}
else
{
GL.DrawElementsBaseVertex(drawCall.Primitive.OpenGL, drawCall.Count, drawCall.IndexType.OpenGL, (nint)drawCall.Offset, drawCall.BaseVertex);
}
}
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);
ITexture IDirectRendering.CreateTexture(TextureType type)
{
return CreateTexture(type);
}
}
}
+137
View File
@@ -0,0 +1,137 @@
using System.Collections.Immutable;
using System.Security.Cryptography.X509Certificates;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL.Drawing
{
public class GLShader : IShader
{
public GLDeviceContext Context { get; }
public int Handle { get; }
public ImmutableList<ShaderMappingProperty> Attributes { get; }
public ImmutableList<ShaderMappingProperty> Uniforms { get; }
public ImmutableList<ShaderMappingProperty> Blocks { get; }
public ImmutableList<ShaderMappingProperty> Textures { get; }
public GLShader(GLDeviceContext context, int handle)
{
Context = context;
Handle = handle;
List<ShaderMappingProperty> list = new List<ShaderMappingProperty>();
List<ShaderMappingProperty> list2 = new List<ShaderMappingProperty>();
int count = GL.GetProgrami(handle, ProgramProperty.ActiveAttributes);
for (int i = 0; i < count; i++)
{
list.Add(GetAttribute(handle, i));
}
Attributes = list.ToImmutableList();
list.Clear();
count = GL.GetProgrami(handle, ProgramProperty.ActiveUniforms);
for (int i = 0; i < count; i++)
{
var property = GetUniform(handle, i, out bool isSampler);
list.Add(property);
if (isSampler)
list2.Add(property);
}
Uniforms = list.ToImmutableList();
Textures = list2.ToImmutableList();
list.Clear();
count = GL.GetProgrami(handle, ProgramProperty.ActiveUniformBlocks);
for (int i = 0; i < count; i++)
{
var property = GetBlock(handle, i);
list.Add(property);
}
Blocks = list.ToImmutableList();
}
private static ShaderMappingProperty GetAttribute(int program, int index)
{
string name = GL.GetActiveAttrib(program, (uint)index, 260, out _, out _, out _);
int location = GL.GetAttribLocation(program, name);
return new ShaderMappingProperty(name, location);
}
private static ShaderMappingProperty GetUniform(int program, int index, out bool isSampler)
{
string name = GL.GetActiveUniform(program, (uint)index, 260, out _, out _, out OpenTK.Graphics.OpenGL.UniformType type);
int location = GL.GetUniformLocation(program, name);
isSampler = type switch
{
OpenTK.Graphics.OpenGL.UniformType.Sampler1D or
OpenTK.Graphics.OpenGL.UniformType.Sampler1DArray or
OpenTK.Graphics.OpenGL.UniformType.Sampler1DShadow or
OpenTK.Graphics.OpenGL.UniformType.Sampler1DArrayShadow or
OpenTK.Graphics.OpenGL.UniformType.IntSampler1D or
OpenTK.Graphics.OpenGL.UniformType.IntSampler1DArray or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler1D or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler1DArray or
OpenTK.Graphics.OpenGL.UniformType.Sampler2D or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DShadow or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DRect or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DRectShadow or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DArray or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DArrayShadow or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DMultisample or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DMultisampleArray or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2D or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DRect or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DArray or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DMultisample or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DMultisampleArray or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2D or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DRect or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DArray or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DMultisample or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DMultisampleArray or
OpenTK.Graphics.OpenGL.UniformType.Sampler3D or
OpenTK.Graphics.OpenGL.UniformType.IntSampler3D or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler3D or
OpenTK.Graphics.OpenGL.UniformType.SamplerCube or
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeShadow or
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeMapArray or
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeMapArrayShadow or
OpenTK.Graphics.OpenGL.UniformType.IntSamplerCube or
OpenTK.Graphics.OpenGL.UniformType.IntSamplerCubeMapArray or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSamplerCube or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSamplerCubeMapArray
=> true,
_ => false,
};
return new ShaderMappingProperty(name, location);
}
private static ShaderMappingProperty GetBlock(int program, int index)
{
int size = 0;
unsafe
{
GL.GetActiveUniformBlockiv(program, (uint)index, UniformBlockPName.UniformBlockDataSize, &size);
}
GL.GetActiveUniformBlockName(program, (uint)index, 1024, out _, out string name);
return new ShaderMappingProperty(name, index);
}
public void Dispose()
{
Context.Collector.DeleteProgram(Handle);
}
}
}
@@ -0,0 +1,499 @@
using System.Drawing;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using UniformType = Dashboard.Drawing.UniformType;
using Vector2 = System.Numerics.Vector2;
namespace Dashboard.OpenGL.Drawing
{
public static class GLStateExtensions
{
extension(VertexAttributeType type)
{
public All OpenGL => type switch
{
VertexAttributeType.UnsignedByte => All.UnsignedByte,
VertexAttributeType.UnsignedShort => All.UnsignedShort,
VertexAttributeType.UnsignedInt => All.UnsignedInt,
VertexAttributeType.Byte => All.Byte,
VertexAttributeType.Short => All.Short,
VertexAttributeType.Int => All.Int,
VertexAttributeType.Half => All.HalfFloat,
VertexAttributeType.Float => All.Float,
VertexAttributeType.Double => All.Double,
_ => throw new ArgumentOutOfRangeException(),
};
public VertexAttribType VertexAttribType => (VertexAttribType)type.OpenGL;
public VertexAttribPointerType VertexAttribPointerType => (VertexAttribPointerType)type.OpenGL;
}
extension(TextureType type)
{
public TextureTarget OpenGL => type switch
{
TextureType.Texture1D => TextureTarget.Texture1D,
TextureType.Texture2D => TextureTarget.Texture2D,
TextureType.Texture2DArray => TextureTarget.Texture2DArray,
TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
TextureType.Texture3D => TextureTarget.Texture3D,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendEquation eq)
{
public BlendEquationMode OpenGL => eq switch
{
BlendEquation.Add => BlendEquationMode.FuncAdd,
BlendEquation.Subtract => BlendEquationMode.FuncSubtract,
BlendEquation.Max => BlendEquationMode.Max,
BlendEquation.Min => BlendEquationMode.Min,
BlendEquation.ReverseSubtract => BlendEquationMode.FuncReverseSubtract,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendFunction mode)
{
public BlendingFactor OpenGL => mode switch
{
BlendFunction.One => BlendingFactor.One,
BlendFunction.Zero => BlendingFactor.Zero,
BlendFunction.SourceAlpha => BlendingFactor.SrcAlpha,
BlendFunction.OneMinusSourceAlpha => BlendingFactor.OneMinusSrcAlpha,
BlendFunction.DestinationAlpha => BlendingFactor.DstAlpha,
BlendFunction.OneMinusDestinationAlpha => BlendingFactor.OneMinusDstAlpha,
BlendFunction.SourceColor => BlendingFactor.SrcColor,
BlendFunction.OneMinusSourceColor => BlendingFactor.OneMinusSrcColor,
BlendFunction.DestinationColor => BlendingFactor.DstColor,
BlendFunction.OneMinusDestinationColor => BlendingFactor.OneMinusDstColor,
BlendFunction.ConstantColor => BlendingFactor.OneMinusConstantColor,
BlendFunction.OneMinusConstantColor => BlendingFactor.OneMinusConstantColor,
BlendFunction.ConstantAlpha => BlendingFactor.OneMinusConstantAlpha,
BlendFunction.OneMinusConstantAlpha => BlendingFactor.OneMinusConstantAlpha,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(TestFunction test)
{
public All OpenGL => test switch
{
TestFunction.Always => All.Always,
TestFunction.Equal => All.Equal,
TestFunction.Greater => All.Greater,
TestFunction.GreaterThanOrEqual => All.Gequal,
TestFunction.LessThan => All.Less,
TestFunction.LessThanOrEqual => All.Lequal,
TestFunction.Never => All.Never,
TestFunction.NotEqual => All.Notequal,
_ => throw new ArgumentOutOfRangeException(),
};
public DepthFunction DepthFunction => (DepthFunction)test.OpenGL;
public StencilFunction StencilFunction => (StencilFunction)test.OpenGL;
}
extension(StencilOperation op)
{
public StencilOp OpenGL => op switch
{
StencilOperation.Keep => StencilOp.Keep,
StencilOperation.Zero => StencilOp.Zero,
StencilOperation.Replace => StencilOp.Replace,
StencilOperation.Increment => StencilOp.Incr,
StencilOperation.UncheckedIncrement => StencilOp.IncrWrap,
StencilOperation.Decrement => StencilOp.Decr,
StencilOperation.UncheckedDecrement => StencilOp.DecrWrap,
StencilOperation.Invert => StencilOp.Invert,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(MeshPrimitive primitive)
{
public PrimitiveType OpenGL => primitive switch
{
MeshPrimitive.Line => PrimitiveType.Lines,
MeshPrimitive.Point => PrimitiveType.Points,
MeshPrimitive.Triangle => PrimitiveType.Triangles,
MeshPrimitive.TriangleFan => PrimitiveType.TriangleFan,
MeshPrimitive.TriangleStrip => PrimitiveType.TriangleStrip,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(IndexType index)
{
public DrawElementsType OpenGL => index switch
{
IndexType.Int or IndexType.UnsignedInt => DrawElementsType.UnsignedInt,
IndexType.Short or IndexType.UnsignedShort => DrawElementsType.UnsignedShort,
IndexType.UnsignedLong => throw new NotSupportedException(),
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendMode mode)
{
public void SetEnabled()
{
if (mode.Enabled)
GL.Enable(EnableCap.Blend);
else
GL.Disable(EnableCap.Blend);
}
public void SetMode()
{
if (mode.IsSeparate)
{
GL.BlendEquationSeparate(mode.ColorEquation.OpenGL, mode.AlphaEquation.OpenGL);
GL.BlendFuncSeparate(
mode.Color.Source.OpenGL, mode.Color.Destination.OpenGL,
mode.Alpha.Source.OpenGL, mode.Alpha.Destination.OpenGL);
}
else
{
GL.BlendEquation(mode.UnifiedEquation.Value.OpenGL);
GL.BlendFunc(mode.Unified.Value.Source.OpenGL, mode.Unified.Value.Destination.OpenGL);
}
}
public void SetConstant()
{
GL.BlendColor(mode.Constant.R / 255f, mode.Constant.G / 255f,mode.Constant.B / 255f,mode.Constant.A / 255f);
}
public void SetAll()
{
mode.SetEnabled();
mode.SetConstant();
mode.SetMode();
}
}
extension(DepthMode mode)
{
public void SetEnabled()
{
if (mode.Test)
GL.Enable(EnableCap.DepthTest);
else
GL.Disable(EnableCap.DepthTest);
}
public void SetMask() => GL.DepthMask(mode.Mask);
public void SetFunction() => GL.DepthFunc(mode.Function.DepthFunction);
public void SetAll()
{
mode.SetEnabled();
mode.SetMask();
mode.SetFunction();
}
}
extension(StencilMode mode)
{
public void SetEnabled()
{
if (mode.Enabled)
GL.Enable(EnableCap.StencilTest);
else
GL.Disable(EnableCap.StencilTest);
}
public void SetFunction() => GL.StencilFunc(mode.Function.StencilFunction, mode.Reference, (uint)mode.Mask);
public void SetOperations() => GL.StencilOp(mode.Fail.OpenGL, mode.DepthFail.OpenGL, mode.Pass.OpenGL);
public void SetAll()
{
mode.SetEnabled();
mode.SetFunction();
mode.SetOperations();
}
}
extension(PipelineState state)
{
public void SetViewport(Vector2 size)
{
Vector2 min = Vector2.Max(Vector2.Zero, state.ViewportRegion.Min);
Vector2 max = Vector2.Min(size, state.ViewportRegion.Max);
GL.Viewport(
(int)Math.Round(min.X),
(int)Math.Round(size.Y - max.Y),
(int)Math.Round(max.X - min.X),
(int)Math.Round(max.Y - min.Y));
}
// TODO: clamp to viewport size
public void SetScissor(Vector2 size)
{
if (state.ScissorRegion == new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity))
{
GL.Disable(EnableCap.ScissorTest);
return;
}
GL.Enable(EnableCap.ScissorTest);
GL.Scissor(
(int)Math.Round(state.ViewportRegion.Left),
(int)Math.Round(size.Y - state.ViewportRegion.Top),
(int)Math.Round(state.ViewportRegion.Right - state.ViewportRegion.Left),
(int)Math.Round(state.ViewportRegion.Bottom - state.ViewportRegion.Top));
}
public void SetFrontFace()
{
GL.FrontFace(state.FrontFace switch
{
WindingOrder.Clockwise => FrontFaceDirection.Cw,
WindingOrder.Counterclockwise => FrontFaceDirection.Ccw,
_ => throw new ArgumentOutOfRangeException(),
});
}
public void SetCullMode()
{
if (state.CullMode == FaceCulling.None)
{
GL.Disable(EnableCap.CullFace);
return;
}
GL.Enable(EnableCap.CullFace);
GL.CullFace(state.CullMode switch
{
FaceCulling.Both => TriangleFace.FrontAndBack,
FaceCulling.Front => TriangleFace.Front,
FaceCulling.Back => TriangleFace.Back,
_ => throw new ArgumentOutOfRangeException(),
});
}
public void SetBlendMode() => state.BlendMode.SetAll();
public void SetDepthMode() => state.DepthMode.SetAll();
public void SetStencilMode() => state.StencilMode.SetAll();
public void SetColorMask()
{
GL.ColorMask(state.RedMask, state.GreenMask, state.BlueMask, state.AlphaMask);
}
public void SetPointSize()
{
GL.PointSize(state.PointSize);
}
public void SetLineWidth()
{
GL.LineWidth(state.LineWidth);
}
}
extension(DrawCall call)
{
public void UsePipeline() => GL.UseProgram((call.ShaderPipeline as GLShader)?.Handle ?? 0);
public void SetViewport(Vector2 size) => call.PipelineState.SetViewport(size);
public void SetScissor(Vector2 size) => call.PipelineState.SetViewport(size);
public void SetFrontFace() => call.PipelineState.SetFrontFace();
public void SetCullMode() => call.PipelineState.SetCullMode();
public void SetBlendMode() => call.PipelineState.BlendMode.SetAll();
public void SetDepthMode() => call.PipelineState.DepthMode.SetAll();
public void SetStencilMode() => call.PipelineState.StencilMode.SetAll();
public void SetColorMask() => call.PipelineState.SetColorMask();
public void SetPointSize() => call.PipelineState.SetPointSize();
public void SetLineWidth() => call.PipelineState.SetLineWidth();
public void SetPrimitiveRestart()
{
if (!call.EnableRestart)
{
GL.Disable(EnableCap.PrimitiveRestart);
return;
}
GL.Enable(EnableCap.PrimitiveRestart);
GL.PrimitiveRestartIndex((uint)call.RestartIndex);
}
public void UseTextures()
{
int i = 0;
foreach (ITexture? texture in call.Textures)
{
GL.ActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + i));
if (texture is GLTexture glTexture)
{
GL.BindTexture(glTexture.Type.OpenGL, glTexture.Handle);
}
else
{
GL.BindTexture(TextureTarget.Texture2D, 0);
}
}
}
public void UpdateVertexArrays(int vao)
{
GL.BindVertexArray(vao);
VertexSpecification spec = call.VertexSpecification;
if (spec.ElementBuffer != null)
{
GL.BindBuffer(BufferTarget.ElementArrayBuffer, (spec.ElementBuffer as GLBuffer)?.Handle ?? 0);
}
for (int i = 0; i < spec.Attributes.Count; i++)
{
VertexAttribute attrib = spec.Attributes[i];
IBuffer? buffer = spec.VertexBuffers[i];
GL.BindBuffer(BufferTarget.ArrayBuffer, (buffer as GLBuffer)?.Handle ?? 0);
GL.VertexAttribPointer(
(uint)attrib.Location,
attrib.Components,
attrib.Type.VertexAttribPointerType,
false,
(int)attrib.Stride,
(nint)attrib.Offset);
GL.EnableVertexAttribArray((uint)attrib.Location);
}
}
public void UpdateUniforms(bool transpose = false)
{
int bufferIndex = 0;
foreach (UniformDescriptor uniform in call.Uniforms)
{
ReadOnlySpan<byte> uniformData = call.UniformData.Span;
switch (uniform.Type)
{
case UniformType.I1:
{
ReadOnlySpan<int> span = CastSpan<int>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform1i(uniform.Location, span.Length, span);
break;
}
case UniformType.I2:
{
ReadOnlySpan<Vector2i> span = CastSpan<Vector2i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform2i(uniform.Location, span.Length, span);
break;
}
case UniformType.I3:
{
ReadOnlySpan<Vector3i> span = CastSpan<Vector3i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform3i(uniform.Location, span.Length, span);
break;
}
case UniformType.I4:
{
ReadOnlySpan<Vector4i> span = CastSpan<Vector4i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform4i(uniform.Location, span.Length, span);
break;
}
case UniformType.F1:
{
ReadOnlySpan<float> span = CastSpan<float>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform1f(uniform.Location, span.Length, span);
break;
}
case UniformType.F2:
{
ReadOnlySpan<Vector2> span = CastSpan<Vector2>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform2f(uniform.Location, span.Length, span);
break;
}
case UniformType.F3:
{
ReadOnlySpan<Vector3> span = CastSpan<Vector3>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform3f(uniform.Location, span.Length, span);
break;
}
case UniformType.F4:
{
ReadOnlySpan<Vector4> span = CastSpan<Vector4>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform4f(uniform.Location, span.Length, span);
break;
}
case UniformType.Mat2:
{
ReadOnlySpan<Matrix2> span = CastSpan<Matrix2>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix2f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Mat3:
{
ReadOnlySpan<Matrix3> span = CastSpan<Matrix3>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix3f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Mat4:
{
ReadOnlySpan<Matrix4> span = CastSpan<Matrix4>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix4f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Buffer:
{
int buffer = (call.UniformBuffer as GLBuffer)?.Handle ?? 0;
int index = bufferIndex++;
GL.BindBufferRange(BufferTarget.UniformBuffer, (uint)index, buffer, (nint)uniform.Offset, (int)uniform.Size);
break;
}
default: throw new NotSupportedException();
}
}
return;
ReadOnlySpan<T> CastSpan<T>(ReadOnlySpan<byte> bytes, long offset, long size)
where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(bytes.Slice((int)offset, (int)size));
}
}
public void SetAll(Vector2 size, int vao, bool transpose = false)
{
call.UsePipeline();
call.SetViewport(size); call.SetScissor(size);
call.SetFrontFace();
call.SetCullMode();
call.SetBlendMode();
call.SetDepthMode();
call.SetStencilMode();
call.SetPrimitiveRestart();
call.SetColorMask();
call.SetPointSize();
call.SetLineWidth();
call.UseTextures();
call.UpdateUniforms(transpose);
call.UpdateVertexArrays(vao);
}
}
}
}
+257
View File
@@ -0,0 +1,257 @@
using System.Drawing;
using Dashboard.Drawing;
using Dashboard.OpenGL.Drawing;
using OpenTK.Graphics.OpenGL;
using OGL = OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public class GLTexture(DirectRendering 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 DirectRendering 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()
};
}
}
}
+214
View File
@@ -0,0 +1,214 @@
using System.Drawing;
using System.Numerics;
using System.Runtime.CompilerServices;
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 IDirectRendering _dr;
private GLShader _program;
private uint _program_apos;
private uint _program_atexcoord;
private uint _program_acolor;
private int _program_transforms;
private int _program_image;
private GLTexture _white;
public void Dispose()
{
}
public void Require(DeviceContext context)
{
Context = context;
_dr = context.ExtensionRequire<IDirectRendering>();
GlslShaderCreateInfo shader;
using (StreamReader vertex = new StreamReader(GetType().Assembly
.GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.vert")!))
using (StreamReader fragment = new StreamReader(GetType().Assembly
.GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.frag")!))
{
shader = new GlslShaderCreateInfo()
{
VertexShader = vertex.ReadToEnd(),
FragmentShader = fragment.ReadToEnd(),
};
}
_program = (GLShader)_dr.CreateShader(shader);
_program_apos = (uint)GL.GetAttribLocation(_program.Handle, "aPos");
_program_atexcoord = (uint)GL.GetAttribLocation(_program.Handle, "aTexCoords");
_program_acolor = (uint)GL.GetAttribLocation(_program.Handle, "aColor");
_program_transforms = GL.GetUniformLocation(_program.Handle, "transforms");
_program_image = GL.GetUniformLocation(_program.Handle, "image");
_white = (GLTexture)_dr.CreateTexture(TextureType.Texture2D);
_white.SetStorage(PixelFormat.Rgb8I, 1, 1, 1, 1);
_white.Swizzle = new ColorSwizzle(ColorChannel.One, ColorChannel.One, ColorChannel.One, ColorChannel.One);
_white.MinifyFilter = TextureFilter.Nearest;
_white.MagnifyFilter = TextureFilter.Nearest;
// TODO: implement the API above instead of writing this manually.
GL.BindTexture(TextureTarget.Texture2D, _white.Handle);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 1, 1, OpenTK.Graphics.OpenGL.PixelFormat.Rgb, PixelType.Byte, stackalloc byte[] { 0xFF, 0xFF, 0xFF, 0xFF });
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);
}
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),
];
DrawImmediate(new ImmediateDrawCall(MeshPrimitive.Triangle, vertices.ToArray())
{
Transforms = Context.ExtensionRequire<IDeviceContextBase>().Transforms,
PipelineState = new PipelineState { CullMode = FaceCulling.None },
});
}
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),
];
DrawImmediate(new ImmediateDrawCall(MeshPrimitive.Triangle, vertices.ToArray())
{
Transforms = Context.ExtensionRequire<IDeviceContextBase>().Transforms,
PipelineState = new PipelineState { CullMode = FaceCulling.None },
});
}
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),
];
DrawImmediate(new ImmediateDrawCall(MeshPrimitive.Triangle, vertices.ToArray())
{
Transforms = Context.ExtensionRequire<IDeviceContextBase>().Transforms,
PipelineState = new PipelineState { CullMode = FaceCulling.None },
Texture = texture,
});
}
IContextBase IContextExtensionBase.Context => Context;
void IContextExtensionBase.Require(IContextBase context)
{
Require((DeviceContext)context);
}
public void DrawImmediate(ImmediateDrawCall call)
{
// This is a terrible implementation as it stands but we can improve this immensely later on.
IDirectRendering dr = Context.ExtensionRequire<IDirectRendering>();
int size = call.Vertices.Length * ImmediateVertex.Size;
IBuffer buffer = dr.CreateBuffer(BufferAccessPattern.Stream, size);
buffer.Write(0, call.Vertices.Span);
VertexSpecification spec = CreateVertexSpec(buffer);
ReadOnlyMemory<byte> uniforms = MemoryMarshal.AsBytes(stackalloc Matrix4x4[] {call.Transforms}).ToArray();
DrawCall drawCall = new DrawCall(call.Primitive, spec, 0, call.Vertices.Length)
{
ShaderPipeline = _program,
PipelineState = call.PipelineState,
UniformData = uniforms,
Uniforms = [
new UniformDescriptor() {
Location = _program_transforms,
Type = Dashboard.Drawing.UniformType.Mat4,
Offset = 0,
Size = Unsafe.SizeOf<Matrix4x4>(),
}
],
Textures = [call.Texture ?? _white]
};
dr.Draw(drawCall);
buffer.Dispose();
}
private VertexSpecification CreateVertexSpec(IBuffer buffer)
{
return new VertexSpecification(
[
new VertexAttribute((int)_program_apos, 3, VertexAttributeType.Float, ImmediateVertex.PosOffset, ImmediateVertex.Size),
new VertexAttribute((int)_program_atexcoord, 2, VertexAttributeType.Float, ImmediateVertex.TexCoordsOffset, ImmediateVertex.Size),
new VertexAttribute((int)_program_acolor, 4, VertexAttributeType.Float, ImmediateVertex.ColorOffset, ImmediateVertex.Size)
],
[
buffer,
buffer,
buffer
]
);
}
}
}
@@ -0,0 +1,156 @@
using System.Text;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL.Drawing
{
public static class KhronosShaderHelper
{
public static int CompileStage(ShaderType type, string source, out string? log)
{
log = null;
int shader = GL.CreateShader(type);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
GL.GetShaderi(shader, ShaderParameterName.CompileStatus, out int flag);
if (flag != 0)
return shader;
GL.GetShaderInfoLog(shader, out log);
GL.DeleteShader(shader);
return 0;
}
public static int LinkStages(ReadOnlySpan<int> stages, out string? log)
{
log = null;
int program = GL.CreateProgram();
foreach (int stage in stages)
{
GL.AttachShader(program, stage);
}
GL.LinkProgram(program);
GL.GetProgrami(program, ProgramProperty.LinkStatus, out int flag);
if (flag != 0)
return program;
GL.GetProgramInfoLog(program, out log);
GL.DeleteProgram(program);
return 0;
}
public static int CreateProgram(this GlslShaderCreateInfo glsl)
{
Span<int> stages = stackalloc int[5];
int failed = 0;
StringBuilder builder = new StringBuilder();
int count = 0;
Compile(ShaderType.VertexShader, glsl.VertexShader, "Vertex", stages);
Compile(ShaderType.FragmentShader, glsl.FragmentShader, "Fragment", stages);
Compile(ShaderType.GeometryShader, glsl.GeometryShader, "Geometry", stages);
Compile(ShaderType.TessControlShader, glsl.TesselationControl, "Tesselation Control", stages);
Compile(ShaderType.TessEvaluationShader, glsl.TesselationEvaluation, "Tesselation Evaluation", stages);
if (failed > 0)
{
throw new Exception($"{failed} shader stage(s) failed to compile. See the exception data for details.")
{
Data =
{
["Log"] = builder.ToString(),
},
};
}
int program = KhronosShaderHelper.LinkStages(stages[..count], out string? log);
if (program == 0)
{
throw new Exception($"Shader program failed to link. See the exception data for details.")
{
Data =
{
["Log"] = log,
},
};
}
return program;
void Compile(ShaderType type, string? source, string name, Span<int> stages)
{
if (source == null)
return;
int stage = CompileStage(type, source, out string? log);
if (stage == 0)
{
failed++;
builder.Append($"=== {name} Shader Compile Log Begin ===\n{log}\n=== {name} Shader Compile Log End ===\n\n");
}
else
{
stages[count++] = stage;
}
}
}
public static int CreateProgram(this GlslComputeShaderCreateInfo glsl)
{
int compute = CompileStage(ShaderType.ComputeShader, glsl.Source, out string? log);
if (compute == 0)
{
throw new Exception("Failed to compile compute shader. See the exception data for details.")
{
Data =
{
["Log"] = log,
},
};
}
int program = KhronosShaderHelper.LinkStages([compute], out log);
if (program != 0)
return program;
throw new Exception("Failed to link compute shader. See the exception data for details.")
{
Data =
{
["Log"] = log,
},
};
}
public static int CreateProgram(this SpirvShaderCreateInfo spirv)
{
int program = GL.CreateProgram();
GL.ProgramBinary(program, (All)spirv.Format, spirv.Binary, spirv.Binary.Length);
GL.GetProgrami(program, ProgramProperty.LinkStatus, out int flag);
if (flag == 0)
return program;
GL.GetProgramInfoLog(program, out string? log);
GL.DeleteProgram(program);
throw new Exception("Failed to load program binary. See the exception data for details.")
{
Data =
{
["Log"] = log,
},
};
}
}
}
+12
View File
@@ -0,0 +1,12 @@
#version 130
uniform sampler2D image;
in vec2 vTexCoords;
in vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor * texture(image, vTexCoords);
}
+18
View File
@@ -0,0 +1,18 @@
#version 130
uniform mat4 transforms;
in vec3 aPos;
in vec2 aTexCoords;
in vec4 aColor;
out vec2 vTexCoords;
out vec4 vColor;
void main() {
vec4 position = transforms * vec4(aPos, 1.0);
gl_Position = position;
vTexCoords = aTexCoords;
vColor = aColor;
}
+98
View File
@@ -0,0 +1,98 @@
using System.Runtime.CompilerServices;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public class GLBuffer(GLDeviceContext dc, BufferAccessPattern pattern, int handle, long size, long offset) : IBuffer
{
// TODO: At the moment, this class does no pooling. Convert it to a pool for better API usage.
public BufferAccessPattern AccessPattern { get; } = pattern;
public long Size { get; private set; } = size;
public long Offset { get; private set; } = offset;
public int Handle { get; private set; } = handle;
public void Reallocate(long newSize)
{
GL.GenBuffer(out int handle);
GL.BindBuffer(BufferTarget.ArrayBuffer, handle);
GL.BufferData(BufferTarget.ArrayBuffer, (nint)newSize, (nint)0, AccessPattern switch
{
BufferAccessPattern.Stream => BufferUsage.StreamDraw,
BufferAccessPattern.Static => BufferUsage.StaticDraw,
BufferAccessPattern.Download => BufferUsage.DynamicCopy,
BufferAccessPattern.Upload => BufferUsage.DynamicCopy,
_ => BufferUsage.DynamicDraw,
});
if (Handle != -1)
GL.DeleteBuffer(Handle);
Handle = handle;
Size = newSize;
Offset = 0;
}
public unsafe Span<T> Map<T>(long offset, int count = -1)
where T : unmanaged
{
ArgumentOutOfRangeException.ThrowIfNegative(offset);
if (count < 0)
count = (int)(Size / Unsafe.SizeOf<T>());
long absOffset = Offset + offset;
long absCount = count * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
nint ptr = (nint)GL.MapBuffer(BufferTarget.ArrayBuffer, BufferAccess.ReadWrite);
return new Span<T>((T*)(ptr + absOffset), count);
}
public void Unmap()
{
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.UnmapBuffer(BufferTarget.ArrayBuffer);
}
public void Read<T>(long offset, Span<T> span) where T : unmanaged
{
long absCount = span.Length * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.GetBufferSubData(BufferTarget.ArrayBuffer, (nint)(Offset + offset), (nint)absCount, span);
}
public void Write<T>(long offset, ReadOnlySpan<T> span) where T : unmanaged
{
long absCount = span.Length * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.BufferSubData(BufferTarget.ArrayBuffer, (nint)(Offset + offset), (nint)absCount, span);
}
public void Dispose()
{
if (Handle == -1)
return;
dc.Collector.DeleteBufffer(Handle);
Handle = -1;
GC.SuppressFinalize(this);
}
public static GLBuffer Create(GLDeviceContext dc, BufferAccessPattern pattern, long size)
{
GLBuffer buffer = new GLBuffer(dc, pattern, -1, 0, 0);
buffer.Reallocate(size);
return buffer;
}
}
}
+175
View File
@@ -0,0 +1,175 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Numerics;
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 override ISwapGroup SwapGroup { get; }
public override Vector2 FramebufferSize => GLContext.FramebufferSize;
public override bool DoubleBuffered { get; } = true;
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, ISwapGroup swap) : base(app, window)
{
GLContext = context;
SwapGroup = swap;
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<ImmediateMode>();
ExtensionPreload<DirectRendering>();
}
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()
{
while (_afterDrawActions.TryDequeue(out Task? action))
{
action.RunSynchronously();
}
base.End();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
GLContext.Disposed -= Dispose;
}
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using System.Numerics;
using Dashboard.Windowing;
namespace Dashboard.OpenGL
{
/// <summary>
/// Interface for GL context operations
/// </summary>
public interface IGLContext : IDeviceContext
{
/// <summary>
/// The associated group for context sharing.
/// </summary>
/// <remarks>-1 assigns no group.</remarks>
public int ContextGroup { get; }
/// <summary>
/// Called when the context is disposed.
/// </summary>
event Action Disposed;
/// <summary>
/// Activate this OpenGL Context.
/// </summary>
void MakeCurrent();
IntPtr GetProcAddress(string procName);
}
}
+16
View File
@@ -0,0 +1,16 @@
namespace Dashboard.OpenGL
{
/// <summary>
/// Interface much like <see cref="IDisposable"/> except GL resources are dropped.
/// </summary>
/// <remarks>
/// The main reason this interface exists is that you need a way to differentiate
/// when a context is deleted, versus when the context is to remain present.
/// </remarks>
public interface IGLDisposable : IDisposable
{
/// <inheritdoc cref="IDisposable.Dispose"/>
/// <param name="safeExit">Set to true to spend the time clearing out GL objects.</param>
void Dispose(bool safeExit);
}
}
+60
View File
@@ -0,0 +1,60 @@
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public static class ShaderUtil
{
public static int CompileShader(ShaderType type, string source)
{
int shader = GL.CreateShader(type);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
int compileStatus = 0;
GL.GetShaderi(shader, ShaderParameterName.CompileStatus, out compileStatus);
if (compileStatus == 0)
{
GL.GetShaderInfoLog(shader, out string log);
GL.DeleteShader(shader);
throw new Exception($"{type} Shader compilation failed: " + log);
}
return shader;
}
public static int CompileShader(ShaderType type, Stream stream)
{
using StreamReader reader = new StreamReader(stream, leaveOpen: true);
return CompileShader(type, reader.ReadToEnd());
}
public static int LinkProgram(int s1, int s2, IReadOnlyList<string>? attribLocations = null)
{
int program = GL.CreateProgram();
GL.AttachShader(program, s1);
GL.AttachShader(program, s2);
for (int i = 0; i < attribLocations?.Count; i++)
{
GL.BindAttribLocation(program, (uint)i, attribLocations[i]);
}
GL.LinkProgram(program);
int linkStatus = 0;
GL.GetProgrami(program, ProgramProperty.LinkStatus, out linkStatus);
if (linkStatus == 0)
{
GL.GetProgramInfoLog(program, out string log);
GL.DeleteProgram(program);
throw new Exception("Shader program linking failed: " + log);
}
return program;
}
}
}
-17
View File
@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK" Version="4.8.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard\Dashboard.csproj" />
<EmbeddedResource Include="glsl\**"/>
</ItemGroup>
</Project>
-104
View File
@@ -1,104 +0,0 @@
using System;
using System.Collections.Generic;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using Dashboard.CommandMachine;
using Dashboard.Media;
using Dashboard.OpenGL;
using Dashboard.PAL;
namespace Dashboard.OpenTK
{
public class OpenTKPlatform : IDashboardPlatform
{
private readonly List<OpenTKPort> _ports = new List<OpenTKPort>();
// These shall remain a sad nop for now.
public string? Title { get; set; }
public QImage? Icon { get; set; } = null;
public event EventHandler? EventRaised;
public NativeWindowSettings DefaultSettings { get; set; } = NativeWindowSettings.Default;
public IReadOnlyList<OpenTKPort> Ports => _ports;
private bool IsGLInitialized = false;
public IDashHandle CreatePort()
{
NativeWindow window = new NativeWindow(DefaultSettings);
OpenTKPort port = new OpenTKPort(window);
_ports.Add(port);
if (!IsGLInitialized)
{
window.Context.MakeCurrent();
GL.LoadBindings(GLFW.GetProcAddress);
IsGLInitialized = true;
}
window.Closing += (ea) =>
{
Environment.Exit(0);
};
return port;
}
public void Dispose()
{
// FIXME: dispose pattern here!
// Copy the array to prevent collection modification exceptions.
foreach (OpenTKPort port in _ports.ToArray())
{
port.Dispose();
}
}
public void ProcessEvents(bool block)
{
NativeWindow.ProcessWindowEvents(block);
}
public void DestroyPort(IDashHandle port) => ((OpenTKPort)port).Dispose();
public string PortGetTitle(IDashHandle port) => ((OpenTKPort)port).Title;
public void PortSetTitle(IDashHandle port, string title) => ((OpenTKPort)port).Title = title;
public QVec2 PortGetSize(IDashHandle port) => ((OpenTKPort)port).Size;
public void PortSetSize(IDashHandle port, QVec2 size) => ((OpenTKPort)port).Size = size;
public QVec2 PortGetPosition(IDashHandle port) => ((OpenTKPort)port).Position;
public void PortSetPosition(IDashHandle port, QVec2 position) => ((OpenTKPort)port).Position = position;
public bool PortIsValid(IDashHandle port) => ((OpenTKPort)port).IsValid;
public void PortSubscribeEvent(IDashHandle port, EventHandler handler) => ((OpenTKPort)port).EventRaised += handler;
public void PortUnsubscribeEvent(IDashHandle port, EventHandler handler) => ((OpenTKPort)port).EventRaised -= handler;
public void PortFocus(IDashHandle port) => ((OpenTKPort)port).Focus();
public void PortShow(IDashHandle port, bool shown = true) => ((OpenTKPort)port).Show(shown);
public void PortPaint(IDashHandle port, CommandList commands) => ((OpenTKPort)port).Paint(commands);
public void GetMaximumImage(out int width, out int height)
{
GL.Get(GLEnum.GL_MAX_TEXTURE_SIZE, out int value);
width = height = value;
}
public void GetMaximumImage(out int width, out int height, out int depth)
{
GetMaximumImage(out width, out height);
GL.Get(GLEnum.GL_MAX_ARRAY_TEXTURE_LAYERS, out int value);
depth = value;
}
}
}
-107
View File
@@ -1,107 +0,0 @@
using System;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using Dashboard.OpenGL;
using Dashboard.CommandMachine;
using Dashboard.PAL;
using Dashboard.VertexGenerator;
namespace Dashboard.OpenTK
{
public class OpenTKPort : IDashHandle
{
private readonly NativeWindow _window;
private readonly GL21Driver _glDriver;
private readonly VertexGeneratorEngine _vertexEngine;
public string Title
{
get => _window.Title;
set => _window.Title = value;
}
public QVec2 Size
{
get
{
Vector2i size = _window.ClientSize;
return new QVec2(size.X, size.Y);
}
set
{
// OpenTK being OpenTK as usual, you can't set the client size.
Vector2i extents = _window.Size - _window.ClientSize;
Vector2i size = extents + new Vector2i((int)value.X, (int)value.Y);
_window.Size = size;
}
}
public QVec2 Position
{
get
{
Vector2i location = _window.Location;
return new QVec2(location.X, location.Y);
}
set
{
Vector2i location = new Vector2i((int)value.X, (int)value.Y);
_window.Location = location;
}
}
public bool IsValid => !isDisposed;
public event EventHandler? EventRaised;
public OpenTKPort(NativeWindow window)
{
_window = window;
_glDriver = new GL21Driver();
_vertexEngine = new VertexGeneratorEngine();
}
public void Focus()
{
_window.Focus();
}
public void Paint(CommandList queue)
{
QRectangle view = new QRectangle(Size, new QVec2(0, 0));
_vertexEngine.Reset();
_vertexEngine.ProcessCommands(view, queue);
if (!_window.Context.IsCurrent)
_window.Context.MakeCurrent();
if (!_glDriver.IsInit)
_glDriver.Init();
GL.Clear(GLEnum.GL_COLOR_BUFFER_BIT | GLEnum.GL_DEPTH_BUFFER_BIT);
_glDriver.Draw(_vertexEngine.DrawQueue, view);
_window.Context.SwapBuffers();
}
public void Show(bool shown = true)
{
_window.IsVisible = shown;
}
private bool isDisposed;
private void Dispose(bool disposing)
{
if (isDisposed) return;
if (disposing)
{
_window?.Dispose();
GC.SuppressFinalize(this);
}
isDisposed = true;
}
public void Dispose() => Dispose(true);
}
}
+56 -29
View File
@@ -3,49 +3,76 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard", "Dashboard\Dashboard.csproj", "{4FE772DD-F424-4EAC-BF88-CB8F751B4926}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Media.Defaults", "Dashboard.Media.Defaults\Dashboard.Media.Defaults.csproj", "{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{2013470A-915C-46F2-BDD3-FCAA39C845EE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}"
ProjectSection(ProjectDependencies) = postProject
{49A62F46-AC1C-4240-8615-020D4FBBF964} = {49A62F46-AC1C-4240-8615-020D4FBBF964}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{40F3B724-88A1-4D4F-93AB-FE0DC07A347E}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Demo", "tests\Dashboard.Demo\Dashboard.Demo.csproj", "{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{9B62A92D-ABF5-4704-B831-FD075515A82F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText", "Dashboard.BlurgText\Dashboard.BlurgText.csproj", "{D05A9DEA-A5D1-43DC-AB41-36B07598B749}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Frameworks\Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{7B064228-2629-486E-95C6-BDDD4B4602C4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenGL", "Dashboard.OpenGL\Dashboard.OpenGL.csproj", "{33EB657C-B53A-41B4-BC3C-F38C09ABA577}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.StbImage", "Frameworks\Dashboard.StbImage\Dashboard.StbImage.csproj", "{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText.OpenGL", "Frameworks\Dashboard.BlurgText.OpenGL\Dashboard.BlurgText.OpenGL.csproj", "{14616F42-663B-4673-8561-5637FAD1B22F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText", "Frameworks\Dashboard.BlurgText\Dashboard.BlurgText.csproj", "{8C68EFB6-B477-48EC-9AAA-31E89883482B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C90B90B-DF31-439B-9080-CD805383B014}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C90B90B-DF31-439B-9080-CD805383B014}.Release|Any CPU.Build.0 = Release|Any CPU
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B064228-2629-486E-95C6-BDDD4B4602C4}.Release|Any CPU.Build.0 = Release|Any CPU
{33EB657C-B53A-41B4-BC3C-F38C09ABA577}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7}.Release|Any CPU.Build.0 = Release|Any CPU
{14616F42-663B-4673-8561-5637FAD1B22F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14616F42-663B-4673-8561-5637FAD1B22F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14616F42-663B-4673-8561-5637FAD1B22F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14616F42-663B-4673-8561-5637FAD1B22F}.Release|Any CPU.Build.0 = Release|Any CPU
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C68EFB6-B477-48EC-9AAA-31E89883482B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Release|Any CPU.Build.0 = Release|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Release|Any CPU.Build.0 = Release|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Release|Any CPU.Build.0 = Release|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Release|Any CPU.Build.0 = Release|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC} = {40F3B724-88A1-4D4F-93AB-FE0DC07A347E}
{7C90B90B-DF31-439B-9080-CD805383B014} = {9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}
{7B064228-2629-486E-95C6-BDDD4B4602C4} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
{85BCEB9E-DEC2-4A53-B2DA-6BFC6F3EE4E7} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
{14616F42-663B-4673-8561-5637FAD1B22F} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
{8C68EFB6-B477-48EC-9AAA-31E89883482B} = {9B62A92D-ABF5-4704-B831-FD075515A82F}
EndGlobalSection
EndGlobal
-62
View File
@@ -1,62 +0,0 @@
namespace Dashboard.CommandMachine
{
/// <summary>
/// Enumeration of built-in Quik commands.
/// </summary>
public enum Command
{
#region Control Commands
/// <summary>
/// Invoke a function directly.
/// </summary>
Invoke,
/// <summary>
/// Begin conditional rendering segment.
/// </summary>
ConditionalBegin,
/// <summary>
/// End conditional rendering segment.
/// </summary>
ConditionalEnd,
PushViewport,
IntersectViewport,
StoreViewport,
PopViewport,
PushZ,
IncrementZ,
AddZ,
StoreZ,
DecrementZ,
PopZ,
PushMatrix,
StoreIdentityMatrix,
StoreMatrix,
PopMatrix,
PushStyle,
StoreStyle,
PopStyle,
#endregion
#region Draw Commands
Line,
Bezier,
Rectangle,
Ellipse,
Triangle,
Polygon,
Image,
#endregion
/// <summary>
/// Start index for custom commands.
/// </summary>
CustomCommandBase = 1024,
}
}
-232
View File
@@ -1,232 +0,0 @@
using System;
using System.Collections.Generic;
namespace Dashboard.CommandMachine
{
public class CommandEngine
{
private int _zIndex = 0;
private readonly Stack<int> _zStack = new Stack<int>();
public int ZIndex => _zIndex;
private QRectangle _viewport;
private readonly Stack<QRectangle> _viewportStack = new Stack<QRectangle>();
private readonly Stack<QMat4> _matrixStack = new Stack<QMat4>();
private Command _customCommandBase = Command.CustomCommandBase;
private readonly List<QuikCommandHandler> _customCommands = new List<QuikCommandHandler>();
public QRectangle Viewport => _viewport;
public QMat4 ActiveTransforms { get; }
public StyleStack Style { get; } = new StyleStack(new Style());
protected CommandEngine()
{
Reset();
}
public Command RegisterCustomCommand(QuikCommandHandler handler)
{
Command id = _customCommandBase++;
_customCommands.Insert(id - Command.CustomCommandBase, handler);
return id;
}
public void ProcessCommands(QRectangle bounds, CommandList queue)
{
CommandQueue iterator = queue.GetEnumerator();
if (!iterator.Peek().IsCommand)
throw new ArgumentException("The first element in the iterator must be a command frame.");
Reset();
_viewport = bounds;
_viewportStack.Push(_viewport);
Frame frame;
while (iterator.TryDequeue(out frame))
{
Command cmd = (Command)frame;
switch (cmd)
{
default:
if (cmd > Command.CustomCommandBase)
{
_customCommands[cmd - Command.CustomCommandBase].Invoke(this, iterator);
}
else
{
ChildProcessCommand(cmd, iterator);
}
break;
case Command.ConditionalBegin: ConditionalHandler(iterator); break;
case Command.ConditionalEnd: /* nop */ break;
case Command.Invoke:
iterator.Dequeue().As<QuikCommandHandler>().Invoke(this, iterator);
break;
case Command.PushViewport:
_viewportStack.Push(_viewport);
break;
case Command.IntersectViewport:
_viewport = QRectangle.Intersect((QRectangle)iterator.Dequeue(), _viewport);
break;
case Command.StoreViewport:
_viewport = (QRectangle)iterator.Dequeue();
break;
case Command.PopViewport:
_viewport = _viewportStack.TryPop(out QRectangle viewport) ? viewport : bounds;
break;
case Command.PushZ:
_zStack.Push(_zIndex);
break;
case Command.IncrementZ:
_zIndex++;
break;
case Command.AddZ:
_zIndex += (int)iterator.Dequeue();
break;
case Command.StoreZ:
_zIndex = (int)iterator.Dequeue();
break;
case Command.DecrementZ:
_zIndex--;
break;
case Command.PopZ:
_zIndex = _zStack.TryPop(out int zindex) ? zindex : 0;
break;
case Command.PushStyle:
Style.Push(iterator.Dequeue().As<Style>());
break;
case Command.StoreStyle:
Style.Pop();
Style.Push(iterator.Dequeue().As<Style>());
break;
case Command.PopStyle:
Style.Pop();
break;
}
}
}
protected virtual void ChildProcessCommand(Command name, CommandQueue queue)
{
}
public virtual void Reset()
{
_zIndex = 0;
_zStack.Clear();
_viewport = new QRectangle(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue);
_viewportStack.Clear();
_matrixStack.Clear();
_matrixStack.Push(QMat4.Identity);
}
private void ConditionalHandler(CommandQueue iterator)
{
Frame frame = iterator.Dequeue();
if (
frame.IsInteger && (int)frame != 0 ||
frame.As<Func<bool>>().Invoke())
{
// Take this branch.
return;
}
// Skip this branch.
int depth = 1;
while (iterator.TryPeek(out frame))
{
if (!frame.IsCommand)
{
iterator.Dequeue();
continue;
}
switch ((Command)frame)
{
case Command.ConditionalBegin:
// Increment conditional depth.
depth++;
break;
case Command.ConditionalEnd:
// Decrement condional depth, exit if zero.
if (--depth == 0)
{
iterator.Dequeue();
return;
}
break;
}
iterator.Dequeue();
}
}
private static readonly Dictionary<Type, ICommandListSerializer> s_serializers = new Dictionary<Type, ICommandListSerializer>();
/// <summary>
/// Add a custom serializer to the command engine.
/// </summary>
/// <typeparam name="T">Type object type.</typeparam>
/// <param name="serializer">The serializer.</param>
/// <param name="overwrite">True to allow overwriting.</param>
public static void AddSerializer<T>(ICommandListSerializer serializer, bool overwrite = false)
{
if (overwrite)
{
s_serializers[typeof(T)] = serializer;
}
else
{
s_serializers.Add(typeof(T), serializer);
}
}
/// <summary>
/// Add a custom serializer to the command engine.
/// </summary>
/// <typeparam name="T">Type object type.</typeparam>
/// <param name="serializer">The serializer.</param>
/// <param name="overwrite">True to allow overwriting.</param>
public static void AddSerializer<T>(ICommandListSerializer<T> serializer, bool overwrite = false)
=> AddSerializer<T>((ICommandListSerializer)serializer, overwrite);
/// <summary>
/// Get a serializer for the given object.
/// </summary>
/// <typeparam name="T">The object type.</typeparam>
/// <param name="value">Required parameter for the C# type inference to work.</param>
/// <returns>The serializer.</returns>
public static ICommandListSerializer<T> GetSerializer<T>(ICommandListSerializable<T>? value)
where T : ICommandListSerializable<T>, new()
{
if (!s_serializers.TryGetValue(typeof(T), out var serializer))
{
serializer = new CommandListSerializableSerializer<T>();
AddSerializer<T>(serializer);
}
return (ICommandListSerializer<T>)serializer;
}
/// <summary>
/// Get a serializer for the given object.
/// </summary>
/// <typeparam name="T">The object type.</typeparam>
/// <param name="value">Required parameter for the C# type inference to work.</param>
/// <returns>The serializer.</returns>
public static ICommandListSerializer<T> GetSerializer<T>(T? value)
{
return (ICommandListSerializer<T>)s_serializers[typeof(T)];
}
}
}
@@ -1,8 +0,0 @@
namespace Dashboard.CommandMachine
{
/// <summary>
/// A delegate for a QUIK command.
/// </summary>
/// <param name="stack">The current stack.</param>
public delegate void QuikCommandHandler(CommandEngine state, CommandQueue queue);
}
-433
View File
@@ -1,433 +0,0 @@
using Dashboard.Media;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Dashboard.CommandMachine
{
public class CommandList : IEnumerable<Frame>
{
private readonly List<Frame> _frames = new List<Frame>();
public void Clear()
{
_frames.Clear();
}
protected void Enqueue(in Frame frame)
{
_frames.Add(frame);
}
public void Invoke(QuikCommandHandler handler)
{
Enqueue(Command.Invoke);
Enqueue(new Frame(handler));
}
public void ConditionalBegin(bool value)
{
Enqueue(Command.ConditionalBegin);
Enqueue((Frame)(value ? 1 : 0));
}
public void ConditionalBegin(Func<bool> condition)
{
Enqueue(Command.ConditionalBegin);
Enqueue(new Frame(condition));
}
public void ConditionalEnd()
{
Enqueue(Command.ConditionalEnd);
}
public void PushViewport()
{
Enqueue(Command.PushViewport);
}
public void IntersectViewport(in QRectangle viewport)
{
Enqueue(Command.IntersectViewport);
Enqueue(viewport);
}
public void StoreViewport(in QRectangle viewport)
{
Enqueue(Command.StoreViewport);
Enqueue(viewport);
}
public void PopViewport()
{
Enqueue(Command.PopViewport);
}
public void PushZ()
{
Enqueue(Command.PushZ);
}
public void IncrementZ()
{
Enqueue(Command.IncrementZ);
}
public void AddZ(int value)
{
if (value == 1)
{
IncrementZ();
}
else if (value == -1)
{
DecrementZ();
}
else
{
Enqueue(Command.AddZ);
Enqueue((Frame)value);
}
}
public void StoreZ(int value)
{
Enqueue(Command.StoreZ);
Enqueue((Frame)value);
}
public void DecrementZ()
{
Enqueue(Command.DecrementZ);
}
public void PopZ()
{
Enqueue(Command.PopZ);
}
public void PushStyle(Style style)
{
Enqueue(Command.PushStyle);
Enqueue(new Frame(style));
}
public void StoreStyle(Style style)
{
Enqueue(Command.StoreStyle);
Enqueue(new Frame(style));
}
public void PopStyle()
{
Enqueue(Command.PopStyle);
}
public void Line(in QLine line)
{
Enqueue(Command.Line);
Enqueue(line);
}
public void Line(params QLine[] lines)
{
Enqueue(Command.Line);
Enqueue((Frame)lines.Length);
foreach (QLine line in lines)
Enqueue(line);
}
public void Bezier(in QBezier bezier)
{
Frame a, b;
Frame.Create(bezier, out a, out b);
Enqueue(Command.Bezier);
Enqueue(a);
Enqueue(b);
}
public void Bezier(params QBezier[] beziers)
{
Frame a, b;
Enqueue(Command.Bezier);
Enqueue((Frame)beziers.Length);
foreach (QBezier bezier in beziers)
{
Frame.Create(bezier, out a, out b);
Enqueue(a);
Enqueue(b);
}
}
public void Rectangle(in QRectangle rectangle)
{
Enqueue(Command.Rectangle);
Enqueue(rectangle);
}
public void Rectangle(QRectangle[] rectangles)
{
Enqueue(Command.Rectangle);
Enqueue((Frame)rectangles.Length);
foreach (QRectangle rectangle in rectangles)
Enqueue(rectangle);
}
public void Ellipse(in QEllipse ellipse)
{
Frame a, b;
Frame.Create(ellipse, out a, out b);
Enqueue(Command.Ellipse);
Enqueue(a);
Enqueue(b);
}
public void Ellipse(params QEllipse[] ellipses)
{
Frame a, b;
Enqueue(Command.Ellipse);
Enqueue((Frame)ellipses.Length);
foreach (QEllipse ellipse in ellipses)
{
Frame.Create(ellipse, out a, out b);
Enqueue(a);
Enqueue(b);
}
}
public void Triangle(in QTriangle triangle)
{
Enqueue(Command.Triangle);
Enqueue(triangle.A);
Enqueue(triangle.B);
Enqueue(triangle.C);
}
public void Triangle(params QTriangle[] triangles)
{
Enqueue(Command.Triangle);
Enqueue((Frame)triangles.Length);
foreach (QTriangle triangle in triangles)
{
Enqueue(triangle.A);
Enqueue(triangle.B);
Enqueue(triangle.C);
}
}
public void Polygon(params QVec2[] polygon)
{
Enqueue(Command.Polygon);
Enqueue((Frame)polygon.Length);
foreach (QVec2 vertex in polygon)
{
Enqueue(vertex);
}
}
public void Image(QImage texture, in QRectangle rectangle)
{
Enqueue(Command.Image);
Enqueue((Frame)(int)ImageCommandFlags.Single);
Enqueue(new Frame(texture));
Enqueue(rectangle);
}
public void Image(QImage texture, in QRectangle rectangle, in QRectangle uv)
{
Enqueue(Command.Image);
Enqueue((Frame)(int)(ImageCommandFlags.Single | ImageCommandFlags.UVs));
Enqueue(new Frame(texture));
Enqueue(rectangle);
Enqueue(uv);
}
public void Image(QImage texture, ReadOnlySpan<QRectangle> rectangles, bool interleavedUV = false)
{
int count = rectangles.Length;
ImageCommandFlags flags = ImageCommandFlags.None;
if (interleavedUV)
{
count /= 2;
flags |= ImageCommandFlags.UVs;
}
Enqueue(Command.Image);
Enqueue(new Frame((int)flags, count));
Enqueue(new Frame(texture));
foreach (QRectangle rectangle in rectangles)
{
Enqueue(rectangle);
}
}
public void Image(QImage texture, ReadOnlySpan<QRectangle> rectangles, ReadOnlySpan<QRectangle> uvs)
{
int count = Math.Min(rectangles.Length, uvs.Length);
Enqueue(Command.Image);
Enqueue(new Frame((int)ImageCommandFlags.UVs, count));
Enqueue(new Frame(texture));
for (int i = 0; i < count; i++)
{
Enqueue(rectangles[i]);
Enqueue(uvs[i]);
}
}
public void Image3D(QImage texture, in Image3DCall call)
{
Enqueue(Command.Image);
Enqueue(new Frame(ImageCommandFlags.Image3d | ImageCommandFlags.Single));
Enqueue(new Frame(texture));
Enqueue(call.Rectangle);
Enqueue(call.UVs);
Enqueue(new Frame(call.Layer));
}
public void Image3D(QImage texture, ReadOnlySpan<Image3DCall> calls)
{
Enqueue(Command.Image);
Enqueue(new Frame((int)ImageCommandFlags.Image3d, calls.Length));
Enqueue(new Frame(texture));
foreach (Image3DCall call in calls)
{
Enqueue(call.Rectangle);
Enqueue(call.UVs);
Enqueue(new Frame(call.Layer));
}
}
public void Splice(CommandList list)
{
foreach (Frame frame in list)
{
Enqueue(frame);
}
}
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <typeparam name="T">The type of the value to serialize.</typeparam>
/// <param name="value">What to write into the command list.</param>
public void Write<T>(T value)
{
CommandEngine.GetSerializer(value).Serialize(value, this);
}
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <typeparam name="T">The type of the value to serialize.</typeparam>
/// <param name="value">What to write into the command list.</param>
public void Write<T>(ICommandListSerializable<T> value)
where T : ICommandListSerializable<T>, new()
{
CommandEngine.GetSerializer(value).Serialize((T)value, this);
}
public CommandQueue GetEnumerator() => new CommandQueue(_frames);
IEnumerator<Frame> IEnumerable<Frame>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class CommandQueue : IEnumerator<Frame>
{
private readonly IReadOnlyList<Frame> _frames;
private int _current;
public Frame Current => _frames[_current];
object IEnumerator.Current => Current;
public CommandQueue(IReadOnlyList<Frame> frames)
{
_current = -1;
_frames = frames;
}
public void Dispose()
{
}
public bool TryDequeue([NotNullWhen(true)] out Frame frame)
{
if (MoveNext())
{
frame = Current;
return true;
}
else
{
frame = default;
return false;
}
}
public Frame Dequeue() => TryDequeue(out Frame frame) ? frame : throw new Exception("No more frames left.");
public bool TryPeek([NotNullWhen(true)] out Frame frame)
{
if (_current + 1 < _frames.Count)
{
frame = _frames[_current + 1];
return true;
}
else
{
frame = default;
return false;
}
}
public Frame Peek() => TryPeek(out Frame frame) ? frame : throw new Exception("No more frames left.");
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
/// <param name="value">The deserialized value.</param>
public void Read<T>([NotNull] out T? value)
{
value = CommandEngine.GetSerializer(default(T)).Deserialize(this);
}
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
/// <param name="value">The deserialized value.</param>
public void Read<T>([NotNull] out ICommandListSerializable<T>? value)
where T : ICommandListSerializable<T>, new()
{
value = CommandEngine.GetSerializer(value = null).Deserialize(this);
}
/// <inheritdoc/>
public bool MoveNext()
{
if (_current + 1 < _frames.Count)
{
_current++;
return true;
}
return false;
}
/// <inheritdoc/>
public void Reset()
{
_current = -1;
}
}
}
-356
View File
@@ -1,356 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace Dashboard.CommandMachine
{
[StructLayout(LayoutKind.Explicit)]
public struct Frame
{
[FieldOffset(0)]
private FrameType _type;
[FieldOffset(sizeof(FrameType) + 0 * sizeof(int))]
private int _i1;
[FieldOffset(sizeof(FrameType) + 1 * sizeof(int))]
private int _i2;
[FieldOffset(sizeof(FrameType) + 2 * sizeof(int))]
private int _i3;
[FieldOffset(sizeof(FrameType) + 3 * sizeof(int))]
private int _i4;
[FieldOffset(sizeof(FrameType) + 0 * sizeof(float))]
private float _f1;
[FieldOffset(sizeof(FrameType) + 1 * sizeof(float))]
private float _f2;
[FieldOffset(sizeof(FrameType) + 2 * sizeof(float))]
private float _f3;
[FieldOffset(sizeof(FrameType) + 3 * sizeof(float))]
private float _f4;
[FieldOffset(24)]
private object? _object = null;
public bool IsCommand => _type == FrameType.Command;
public bool IsInteger =>
_type == FrameType.IVec1 ||
_type == FrameType.IVec2 ||
_type == FrameType.IVec3 ||
_type == FrameType.IVec4;
public bool IsFloat =>
_type == FrameType.Vec1 ||
_type == FrameType.Vec2 ||
_type == FrameType.Vec3 ||
_type == FrameType.Vec4;
public int VectorSize
{
get
{
switch (_type)
{
case FrameType.None:
return 0;
default:
return 1;
case FrameType.Vec2: case FrameType.IVec2:
return 2;
case FrameType.Vec3: case FrameType.IVec3:
return 3;
case FrameType.Vec4: case FrameType.IVec4:
return 4;
}
}
}
public FrameType Type => _type;
public int I1 => _i1;
public int I2 => _i2;
public int I3 => _i3;
public int I4 => _i4;
public float F1 => _f1;
public float F2 => _f2;
public float F3 => _f3;
public float F4 => _f4;
public static Frame None { get; } = new Frame() {
_type = FrameType.None
};
#region Constructors
public Frame(Command command) : this()
{
_type = FrameType.Command;
_i1 = (int)command;
}
public Frame(object o)
{
_type = FrameType.Object;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_object = o;
}
public Frame(int i1)
{
_type = FrameType.IVec1;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_i1 = i1;
}
public Frame(int i1, int i2)
{
_type = FrameType.IVec2;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_i1 = i1;
_i2 = i2;
}
public Frame(int i1, int i2, int i3)
{
_type = FrameType.IVec3;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_i1 = i1;
_i2 = i2;
_i3 = i3;
}
public Frame(int i1, int i2, int i3, int i4)
{
_type = FrameType.IVec4;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_i1 = i1;
_i2 = i2;
_i3 = i3;
_i4 = i4;
}
public Frame(float f1)
{
_type = FrameType.Vec1;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_f1 = f1;
}
public Frame(float f1, float f2)
{
_type = FrameType.Vec2;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_f1 = f1;
_f2 = f2;
}
public Frame(float f1, float f2, float f3)
{
_type = FrameType.Vec3;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_f1 = f1;
_f2 = f2;
_f3 = f3;
}
public Frame(float f1, float f2, float f3, float f4)
{
_type = FrameType.Vec4;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_f1 = f1;
_f2 = f2;
_f3 = f3;
_f4 = f4;
}
#endregion
public T As<T>()
{
return (T)_object!;
}
public float GetF(int i)
{
switch (i)
{
case 0: return _f1;
case 1: return _f2;
case 2: return _f3;
case 3: return _f4;
default:
throw new ArgumentOutOfRangeException();
}
}
public int GetI(int i)
{
switch (i)
{
case 0: return _i1;
case 1: return _i2;
case 2: return _i3;
case 3: return _i4;
default:
throw new ArgumentOutOfRangeException();
}
}
#region Frame->T Conversion
public static explicit operator int(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.Command:
case FrameType.IVec1:
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return frame._i1;
case FrameType.Vec1:
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return (int)frame._f1;
}
}
public static explicit operator float(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec1:
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return frame._i1;
case FrameType.Vec1:
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return frame._f1;
}
}
public static explicit operator Command(in Frame frame)
{
if (frame.Type != FrameType.Command)
{
throw new InvalidCastException("Not a command frame.");
}
return (Command)frame._i1;
}
public static explicit operator QVec2(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return new QVec2(frame._i1, frame._i2);
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return new QVec2(frame._f1, frame._f2);
}
}
public static explicit operator QColor(in Frame frame)
{
if (frame.Type != FrameType.IVec4)
throw new InvalidCastException();
return new QColor((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4);
}
public static explicit operator QRectangle(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec4:
return new QRectangle(frame._i1, frame._i2, frame._i3, frame._i4);
case FrameType.Vec4:
return new QRectangle(frame._f1, frame._f2, frame._f3, frame._f4);
}
}
public static explicit operator QLine(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec4:
return new QLine(frame._i1, frame._i2, frame._i3, frame._i4);
case FrameType.Vec4:
return new QLine(frame._f1, frame._f2, frame._f3, frame._f4);
}
}
#endregion
public static explicit operator Frame(int i) => new Frame(i);
public static explicit operator Frame(float f) => new Frame(f);
public static implicit operator Frame(Command cmd) => new Frame(cmd);
public static implicit operator Frame(in QVec2 vector) => new Frame(vector.X, vector.Y);
public static implicit operator Frame(in QColor color) => new Frame(color.R, color.G, color.B, color.A);
public static implicit operator Frame(in QRectangle rect) => new Frame(rect.Max.X, rect.Max.Y, rect.Min.X, rect.Min.Y);
public static implicit operator Frame(in QLine line) => new Frame(line.Start.X, line.Start.Y, line.End.X, line.Start.Y);
public static void Create(in QBezier bezier, out Frame a, out Frame b)
{
a = new Frame(bezier.Start.X, bezier.Start.Y, bezier.End.X, bezier.End.Y);
b = new Frame(bezier.ControlA.X, bezier.ControlA.Y, bezier.ControlB.X, bezier.ControlB.Y);
}
public static void Create(in QEllipse ellipse, out Frame a, out Frame b)
{
a = new Frame(ellipse.Center.X, ellipse.Center.Y);
b = new Frame(ellipse.AxisA.X, ellipse.AxisA.Y, ellipse.AxisB.X, ellipse.AxisB.Y);
}
}
}
-68
View File
@@ -1,68 +0,0 @@
namespace Dashboard.CommandMachine
{
/// <summary>
/// Enumeration of command types in the Dashboard command lists.
/// </summary>
public enum FrameType
{
/// <summary>
/// A null value.
/// </summary>
None,
/// <summary>
/// A command frame.
/// </summary>
Command,
/// <summary>
/// An integer frame.
/// </summary>
IVec1,
/// <summary>
/// A two dimensional integer vector frame.
/// </summary>
IVec2,
/// <summary>
/// A three dimensional integer vector frame.
/// </summary>
IVec3,
/// <summary>
/// A four dimensional integer vector frame.
/// </summary>
IVec4,
/// <summary>
/// A floating point frame.
/// </summary>
Vec1,
/// <summary>
/// A two dimensional floating point vector frame.
/// </summary>
Vec2,
/// <summary>
/// A three dimensional floating point vector frame.
/// </summary>
Vec3,
/// <summary>
/// A four dimensional floating point vector frame.
/// </summary>
Vec4,
/// <summary>
/// A serialized object frame.
/// </summary>
Serialized,
/// <summary>
/// A .Net object frame.
/// </summary>
Object,
}
}
-18
View File
@@ -1,18 +0,0 @@
namespace Dashboard.CommandMachine
{
public enum ImageCommandFlags
{
None = 0,
Single = 1 << 0,
UVs = 1 << 1,
Image3d = 1 << 2,
}
public struct Image3DCall
{
public QRectangle Rectangle;
public QRectangle UVs;
public int Layer;
}
}
-69
View File
@@ -1,69 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace Dashboard.CommandMachine
{
public interface ICommandListSerializable { }
/// <summary>
/// Interface for objects that can be serialized into the Dashboard command stream.
/// </summary>
public interface ICommandListSerializable<T> : ICommandListSerializable
{
/// <summary>
/// Seralize object.
/// </summary>
/// <param name="list">The object to serialize into.</param>
void Serialize(CommandList list);
/// <summary>
/// Deserialize object.
/// </summary>
/// <param name="queue">The command queue to deserialize from.</param>
void Deserialize(CommandQueue queue);
}
/// <summary>
/// Base interface for all Command List serializers.
/// </summary>
public interface ICommandListSerializer { }
public interface ICommandListSerializer<T> : ICommandListSerializer
{
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <param name="value">The object to serialize.</param>
/// <param name="list">The command list to serialize into.</param>
void Serialize(T value, CommandList list);
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <param name="queue">The command queue.</param>
/// <returns>The object deserialized from the command queue.</returns>
[return: NotNull]
T Deserialize(CommandQueue queue);
}
/// <summary>
/// Class for automatic serialization of <see cref="ICommandListSerializable"/> objects.
/// </summary>
/// <typeparam name="T">The object type to convert.</typeparam>
internal class CommandListSerializableSerializer<T> : ICommandListSerializer<T>
where T : ICommandListSerializable<T>, new()
{
public T Deserialize(CommandQueue queue)
{
T value = new T();
value.Deserialize(queue);
return value;
}
public void Serialize(T value, CommandList list)
{
value.Serialize(list);
}
}
}
+69
View 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();
}
}
}
-56
View File
@@ -1,56 +0,0 @@
using System;
namespace Dashboard.Controls
{
public enum Dock
{
None,
Top,
Left,
Bottom,
Right,
Center
}
[Flags]
public enum Anchor
{
None = 0,
Top = 1 << 0,
Left = 1 << 1,
Bottom = 1 << 2,
Right = 1 << 3,
All = Top | Left | Bottom | Right
}
public enum Direction
{
Vertical,
Horizontal
}
public enum TextAlignment
{
Left,
Center,
Right,
Justify,
}
public enum VerticalAlignment
{
Top,
Center,
Bottom,
Justify,
}
public enum HorizontalAlignment
{
Left,
Center,
Right,
Justify
}
}
+145
View 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;
}
}
-50
View File
@@ -1,50 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Dashboard.Controls
{
public abstract class ContainerControl : Control, ICollection<Control>
{
private readonly List<Control> children = new List<Control>();
public int Count => children.Count;
public bool IsReadOnly => false;
public void Add(Control item)
{
children.Add(item);
}
public void Clear()
{
children.Clear();
}
public bool Contains(Control item)
{
return children.Contains(item);
}
public void CopyTo(Control[] array, int arrayIndex)
{
children.CopyTo(array, arrayIndex);
}
public IEnumerator<Control> GetEnumerator()
{
return children.GetEnumerator();
}
public bool Remove(Control item)
{
return children.Remove(item);
}
IEnumerator IEnumerable.GetEnumerator()
{
return children.GetEnumerator();
}
}
}
+151 -95
View File
@@ -1,125 +1,181 @@
using System;
using System.Collections.Generic;
using Dashboard.CommandMachine;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.Layout;
using Dashboard.Pal;
using Dashboard.Windowing;
namespace Dashboard.Controls
{
public abstract class Control : UIBase
public class Control : IEventListener, ILayoutItem, IDisposable
{
private readonly CommandList drawCommands = new CommandList();
private Form? _owner = null;
public Style Style { get; set; } = new Style();
public float Padding
public string? Id { get; set; }
public Form Owner
{
get => (float)(Style["padding"] ?? 0.0f);
set => Style["padding"] = value;
get => _owner ?? throw NoOwnerException;
protected set
{
_owner = value;
OnOwnerChanged(value);
}
}
public bool IsVisualsValid { get; private set; } = false;
public bool IsLayoutValid { get; private set; } = false;
public Control? Parent { get; private set; } = null;
public bool Disposed { get; private set; }
public virtual Box2d ClientArea { get; set; }
public bool IsFocused => _owner?.FocusedControl == this;
protected bool IsLayoutSuspended { get; private set; } = false;
public Brush Background { get; set; } = new SolidColorBrush(Color.Transparent);
public Brush BorderBrush { get; set; } = new SolidColorBrush(Color.Black);
public void InvalidateVisual()
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? ParentChanged;
public event EventHandler? FocusGained;
public event EventHandler? FocusLost;
public event EventHandler? Disposing;
public event EventHandler? Resized;
public virtual Vector2 CalculateIntrinsicSize()
{
IsVisualsValid = false;
OnVisualsInvalidated(this, EventArgs.Empty);
return Vector2.Zero;
// return Vector2.Max(Vector2.Zero, Vector2.Max(Layout.Size, Layout.MinimumSize));
}
public Vector2 CalculateSize(Vector2 limits)
{
return CalculateIntrinsicSize();
}
public virtual void OnPaint(DeviceContext dc)
{
Painting?.Invoke(this, dc);
}
public virtual void OnAnimationTick(TickEventArgs tick)
{
AnimationTick?.Invoke(this, tick);
}
protected void InvokeDispose(bool disposing)
{
if (Disposed)
return;
Disposed = true;
Dispose(disposing);
if (disposing)
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing) Disposing?.Invoke(this, EventArgs.Empty);
}
public void Dispose() => InvokeDispose(true);
public event EventHandler? EventRaised;
protected virtual void OnEventRaised(object? sender, EventArgs args)
{
switch (args)
{
case PaintEventArgs paint:
OnPaint(paint.DeviceContext);
break;
}
EventRaised?.Invoke(this, TransformEvent(sender, args));
}
protected virtual EventArgs TransformEvent(object? sender, EventArgs args)
{
return args;
}
public void SendEvent(object? sender, EventArgs args)
{
OnEventRaised(sender, args);
}
internal static void SetParent(Container parent, Control child)
{
child.Parent = parent;
child.ParentChanged?.Invoke(child, EventArgs.Empty);
}
public virtual void Focus()
{
(Owner ?? throw NoOwnerException).Focus(this);
}
protected virtual void OnFocusGained(object sender)
{
FocusGained?.Invoke(sender, EventArgs.Empty);
}
protected virtual void OnFocusLost(object sender)
{
FocusLost?.Invoke(sender, EventArgs.Empty);
}
internal static void InvokeFocusGained(Form form, Control control)
{
control.OnFocusGained(form);
}
internal static void InvokeFocusLost(Form form, Control control)
{
control.OnFocusLost(form);
}
protected virtual void OnResize()
{
Resized?.Invoke(this, EventArgs.Empty);
InvalidateLayout();
}
private void OnOwnerChanged(Form value)
{
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
public void InvalidateLayout()
{
IsLayoutValid = false;
OnLayoutInvalidated(this, EventArgs.Empty);
}
public void SuspendLayout()
protected virtual void ValidateLayout()
{
IsLayoutSuspended = true;
IsLayoutValid = true;
}
public void ResumeLayout()
{
IsLayoutSuspended = false;
InvalidateLayout();
IsLayoutEnabled = true;
}
protected abstract void ValidateVisual(CommandList cmd);
protected abstract void ValidateLayout();
protected override void PaintBegin(CommandList cmd)
public void SuspendLayout()
{
base.PaintBegin(cmd);
if (!IsLayoutValid && !IsLayoutSuspended)
{
ValidateLayout();
OnLayoutValidated(this, EventArgs.Empty);
IsLayoutValid = true;
InvalidateVisual();
}
if (!IsVisualsValid)
{
ValidateVisual(drawCommands);
OnVisualsValidated(this, EventArgs.Empty);
IsVisualsValid = true;
}
cmd.PushStyle(Style);
cmd.PushViewport();
cmd.StoreViewport(AbsoluteBounds);
cmd.Splice(drawCommands);
cmd.PopViewport();
cmd.PopStyle();
IsLayoutEnabled = false;
IsLayoutValid = false;
}
public event EventHandler? StyleChanged;
public event EventHandler? VisualsInvalidated;
public event EventHandler? VisualsValidated;
public event EventHandler? LayoutInvalidated;
public event EventHandler? LayoutValidated;
protected virtual void OnStyleChanged(object sender, EventArgs ea)
{
StyleChanged?.Invoke(sender, ea);
InvalidateLayout();
}
protected virtual void OnVisualsInvalidated(object sender, EventArgs ea)
{
VisualsInvalidated?.Invoke(sender, ea);
}
protected virtual void OnVisualsValidated(object sender, EventArgs ea)
{
VisualsValidated?.Invoke(sender, ea);
}
protected virtual void OnLayoutInvalidated(object sender, EventArgs ea)
{
LayoutInvalidated?.Invoke(sender, ea);
}
protected virtual void OnLayoutValidated(object sender, EventArgs ea)
{
LayoutValidated?.Invoke(sender, ea);
}
protected void ValidateChildrenLayout()
{
if (this is IEnumerable<Control> enumerable)
{
foreach (Control child in enumerable)
{
if (child.IsLayoutValid)
continue;
child.ValidateLayout();
}
}
}
protected static Exception NoOwnerException => new Exception("No form owns this control");
public event PropertyChangedEventHandler? PropertyChanged;
}
}
}

Some files were not shown because too many files have changed in this diff Show More