Compare commits
80 Commits
master
..
dashboard2
| Author | SHA1 | Date | |
|---|---|---|---|
| 362165f911 | |||
| 008882cb81 | |||
| a6dbba19b4 | |||
| 6665a023c8 | |||
| 3b20c53088 | |||
| bda8e173c9 | |||
| 1b5e13eb10 | |||
| 97f8a29f19 | |||
| abdb78eae7 | |||
| 99a853f35d | |||
| 3d0f15af45 | |||
| 01d7f27dc7 | |||
| 7bf7b6c53c | |||
| fe96499512 | |||
| 18cccf1e41 | |||
| 019ac6f4ae | |||
| 449c398f05 | |||
| 1457e32095 | |||
| 9dc6175f8b | |||
| faaadbf5b1 | |||
| 764b2bff8b | |||
| aaa79d1878 | |||
| f9e374db50 | |||
| 5bcc2d8214 | |||
| 804a3979fd | |||
| d299084db5 | |||
| d20d929515 | |||
| 3b9fa5c9fb | |||
| 027ebe8dbd | |||
| 36dbcac8d9 | |||
| 7c3141822a | |||
| e30e50e860 | |||
| 4f67e0fb75 | |||
| 5cba1ab7db | |||
| 9ca309bd52 | |||
| 043060db66 | |||
| 66c5eecc26 | |||
| 9ee6c1180d | |||
| c4fe4841fe | |||
| 2932b3b85e | |||
| 6e8888df48 | |||
| edc85c3f24 | |||
| 50eda46b13 | |||
| c538dbd56b | |||
| 1dcf167022 | |||
| 2690c5bec0 | |||
| 1c3c730e82 | |||
| 2c957a0c1a | |||
| 49257574f4 | |||
| 1cd005f827 | |||
| ef860f39bf | |||
| b1aadae0f2 | |||
| afa6ed4f4c | |||
| 57620f5d5c | |||
| 78f06a2359 | |||
| 87ab64f727 | |||
| 95cc1648b2 | |||
| 5014d218e2 | |||
| 8ce1329dfc | |||
| cde0fe2901 | |||
| 1a52f17990 | |||
| b841dbe6c2 | |||
| bb5b87417f | |||
| 165801800e | |||
| cf7cf9a77a | |||
| 3dff7438b3 | |||
| 6ad11812e2 | |||
| f8dea00021 | |||
| 7485ec5eaa | |||
| 6acaf0a7d5 | |||
| b5fda1ce3e | |||
| 1067d3e188 | |||
| 1b6bf09f95 | |||
| 509bad221f | |||
| 9ab2edffee | |||
| 1a1e761326 | |||
| 34720970ff | |||
| c706e16db1 | |||
| 8bc2685206 | |||
| fb76258e41 |
@@ -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>
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Dashboard.Events
|
||||
{
|
||||
public class WindowCloseEvent() : UiEventArgs(UiEventType.WindowClose)
|
||||
{
|
||||
public bool Cancel { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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}'.");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Dashboard.Windowing
|
||||
{
|
||||
public interface IForm : IEventListener, IDisposable
|
||||
{
|
||||
public IWindow Window { get; }
|
||||
public string Title { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Dashboard.Windowing
|
||||
{
|
||||
public interface IPaintable
|
||||
{
|
||||
event EventHandler Painting;
|
||||
|
||||
/// <summary>
|
||||
/// Paint this paintable object.
|
||||
/// </summary>
|
||||
void Paint();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using Dashboard.Drawing;
|
||||
using Dashboard.Layout;
|
||||
using Dashboard.Pal;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public class Button : Control
|
||||
{
|
||||
private Vector2 _intrinsicSize = Vector2.Zero;
|
||||
|
||||
public bool AutoSize { get; set; } = true;
|
||||
public string Text { get; set; } = "Click!";
|
||||
|
||||
public Font Font { get; set; } = Font.Create(new FontInfo("Rec Mono Linear"));
|
||||
public float TextSize { get; set; } = 12f;
|
||||
public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black);
|
||||
public Brush ButtonBrush { get; set; } = new SolidColorBrush(Color.DarkSlateGray);
|
||||
|
||||
public Vector2 Padding { get; set; } = new Vector2(4, 4);
|
||||
|
||||
public event EventHandler? Clicked;
|
||||
|
||||
public override Vector2 CalculateIntrinsicSize()
|
||||
{
|
||||
return _intrinsicSize + 2 * Padding;
|
||||
}
|
||||
|
||||
protected void CalculateSize(DeviceContext dc)
|
||||
{
|
||||
Box2d box = dc.ExtensionRequire<ITextRenderer>().MeasureText(Font.Base, TextSize, Text);
|
||||
_intrinsicSize = box.Size;
|
||||
// Layout.Size = box.Size;
|
||||
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size);
|
||||
}
|
||||
|
||||
public override void OnPaint(DeviceContext dc)
|
||||
{
|
||||
base.OnPaint(dc);
|
||||
|
||||
if (AutoSize)
|
||||
CalculateSize(dc);
|
||||
|
||||
bool hidden = Layout.OverflowMode == OverflowMode.Hidden;
|
||||
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
|
||||
if (hidden)
|
||||
dcb.PushScissor(ClientArea);
|
||||
|
||||
dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0));
|
||||
|
||||
var imm = dc.ExtensionRequire<IImmediateMode>();
|
||||
Color color = (ButtonBrush as SolidColorBrush)?.Color ?? Color.Black;
|
||||
Vector4 colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||
imm.Rectangle(ClientArea, 0, colorVector);
|
||||
|
||||
var text = dc.ExtensionRequire<ITextRenderer>();
|
||||
color = (TextBrush as SolidColorBrush)?.Color ?? Color.Black;
|
||||
colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||
text.DrawText(Vector2.Zero, colorVector, TextSize, Font.Base, Text);
|
||||
|
||||
if (hidden)
|
||||
dcb.PopScissor();
|
||||
|
||||
dcb.PopTransforms();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Dashboard.Drawing;
|
||||
using Dashboard.Events;
|
||||
using Dashboard.Layout;
|
||||
using Dashboard.Pal;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public class Container : Control, IList<Control>, ILayoutContainer
|
||||
{
|
||||
private readonly List<Control> _controls = new List<Control>();
|
||||
|
||||
public int Count => _controls.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public ContainerLayoutInfo ContainerLayout { get; } = new ContainerLayoutInfo();
|
||||
|
||||
public event EventHandler<ContainerChildAddedEventArgs>? ChildAdded;
|
||||
public event EventHandler<ContainerChildRemovedEventArgs>? ChildRemoved;
|
||||
|
||||
|
||||
public Control this[int index]
|
||||
{
|
||||
get => _controls[index];
|
||||
set => _controls[index] = value;
|
||||
}
|
||||
|
||||
protected override void ValidateLayout()
|
||||
{
|
||||
if (!IsLayoutEnabled || IsLayoutValid)
|
||||
return;
|
||||
|
||||
// LayoutSolution solution = LayoutSolution.CalculateLayout(this, ClientArea.Size);
|
||||
|
||||
base.ValidateLayout();
|
||||
}
|
||||
|
||||
public override void OnPaint(DeviceContext dc)
|
||||
{
|
||||
base.OnPaint(dc);
|
||||
|
||||
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
|
||||
dcb.PushClip(ClientArea);
|
||||
ValidateLayout();
|
||||
|
||||
foreach (Control child in _controls)
|
||||
{
|
||||
if (child.Layout.DisplayMode == DisplayMode.None)
|
||||
continue;
|
||||
|
||||
child.SendEvent(this, new PaintEventArgs(dc));
|
||||
}
|
||||
|
||||
dcb.PopClip();
|
||||
}
|
||||
|
||||
IEnumerator<ILayoutItem> IEnumerable<ILayoutItem>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerator<Control> GetEnumerator()
|
||||
{
|
||||
return _controls.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_controls).GetEnumerator();
|
||||
}
|
||||
|
||||
public void Add(Control item)
|
||||
{
|
||||
SetParent(this, item);
|
||||
|
||||
_controls.Add(item);
|
||||
ChildAdded?.Invoke(this, new ContainerChildAddedEventArgs(this, item));
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (Control control in this)
|
||||
{
|
||||
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, control));
|
||||
}
|
||||
_controls.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(Control item)
|
||||
{
|
||||
return _controls.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(Control[] array, int arrayIndex)
|
||||
{
|
||||
_controls.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(Control item)
|
||||
{
|
||||
if (!_controls.Remove(item))
|
||||
return false;
|
||||
|
||||
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, item));
|
||||
return true;
|
||||
}
|
||||
|
||||
public int IndexOf(Control item)
|
||||
{
|
||||
return _controls.IndexOf(item);
|
||||
}
|
||||
|
||||
public void Insert(int index, Control item)
|
||||
{
|
||||
SetParent(this, item);
|
||||
|
||||
_controls.Insert(index, item);
|
||||
ChildAdded?.Invoke(this, new ContainerChildAddedEventArgs(this, item));
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Control child = _controls[index];
|
||||
_controls.RemoveAt(index);
|
||||
ChildRemoved?.Invoke(this, new ContainerChildRemovedEventArgs(this, child));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class ContainerChildAddedEventArgs(Container parent, Control child) : EventArgs
|
||||
{
|
||||
public Container Parent { get; } = parent;
|
||||
public Control Child { get; } = child;
|
||||
}
|
||||
|
||||
public class ContainerChildRemovedEventArgs(Container parent, Control child) : EventArgs
|
||||
{
|
||||
public Container Parent { get; } = parent;
|
||||
public Control Child { get; } = child;
|
||||
}
|
||||
}
|
||||
@@ -1,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
@@ -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
Reference in New Issue
Block a user