142 Commits

Author SHA1 Message Date
themixedupstuff c6a9dd8008 Change the way container controls work and add Grid layout. 2024-08-04 13:21:00 +03:00
themixedupstuff ff3b3ee961 Rename UserData to Attribute. 2024-08-02 21:20:35 +03:00
themixedupstuff 9b6a15c6ec Add userdata fields to all control hierarchy. 2024-07-30 21:54:18 +03:00
themixedupstuff 50552914c1 Rename various types. 2024-07-28 14:19:13 +03:00
themixedupstuff d959c42e99 Rename various objects. 2024-07-28 14:11:23 +03:00
themixedupstuff 9b2f0859e5 Replace own matrix type with OpenTK. 2024-07-28 14:07:42 +03:00
themixedupstuff 831c93b916 Replace QColor and QColorF with OpenTK.Mathematics.Color4. 2024-07-28 13:59:35 +03:00
themixedupstuff 42782b8a71 Replace QVec2 with OpenTK.Mathematics.Vector2. 2024-07-28 13:50:33 +03:00
themixedupstuff 1301868269 Remove old OpenGL bindings and just pull in OpenTK. 2024-07-28 13:34:53 +03:00
themixedupstuff 21591c3d11 Go over vertex generator. 2024-07-28 12:37:33 +03:00
themixedupstuff 3856b3c66e Refactoring some names I don't like. 2024-07-28 12:34:22 +03:00
themixedupstuff 92fb8f06a7 Rename QuikCommandHandler. 2024-07-28 12:24:24 +03:00
themixedupstuff 468648de95 Rename a few files and classes. 2024-07-28 12:21:18 +03:00
themixedupstuff 3f1875252e Merge branch 'master' into blurg-test 2024-07-28 11:36:20 +03:00
themixedupstuff 65609bf685 Fix buffer size in QImageBuffer.cs 2024-07-28 11:33:12 +03:00
themixedupstuff a95ddb46ad Add some blurg test code. 2024-07-28 11:16:07 +03:00
themixedupstuff dadd9f137a Add utility for custom commands into command lists. 2024-07-26 23:48:48 +03:00
themixedupstuff 23fc485b2a Create Dashboard.BlurgText 2024-07-26 22:20:44 +03:00
themixedupstuff c6c76aa8be Update dependencies. 2024-07-18 21:54:44 +03:00
themixedupstuff 6670667ff6 Fix some broken dependencies. 2024-07-18 20:34:39 +03:00
themixedupstuff d06264e424 Rename some classes to fit the new names. 2024-07-18 20:28:02 +03:00
themixedupstuff a1f4e6a4dc Rename from QUIK to Dashboard
Due to unforseen naming conflicts, the project has been rebranded under the ReFuel
umbrealla and will now be referred to as Dashboard from now on. Other changes will
occur to suit the library more for the engine whilst keeping the freestanding
nature of the library.

Rename folder.

Rename to Dashboard.OpenTK

Rename to Dashboard.Media.Defaults.

Do the last renames and path fixes.
2024-07-17 23:26:58 +03:00
themixedupstuff 470475092a Rename namespace to Dashboard. 2024-07-17 23:18:20 +03:00
themixedupstuff 1ee492ccd4 Push WIP changes before rename. 2024-07-17 23:12:07 +03:00
themixedupstuff 3b52649ad2 Change over to the new nuget package and change namespaces. 2024-06-24 22:28:41 +03:00
themixedupstuff f8a4c73367 Fix load order in FallbackFontDatabase. 2024-06-21 11:59:04 +03:00
themixedupstuff 88e060a92b Update Stbi to new rebranded package. 2024-06-21 09:52:29 +03:00
themixedupstuff 1ccab1c85a Converted project to ^nullable enable. 2024-06-09 22:54:33 +03:00
themixedupstuff 55e9d775cd Some changes to the UI elements. 2024-06-09 22:06:13 +03:00
themixedupstuff 8fd00257d3 Fix GL21 driver bug. 2024-06-09 19:55:13 +03:00
themixedupstuff 82d2027cf3 Add measure string function to Typesetter. 2024-06-09 19:20:35 +03:00
themixedupstuff 9b16e015f6 Fix depth test issue in demo. 2024-06-06 22:33:55 +03:00
themixedupstuff ff4a158cdb Update stbi. 2024-06-01 14:33:20 +03:00
themixedupstuff 29829fd299 Made font rendering easier. 2024-05-27 21:49:25 +03:00
themixedupstuff 19cc765955 Implement Label control. 2024-05-16 22:58:22 +03:00
themixedupstuff ce2a569a20 Add texture swizzle parameters. 2024-05-16 22:57:55 +03:00
themixedupstuff ab1849a891 Add missing commands. 2024-05-16 22:57:38 +03:00
themixedupstuff 3ae107c83e Fix translation matrix to be column major and implement multiplication. 2024-05-16 22:57:14 +03:00
themixedupstuff bd69c0d93f Preliminary typesetting work. 2024-05-15 23:17:01 +03:00
themixedupstuff 279e619c3b Remove extra accidental copy of one file. 2024-05-15 23:16:37 +03:00
themixedupstuff 7cb47c721b Fix 90 degree rotation error in Image2D 2024-05-05 16:45:43 +03:00
themixedupstuff 9105b16df8 Add singleton support for QuikApplication. 2024-05-05 16:45:25 +03:00
themixedupstuff 3b73090f79 Fix error in the mouse button enum. 2024-05-05 16:45:12 +03:00
themixedupstuff 3484dce8c5 Create a font atlaser. 2024-05-01 16:53:30 +03:00
themixedupstuff 3418537b43 Add image copy functions to QImage. 2024-05-01 16:52:20 +03:00
themixedupstuff d831c9b72d Added better font search stuff. 2024-05-01 15:34:21 +03:00
themixedupstuff 66644be699 Remove popz command in UIBase.cs 2024-05-01 14:15:47 +03:00
themixedupstuff 39dfca18f2 Remove the readme under lib/ 2024-05-01 14:13:16 +03:00
themixedupstuff 75a11adbe7 Remove old submodules from the repository. 2024-05-01 14:12:12 +03:00
themixedupstuff 21233c8173 Add fontconfig support. 2024-04-28 15:39:12 +03:00
themixedupstuff ccb0c6ffe7 Add new font search criterea. 2024-04-28 13:45:46 +03:00
themixedupstuff bc3dcff3ea Remove the old Quik texture API. 2024-04-28 13:44:40 +03:00
themixedupstuff 2aa1066a9d Fix one of the image command lists. 2024-04-14 23:21:31 +03:00
themixedupstuff fe3c2d0550 Image support for vertex generator. 2024-04-14 23:21:08 +03:00
themixedupstuff 5aa9a2f4e6 Support for 3d images in the renderer. 2024-04-14 22:51:10 +03:00
themixedupstuff 4d5e0dd8f2 Add unit vectors to QVec2. 2024-04-14 22:35:54 +03:00
themixedupstuff f07208ebe9 Replace textures with QImage. 2024-04-14 22:35:30 +03:00
themixedupstuff b57d7bed41 Texture 3d entry points and enums. 2024-04-14 22:34:51 +03:00
themixedupstuff a1573d3786 Merge branch 'master' of https://git.mixedup.dev/QUIK/Quik 2024-04-11 19:35:30 +03:00
themixedupstuff 20c126fb88 New Image3D command. 2024-04-11 19:33:42 +03:00
themixedupstuff 7ce474d92a Changes to platform abstraction layer. 2024-04-11 19:09:00 +03:00
themixedupstuff 2eb5663ee9 Renamed the classes in the command engine. 2024-04-11 19:06:58 +03:00
themixedupstuff 09ce8d3229 Add invalid cast exceptions to frame conversion operators. 2024-04-09 10:50:52 +03:00
themixedupstuff 98aee237bd Update README.md 2024-04-08 22:55:37 +02:00
themixedupstuff d8beb2a274 Update README.md 2024-04-08 22:54:26 +02:00
themixedupstuff a8d805b461 Update README.md 2024-04-08 22:54:02 +02:00
themixedupstuff 4dff6eba91 Math library changes and fixes. 2024-04-08 23:51:03 +03:00
themixedupstuff 9c9efc6eeb Convert command queue to a command list. 2024-04-08 23:50:05 +03:00
themixedupstuff 51e9018a22 These dependencies have been moved into their own repositories. 2024-04-06 23:36:32 +03:00
themixedupstuff 032a38e13b Small fixes on OpenTK support. 2024-03-04 21:41:16 +03:00
themixedupstuff cea243a3b8 Finish backporting Freetype from in development quik. 2023-11-10 20:57:33 +03:00
themixedupstuff 6240f5921b Add new assets for the quik logo. 2023-10-17 22:26:14 +03:00
themixedupstuff 959788563f Fix small errors in driver. 2023-09-22 21:23:59 +03:00
themixedupstuff 118b50cee2 Add font rendering. 2023-09-22 19:30:17 +03:00
themixedupstuff 1f6a3a55e1 Ressurrect old freetype library. 2023-09-16 09:59:51 +03:00
themixedupstuff ac0a70cefc Remove auto-generated freetype bindings. 2023-09-16 09:50:14 +03:00
themixedupstuff 845ed1c27a Rename Quik.Media.Stb to *.Defaults 2023-09-16 09:24:57 +03:00
themixedupstuff 4a35f18737 Add partial FreeType bindings. 2023-09-15 20:40:30 +03:00
themixedupstuff ae0c9e742c Begin working on freetype bindings, again. 2023-09-13 21:38:38 +03:00
themixedupstuff ebb2ee7fee Add freetype as a submodule. 2023-07-28 22:45:24 +03:00
themixedupstuff 65fb6dcb87 Add file and lineno to exception msg. 2023-07-28 22:38:55 +03:00
themixedupstuff d65bb8ad0a Add new demo application template. 2023-07-28 22:37:49 +03:00
themixedupstuff 2bcac4a83e Remove references to old control types, at least for now. 2023-07-28 22:37:13 +03:00
themixedupstuff 6b8b3f2f0d Create a super basic test for fonts. 2023-07-16 18:31:51 +03:00
themixedupstuff ecd6e8cab7 Disabled obsolete warning. 2023-07-15 16:23:42 +03:00
themixedupstuff 99277153d2 Add the stbimage test. 2023-07-15 16:23:22 +03:00
themixedupstuff f0132e3459 Delete .idea directory 2023-07-09 17:49:09 +03:00
themixedupstuff c3a815171b Begin work on Quik.Media.Stb 2023-07-09 17:46:35 +03:00
themixedupstuff 648f44f12d Create nicer wrapper of Stbi. 2023-07-09 17:33:35 +03:00
themixedupstuff 3e7de074c6 Temporarily fix Stbi. 2023-07-09 17:05:01 +03:00
themixedupstuff afd73d1622 Move stbtt to it's own 2023-07-09 16:44:49 +03:00
themixedupstuff 03c477e98f Cannot get the nuget package to work properly. 2023-07-09 16:33:09 +03:00
themixedupstuff 5f859adab9 Create preliminary bindings for stb libraries. 2023-07-08 15:32:01 +03:00
themixedupstuff aea346180b Reference the redist nuget packages. 2023-07-08 14:22:18 +03:00
themixedupstuff 17950b6870 Add the wrapper packages. 2023-07-08 14:17:05 +03:00
themixedupstuff 10dcbeb55e Added stb redistributables. 2023-07-08 14:08:11 +03:00
themixedupstuff 7d2dee5a4d Create stbi redist. 2023-07-08 13:25:44 +03:00
themixedupstuff fad876c1b3 Add stb back again. 2023-07-08 13:22:26 +03:00
themixedupstuff 1b9e50da82 Remove submodules for now. 2023-07-08 13:22:03 +03:00
themixedupstuff 33427146e1 Design a docker container for cross compiling native dependencies. 2023-07-08 13:17:14 +03:00
themixedupstuff 79242127e5 Add stb into project directory. 2023-07-03 13:24:16 +03:00
themixedupstuff 3906222506 Add alpha premultiplication. 2023-07-02 11:39:02 +03:00
themixedupstuff 1676184118 Add image buffers and color conversion. 2023-07-02 11:22:52 +03:00
themixedupstuff 995943d83b Fix incorrect nomenclature in interface name. 2023-07-02 10:38:46 +03:00
themixedupstuff f830626579 Add RedAlpha image formats. 2023-07-02 10:38:32 +03:00
themixedupstuff 98883db604 Add float color structure. 2023-07-02 10:38:19 +03:00
themixedupstuff 8e2db62e56 Add a very simple matrix class. 2023-06-30 19:57:04 +03:00
themixedupstuff d72a07354a We are going real old school GL. 2023-06-30 19:35:24 +03:00
themixedupstuff 1297365b38 Add missing stuff. 2023-06-30 16:34:16 +03:00
themixedupstuff 72d0f02440 Purge all old files. 2023-06-29 14:17:32 +03:00
themixedupstuff 8d2afd0955 Remove old test projects. 2023-06-29 14:04:32 +03:00
themixedupstuff 41b7bebba5 Rename geometry types with Q prefix instead of Quik. 2023-06-29 10:42:02 +03:00
themixedupstuff fe71028573 Add GL_UNSIGNED_INT 2023-06-29 10:37:23 +03:00
themixedupstuff 46c18d97d2 Small fixes in the command engine components. 2023-06-29 10:31:28 +03:00
themixedupstuff 5c71afe9c7 Rectangles added :) 2023-06-28 15:22:48 +03:00
themixedupstuff 61e4d2bd16 Begin work on new command engine. 2023-06-23 22:56:07 +03:00
themixedupstuff 78c71054b4 Add more useful stuff into frames. 2023-06-23 22:55:41 +03:00
themixedupstuff 2e07b0ffc9 Add pretty colors and new functions into the math types. 2023-06-23 22:53:22 +03:00
themixedupstuff a50650e54a Create custom OpenGL bindings.
This removes the hard dependency on OpenTK for a core component of QUIK.
Although not all environments are going to be using OpenGL, having it be
independent from a vendor library would be more benefitial for more
people.
2023-06-22 23:11:32 +03:00
themixedupstuff b57677a6c2 Rename namespace 'CommandQueue' as 'CommandMachine'. 2023-05-20 19:37:51 +03:00
themixedupstuff 4b2271dd29 Change textures from interface to abstract class. 2023-05-15 21:49:48 +03:00
themixedupstuff e731e8af49 Add new style classes. 2023-05-15 12:07:12 +03:00
themixedupstuff 9eadc26f2f New command queue implementation with hopefully less GC invocations. 2023-05-15 10:22:09 +03:00
themixedupstuff 39a9f67367 Add new commands. 2023-05-14 23:32:32 +03:00
themixedupstuff 3de9de4e2d New conversion operators and make some explicit. 2023-05-14 23:30:26 +03:00
themixedupstuff fc1cc6a1ba Add intersect to rectangle. 2023-05-14 23:29:52 +03:00
themixedupstuff 9339295378 Push all uncommitted changes.
I have had a long break from this project due to other higher priority
things going on in my life. Big changes inbound.
2023-05-13 16:17:57 +03:00
themixedupstuff 98b1c1a277 New in development queue system. 2023-05-13 16:17:23 +03:00
themixedupstuff 3d672022e1 Update .gitignore 2023-05-13 15:43:03 +03:00
themixedupstuff 1d3f22f6ce Clean up the font test code slightly. 2022-08-20 12:57:57 +03:00
themixedupstuff bf47915491 Very quick test text implementation. 2022-08-19 16:13:19 +03:00
themixedupstuff a0473d9e83 Change over draw call propagation in the vertex generator. 2022-08-18 13:58:03 +03:00
themixedupstuff 7c0c6d9a75 Remove stroke stippling for a good long time. 2022-08-16 21:38:18 +03:00
themixedupstuff 72cad718cb Implement rectangle rendering. 2022-08-16 21:35:12 +03:00
themixedupstuff 16bb3f41a7 Implement bezier lines. 2022-08-08 10:52:03 +03:00
themixedupstuff 502eb95278 Create joint generating code for multi-segment lines and refactor code for end caps. 2022-08-06 10:05:27 +03:00
themixedupstuff 772906bec7 Add single line rendering. 2022-08-04 16:40:58 +03:00
themixedupstuff a6465730c1 Design a simple logo and add it to README.md 2022-08-04 10:38:41 +03:00
themixedupstuff ecd31a5cde Create a quick and dirty style system. 2022-08-03 22:54:16 +03:00
themixedupstuff c26dcfe3f3 Implement own vector types to releive the System.Numerics dependency. 2022-08-03 17:52:00 +03:00
themixedupstuff 9838540a8f Move project into its own subfolder to make room for other components. 2022-08-03 17:30:31 +03:00
themixedupstuff 4c42b68033 Create initial draw command list. 2022-08-03 15:04:40 +03:00
191 changed files with 8822 additions and 11177 deletions
+27
View File
@@ -0,0 +1,27 @@
using BlurgText;
using Dashboard.ImmediateDraw;
using OpenTK.Mathematics;
namespace Dashboard.BlurgText
{
public static class BlurgCommand
{
public static void PutBlurgText(this DrawList list, DashboardBlurg blurg, BlurgResult result, Vector2 origin)
{
for (int i = 0; i < result.Count; i++)
{
BlurgRect rect = result[i];
Rectangle pos = new Rectangle()
{
Min = origin + new Vector2(rect.X, rect.Y),
Size = new Vector2(rect.Width, rect.Height)
};
Rectangle uv = new Rectangle(rect.U1, rect.V1, rect.U0, rect.V0);
list.Image(blurg.Images[(int)rect.UserData], pos, uv);
}
}
}
}
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard\Dashboard.csproj" />
<PackageReference Include="BlurgText" Version="0.1.0-nightly-4" />
</ItemGroup>
</Project>
+63
View File
@@ -0,0 +1,63 @@
using BlurgText;
using Dashboard.Media;
using Dashboard.Media.Color;
namespace Dashboard.BlurgText
{
public class DashboardBlurg : IDisposable
{
private readonly List<QImageBuffer> images = new List<QImageBuffer>();
public Blurg Blurg { get; }
public IReadOnlyList<QImageBuffer> Images => images.AsReadOnly();
public DashboardBlurg()
{
Blurg = new Blurg(TextureAllocationCallback, TextureUpdateCallback);
}
private nint TextureAllocationCallback(int width, int height)
{
QImageBuffer image = new QImageBuffer(ImageFormat.RgbaU8, width, height);
images.Add(image);
return images.Count - 1;
}
private void TextureUpdateCallback(nint userData, nint buffer, int x, int y, int width, int height)
{
if (width == 0 || height == 0)
return;
QImageLock src = new QImageLock(ImageFormat.RgbaU8, width, height, 1, buffer);
QImageBuffer image = images[(int)userData];
image.LockBits2d(out QImageLock dest, QImageLockOptions.Default);
src.CopyTo(dest, x, y);
image.UnlockBits();
}
private bool _isDisposed = false;
private void Dispose(bool disposing)
{
if (_isDisposed) return;
if (disposing)
{
Blurg.Dispose();
foreach (QImageBuffer image in images)
{
image.Dispose();
}
images.Clear();
}
_isDisposed = true;
}
public void Dispose() => Dispose(true);
}
}
-15
View File
@@ -1,15 +0,0 @@
namespace Dashboard
{
[Flags]
public enum Anchor
{
Auto = 0,
Right = (1 << 0),
Left = (1 << 1),
HCenter = Left | Right,
Top = (1 << 2),
Bottom = (1 << 3),
VCenter = Top | Bottom,
Middle = HCenter | VCenter,
}
}
-65
View File
@@ -1,65 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Drawing;
using System.Numerics;
namespace Dashboard
{
public readonly record struct Box2d(Vector2 Min, Vector2 Max)
{
public float Left => Min.X;
public float Right => Max.X;
public float Top => Min.Y;
public float Bottom => Max.Y;
public Vector2 Size => Max - Min;
public Vector2 Center => (Min + Max) * 0.5f;
public Box2d(RectangleF rectangle)
: this(new Vector2(rectangle.Left, rectangle.Top), new Vector2(rectangle.Right, rectangle.Bottom))
{
}
public Box2d(float x0, float y0, float x1, float y1)
: this(new Vector2(x0, y0), new Vector2(x1, y1))
{
}
public static Box2d FromPositionAndSize(Vector2 position, Vector2 size, Origin anchor = Origin.Center)
{
Vector2 half = size * 0.5f;
switch (anchor)
{
case Origin.Center:
return new Box2d(position - half, position + half);
case Origin.TopLeft:
return new Box2d(position, position + size);
default:
throw new NotImplementedException();
}
}
public static Box2d Union(Box2d left, Box2d right)
{
Vector2 min = Vector2.Min(left.Min, right.Min);
Vector2 max = Vector2.Max(left.Max, right.Max);
return new Box2d(min, max);
}
public static Box2d Intersect(Box2d left, Box2d right)
{
Vector2 min = Vector2.Max(left.Min, right.Min);
Vector2 max = Vector2.Min(left.Max, right.Max);
return new Box2d(min, max);
}
public static explicit operator RectangleF(Box2d box2d)
{
return new RectangleF((PointF)box2d.Center, (SizeF)box2d.Size);
}
public static explicit operator Box2d(RectangleF rectangle)
{
return new Box2d(rectangle);
}
}
}
-46
View File
@@ -1,46 +0,0 @@
using System.Drawing;
using System.Numerics;
namespace Dashboard
{
public readonly record struct Box3d(Vector3 Min, Vector3 Max)
{
public float Left => Min.X;
public float Right => Max.X;
public float Top => Min.Y;
public float Bottom => Max.Y;
public float Far => Min.Z;
public float Near => Max.Z;
public Vector3 Size => Max - Min;
public Vector3 Center => Min + Size * 0.5f;
public static Box3d Union(Box3d left, Box3d right)
{
Vector3 min = Vector3.Min(left.Min, right.Min);
Vector3 max = Vector3.Max(left.Max, right.Max);
return new Box3d(min, max);
}
public static Box3d Union(Box3d box, Box2d bounds, float depth)
{
Vector3 min = Vector3.Min(box.Min, new Vector3(bounds.Left, bounds.Top, depth));
Vector3 max = Vector3.Max(box.Max, new Vector3(bounds.Right, bounds.Bottom, depth));
return new Box3d(min, max);
}
public static Box3d Intersect(Box3d left, Box3d right)
{
Vector3 min = Vector3.Max(left.Min, right.Min);
Vector3 max = Vector3.Min(left.Max, right.Max);
return new Box3d(min, max);
}
public static Box3d Intersect(Box3d box, Box2d bounds, float depth)
{
Vector3 min = Vector3.Max(box.Min, new Vector3(bounds.Min, depth));
Vector3 max = Vector3.Min(box.Max, new Vector3(bounds.Max, depth));
return new Box3d(min, max);
}
}
}
-95
View File
@@ -1,95 +0,0 @@
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() { }
}
}
@@ -1,157 +0,0 @@
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();
}
}
@@ -1,44 +0,0 @@
using System.Collections;
namespace Dashboard.Collections
{
public class TypeHashSet(bool hierarchical = false) : IEnumerable<TypeAtom>
{
private readonly HashSet<int> _set = new HashSet<int>();
public bool Contains<T>() => _set.Contains(TypeAtom<T>.Id);
public bool Set<T>()
{
if (!_set.Add(TypeAtom<T>.Id))
return false;
if (hierarchical)
foreach (TypeAtom ancestor in TypeAtom<T>.Ancestors)
{
_set.Add(ancestor.Id);
}
return true;
}
public bool Reset<T>()
{
if (!_set.Remove(TypeAtom<T>.Id))
return false;
if (hierarchical)
foreach (TypeAtom ancestor in TypeAtom<T>.Ancestors)
{
_set.Remove(ancestor.Id);
}
return true;
}
public void Clear() => _set.Clear();
public IEnumerator<TypeAtom> GetEnumerator() => _set.Select(x => TypeAtom.Get(x)!).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
-10
View File
@@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Dashboard</RootNamespace>
</PropertyGroup>
</Project>
-27
View File
@@ -1,27 +0,0 @@
using System.Drawing;
using System.Numerics;
namespace Dashboard.Drawing
{
public abstract class Brush
{
}
public class SolidColorBrush(Color color) : Brush
{
public Color Color { get; } = color;
}
public class ImageBrush(Image image) : Brush
{
public Image Image { get; set; } = image;
public Box2d TextureCoordinates { get; set; } = new Box2d(0, 0, 1, 1);
}
public class NinePatchImageBrush(Image image) : Brush
{
public Image Image { get; set; } = image;
public Box2d CenterCoordinates { get; set; } = new Box2d(0, 0, 1, 1);
public Vector4 Extents { get; set; } = Vector4.Zero;
}
}
-242
View File
@@ -1,242 +0,0 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Numerics;
namespace Dashboard.Drawing
{
public enum MeshPrimitive
{
Point,
Line,
Triangle,
TriangleFan,
TriangleStrip,
}
public enum WindingOrder
{
Clockwise,
Counterclockwise,
}
public enum FaceCulling
{
None,
Front,
Back,
Both,
}
public enum VertexAttributeType
{
UnsignedByte,
UnsignedShort,
UnsignedInt,
Byte,
Short,
Int,
Half,
Float,
Double,
}
public enum IndexType
{
UnsignedShort,
UnsignedInt,
UnsignedLong,
Short,
Int,
}
public enum BlendFunction
{
One,
Zero,
SourceAlpha,
OneMinusSourceAlpha,
DestinationAlpha,
OneMinusDestinationAlpha,
SourceColor,
OneMinusSourceColor,
DestinationColor,
OneMinusDestinationColor,
ConstantColor,
OneMinusConstantColor,
ConstantAlpha,
OneMinusConstantAlpha,
}
public enum BlendEquation
{
Add,
Subtract,
ReverseSubtract,
Min,
Max,
}
public enum TestFunction
{
Never,
LessThan,
LessThanOrEqual,
Equal,
NotEqual,
GreaterThanOrEqual,
Greater,
Always,
}
public enum StencilOperation
{
Keep,
Zero,
Replace,
Increment,
UncheckedIncrement,
Decrement,
UncheckedDecrement,
Invert,
}
public enum UniformType
{
U1,
U2,
U3,
U4,
I1,
I2,
I3,
I4,
F1,
F2,
F3,
F4,
Mat2,
Mat3,
Mat4,
Buffer,
}
public readonly record struct DepthMode(bool Test, bool Mask, TestFunction Function)
{
public static readonly DepthMode Off = new DepthMode(false, false, TestFunction.Always);
public static readonly DepthMode WriteOnly = new DepthMode(false, true, TestFunction.LessThan);
public static readonly DepthMode Equal = new DepthMode(true, false, TestFunction.Equal);
public static readonly DepthMode Enabled = new DepthMode(true, true, TestFunction.LessThan);
}
public readonly record struct BlendFactor(BlendFunction Source, BlendFunction Destination)
{
public static readonly BlendFactor Default =
new BlendFactor(BlendFunction.SourceAlpha, BlendFunction.OneMinusSourceAlpha);
}
public readonly record struct BlendMode(bool Enabled, BlendFactor Color, BlendFactor Alpha)
{
[MemberNotNullWhen(false, nameof(Unified), nameof(UnifiedEquation))]
public bool IsSeparate => Color != Alpha;
public BlendFactor? Unified => IsSeparate ? null : Color;
public BlendEquation? UnifiedEquation => IsSeparate ? null : ColorEquation;
public BlendEquation ColorEquation { get; init; } = BlendEquation.Add;
public BlendEquation AlphaEquation { get; init; } = BlendEquation.Add;
public Color Constant { get; init; } = System.Drawing.Color.Black;
public BlendMode(BlendFunction source, BlendFunction destination)
: this(true, new BlendFactor(source, destination), new BlendFactor(source, destination))
{
}
public static readonly BlendMode Off = new BlendMode(false, BlendFactor.Default, BlendFactor.Default);
public static readonly BlendMode Normal =
new BlendMode(true, BlendFactor.Default, BlendFactor.Default);
}
public readonly record struct StencilMode(bool Enabled, TestFunction Function)
{
public int Reference { get; init; } = 0;
public int Mask { get; init; } = ~0;
public StencilOperation Pass { get; init; } = StencilOperation.Keep;
public StencilOperation DepthFail { get; init; } = StencilOperation.Keep;
public StencilOperation Fail { get; init; } = StencilOperation.Keep;
public static readonly StencilMode Off = new StencilMode(false, TestFunction.Always);
}
public record struct VertexAttribute(
int Location,
int Components,
VertexAttributeType Type,
long Offset,
long Stride)
{
public int Divisor { get; init; } = 1;
}
public record VertexSpecification(
ImmutableList<VertexAttribute> Attributes,
ImmutableList<IBuffer?> VertexBuffers
)
{
public IBuffer? ElementBuffer { get; init; } = null;
}
public record struct UniformDescriptor(int Location, UniformType Type, long Offset, long Size);
public struct PipelineState()
{
public Box2d ViewportRegion { get; init; } = new Box2d(Vector2.Zero, Vector2.PositiveInfinity);
public Box2d ScissorRegion { get; init; } = new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity);
public WindingOrder FrontFace { get; init; } = WindingOrder.Counterclockwise;
public FaceCulling CullMode { get; init; } = FaceCulling.Back;
public BlendMode BlendMode { get; init; } = BlendMode.Normal;
public DepthMode DepthMode { get; init; } = DepthMode.Enabled;
public StencilMode StencilMode { get; init; } = StencilMode.Off;
public bool RedMask { get; init; } = true;
public bool GreenMask { get; init; } = true;
public bool BlueMask { get; init; } = true;
public bool AlphaMask { get; init; } = true;
public float PointSize { get; init; }= 1.0f;
public float LineWidth { get; init; }= 1.0f;
}
public struct DrawCall(MeshPrimitive primitive, VertexSpecification vertexSpec, int first, int count)
{
public IShader? ShaderPipeline { get; init; }
public PipelineState PipelineState { get; init; } = new PipelineState();
public VertexSpecification VertexSpecification { get; init; } = vertexSpec;
public MeshPrimitive Primitive { get; init; } = primitive;
public int First { get; init; } = first;
public int Count { get; init; } = count;
public int BaseVertex { get; init; } = 0;
public long Offset { get; init; } = 0;
public bool EnableRestart { get; init; } = false;
public long RestartIndex { get; init; } = -1;
public Box2d ViewportRegion => PipelineState.ViewportRegion;
public Box2d ScissorRegion => PipelineState.ScissorRegion;
public WindingOrder FrontFace => PipelineState.FrontFace;
public FaceCulling CullMode => PipelineState.CullMode;
public BlendMode BlendMode => PipelineState.BlendMode;
public DepthMode DepthMode => PipelineState.DepthMode;
public StencilMode StencilMode => PipelineState.StencilMode;
public bool RedMask => PipelineState.RedMask;
public bool GreenMask => PipelineState.GreenMask;
public bool BlueMask => PipelineState.BlueMask;
public bool AlphaMask => PipelineState.AlphaMask;
public float PointSize => PipelineState.PointSize;
public float LineWidth => PipelineState.LineWidth;
public List<ITexture?> Textures { get; init; } = [];
public List<UniformDescriptor> Uniforms { get; init; } = [];
public IndexType IndexType { get; init; } = IndexType.UnsignedShort;
public ReadOnlyMemory<byte> UniformData { get; init; } = ReadOnlyMemory<byte>.Empty;
public IBuffer? UniformBuffer { get; init; }
}
}
-42
View File
@@ -1,42 +0,0 @@
using System;
using System.IO;
using System.Net.Mime;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public class Font(IFont iFont) : IFont
{
public IFont Base => iFont;
public string Family => iFont.Family;
public FontWeight Weight => iFont.Weight;
public FontSlant Slant => iFont.Slant;
public FontStretch Stretch => iFont.Stretch;
public void Dispose()
{
iFont.Dispose();
}
public static Font Create(Stream stream)
{
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(stream);
return new Font(iFont);
}
public static Font Create(FontInfo info)
{
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(info);
return new Font(iFont);
}
public static Font Create(string path)
{
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(path);
return new Font(iFont);
}
}
}
-43
View File
@@ -1,43 +0,0 @@
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;
}
}
@@ -1,33 +0,0 @@
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();
}
}
@@ -1,50 +0,0 @@
using Dashboard.Pal;
namespace Dashboard.Drawing
{
/// <summary>
/// This extension provides direct access to the backend rendering pipeline without writing with a higher level
/// abstraction than the rendering backend.
/// </summary>
public interface IDirectRendering : IDeviceContextExtension
{
/// <summary>
/// Create a shader program.
/// </summary>
/// <typeparam name="T">Type of the shader create info object.</typeparam>
/// <param name="createInfo">An object which describes how the shader is created.</param>
/// <returns>A shader program object.</returns>
/// <seealso cref="SpirvShaderCreateInfo"/>
/// <seealso cref="GlslShaderCreateInfo"/>
/// <seealso cref="GlslComputeShaderCreateInfo"/>
IShader CreateShader<T>(T createInfo) where T : IShaderCreateInfo;
/// <summary>
/// Create a texture object.
/// </summary>
/// <param name="type">Type of texture to create.</param>
/// <returns>An empty texture object.</returns>
ITexture CreateTexture(TextureType type);
/// <summary>
/// Create a buffer object.
/// </summary>
/// <param name="pattern">Buffer access pattern hint.</param>
/// <param name="size">Initial capacity of the buffer in bytes.</param>
/// <returns>An empty buffer object.</returns>
IBuffer CreateBuffer(BufferAccessPattern pattern, long size);
/// <summary>
/// Enqueue a draw call.
/// </summary>
/// <param name="drawCall">The draw call to enqueue.</param>
void Draw(DrawCall drawCall);
}
[Obsolete("Use IDirectRendering instead.")]
public interface ITextureExtension : IDeviceContextExtension
{
/// <inheritdoc cref="IDirectRendering.CreateTexture(TextureType)"/>
ITexture CreateTexture(TextureType type);
}
}
-27
View File
@@ -1,27 +0,0 @@
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public interface IFont : IDisposable
{
string Family { get; }
FontWeight Weight { get; }
FontSlant Slant { get; }
FontStretch Stretch { get; }
}
public record struct FontInfo(string Family, FontWeight Weight = FontWeight.Normal,
FontSlant Slant = FontSlant.Normal, FontStretch Stretch = FontStretch.Normal) : IFont
{
public void Dispose()
{
}
}
public interface IFontLoader : IApplicationExtension
{
IFont Load(FontInfo info);
IFont Load(string path);
IFont Load(Stream stream);
}
}
-61
View File
@@ -1,61 +0,0 @@
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);
}
}
@@ -1,68 +0,0 @@
using System.Data;
using System.Drawing;
using System.Numerics;
using System.Runtime.InteropServices;
using Dashboard.Layout;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public record struct RectangleDrawInfo(Vector2 Position, ComputedBox Box, Brush Fill, Brush? Border = null);
[StructLayout(LayoutKind.Explicit, Size = Size)]
public struct ImmediateVertex()
{
[field: FieldOffset(PosOffset)]
public Vector3 Position { get; init; }= Vector3.Zero;
[field: FieldOffset(TexCoordsOffset)]
public Vector2 TexCoords { get; init; } = Vector2.Zero;
[field: FieldOffset(ColorOffset)]
public Vector4 Color { get; init; } = Vector4.One;
public ImmediateVertex(Vector3 position, Vector2 texCoords, Vector4 color) : this()
{
Position = position;
TexCoords = texCoords;
Color = color;
}
public ImmediateVertex(Vector3 position, Vector2 texCoords, Color color)
: this(
position,
texCoords,
new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f))
{
}
public const int Size = 16 * sizeof(float);
public const int PosOffset = 0 * sizeof(float);
public const int TexCoordsOffset = 4 * sizeof(float);
public const int ColorOffset = 8 * sizeof(float);
}
public struct ImmediateDrawCall(MeshPrimitive primitive, ReadOnlyMemory<ImmediateVertex> vertices)
{
public MeshPrimitive Primitive { get; init; } = primitive;
public ReadOnlyMemory<ImmediateVertex> Vertices { get; init; } = vertices;
public PipelineState PipelineState { get; init; } = new PipelineState();
public Matrix4x4 Transforms { get; init; } = Matrix4x4.Identity;
public ITexture? Texture { get; init; } = null;
}
public interface IImmediateMode : IDeviceContextExtension
{
/// <summary>
/// Enqueue a draw with the immediate mode shader pipeline.
/// </summary>
/// <param name="call">Information about this draw call.</param>
void DrawImmediate(ImmediateDrawCall call);
// TODO: move these to their own extension?
void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color);
void Rectangle(Box2d rectangle, float depth, Vector4 color);
void Rectangle(in RectangleDrawInfo rectangle);
void Image(Box2d rectangle, Box2d uv, float depth, ITexture texture);
}
}
-21
View File
@@ -1,21 +0,0 @@
using System.Collections.Immutable;
namespace Dashboard.Drawing
{
/// <summary>
/// Base interface for creating shader pipelines.
/// </summary>
public interface IShaderCreateInfo
{
}
public readonly record struct ShaderMappingProperty(string Name, int Index);
public interface IShader : IDisposable
{
public ImmutableList<ShaderMappingProperty> Attributes { get; }
public ImmutableList<ShaderMappingProperty> Uniforms { get; }
public ImmutableList<ShaderMappingProperty> Blocks { get; }
public ImmutableList<ShaderMappingProperty> Textures { get; }
}
}
-12
View File
@@ -1,12 +0,0 @@
using System.Numerics;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public interface ITextRenderer : IDeviceContextExtension
{
Box2d MeasureText(IFont font, float size, string text);
void DrawText(Vector2 position, Vector4 color, float size, IFont font, string text);
}
}
-78
View File
@@ -1,78 +0,0 @@
using System.Drawing;
namespace Dashboard.Drawing
{
public enum TextureType
{
Texture1D,
Texture2D,
Texture2DArray,
Texture2DCube,
Texture3D,
}
public enum TextureFilter
{
Nearest,
Linear,
NearestMipmapNearest,
LinearMipmapNearest,
NearestMipmapLinear,
LinearMipmapLinear,
Anisotropic,
}
public enum TextureRepeat
{
Repeat,
MirroredRepeat,
ClampToEdge,
ClampToBorder,
MirrorClampToEdge,
}
public enum CubeMapFace
{
PositiveX,
PositiveY,
PositiveZ,
NegativeX,
NegativeY,
NegativeZ,
}
public interface ITexture : IDisposable
{
public TextureType Type { get; }
public PixelFormat Format { get; }
public int Width { get; }
public int Height { get; }
public int Depth { get; }
public int Levels { get; }
public bool Premultiplied { get; set; }
public ColorSwizzle Swizzle { get; set; }
public TextureFilter MinifyFilter { get; set; }
public TextureFilter MagnifyFilter { get; set; }
public Color BorderColor { get; set; }
public TextureRepeat RepeatS { get; set; }
public TextureRepeat RepeatT { get; set; }
public TextureRepeat RepeatR { get; set; }
public int Anisotropy { get; set; }
void SetStorage(PixelFormat format, int width, int height, int depth, int levels);
void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged;
void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4) where T : unmanaged;
void Premultiply();
void Unmultiply();
void GenerateMipmaps();
}
}
-72
View File
@@ -1,72 +0,0 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Dashboard.Pal;
namespace Dashboard.Drawing
{
public class Image(ImageData data) : IDisposable
{
protected readonly ConditionalWeakTable<DeviceContext, ITexture> Textures =
new ConditionalWeakTable<DeviceContext, ITexture>();
public virtual TextureType Type => data.Type;
public PixelFormat Format { get; } = data.Format;
public int Width { get; } = data.Width;
public int Height { get; } = data.Height;
public int Depth { get; } = data.Depth;
public int Levels { get; } = data.Levels;
public bool Premultiplied { get; } = data.Premultiplied;
public bool IsDisposed { get; private set; } = false;
~Image()
{
InvokeDispose(false);
}
public virtual ITexture InternTexture(DeviceContext dc)
{
if (Textures.TryGetValue(dc, out ITexture? texture))
return texture;
IDirectRendering ext = dc.ExtensionRequire<IDirectRendering>();
texture = ext.CreateTexture(Type);
texture.SetStorage(Format, Width, Height, Depth, Levels);
for (int i = 0; i < Levels; i++)
{
texture.Write<byte>(Format, data.Bitmap.AsSpan()[(int)data.GetLevelOffset(i)..], level: i, align: data.Alignment);
}
texture.Premultiplied = Premultiplied;
texture.GenerateMipmaps();
Textures.Add(dc, texture);
return texture;
}
private void InvokeDispose(bool disposing)
{
if (IsDisposed)
return;
IsDisposed = true;
Dispose(disposing);
}
protected virtual void Dispose(bool disposing)
{
foreach ((DeviceContext dc, ITexture texture) in Textures)
{
texture.Dispose();
}
}
public void Dispose() => InvokeDispose(true);
public static Image Load(Stream stream)
{
IImageLoader imageLoader = Application.Current.ExtensionRequire<IImageLoader>();
return new Image(imageLoader.LoadImageData(stream));
}
}
}
-39
View File
@@ -1,39 +0,0 @@
namespace Dashboard.Drawing
{
public record GlslShaderCreateInfo() : IShaderCreateInfo
{
public required string VertexShader { get; init; }
public string? GeometryShader { get; init; } = null;
public string? TesselationControl { get; init; } = null;
public string? TesselationEvaluation { get; init; } = null;
public string? FragmentShader { get; init; } = null;
}
public record GlslComputeShaderCreateInfo(string Source) : IShaderCreateInfo
{
}
public record SpirvShaderCreateInfo(byte[] Binary) : IShaderCreateInfo
{
public int Format { get; init; } = Spirv;
public SpirvShaderCreateInfo(Stream stream) : this(GetBinary(stream)) { }
private static byte[] GetBinary(Stream stream)
{
//FIXME: this might actually be copying twice.
if (stream is MemoryStream ms)
{
return ms.ToArray();
}
else
{
using MemoryStream memory = new MemoryStream();
stream.CopyTo(memory);
return memory.ToArray();
}
}
private const int Spirv = 38225;
}
}
-17
View File
@@ -1,17 +0,0 @@
namespace Dashboard.Drawing
{
public enum ShaderStage
{
Vertex,
Geometry,
TesselationEvaluation,
TesselationControl,
Fragment,
Compute,
}
public abstract class ShaderBuilder(string language)
{
public string Language { get; } = language;
}
}
-224
View File
@@ -1,224 +0,0 @@
namespace Dashboard.Events
{
[Flags]
public enum ModifierKeys
{
None = 0,
LeftBitPos = 8,
RightBitPos = 16,
Shift = (1 << 0),
Control = (1 << 1),
Alt = (1 << 2),
Meta = (1 << 3),
NumLock = (1 << 4),
CapsLock = (1 << 5),
ScrollLock = (1 << 6),
LeftShift = (Shift << LeftBitPos),
LeftControl = (Control << LeftBitPos),
LeftAlt = (Alt << LeftBitPos),
LeftMeta = (Meta << LeftBitPos),
RightShift = (Shift << RightBitPos),
RightControl = (Control << RightBitPos),
RightAlt = (Alt << RightBitPos),
RightMeta = (Meta << RightBitPos),
}
public enum KeyCode
{
// TODO: Keycode table. Keycodes are subject to change upstream. SeeAlso: Extensions.cs@KeyCode ToDashboard(this Key key)
}
public enum ScanCode
{
// This scan code table is based on the Win32 scan code table.
// See https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
Error = 0xFF,
SystemPowerDown = 0xE05E,
SystemSleep = 0xE05F,
SystemWakeUp = 0xE063,
A = 0x1E,
B = 0x30,
C = 0x2E,
D = 0x20,
E = 0x12,
F = 0x21,
G = 0x22,
H = 0x23,
I = 0x17,
J = 0x24,
K = 0x25,
L = 0x26,
M = 0x32,
N = 0x31,
O = 0x18,
P = 0x19,
Q = 0x10,
R = 0x13,
S = 0x1F,
T = 0x14,
U = 0x16,
V = 0x2F,
W = 0x11,
X = 0x2D,
Y = 0x15,
Z = 0x2C,
D1 = 0x02,
D2 = 0x03,
D3 = 0x04,
D4 = 0x05,
D5 = 0x06,
D6 = 0x07,
D7 = 0x08,
D8 = 0x09,
D9 = 0x0A,
D0 = 0x0B,
Return = 0x1C,
Esc = 0x01,
Delete = 0x0E,
Tab = 0x0F,
Space = 0x39,
Dash = 0x0C,
Equals = 0x0D,
LBracket = 0x1A,
RBracket = 0x1B,
Backslash = 0x2B,
Semicolon = 0x27,
Apostrophe = 0x28,
Grave = 0x29,
Comma = 0x33,
Period = 0x34,
ForwardSlash = 0x35,
CapsLock = 0x3A,
F1 = 0x3B,
F2 = 0x3C,
F3 = 0x3D,
F4 = 0x3E,
F5 = 0x3F,
F6 = 0x40,
F7 = 0x41,
F8 = 0x42,
F9 = 0x43,
F10 = 0x44,
F11 = 0x45,
F12 = 0x46,
PrintScr = 0xE037,
ScrollLock = 0x46,
Pause = 0xE046,
Insert = 0xE052,
Home = 0xE047,
PageUp = 0xE049,
Backspace = 0xE053,
End = 0xE04F,
PageDown = 0xE051,
RightArrow = 0xE04D,
LeftArrow = 0xE04B,
DownArrow = 0xE050,
UpArrow = 0xE048,
NumLock = 0xE045,
NumDiv = 0xE035,
NumMul = 0x37,
NumSub = 0x4A,
NumAdd = 0x4E,
NumEnter = 0xE01C,
Num1 = 0x4F,
Num2 = 0x50,
Num3 = 0x51,
Num4 = 0x4B,
Num5 = 0x4C,
Num6 = 0x4D,
Num7 = 0x47,
Num8 = 0x48,
Num9 = 0x49,
Num0 = 0x52,
NumDecimal = 0x53,
NumBackslash = 0x56,
NumEquals = 0x59,
Application = 0xE05D,
F13 = 0x64,
F14 = 0x65,
F15 = 0x66,
F16 = 0x67,
F17 = 0x68,
F18 = 0x69,
F19 = 0x6A,
F20 = 0x6B,
F21 = 0x6C,
F22 = 0x6D,
F23 = 0x6E,
F24 = 0x76,
NumComma = 0x7E,
International1 = 0x73,
International2 = 0x70,
International3 = 0x7D,
International4 = 0x79,
International5 = 0x7B,
International6 = 0x5C,
International7,
International8,
International9,
Lang1 = 0x72,
Lang2 = 0x71,
Lang3 = 0x78,
Lang4 = 0x77,
Lang5,
Lang6,
Lang7,
Lang8,
Lang9,
LCtrl = 0x1D,
LShift = 0x2A,
LWin = 0xE05B,
LAlt = 0x38,
RAlt = 0xE038,
RWin = 0xE05C,
RCtrl = 0xE01D,
RShift = 0x36,
NextTrack = 0xE019,
PrevTrack = 0xE010,
Stop = 0xE024,
PlayPause = 0xE022,
VolUp = 0xE030,
VolDown = 0xE029,
Configuration = 0xE06D,
Email = 0xE06C,
Calculator = 0xE021,
Browser = 0xE06B,
Search = 0xE065,
HomePage = 0xE032,
Back = 0xE06A,
Forward = 0xE69,
Halt = 0xE068,
Refresh = 0xE67,
Bookmarks = 0xE066,
NonUsSlashBar = 0xFF01,
Mute,
}
public class KeyboardButtonEventArgs(KeyCode keyCode, ScanCode scanCode, ModifierKeys modifierKeys, bool up)
: UiEventArgs(up ? UiEventType.KeyUp : UiEventType.KeyDown)
{
public KeyCode KeyCode { get; } = keyCode;
public ScanCode ScanCode { get; } = scanCode;
public ModifierKeys ModifierKeys { get; } = modifierKeys;
}
public class TextInputEventArgs(string text) : UiEventArgs(UiEventType.TextEdit)
{
public string Text { get; } = text;
}
public class TextEditEventArgs(string candidate, int cursor, int length) : UiEventArgs(UiEventType.TextEdit)
{
public string Candidate { get; } = candidate;
public int Cursor { get; } = cursor;
public int Length { get; } = length;
}
}
-43
View File
@@ -1,43 +0,0 @@
using System.Drawing;
using System.Numerics;
namespace Dashboard.Events
{
[Flags]
public enum MouseButtons
{
M1 = 1 << 0,
M2 = 1 << 1,
M3 = 1 << 2,
M4 = 1 << 3,
M5 = 1 << 4,
M6 = 1 << 5,
M7 = 1 << 6,
M8 = 1 << 7,
Left = M1,
Right = M2,
Middle = M3,
}
public sealed class MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) : UiEventArgs(UiEventType.MouseMove)
{
public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 Delta { get; } = delta;
}
public sealed class MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, ModifierKeys modifierKeys, bool up)
: UiEventArgs(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
{
public ModifierKeys ModifierKeys { get; } = modifierKeys;
public Vector2 ClientPosition { get; } = clientPosition;
public MouseButtons Buttons { get; } = buttons;
}
public sealed class MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
: UiEventArgs(UiEventType.MouseScroll)
{
public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 ScrollDelta { get; } = scrollDelta;
}
}
-15
View File
@@ -1,15 +0,0 @@
namespace Dashboard.Events
{
public class TickEventArgs : UiEventArgs
{
/// <summary>
/// Animation delta time in seconds.
/// </summary>
public float Delta { get; }
public TickEventArgs(float delta) : base(UiEventType.AnimationTick)
{
Delta = delta;
}
}
}
-82
View File
@@ -1,82 +0,0 @@
using System.Numerics;
using Dashboard.Pal;
namespace Dashboard.Events
{
public enum UiEventType
{
None,
AnimationTick, // Generic timer event.
Paint, // Generic paint event.
// Text input related events.
KeyDown, // Keyboard key down.
KeyUp, // Keyboard key up.
TextInput, // Non-IME text event.
TextEdit, // IME text event.
TextCandidates, // IME text candidate list.
TextLanguage, // Keyboard language changed event.
// Mouse & touch related events
MouseButtonDown, // Mouse button down.
MouseButtonUp, // Mouse button up.
MouseMove, // Mouse moved.
MouseScroll, // Mouse scrolled.
// Reserved event names
StylusEnter, // The stylus has entered the hover region.
StylusLeave, // The stylus has left the hover region.
StylusMove, // The stylus has moved.
StylusDown, // The stylus is touching.
StylusUp, // The stylus is no longer touching.
StylusButtonUp, // Stylus button up.
StylusButtonDown, // Stylus button down.
StylusAxes, // Extra stylus axes data.
// Window & Control Events
ControlInvalidateVisual, // Force rendering the control again.
ControlStateChanged, // Control state changed.
ControlMoved, // Control moved.
ControlResized, // Control resized.
ControlEnter, // The pointing device entered the control.
ControlLeave, // The pointing device left the control.
ControlFocusGet, // The control acquired focus.
ControlFocusLost, // The control lost focus.
WindowClose, // The window closed.
UserRangeStart = 1 << 12,
}
public class UiEventArgs : EventArgs
{
public UiEventType Type { get; }
public UiEventArgs(UiEventType type)
{
Type = type;
}
public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None);
}
public class PaintEventArgs(DeviceContext dc) : UiEventArgs(UiEventType.Paint)
{
public DeviceContext DeviceContext { get; } = dc;
}
public class ControlMovedEventArgs : UiEventArgs
{
public Vector2 OldPosition { get; }
public Vector2 NewPosition { get; }
public ControlMovedEventArgs(Vector2 oldPosition, Vector2 newPosition) : base(UiEventType.ControlMoved)
{
OldPosition = oldPosition;
NewPosition = newPosition;
}
}
public class ResizeEventArgs() : UiEventArgs(UiEventType.ControlResized)
{
}
}
-7
View File
@@ -1,7 +0,0 @@
namespace Dashboard.Events
{
public class WindowCloseEvent() : UiEventArgs(UiEventType.WindowClose)
{
public bool Cancel { get; set; } = false;
}
}
-41
View File
@@ -1,41 +0,0 @@
namespace Dashboard
{
public enum FontWeight
{
_100 = 100,
_200 = 200,
_300 = 300,
_400 = 400,
_500 = 500,
_600 = 600,
_700 = 700,
_800 = 800,
_900 = 900,
Thin = _100,
Normal = _400,
Bold = _600,
Heavy = _900,
}
public enum FontSlant
{
Normal,
Italic,
Oblique,
}
public enum FontStretch
{
UltraCondensed = 500,
ExtraCondensed = 625,
Condensed = 750,
SemiCondensed = 875,
Normal = 1000,
SemiExpanded = 1125,
Expanded = 1250,
ExtraExpanded = 1500,
UltraExpanded = 2000,
}
}
-227
View File
@@ -1,227 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Numerics;
namespace Dashboard
{
/// <summary>
/// Enumeration of the kinds of gradients available.
/// </summary>
public enum GradientType
{
/// <summary>
/// A gradient which transitions over a set axis.
/// </summary>
Axial,
/// <summary>
/// A gradient which transitions along elliptical curves.
/// </summary>
Radial,
}
/// <summary>
/// A single gradient stop.
/// </summary>
/// <param name="Position">The position of the gradient stop. Must be [0,1].</param>
/// <param name="Color">The color value for the stop.</param>
public record struct GradientStop(float Position, Color Color);
/// <summary>
/// Represents a linear gradient.
/// </summary>
public struct Gradient : ICollection<GradientStop>, ICloneable, IEquatable<Gradient>
{
private readonly List<GradientStop> _stops = new List<GradientStop>();
/// <summary>
/// Gradient type.
/// </summary>
public GradientType Type { get; set; } = GradientType.Axial;
/// <summary>
/// First gradient control point.
/// </summary>
public Vector2 C0 { get; set; } = Vector2.Zero;
/// <summary>
/// Second gradient control point.
/// </summary>
public Vector2 C1 { get; set; } = Vector2.One;
/// <summary>
/// Number of stops in a gradient.
/// </summary>
public int Count => _stops.Count;
public bool IsReadOnly => false;
/// <summary>
/// Get a gradient control point.
/// </summary>
/// <param name="index">The index to get the control point for.</param>
public GradientStop this[int index]
{
get => _stops[index];
set
{
RemoveAt(index);
Add(value);
}
}
public Gradient()
{
}
public Gradient(Color a, Color b)
{
Add(new GradientStop(0, a));
Add(new GradientStop(1, b));
}
public Gradient(IEnumerable<GradientStop> stops)
{
_stops.AddRange(stops);
if (_stops.Any(x => x.Position < 0 || x.Position > 1))
throw new Exception("Gradient stop positions must be in the range [0, 1].");
_stops.Sort((a, b) => a.Position.CompareTo(b.Position));
}
public Color GetColor(float position)
{
if (Count == 0)
return Color.Black;
else if (Count == 1)
return _stops[0].Color;
int pivot = _stops.FindIndex(x => x.Position < position);
GradientStop left, right;
if (pivot == -1)
{
left = right = _stops[^1];
}
else if (pivot == 0)
{
left = right = _stops[0];
}
else
{
left = _stops[pivot-1];
right = _stops[pivot];
}
float weight = (position - left.Position) / (right.Position - left.Position);
Vector4 lcolor = new Vector4(left.Color.R, left.Color.G, left.Color.B, left.Color.A) * (1-weight);
Vector4 rcolor = new Vector4(right.Color.R, right.Color.G, right.Color.B, right.Color.A) * weight;
Vector4 color = lcolor + rcolor;
return Color.FromArgb((byte)color.W, (byte)color.X, (byte)color.Y, (byte)color.Z);
}
public Gradient Clone()
{
Gradient gradient = new Gradient()
{
Type = Type,
C0 = C0,
C1 = C1,
};
foreach (GradientStop stop in _stops)
{
gradient.Add(stop);
}
return gradient;
}
object ICloneable.Clone()
{
return Clone();
}
public IEnumerator<GradientStop> GetEnumerator()
{
return _stops.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_stops).GetEnumerator();
}
public void Add(GradientStop item)
{
if (item.Position < 0 || item.Position > 1)
throw new Exception("Gradient stop positions must be in the range [0, 1].");
int index = _stops.FindIndex(x => x.Position > item.Position);
if (index == -1)
index = _stops.Count;
_stops.Insert(index, item);
}
public void Clear()
{
_stops.Clear();
}
public bool Contains(GradientStop item)
{
return _stops.Contains(item);
}
public void CopyTo(GradientStop[] array, int arrayIndex)
{
_stops.CopyTo(array, arrayIndex);
}
public bool Remove(GradientStop item)
{
return _stops.Remove(item);
}
public void RemoveAt(int index)
{
_stops.RemoveAt(index);
}
public override int GetHashCode()
{
HashCode code = new HashCode();
code.Add(Count);
foreach (GradientStop item in this)
code.Add(item.GetHashCode());
return code.ToHashCode();
}
public bool Equals(Gradient other)
{
return
Type == other.Type &&
C0 == other.C0 &&
C1 == other.C1 &&
_stops.Equals(other._stops);
}
public override bool Equals(object? obj)
{
return obj is Gradient other && Equals(other);
}
public static bool operator ==(Gradient left, Gradient right)
{
return left.Equals(right);
}
public static bool operator !=(Gradient left, Gradient right)
{
return !left.Equals(right);
}
}
}
-38
View File
@@ -1,38 +0,0 @@
using System.Collections;
using System.Collections.Generic;
namespace Dashboard
{
public class HashList<T> : IReadOnlyList<T>
where T : notnull
{
private readonly List<T> _list = new List<T>();
private readonly Dictionary<T, int> _map = new Dictionary<T, int>();
public T this[int index] => _list[index];
public int Count => _list.Count;
public int Intern(T value)
{
if (_map.TryGetValue(value, out int index))
return index;
index = Count;
_list.Add(value);
_map.Add(value, index);
return index;
}
public void Clear()
{
_list.Clear();
_map.Clear();
}
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
}
}
-211
View File
@@ -1,211 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Dashboard
{
/// <summary>
/// Pixel format for images.
/// </summary>
[Flags]
public enum PixelFormat
{
None = 0,
R8I = R | I8,
Rg8I = Rg | I8,
Rgb8I = Rgb | I8,
Rgba8I = Rgba | I8,
R16F = R | F16,
Rg16F = Rg | F16,
Rgb16F = Rgb | F16,
Rgba16F = Rgba | F16,
// Channels
R = 0x01,
Rg = 0x02,
Rgb = 0x03,
Rgba = 0x04,
A = 0x05,
ColorMask = 0x0F,
I8 = 0x10,
F16 = 0x20,
TypeMask = 0xF0,
}
/// <summary>
/// Color channels for images.
/// </summary>
public enum ColorChannel
{
/// <summary>
/// The zero channel. Used for swizzle masks.
/// </summary>
Zero = 0,
/// <summary>
/// The one channel. Used for swizzle masks.
/// </summary>
One = 1,
/// <summary>
/// An invalid swizzle mask.
/// </summary>
Unknown = 2,
/// <summary>
/// The red channel.
/// </summary>
Red = 4,
/// <summary>
/// The green channel.
/// </summary>
Green = 5,
/// <summary>
/// The blue channel.
/// </summary>
Blue = 6,
/// <summary>
/// The alpha channel.
/// </summary>
Alpha = 7,
}
/// <summary>
/// Defines a image swizzle mask.
/// </summary>
public struct ColorSwizzle : IEquatable<ColorSwizzle>
{
public short Mask;
private const int MASK = 7;
private const int RBIT = 0;
private const int GBIT = 3;
private const int BBIT = 6;
private const int ABIT = 9;
/// <summary>
/// Swizzle the red channel.
/// </summary>
public ColorChannel R
{
get => (ColorChannel)((Mask >> RBIT) & MASK);
set => Mask = (short)(((int)value << RBIT) | (Mask & ~(MASK << RBIT)));
}
/// <summary>
/// Swizzle the green channel.
/// </summary>
public ColorChannel G
{
get => (ColorChannel)((Mask >> GBIT) & MASK);
set => Mask = (short)(((int)value << GBIT) | (Mask & ~(MASK << GBIT)));
}
/// <summary>
/// Swizzle the blue channel.
/// </summary>
public ColorChannel B
{
get => (ColorChannel)((Mask >> BBIT) & MASK);
set => Mask = (short)(((int)value << BBIT) | (Mask & ~(MASK << BBIT)));
}
/// <summary>
/// Swizzle the alpha channel.
/// </summary>
public ColorChannel A
{
get => (ColorChannel)((Mask >> ABIT) & MASK);
set => Mask = (short)(((int)value << ABIT) | (Mask & ~(MASK << ABIT)));
}
public ColorSwizzle(short mask)
{
Mask = mask;
}
public ColorSwizzle(ColorChannel r, ColorChannel g, ColorChannel b, ColorChannel a)
{
Mask = (short)(((int)r << RBIT) | ((int)g << GBIT) | ((int)b << BBIT) | ((int)a << ABIT));
}
public override string ToString()
{
return $"{GetChannelChar(R)}{GetChannelChar(G)}{GetChannelChar(B)}{GetChannelChar(A)}";
char GetChannelChar(ColorChannel channel) => channel switch
{
ColorChannel.Zero => '0',
ColorChannel.Red => 'R',
ColorChannel.Green => 'G',
ColorChannel.Blue => 'B',
ColorChannel.Alpha => 'A',
ColorChannel.One => '1',
_ => '?',
};
}
public override int GetHashCode()
{
return Mask.GetHashCode();
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is ColorSwizzle other && Equals(other);
}
public bool Equals(ColorSwizzle other)
{
return Mask == other.Mask;
}
public static readonly ColorSwizzle Default = Parse("RGBA");
public static readonly ColorSwizzle White = Parse("1111");
public static readonly ColorSwizzle Black = Parse("0001");
public static readonly ColorSwizzle Transparent = Parse("0000");
public static readonly ColorSwizzle RedToGrayscale = Parse("RRR1");
public static readonly ColorSwizzle RedToWhiteAlpha = Parse("111A");
public static bool TryParse(ReadOnlySpan<char> str, out ColorSwizzle value)
{
if (str.Length < 4)
{
value = default;
return false;
}
ColorChannel r = GetChannelFromChar(str[0]);
ColorChannel g = GetChannelFromChar(str[1]);
ColorChannel b = GetChannelFromChar(str[2]);
ColorChannel a = GetChannelFromChar(str[3]);
value = new ColorSwizzle(r, g, b, a);
return true;
ColorChannel GetChannelFromChar(char chr) => chr switch
{
'0' => ColorChannel.Zero,
'R' => ColorChannel.Red,
'G' => ColorChannel.Green,
'B' => ColorChannel.Blue,
'A' => ColorChannel.Alpha,
'1' => ColorChannel.One,
_ => ColorChannel.Unknown,
};
}
public static ColorSwizzle Parse(ReadOnlySpan<char> str) =>
TryParse(str, out ColorSwizzle value) ? value : throw new FormatException(nameof(str));
public static bool operator ==(ColorSwizzle left, ColorSwizzle right) =>
left.Mask == right.Mask;
public static bool operator !=(ColorSwizzle left, ColorSwizzle right) =>
left.Mask != right.Mask;
}
}
-18
View File
@@ -1,18 +0,0 @@
using System.ComponentModel;
using System.Numerics;
namespace Dashboard.Layout
{
public interface ILayoutItem : INotifyPropertyChanged
{
public LayoutInfo Layout { get; }
public Vector2 CalculateIntrinsicSize();
public Vector2 CalculateSize(Vector2 limits);
}
public interface ILayoutContainer : ILayoutItem, IEnumerable<ILayoutItem>
{
public ContainerLayoutInfo ContainerLayout { get; }
}
}
-435
View File
@@ -1,435 +0,0 @@
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Dashboard.Layout
{
public readonly record struct ComputedBox(Vector4 Margin, Vector4 Padding, Vector4 Border, Vector2 Size)
{
public float MarginLeft => Margin.X;
public float MarginTop => Margin.Y;
public float MarginRight => Margin.Z;
public float MarginBottom => Margin.W;
public float PaddingLeft => Padding.X;
public float PaddingTop => Padding.Y;
public float PaddingRight => Padding.Z;
public float PaddingBottom => Padding.W;
public float BorderLeft => Border.X;
public float BorderTop => Border.Y;
public float BorderRight => Border.Z;
public float BorderBottom => Border.W;
public float Width => Size.X;
public float Height => Size.Y;
public Vector2 BoundingSize => new Vector2(
MarginLeft + BorderLeft + Width + BorderRight + MarginRight,
MarginTop + BorderTop + Height + BorderBottom + MarginBottom);
public Vector2 ContentSize => new Vector2(
Width - PaddingLeft - PaddingRight,
Height - PaddingTop - PaddingBottom);
public Vector4 ContentExtents => new Vector4(
PaddingLeft,
PaddingTop,
PaddingLeft + PaddingRight + Width,
PaddingTop + PaddingBottom + Height);
public Vector4 CornerRadii { get; init; } = Vector4.Zero;
}
public class LayoutBox : INotifyPropertyChanged
{
private Vector4 _margin = Vector4.Zero;
private Vector4 _padding = Vector4.Zero;
private Vector4 _border = Vector4.Zero;
private Vector2 _size = Vector2.Zero;
private Vector2 _minimumSize = -Vector2.One;
private Vector2 _maximumSize = -Vector2.One;
private Vector4 _cornerRadii = Vector4.Zero;
private LayoutUnit _marginLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _marginTopUnit = LayoutUnit.Pixel;
private LayoutUnit _marginRightUnit = LayoutUnit.Pixel;
private LayoutUnit _marginBottomUnit = LayoutUnit.Pixel;
private LayoutUnit _paddingLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _paddingTopUnit = LayoutUnit.Pixel;
private LayoutUnit _paddingRightUnit = LayoutUnit.Pixel;
private LayoutUnit _paddingBottomUnit = LayoutUnit.Pixel;
private LayoutUnit _borderLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _borderTopUnit = LayoutUnit.Pixel;
private LayoutUnit _borderRightUnit = LayoutUnit.Pixel;
private LayoutUnit _borderBottomUnit = LayoutUnit.Pixel;
private LayoutUnit _widthUnit = LayoutUnit.Pixel;
private LayoutUnit _heightUnit = LayoutUnit.Pixel;
private LayoutUnit _minimumWidthUnit = LayoutUnit.Pixel;
private LayoutUnit _minimumHeightUnit = LayoutUnit.Pixel;
private LayoutUnit _maximumWidthUnit = LayoutUnit.Pixel;
private LayoutUnit _maximumHeightUnit = LayoutUnit.Pixel;
private LayoutUnit _cornerRadiusTopLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _cornerRadiusBottomLeftUnit = LayoutUnit.Pixel;
private LayoutUnit _cornerRadiusBottomRightUnit = LayoutUnit.Pixel;
private LayoutUnit _cornerRadiusTopRightUnit = LayoutUnit.Pixel;
private ComputedBox _computedBox = new ComputedBox();
public bool UpdateRequired { get; private set; } = true;
public Vector4 Margin
{
get => _margin;
set => SetField(ref _margin, value);
}
public Vector4 Padding
{
get => _padding;
set => SetField(ref _padding, value);
}
public Vector4 Border
{
get => _border;
set => SetField(ref _border, value);
}
public Vector2 Size
{
get => _size;
set => SetField(ref _size, value);
}
public Vector2 MinimumSize
{
get => _minimumSize;
set => SetField(ref _minimumSize, value);
}
public Vector2 MaximumSize
{
get => _maximumSize;
set => SetField(ref _maximumSize, value);
}
public Vector4 CornerRadii
{
get => _cornerRadii;
set => SetField(ref _cornerRadii, value);
}
public float MarginLeft
{
get => Margin.X;
set => Margin = Margin with { X = value };
}
public float MarginTop
{
get => Margin.Y;
set => Margin = Margin with { Y = value };
}
public float MarginRight
{
get => Margin.Z;
set => Margin = Margin with { Z = value };
}
public float MarginBottom
{
get => Margin.W;
set => Margin = Margin with { W = value };
}
public float PaddingLeft
{
get => Padding.X;
set => Padding = Padding with { X = value };
}
public float PaddingTop
{
get => Padding.Y;
set => Padding = Padding with { Y = value };
}
public float PaddingRight
{
get => Padding.Z;
set => Padding = Padding with { Z = value };
}
public float PaddingBottom
{
get => Padding.W;
set => Padding = Padding with { W = value };
}
public float BorderLeft
{
get => Border.X;
set => Border = Border with { X = value };
}
public float BorderTop
{
get => Border.Y;
set => Border = Border with { Y = value };
}
public float BorderRight
{
get => Border.Z;
set => Border = Border with { Z = value };
}
public float BorderBottom
{
get => Border.W;
set => Border = Border with { W = value };
}
public float CornerRadiusTopLeft
{
get => CornerRadii.X;
set => CornerRadii = CornerRadii with { X = value };
}
public float CornerRadiusBottomLeft
{
get => CornerRadii.Y;
set => CornerRadii = CornerRadii with { Y = value };
}
public float CornerRadiusBottomRight
{
get => CornerRadii.Z;
set => CornerRadii = CornerRadii with { Z = value };
}
public float CornerRadiusTopRight
{
get => CornerRadii.W;
set => CornerRadii = CornerRadii with { W = value };
}
public LayoutUnit MarginLeftUnit
{
get => _marginLeftUnit;
set => SetField(ref _marginLeftUnit, value);
}
public LayoutUnit MarginTopUnit
{
get => _marginTopUnit;
set => SetField(ref _marginTopUnit, value);
}
public LayoutUnit MarginRightUnit
{
get => _marginRightUnit;
set => SetField(ref _marginRightUnit, value);
}
public LayoutUnit MarginBottomUnit
{
get => _marginBottomUnit;
set => SetField(ref _marginBottomUnit, value);
}
public LayoutUnit PaddingLeftUnit
{
get => _paddingLeftUnit;
set => SetField(ref _paddingLeftUnit, value);
}
public LayoutUnit PaddingTopUnit
{
get => _paddingTopUnit;
set => SetField(ref _paddingTopUnit, value);
}
public LayoutUnit PaddingRightUnit
{
get => _paddingRightUnit;
set => SetField(ref _paddingRightUnit, value);
}
public LayoutUnit PaddingBottomUnit
{
get => _paddingBottomUnit;
set => SetField(ref _paddingBottomUnit, value);
}
public LayoutUnit BorderLeftUnit
{
get => _borderLeftUnit;
set => SetField(ref _borderLeftUnit, value);
}
public LayoutUnit BorderTopUnit
{
get => _borderTopUnit;
set => SetField(ref _borderTopUnit, value);
}
public LayoutUnit BorderRightUnit
{
get => _borderRightUnit;
set => SetField(ref _borderRightUnit, value);
}
public LayoutUnit BorderBottomUnit
{
get => _borderBottomUnit;
set => SetField(ref _borderBottomUnit, value);
}
public LayoutUnit WidthUnit
{
get => _widthUnit;
set => SetField(ref _widthUnit, value);
}
public LayoutUnit HeightUnit
{
get => _heightUnit;
set => SetField(ref _heightUnit, value);
}
public LayoutUnit MinimumWidthUnit
{
get => _minimumWidthUnit;
set => SetField(ref _minimumWidthUnit, value);
}
public LayoutUnit MinimumHeightUnit
{
get => _minimumHeightUnit;
set => SetField(ref _minimumHeightUnit, value);
}
public LayoutUnit MaximumWidthUnit
{
get => _maximumWidthUnit;
set => SetField(ref _maximumWidthUnit, value);
}
public LayoutUnit MaximumHeightUnit
{
get => _maximumHeightUnit;
set => SetField(ref _maximumHeightUnit, value);
}
public LayoutUnit CornerRadiusTopLeftUnit
{
get => _cornerRadiusTopLeftUnit;
set => SetField(ref _cornerRadiusTopLeftUnit, value);
}
public LayoutUnit CornerRadiusBottomLeftUnit
{
get => _cornerRadiusBottomLeftUnit;
set => SetField(ref _cornerRadiusBottomLeftUnit, value);
}
public LayoutUnit CornerRadiusBottomRightUnit
{
get => _cornerRadiusBottomRightUnit;
set => SetField(ref _cornerRadiusBottomRightUnit, value);
}
public LayoutUnit CornerRadiusTopRightUnit
{
get => _cornerRadiusTopRightUnit;
set => SetField(ref _cornerRadiusTopRightUnit, value);
}
public ComputedBox ComputedBox
{
get => _computedBox;
private set => SetField(ref _computedBox, value, false);
}
public event PropertyChangedEventHandler? PropertyChanged;
public ComputedBox ComputeLayout(Vector2 intrinsic, Vector2 dpi, Vector2 area, Vector2 star)
{
// TODO: take intrinsic into account.
Vector4 margin = Compute(_margin, dpi, area, star, _marginLeftUnit, _marginTopUnit, _marginRightUnit, _marginBottomUnit);
Vector4 padding = Compute(_padding, dpi, area, star, _paddingLeftUnit, _paddingTopUnit, _paddingRightUnit, _paddingBottomUnit);
Vector4 border = Compute(_border, dpi, area, star, _borderLeftUnit, _borderTopUnit, _borderRightUnit, _borderBottomUnit);
Vector2 size = Compute(_size, dpi, area, star, _widthUnit, _heightUnit);
Vector2 minimumSize = Compute(_minimumSize, dpi, area, star, _minimumWidthUnit, _minimumHeightUnit);
Vector2 maximumSize = Compute(_maximumSize, dpi, area, star, _maximumWidthUnit, _maximumHeightUnit);
Vector4 cornerRadii = Compute(_cornerRadii, dpi, area, star, _cornerRadiusTopLeftUnit,
_cornerRadiusBottomLeftUnit, _cornerRadiusBottomRightUnit, _cornerRadiusTopRightUnit);
size = Vector2.Clamp(size, minimumSize, maximumSize);
ComputedBox = new ComputedBox(margin, padding, border, size)
{
CornerRadii = cornerRadii,
};
UpdateRequired = false;
return ComputedBox;
}
private static float Compute(float value, float dpi, float length, float star, LayoutUnit unit)
{
const float dpiToMm = 0f;
const float dpiToPt = 0f;
return unit switch
{
LayoutUnit.Pixel => value,
LayoutUnit.Millimeter => value * dpi * dpiToMm,
LayoutUnit.Percent => value * length,
LayoutUnit.Point => value * dpi * dpiToPt,
LayoutUnit.Star => value * star,
_ => throw new ArgumentException(nameof(unit)),
};
}
private static Vector2 Compute(Vector2 value, Vector2 dpi, Vector2 size, Vector2 star, LayoutUnit xUnit, LayoutUnit yUnit)
{
return new Vector2(
Compute(value.X, dpi.X, size.X, star.X, xUnit),
Compute(value.Y, dpi.Y, size.Y, star.Y, yUnit));
}
private static Vector4 Compute(Vector4 value, Vector2 dpi, Vector2 size, Vector2 star, LayoutUnit xUnit, LayoutUnit yUnit, LayoutUnit zUnit, LayoutUnit wUnit)
{
return new Vector4(
Compute(value.X, dpi.X, size.X, star.X, xUnit),
Compute(value.Y, dpi.Y, size.Y, star.Y, yUnit),
Compute(value.Z, dpi.X, size.X, star.X, zUnit),
Compute(value.W, dpi.Y, size.Y, star.Y, wUnit));
}
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool SetField<T>(ref T field, T value, bool updateRequired = true, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
UpdateRequired |= updateRequired;
OnPropertyChanged(propertyName);
return true;
}
}
}
-171
View File
@@ -1,171 +0,0 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Dashboard.Layout
{
public enum DisplayMode
{
None,
Inline,
Block,
}
public enum ContainerMode
{
Basic,
Flex,
Grid,
}
public enum FlowDirection
{
Row,
Column,
RowReverse,
ColumnReverse,
}
public enum PositionMode
{
Absolute,
Relative,
}
public enum OverflowMode
{
Hidden,
Overflow,
ScrollHorizontal,
ScrollVertical,
ScrollBoth,
}
public record struct TrackInfo(float Width, LayoutUnit Unit)
{
public static readonly TrackInfo Default = new TrackInfo(0, LayoutUnit.Auto);
}
public class ContainerLayoutInfo : INotifyPropertyChanged
{
private ContainerMode _containerMode;
private FlowDirection _flowDirection = FlowDirection.Row;
public ObservableCollection<TrackInfo> Rows { get; } = new ObservableCollection<TrackInfo>() { TrackInfo.Default };
public ObservableCollection<TrackInfo> Columns { get; } =
new ObservableCollection<TrackInfo>() { TrackInfo.Default };
public ContainerMode ContainerMode
{
get => _containerMode;
set => SetField(ref _containerMode, value);
}
public FlowDirection FlowDirection
{
get => _flowDirection;
set => SetField(ref _flowDirection, value);
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
public class LayoutInfo : INotifyPropertyChanged
{
private DisplayMode _displayMode = DisplayMode.Inline;
private PositionMode _positionMode = PositionMode.Relative;
private OverflowMode _overflowMode = OverflowMode.Overflow;
private int _row = 0;
private int _column = 0;
/// <summary>
/// Changes the control display.
/// </summary>
public DisplayMode DisplayMode
{
get => _displayMode;
set => SetField(ref _displayMode, value);
}
/// <summary>
/// Changes how the control is positioned.
/// </summary>
public PositionMode PositionMode
{
get => _positionMode;
set => SetField(ref _positionMode, value);
}
/// <summary>
/// Changes how overflows are handled.
/// </summary>
public OverflowMode OverflowMode
{
get => _overflowMode;
set => SetField(ref _overflowMode, value);
}
public LayoutBox Box { get; } = new LayoutBox();
/// <summary>
/// The row of the control in a grid container.
/// </summary>
public int Row
{
get => _row;
set => SetField(ref _row, value);
}
/// <summary>
/// The column of the control in a grid container.
/// </summary>
public int Column
{
get => _column;
set => SetField(ref _column, value);
}
public event PropertyChangedEventHandler? PropertyChanged;
public LayoutInfo()
{
Box.PropertyChanged += BoxOnPropertyChanged;
// Rows.CollectionChanged += RowsChanged;
// Columns.CollectionChanged += ColumnsChanged;
}
private void BoxOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(Box));
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
-364
View File
@@ -1,364 +0,0 @@
using System.Numerics;
namespace Dashboard.Layout
{
public record struct LayoutItemSolution(ILayoutItem Item, ComputedBox Solution);
public class LayoutSolution
{
public ILayoutContainer Container { get; }
public IReadOnlyList<LayoutItemSolution> Items { get; }
private LayoutSolution(ILayoutContainer container, IEnumerable<LayoutItemSolution> itemSolutions)
{
Container = container;
Items = itemSolutions.ToList().AsReadOnly();
}
public static LayoutSolution CalculateLayout<T1>(T1 container, Vector2 limits, int iterations = 3, float absTol = 0.001f, float relTol = 0.01f)
where T1 : ILayoutContainer
{
switch (container.ContainerLayout.ContainerMode)
{
default:
case ContainerMode.Basic:
return SolveForBasicLayout(container, limits, iterations, absTol, relTol);
case ContainerMode.Flex:
return SolveForFlex(container, limits, iterations, absTol, relTol);
case ContainerMode.Grid:
return SolveForGrid(container, limits, iterations, absTol, relTol);
}
}
private static LayoutSolution SolveForGrid<T1>(T1 container, Vector2 limits, int iterations, float absTol, float relTol) where T1 : ILayoutContainer
{
throw new NotImplementedException();
}
private static LayoutSolution SolveForFlex<T1>(T1 container, Vector2 limits,int iterations, float absTol, float relTol) where T1 : ILayoutContainer
{
throw new NotImplementedException();
}
private static LayoutSolution SolveForBasicLayout<T1>(T1 container, Vector2 limits, int iterations, float absTol, float relTol) where T1 : ILayoutContainer
{
int count = container.Count();
LayoutItemSolution[] items = new LayoutItemSolution[count];
int i = 0;
foreach (ILayoutItem item in container)
{
items[i++] = new LayoutItemSolution(item, default);
}
bool limitX = limits.X > 0;
bool limitY = limits.Y > 0;
while (iterations-- > 0)
{
Vector2 pen = Vector2.Zero;
i = 0;
foreach (ILayoutItem item in container)
{
Vector2 size = item.CalculateIntrinsicSize();
}
}
return new LayoutSolution(container, items);
}
private record TrackSolution(TrackInfo Track)
{
public bool Auto => Track.Unit == LayoutUnit.Auto;
public bool Absolute => Track.Unit.IsAbsolute();
public float Requested { get; private set; }
public float Value { get; private set; }
public float Result { get; set; } = 0.0f;
public bool IsFrozen { get; set; } = false;
public void CalculateRequested(float dpi, float rel, float star)
{
Requested = new Metric(Track.Unit, Track.Width).Compute(dpi, rel, star);
}
public void Freeze()
{
if (IsFrozen)
return;
IsFrozen = true;
Result = Value;
}
}
private delegate float GetItemLength<in T1>(float dpi, float rel, float star, T1 item)
where T1 : ILayoutItem;
private TrackSolution[] SolveForGridTracks<T1, T2, T3>(
float limit,
T1 tracks,
T2 container,
int iterations,
float absTol,
float relTol,
Func<int, T3> getItemTrack,
GetItemLength<T3> getItemLength)
where T1 : IList<TrackInfo>
where T2 : ILayoutContainer
where T3 : ILayoutItem
{
int itemCount = container.Count();
bool auto = limit < 0;
TrackSolution[] solution = new TrackSolution[tracks.Count];
foreach (TrackSolution track in solution)
{
if (track.Absolute) {
// FIXME: pass DPI here.
track.CalculateRequested(96f, limit, 0);
track.Freeze();
}
}
while (iterations-- > 0)
{
}
for (int i = 0; i < tracks.Count; i++)
{
solution[i].Freeze();
}
return solution;
}
// private static void GetIntrinsicGridSizes<T1, T2>(Span<float> cols, Span<float> rows, T1 parent, T2 items)
// where T1 : ILayoutItem
// where T2 : IEnumerable<ILayoutItem>
// {
// CopyToSpan(rows, parent.Layout.Rows);
// CopyToSpan(cols, parent.Layout.Columns);
//
// foreach (ILayoutItem item in items)
// {
// int col = Math.Clamp(item.Layout.Column, 0, cols.Length - 1);
// int row = Math.Clamp(item.Layout.Row, 0, rows.Length - 1);
//
// bool autoCols = parent.Layout.Columns[col] < 0;
// bool autoRows = parent.Layout.Rows[row] < 0;
//
// if (!autoRows && !autoCols)
// continue;
//
// Vector2 size = item.CalculateIntrinsicSize();
// cols[col] = autoCols ? Math.Max(size.X, cols[col]) : cols[col];
// rows[row] = autoRows ? Math.Max(size.Y, rows[row]) : rows[row];
// }
// }
//
// public static Vector2 CalculateIntrinsicSize<T1, T2>(T1 parent, T2 items)
// where T1 : ILayoutItem
// where T2 : IEnumerable<ILayoutItem>
// {
// // Copy layout details.
// Span<float> cols = stackalloc float[parent.Layout.Columns.Count];
// Span<float> rows = stackalloc float[parent.Layout.Rows.Count];
//
// GetIntrinsicGridSizes(cols, rows, parent, items);
//
// float width = parent.Layout.Margin.X + parent.Layout.Margin.Z + parent.Layout.Padding.X + parent.Layout.Padding.Z + SumSpan<float>(cols);
// float height = parent.Layout.Margin.Y + parent.Layout.Margin.W + parent.Layout.Padding.Y + parent.Layout.Padding.W + SumSpan<float>(rows);
//
// return new Vector2(width, height);
// }
//
// public static GridResult Layout<T1, T2>(T1 parent, T2 items, Vector2 limits, int iterations = 3, float abstol = 0.0001f, float reltol = 0.01f)
// where T1 : ILayoutItem
// where T2 : IEnumerable<ILayoutItem>
// {
// Vector4 contentSpace = parent.Layout.Margin + parent.Layout.Padding;
// Vector2 contentLimits = new Vector2(
// limits.X > 0 ? limits.X - contentSpace.X - contentSpace.Z : -1,
// limits.Y > 0 ? limits.Y - contentSpace.Y - contentSpace.W : -1);
//
// // Get rows and columns for now.
// Span<Track> cols = stackalloc Track[parent.Layout.Columns.Count];
// Span<Track> rows = stackalloc Track[parent.Layout.Rows.Count];
//
// for (int i = 0; i < cols.Length; i++)
// {
// cols[i] = new Track(parent.Layout.Columns[i]);
//
// if (!cols[i].Auto)
// {
// cols[i].Freeze();
// }
// }
//
// for (int i = 0; i < rows.Length; i++)
// {
// rows[i] = new Track(parent.Layout.Rows[i]);
//
// if (!rows[i].Auto)
// {
// rows[i].Freeze();
// }
// }
//
// int freeRows = 0;
// int freeCols = 0;
// while (iterations-- > 0 && ((freeRows = CountFree(rows)) > 0 || (freeCols = CountFree(cols)) > 0))
// {
// // Calculate the remaining size.
// Vector2 remaining = contentLimits;
//
// for (int i = 0; contentLimits.X > 0 && i < cols.Length; i++)
// {
// if (cols[i].IsFrozen)
// remaining.X -= cols[i].Value;
// }
//
// for (int i = 0; contentLimits.Y > 0 && i < rows.Length; i++)
// {
// if (rows[i].IsFrozen)
// remaining.Y -= rows[i].Value;
// }
//
// Vector2 childLimits = remaining / new Vector2(Math.Max(freeCols, 1), Math.Max(freeRows, 1));
//
//
// // Calculate the size of each free track.
// foreach (ILayoutItem child in items)
// {
// int c = Math.Clamp(child.Layout.Column, 0, cols.Length - 1);
// int r = Math.Clamp(child.Layout.Row, 0, rows.Length - 1);
//
// bool autoRow = rows[r].Auto;
// bool autoCol = cols[c].Auto;
//
// if (!autoRow && !autoCol)
// continue;
//
// Vector2 childSize = child.CalculateSize(childLimits);
//
// if (autoCol)
// cols[c].Value = Math.Max(childLimits.X, childSize.X);
//
// if (autoRow)
// rows[r].Value = Math.Max(childLimits.Y, childSize.Y);
// }
//
// // Calculate for errors and decide to freeze them.
//
// for (int i = 0; limits.X > 0 && i < cols.Length; i++)
// {
// if (WithinTolerance(cols[i].Value, childLimits.X, abstol, reltol))
// {
// cols[i].Freeze();
// }
// }
//
// for (int i = 0; limits.Y > 0 && i < rows.Length; i++)
// {
// if (WithinTolerance(rows[i].Value, childLimits.Y, abstol, reltol))
// {
// rows[i].Freeze();
// }
// }
// }
//
// Vector2 size = new Vector2(
// parent.Layout.Margin.X + parent.Layout.Margin.Z + parent.Layout.Padding.X + parent.Layout.Padding.Z,
// parent.Layout.Margin.Y + parent.Layout.Margin.W + parent.Layout.Padding.Y + parent.Layout.Padding.W);
//
// foreach (ref Track col in cols)
// {
// col.Freeze();
// size.X += col.Result;
// }
//
// foreach (ref Track row in rows)
// {
// row.Freeze();
// size.Y += row.Result;
// }
//
// if (limits.X > 0) size.X = Math.Max(size.X, limits.X);
// if (limits.Y > 0) size.Y = Math.Max(size.Y, limits.Y);
//
// // Temporary solution
// return new GridResult(size, cols.ToArray().Select(x => x.Result).ToArray(), rows.ToArray().Select(x => x.Result).ToArray());
//
// static int CountFree(Span<Track> tracks)
// {
// int i = 0;
// foreach (Track track in tracks)
// {
// if (!track.IsFrozen)
// i++;
// }
//
// return i;
// }
// }
//
// private static void CopyToSpan<T1, T2>(Span<T1> span, T2 items)
// where T2 : IEnumerable<T1>
// {
// using IEnumerator<T1> iterator = items.GetEnumerator();
// for (int i = 0; i < span.Length; i++)
// {
// if (!iterator.MoveNext())
// break;
//
// span[i] = iterator.Current;
// }
// }
//
// private static T1 SumSpan<T1>(ReadOnlySpan<T1> span)
// where T1 : struct, INumber<T1>
// {
// T1 value = default;
//
// foreach (T1 item in span)
// {
// value += item;
// }
//
// return value;
// }
//
// private static bool WithinTolerance<T>(T value, T limit, T abstol, T reltol)
// where T : INumber<T>
// {
// T tol = T.Max(abstol, value * reltol);
// return T.Abs(value - limit) < tol;
// }
//
// public record GridResult(Vector2 Size, float[] Columns, float[] Rows)
// {
//
// }
//
// private record struct Track(float Request)
// {
// public bool Auto => Request < 0;
//
// public bool IsFrozen { get; private set; } = false;
// public float Value { get; set; } = Request;
//
// public float Result { get; private set; }
//
// public void Freeze()
// {
// Result = Value;
// IsFrozen = true;
// }
// }
}
}
-30
View File
@@ -1,30 +0,0 @@
namespace Dashboard
{
public enum LayoutUnit : short
{
/// <summary>
/// Does not specify a unit.
/// </summary>
Auto,
/// <summary>
/// The default unit. A size of a single picture element.
/// </summary>
Pixel = 1,
/// <summary>
/// 1/72th of an inch traditional in graphics design.
/// </summary>
Point = 2,
/// <summary>
/// The universal length unit for small distances.
/// </summary>
Millimeter = 3,
/// <summary>
/// An inverse proportional unit with respect to the container size.
/// </summary>
Star = 4,
/// <summary>
/// A directly proportional unit with respect to the container size.
/// </summary>
Percent = 5,
}
}
-23
View File
@@ -1,23 +0,0 @@
namespace Dashboard
{
public enum BorderKind
{
Inset = -1,
Center = 0,
Outset = 1,
}
public enum CapType
{
None,
Circular,
Rectangular,
}
public enum CuspType
{
None,
Circular,
Rectangular,
}
}
-128
View File
@@ -1,128 +0,0 @@
using System.Numerics;
namespace Dashboard
{
public record struct LayoutUnits(LayoutUnit All)
{
public LayoutUnit XUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 0);
set => All = (LayoutUnit)(((int)All & ~(0xF << 0)) | ((int)value << 0));
}
public LayoutUnit YUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 4);
set => All = (LayoutUnit)(((int)All & ~(0xF << 4)) | ((int)value << 4));
}
public LayoutUnit ZUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 8);
set => All = (LayoutUnit)(((int)All & ~(0xF << 8)) | ((int)value << 8));
}
public LayoutUnit WUnit
{
get => (LayoutUnit)(((int)All & 0xF) >> 12);
set => All = (LayoutUnit)(((int)All & ~(0xF << 12)) | ((int)value << 12));
}
}
public record struct Metric(LayoutUnit Units, float Value)
{
public float Compute(float dpi, float rel, float star)
{
switch (Units)
{
case LayoutUnit.Auto:
return -1;
case LayoutUnit.Millimeter:
float mm2Px = dpi / 25.4f;
return Value * mm2Px;
case LayoutUnit.Pixel:
return Value;
case LayoutUnit.Point:
float pt2Px = 72 / dpi;
return Value * pt2Px;
case LayoutUnit.Percent:
return rel * Value;
case LayoutUnit.Star:
return star * Value;
default:
throw new Exception("Unrecognized unit.");
}
}
}
public record struct Metric2D(LayoutUnits Units, Vector2 Value)
{
public float X
{
get => Value.X;
set => Value = new Vector2(value, Value.Y);
}
public LayoutUnit XUnits
{
get => Units.XUnit;
set => Units = Units with { XUnit = value };
}
public float Y
{
get => Value.Y;
set => Value = new Vector2(Value.X, value);
}
public LayoutUnit YUnits
{
get => Units.YUnit;
set => Units = Units with { YUnit = value };
}
}
public record struct BoxMetric(LayoutUnit Units, Box2d Value);
public record struct AdvancedMetric(LayoutUnit Unit, float Value)
{
public AdvancedMetric Convert(LayoutUnit target, float dpi, float rel, int stars)
{
if (Unit == target)
return this;
float pixels = Unit switch {
LayoutUnit.Pixel => Value,
LayoutUnit.Point => Value * (72f / dpi),
LayoutUnit.Millimeter => Value * (28.3464566929f / dpi),
LayoutUnit.Star => Value * rel / stars,
LayoutUnit.Percent => Value * rel / 100,
_ => throw new Exception(),
};
float value = target switch {
LayoutUnit.Pixel => pixels,
LayoutUnit.Point => Value * (dpi / 72f),
// MeasurementUnit.Millimeter =>
};
return new AdvancedMetric(target, value);
}
public override string ToString()
{
return $"{Value} {Unit.ToShortString()}";
}
public static bool TryParse(ReadOnlySpan<char> str, out AdvancedMetric metric)
{
metric = default;
return false;
}
public static AdvancedMetric Parse(ReadOnlySpan<char> str) =>
TryParse(str, out AdvancedMetric metric)
? metric
: throw new Exception($"Could not parse the value '{str}'.");
}
}
-30
View File
@@ -1,30 +0,0 @@
namespace Dashboard
{
public static class MeasurementExtensions
{
public static bool IsRelative(this LayoutUnit unit) => unit switch {
LayoutUnit.Star or LayoutUnit.Percent => true,
_ => false,
};
public static bool IsAbsolute(this LayoutUnit unit) => !IsRelative(unit);
public static string ToShortString(this LayoutUnit unit) => unit switch {
LayoutUnit.Pixel => "px",
LayoutUnit.Point => "pt",
LayoutUnit.Millimeter => "mm",
LayoutUnit.Star => "*",
LayoutUnit.Percent => "%",
_ => throw new Exception("Unknown unit."),
};
public static bool WithinTolerance(this float value, float reference, float absTol, float relTol)
=> value.CompareTolerance(reference, absTol, relTol) == 0;
public static int CompareTolerance(this float value, float reference, float absTol, float relTol)
{
float tolerance = Math.Max(absTol, Math.Abs(reference) * relTol);
float difference = value - reference;
return difference < -tolerance ? -1 : difference > tolerance ? 1 : 0;
}
}
}
-17
View File
@@ -1,17 +0,0 @@
namespace Dashboard
{
public enum Origin
{
Center = 0,
Left = (1 << 0),
Top = (1 << 1),
Right = (1 << 2),
Bottom = (1 << 3),
TopLeft = Top | Left,
BottomLeft = Bottom | Left,
BottomRight = Bottom | Right,
TopRight = Top | Right,
}
}
-202
View File
@@ -1,202 +0,0 @@
using Dashboard.Collections;
using Dashboard.Windowing;
using BindingFlags = System.Reflection.BindingFlags;
namespace Dashboard.Pal
{
public abstract class Application : IContextBase<Application, IApplicationExtension>
{
public abstract string DriverName { get; }
public abstract string DriverVendor { get; }
public abstract Version DriverVersion { get; }
public virtual string AppTitle { get; set; } = "Dashboard Application";
public bool IsInitialized { get; private set; } = false;
public bool IsDisposed { get; private set; } = false;
public IContextDebugger? Debugger { get; set; }
protected CancellationToken? CancellationToken { get; private set; }
protected bool Quit { get; set; } = false;
private readonly TypeDictionary<IApplicationExtension> _extensions =
new TypeDictionary<IApplicationExtension>(true);
private readonly TypeDictionary<IApplicationExtension, Func<IApplicationExtension>> _preloadedExtensions =
new TypeDictionary<IApplicationExtension, Func<IApplicationExtension>>(true);
public event EventHandler<DeviceContext>? DeviceContextCreated;
public Application()
{
Current = this;
}
~Application()
{
InvokeDispose(false);
}
public void Initialize()
{
if (IsInitialized)
return;
IsInitialized = true;
InitializeInternal();
}
protected virtual void InitializeInternal()
{
}
protected internal virtual void OnDeviceContextCreated(DeviceContext dc)
{
DeviceContextCreated?.Invoke(this, dc);
}
public virtual void RunEvents(bool wait)
{
if (!IsInitialized)
Initialize();
}
public void Run() => Run(true, System.Threading.CancellationToken.None);
public void Run(bool wait) => Run(wait, System.Threading.CancellationToken.None);
public void Run(bool waitForEvents, CancellationToken token)
{
CancellationToken = token;
CancellationToken.Value.Register(() => Quit = true);
Initialize();
while (!Quit && !token.IsCancellationRequested)
{
RunEvents(waitForEvents);
}
}
#region Window API
/// <summary>
/// Creates a window. It could be a virtual window, or a physical window.
/// </summary>
/// <returns>A window.</returns>
public abstract IWindow CreateWindow();
/// <summary>
/// Always creates a physical window.
/// </summary>
/// <returns>A physical window.</returns>
public abstract IPhysicalWindow CreatePhysicalWindow();
/// <summary>
/// Create a physical window with a window manager.
/// </summary>
/// <returns>A physical window with the given window manager.</returns>
public IPhysicalWindow CreatePhysicalWindow(IWindowManager wm)
{
IPhysicalWindow window = CreatePhysicalWindow();
window.WindowManager = wm;
return window;
}
public IWindow CreateDialogWindow(IWindow? parent = null)
{
if (parent is IVirtualWindow virtualWindow)
{
IWindow? window = virtualWindow.WindowManager?.CreateWindow();
if (window != null)
return window;
}
return CreatePhysicalWindow();
}
#endregion
public bool IsExtensionAvailable<T>() where T : IApplicationExtension
{
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
}
public bool ExtensionPreload<T>(Func<IApplicationExtension> loader) where T : IApplicationExtension
{
return _preloadedExtensions.Add<T>(loader);
}
public bool ExtensionPreload<T>() where T : IApplicationExtension, new()
{
return _preloadedExtensions.Add<T>(() => new T());
}
public T ExtensionRequire<T>() where T : IApplicationExtension
{
T? extension = default;
if (_extensions.TryGet(out extension))
return extension;
lock (_extensions)
{
if (_extensions.TryGet(out extension))
return extension;
if (_preloadedExtensions.Remove<T>(out Func<IApplicationExtension>? loader))
{
extension = (T)loader!();
}
else
{
extension = Activator.CreateInstance<T>();
}
_extensions.Add(extension);
extension.Require(this);
}
return extension;
}
public bool ExtensionLoad<T>(T instance) where T : IApplicationExtension
{
if (_extensions.Contains(instance))
return false;
_extensions.Add(instance);
instance.Require(this);
return true;
}
protected virtual void Dispose(bool isDisposing)
{
if (!isDisposing) return;
Quit = true;
foreach (IApplicationExtension extension in _extensions)
{
extension.Dispose();
}
GC.SuppressFinalize(this);
}
private void InvokeDispose(bool isDisposing)
{
if (IsDisposed) return;
IsDisposed = true;
Dispose(isDisposing);
}
public void Dispose() => InvokeDispose(true);
[field: ThreadStatic]
public static Application Current
{
get => field ?? throw new InvalidOperationException("There is currently no current application.");
set;
}
}
}
-164
View File
@@ -1,164 +0,0 @@
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);
}
}
@@ -1,7 +0,0 @@
namespace Dashboard.Pal
{
public interface IApplicationExtension : IContextExtensionBase<Application>
{
}
}
-122
View File
@@ -1,122 +0,0 @@
namespace Dashboard.Pal
{
/// <summary>
/// Information about this context interface.
/// </summary>
public interface IContextInterfaceInfo
{
/// <summary>
/// Name of this driver.
/// </summary>
string DriverName { get; }
/// <summary>
/// The vendor for this driver.
/// </summary>
string DriverVendor { get; }
/// <summary>
/// The version of this driver.
/// </summary>
Version DriverVersion { get; }
}
/// <summary>
/// The base context interface.
/// </summary>
public interface IContextBase : IContextInterfaceInfo, IDisposable
{
/// <summary>
/// The debugger for this context.
/// </summary>
IContextDebugger? Debugger { get; set; }
}
/// <summary>
/// The base context interface.
/// </summary>
/// <typeparam name="TContext">The context type.</typeparam>
/// <typeparam name="TExtension">The extension type, if used.</typeparam>
public interface IContextBase<TContext, in TExtension> : IContextBase
where TContext : IContextBase<TContext, TExtension>
where TExtension : IContextExtensionBase<TContext>
{
/// <summary>
/// Is such an extension available?
/// </summary>
/// <typeparam name="T">The extension to check.</typeparam>
/// <returns>True if the extension is available.</returns>
bool IsExtensionAvailable<T>() where T : TExtension;
/// <summary>
/// Preload extensions, to be lazy loaded when required.
/// </summary>
/// <typeparam name="T">The extension to preload.</typeparam>
/// <returns>
/// True if the extension was added to the preload set. Otherwise, already loaded or another extension
/// exists which provides this.
/// </returns>
bool ExtensionPreload<T>() where T : TExtension, new();
/// <summary>
/// Preload extensions, to be lazy loaded when required.
/// </summary>
/// <param name="loader">The loader delegate.</param>
/// <typeparam name="T">The extension to preload.</typeparam>
/// <returns>
/// True if the extension was added to the preload set. Otherwise, already loaded or another extension
/// exists which provides this.
/// </returns>
bool ExtensionPreload<T>(Func<TExtension> loader) where T : TExtension;
/// <summary>
/// Require an extension.
/// </summary>
/// <typeparam name="T">The extension to require.</typeparam>
/// <returns>The extension instance.</returns>
T ExtensionRequire<T>() where T : TExtension;
/// <summary>
/// Load an extension.
/// </summary>
/// <param name="instance">The extension instance.</param>
/// <typeparam name="T">The extension to require.</typeparam>
/// <returns>True if the extension was loaded, false if there was already one.</returns>
bool ExtensionLoad<T>(T instance) where T : TExtension;
}
/// <summary>
/// Base interface for all context extensions.
/// </summary>
public interface IContextExtensionBase : IContextInterfaceInfo, IDisposable
{
/// <summary>
/// The context that loaded this extension.
/// </summary>
IContextBase Context { get; }
/// <summary>
/// Require this extension.
/// </summary>
/// <param name="context">The context that required this extension.</param>
void Require(IContextBase context);
}
/// <summary>
/// Base interface for all context extensions.
/// </summary>
public interface IContextExtensionBase<TContext> : IContextExtensionBase
where TContext : IContextBase
{
/// <summary>
/// The context that loaded this extension.
/// </summary>
new TContext Context { get; }
/// <summary>
/// Require this extension.
/// </summary>
/// <param name="context">The context that required this extension.</param>
void Require(TContext context);
}
}
-10
View File
@@ -1,10 +0,0 @@
namespace Dashboard.Pal
{
public interface IContextDebugger : IDisposable
{
void LogDebug(string message);
void LogInfo(string message);
void LogWarning(string message);
void LogError(string message);
}
}
@@ -1,8 +0,0 @@
namespace Dashboard.Pal
{
public interface IDeviceContextExtension : IContextExtensionBase<DeviceContext>
{
void Begin() {}
void End() {}
}
}
-43
View File
@@ -1,43 +0,0 @@
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);
}
}
@@ -1,21 +0,0 @@
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; }
}
}
@@ -1,14 +0,0 @@
namespace Dashboard.Windowing
{
public interface IEventListener
{
event EventHandler? EventRaised;
/// <summary>
/// Send an event to this windowing object.
/// </summary>
/// <param name="sender">The object which generated the event.</param>
/// <param name="args">The event arguments sent.</param>
void SendEvent(object? sender, EventArgs args);
}
}
-8
View File
@@ -1,8 +0,0 @@
namespace Dashboard.Windowing
{
public interface IForm : IEventListener, IDisposable
{
public IWindow Window { get; }
public string Title { get; }
}
}
-12
View File
@@ -1,12 +0,0 @@
namespace Dashboard.Windowing
{
public interface IPaintable
{
event EventHandler Painting;
/// <summary>
/// Paint this paintable object.
/// </summary>
void Paint();
}
}
-18
View File
@@ -1,18 +0,0 @@
namespace Dashboard.Windowing
{
/// <summary>
/// Interface that is used to swap the buffers of windows
/// </summary>
public interface ISwapGroup
{
/// <summary>
/// The swap interval for this swap group.
/// </summary>
public int SwapInterval { get; set; }
/// <summary>
/// Swap buffers.
/// </summary>
void Swap();
}
}
-93
View File
@@ -1,93 +0,0 @@
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; }
}
}
@@ -0,0 +1,19 @@
<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>
@@ -0,0 +1,13 @@
namespace Dashboard.Media.Defaults
{
internal static class EnvironmentVariables
{
public const string SerifFont = "QUIK_SERIF_FONT";
public const string SansFont = "QUIK_SANS_FONT";
public const string MonospaceFont = "QUIK_MONOSPACE_FONT";
public const string CursiveFont = "QUIK_CURSIVE_FONT";
public const string FantasyFont = "QUIK_FANTASY_FONT";
public const string FallbackFontDatabase = "QUIK_FALLBACK_FONT_DB";
}
}
+16
View File
@@ -0,0 +1,16 @@
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);
}
}
}
@@ -0,0 +1,269 @@
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.Fonts;
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;
}
}
}
}
@@ -0,0 +1,93 @@
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.Fonts;
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);
}
}
}
}
+84
View File
@@ -0,0 +1,84 @@
using System;
using System.Buffers;
using System.IO;
using ReFuel.FreeType;
using Dashboard.Media.Color;
using Dashboard.Media.Fonts;
using OpenTK.Mathematics;
namespace Dashboard.Media.Defaults
{
public class FontFreeType : Font
{
private MemoryStream ms;
private FTFace face;
public override FontFace Face => throw new NotImplementedException();
public FontFreeType(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 Image Render(out GlyphMetrics 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 GlyphMetrics(codepoint,
new Vector2(ftmetrics.Width/64f, ftmetrics.Height/64f),
new Vector2(ftmetrics.HorizontalBearingX/64f, ftmetrics.HorizontalBearingY/64f),
new Vector2(ftmetrics.VerticalBearingX/64f, ftmetrics.VerticalBearingY/64f),
new Vector2(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(ImageFormat.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;
}
}
@@ -0,0 +1,24 @@
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 Font font)
{
try
{
font = new FontFreeType(stream);
return true;
}
catch
{
font = null;
return false;
}
}
}
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.IO;
using Dashboard.Media.Color;
using ReFuel.Stb;
namespace Dashboard.Media.Defaults
{
public unsafe class ImageStbi : Image
{
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 ImageFormat InternalFormat => Stb2QImageFormat(image.Format);
public ImageStbi(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 ImageFormat Stb2QImageFormat(StbiImageFormat src)
{
switch (src)
{
case StbiImageFormat.Grey: return ImageFormat.RedU8;
case StbiImageFormat.Rgb: return ImageFormat.RgbU8;
case StbiImageFormat.Rgba: return ImageFormat.RgbaU8;
case StbiImageFormat.GreyAlpha: return ImageFormat.RaU8;
default: return ImageFormat.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();
}
}
}
}
@@ -0,0 +1,230 @@
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;
}
}
@@ -0,0 +1,168 @@
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.Fonts;
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];
}
}
}
@@ -0,0 +1,11 @@
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 = "";
}
}
+155
View File
@@ -0,0 +1,155 @@
using System;
using System.Buffers;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using Dashboard.Media.Fonts;
// 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 */
};
}
}
@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
#if false
namespace Quik.Media.Defaults.Win32
{
public class EnumerateFonts
{
private const byte DEFAULT_CHARSET = 1;
public static void Enumerate(FontFace font)
{
/* It's windows, just borrow the desktop window. */
IntPtr hdc = GetDC(GetDesktopWindow());
List<(LogFontA, TextMetricA)> list = new List<(LogFontA, TextMetricA)>();
LogFontA font2 = new LogFontA()
{
//FaceName = font.Family,
Weight = ((font.Style & FontStyle.Bold) != 0) ? FontWeight.Bold : FontWeight.Regular,
Italic = (font.Style & FontStyle.Italic) != 0,
CharSet = DEFAULT_CHARSET
};
Console.WriteLine(font2.FaceName);
EnumFontFamiliesExProc proc = (in LogFontA font, in TextMetricA metric, int type, IntPtr lparam) =>
{
list.Add((font, metric));
return 0;
};
EnumFontFamiliesExA(hdc, font2, proc, IntPtr.Zero, 0);
}
private const string gdi32 = "Gdi32.dll";
private const string user32 = "User32.dll";
[DllImport(gdi32)]
private static extern int EnumFontFamiliesExA(
IntPtr hdc,
in LogFontA font,
[MarshalAs(UnmanagedType.FunctionPtr)] EnumFontFamiliesExProc proc,
IntPtr lparam,
int flags /* Should be zero. */);
[DllImport(user32)]
private static extern IntPtr /* HWND */ GetDesktopWindow();
[DllImport(user32)]
private static extern IntPtr /* HDC */ GetDC(IntPtr hwnd);
private delegate int EnumFontFamiliesExProc(in LogFontA font, in TextMetricA metric, int fontType, IntPtr lParam);
private struct LogFontA
{
public long Height;
public long Width;
public long Escapement;
public long Orientation;
public FontWeight Weight;
[MarshalAs(UnmanagedType.U1)]
public bool Italic;
[MarshalAs(UnmanagedType.U1)]
public bool Underline;
[MarshalAs(UnmanagedType.U1)]
public bool StrikeOut;
public byte CharSet;
public byte OutPrecision;
public byte ClipPrecision;
public byte PitchAndFamily;
private unsafe fixed byte aFaceName[32];
public unsafe string FaceName
{
get
{
fixed (byte* str = aFaceName)
{
int len = 0;
for (; str[len] != 0 && len < 32; len++) ;
return Encoding.UTF8.GetString(str, len);
}
}
set
{
fixed (byte *str = aFaceName)
{
Span<byte> span = new Span<byte>(str, 32);
Encoding.UTF8.GetBytes(value, span);
span[31] = 0;
}
}
}
}
private struct TextMetricA
{
public long Height;
public long Ascent;
public long Descent;
public long InternalLeading;
public long ExternalLeading;
public long AveCharWidth;
public long MaxCharWidth;
public long Weight;
public long Overhang;
public long DigitizedAspectX;
public long DigitizedAspectY;
public byte FirstChar;
public byte LastChar;
public byte DefaultChar;
public byte BreakChar;
public byte Italic;
public byte Underlined;
public byte StruckOut;
public byte PitchAndFamily;
public byte CharSet;
}
private enum FontWeight : long
{
DontCare = 0,
Thin = 100,
ExtraLight = 200,
UltraLight = 200,
Light = 300,
Normal = 400,
Regular = 400,
Medium = 500,
Semibold = 600,
Demibold = 600,
Bold = 700,
Extrabold = 800,
Ultrabold = 800,
Heavy = 900,
Black = 900
}
}
}
#endif
-77
View File
@@ -1,77 +0,0 @@
using System.Collections.Concurrent;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public class ContextCollector : IDisposable
{
private readonly ConcurrentQueue<GLObject> _disposedObjects = new ConcurrentQueue<GLObject>();
public void Dispose()
{
while (_disposedObjects.TryDequeue(out GLObject obj))
{
obj.Dispose();
}
}
void DeleteObject(ObjectIdentifier identifier, int handle) => _disposedObjects.Enqueue(new GLObject(identifier, handle));
public void DeleteTexture(int texture) => DeleteObject(ObjectIdentifier.Texture, texture);
public void DeleteBufffer(int buffer) => DeleteObject(ObjectIdentifier.Buffer, buffer);
public void DeleteFramebuffer(int framebuffer) => DeleteObject(ObjectIdentifier.Framebuffer, framebuffer);
public void DeleteRenderBuffer(int renderbuffer) => DeleteObject(ObjectIdentifier.Renderbuffer, renderbuffer);
public void DeleteSampler(int sampler) => DeleteObject(ObjectIdentifier.Sampler, sampler);
public void DeleteShader(int shader) => DeleteObject(ObjectIdentifier.Shader, shader);
public void DeleteProgram(int program) => DeleteObject(ObjectIdentifier.Program, program);
public void DeleteVertexArray(int vertexArray) => DeleteObject(ObjectIdentifier.VertexArray, vertexArray);
public void DeleteQuery(int query) => DeleteObject(ObjectIdentifier.Query, query);
public void DeleteProgramPipeline(int programPipeline) => DeleteObject(ObjectIdentifier.ProgramPipeline, programPipeline);
public void DeleteTransformFeedback(int transformFeedback) => DeleteObject(ObjectIdentifier.TransformFeedback, transformFeedback);
private readonly record struct GLObject(ObjectIdentifier Type, int Handle)
{
public void Dispose()
{
switch (Type)
{
case ObjectIdentifier.Texture:
GL.DeleteTexture(Handle);
break;
case ObjectIdentifier.Buffer:
GL.DeleteBuffer(Handle);
break;
case ObjectIdentifier.Framebuffer:
GL.DeleteFramebuffer(Handle);
break;
case ObjectIdentifier.Renderbuffer:
GL.DeleteRenderbuffer(Handle);
break;
case ObjectIdentifier.Sampler:
GL.DeleteSampler(Handle);
break;
case ObjectIdentifier.Shader:
GL.DeleteShader(Handle);
break;
case ObjectIdentifier.VertexArray:
GL.DeleteVertexArray(Handle);
break;
case ObjectIdentifier.Program:
GL.DeleteProgram(Handle);
break;
case ObjectIdentifier.Query:
GL.DeleteQuery(Handle);
break;
case ObjectIdentifier.ProgramPipeline:
GL.DeleteProgramPipeline(Handle);
break;
case ObjectIdentifier.TransformFeedback:
GL.DeleteTransformFeedback(Handle);
break;
}
}
}
public static readonly ContextCollector Global = new ContextCollector();
}
}
-22
View File
@@ -1,22 +0,0 @@
<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>
@@ -1,167 +0,0 @@
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Pal;
using Dashboard.Windowing;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics.Wgl;
using OpenTK.Mathematics;
using ColorBuffer = OpenTK.Graphics.OpenGL.ColorBuffer;
using Vector2 = System.Numerics.Vector2;
namespace Dashboard.OpenGL.Drawing
{
public class DeviceContextBase : IDeviceContextBase
{
private readonly Stack<Matrix4x4> _transforms = new Stack<Matrix4x4>();
private readonly Stack<Box2d> _clipRegions = new Stack<Box2d>();
private readonly Stack<Box2d> _scissorRegions = new Stack<Box2d>();
private int _z = 0;
public DeviceContext Context { get; private set; } = null!;
IContextBase IContextExtensionBase.Context => Context;
public string DriverName => "Dashboard OpenGL Device Context";
public string DriverVendor => "Dashboard";
public Version DriverVersion => new Version(0, 1);
public Box2d ClipRegion => _clipRegions.Peek();
public Box2d ScissorRegion => _scissorRegions.Peek();
public Matrix4x4 Transforms => _transforms.Peek();
public float Scale => ScaleOverride > 0 ? ScaleOverride : (Context.Window as IDpiAwareWindow)?.Scale ?? 1;
public float ScaleOverride { get; set; } = -1f;
public void Dispose()
{
}
public void Require(DeviceContext context)
{
Context = context;
ResetClip();
ResetScissor();
ResetTransforms();
}
void IContextExtensionBase.Require(IContextBase context) => Require((DeviceContext)context);
public void ResetClip()
{
_clipRegions.Clear();
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
_clipRegions.Push(new Box2d(Vector2.Zero, size));
SetClip(ClipRegion);
}
public void PushClip(Box2d clipRegion)
{
clipRegion = new Box2d(ClipRegion.Min.X + clipRegion.Min.X, ClipRegion.Min.Y + clipRegion.Min.Y,
Math.Min(ClipRegion.Max.X, ClipRegion.Min.X + clipRegion.Max.X),
Math.Min(ClipRegion.Max.Y, ClipRegion.Max.Y + clipRegion.Max.Y));
_clipRegions.Push(clipRegion);
SetClip(clipRegion);
}
public void PopClip()
{
_clipRegions.Pop();
SetClip(ClipRegion);
}
public void ResetScissor()
{
GL.Disable(EnableCap.ScissorTest);
_scissorRegions.Clear();
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
_scissorRegions.Push(new Box2d(Vector2.Zero, size));
}
public void PushScissor(Box2d scissorRegion)
{
GL.Enable(EnableCap.ScissorTest);
// scissorRegion = new RectangleF(scissorRegion.X + scissorRegion.X, scissorRegion.Y + scissorRegion.Y,
// Math.Min(ScissorRegion.Right - scissorRegion.X, scissorRegion.Width),
// Math.Min(ScissorRegion.Bottom - scissorRegion.Y, scissorRegion.Height));
_scissorRegions.Push(scissorRegion);
SetScissor(scissorRegion);
}
public void PopScissor()
{
if (_scissorRegions.Count == 1)
GL.Disable(EnableCap.ScissorTest);
_scissorRegions.Pop();
SetScissor(ClipRegion);
}
private void SetClip(Box2d rect)
{
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
GL.Viewport(
(int)Math.Round(rect.Min.X),
(int)Math.Round(size.Y - rect.Min.Y - rect.Size.Y),
(int)Math.Round(rect.Size.X),
(int)Math.Round(rect.Size.Y));
}
void SetScissor(Box2d rect)
{
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
GL.Scissor(
(int)Math.Round(rect.Min.X),
(int)Math.Round(size.Y - rect.Min.Y - rect.Size.Y),
(int)Math.Round(rect.Size.X),
(int)Math.Round(rect.Size.Y));
}
public void ResetTransforms()
{
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
Matrix4x4 m = Matrix4x4.CreateOrthographicOffCenterLeftHanded(0, size.X, size.Y, 0, 1, -1);
_transforms.Clear();
_transforms.Push(m);
}
public void PushTransforms(in Matrix4x4 matrix)
{
Matrix4x4 result = matrix * Transforms;
_transforms.Push(result);
}
public void PopTransforms()
{
_transforms.Pop();
}
public void ClearColor(Color color)
{
GL.ClearColor(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
GL.Clear(ClearBufferMask.ColorBufferBit);
}
public void ClearDepth()
{
GL.Clear(ClearBufferMask.DepthBufferBit);
}
public int IncrementZ()
{
return ++_z;
}
public int DecrementZ()
{
return --_z;
}
}
}
-113
View File
@@ -1,113 +0,0 @@
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Pal;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL.Drawing
{
public class DirectRendering : IDirectRendering, ITextureExtension
{
public GLDeviceContext Context { get; private set; } = null!;
public string DriverName { get; } = "Dashboard OpenGL";
public string DriverVendor { get; } = "Dashboard";
public Version DriverVersion { get; } = new Version(0, 1);
IContextBase IContextExtensionBase.Context => Context;
DeviceContext IContextExtensionBase<DeviceContext>.Context => Context;
public bool SupportsArbTextureStorage { get; private set; }
public bool SupportsAnisotropy { get; private set; }
private int _vao = -1;
private List<GLTexture> _textures = new List<GLTexture>();
public void Dispose()
{
GC.SuppressFinalize(this);
}
public void Require(DeviceContext context)
{
Context = (GLDeviceContext)context;
SupportsArbTextureStorage = Context.DriverVersion >= new Version(4, 2) ||
Context.IsGLExtensionAvailable("GL_ARB_texture_storage");
SupportsAnisotropy = Context.DriverVersion >= new Version() ||
Context.IsGLExtensionAvailable("GL_EXT_texture_filter_anisotropic") ||
Context.IsGLExtensionAvailable("GL_ARB_texture_filter_anisotropic");
}
public void Require(IContextBase context) => Require((DeviceContext)context);
public void Begin()
{
if (_vao == -1)
{
GL.GenVertexArray(out _vao);
}
}
public void End()
{
}
public IShader CreateShader<T>(T createInfo) where T : IShaderCreateInfo
{
int program = createInfo switch
{
GlslShaderCreateInfo glsl => glsl.CreateProgram(),
GlslComputeShaderCreateInfo compute => compute.CreateProgram(),
SpirvShaderCreateInfo spirv => spirv.CreateProgram(),
_ => throw new Exception($"Unsupported shader pipeline type {createInfo.GetType()}."),
};
return new GLShader((GLDeviceContext)Context, program);
}
public IBuffer CreateBuffer(BufferAccessPattern pattern, long size)
{
return GLBuffer.Create((GLDeviceContext)Context, pattern, size);
}
public void Draw(DrawCall drawCall)
{
Vector2 size = Context.FramebufferSize;
drawCall.SetAll(size, _vao, false);
if (drawCall.VertexSpecification.ElementBuffer is null)
{
GL.DrawArrays(drawCall.Primitive.OpenGL, drawCall.First, drawCall.Count);
}
else if (drawCall.BaseVertex == 0)
{
GL.DrawElements(drawCall.Primitive.OpenGL, drawCall.Count, drawCall.IndexType.OpenGL, (nint)drawCall.Offset);
}
else
{
GL.DrawElementsBaseVertex(drawCall.Primitive.OpenGL, drawCall.Count, drawCall.IndexType.OpenGL, (nint)drawCall.Offset, drawCall.BaseVertex);
}
}
public GLTexture CreateTexture(TextureType type)
{
GLTexture texture = new GLTexture(this, type);
lock (_textures) _textures.Add(texture);
return texture;
}
internal void TextureDisposed(GLTexture texture)
{
lock (_textures) _textures.Remove(texture);
}
ITexture ITextureExtension.CreateTexture(TextureType type) => CreateTexture(type);
ITexture IDirectRendering.CreateTexture(TextureType type)
{
return CreateTexture(type);
}
}
}
-137
View File
@@ -1,137 +0,0 @@
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);
}
}
}
@@ -1,499 +0,0 @@
using System.Drawing;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using UniformType = Dashboard.Drawing.UniformType;
using Vector2 = System.Numerics.Vector2;
namespace Dashboard.OpenGL.Drawing
{
public static class GLStateExtensions
{
extension(VertexAttributeType type)
{
public All OpenGL => type switch
{
VertexAttributeType.UnsignedByte => All.UnsignedByte,
VertexAttributeType.UnsignedShort => All.UnsignedShort,
VertexAttributeType.UnsignedInt => All.UnsignedInt,
VertexAttributeType.Byte => All.Byte,
VertexAttributeType.Short => All.Short,
VertexAttributeType.Int => All.Int,
VertexAttributeType.Half => All.HalfFloat,
VertexAttributeType.Float => All.Float,
VertexAttributeType.Double => All.Double,
_ => throw new ArgumentOutOfRangeException(),
};
public VertexAttribType VertexAttribType => (VertexAttribType)type.OpenGL;
public VertexAttribPointerType VertexAttribPointerType => (VertexAttribPointerType)type.OpenGL;
}
extension(TextureType type)
{
public TextureTarget OpenGL => type switch
{
TextureType.Texture1D => TextureTarget.Texture1D,
TextureType.Texture2D => TextureTarget.Texture2D,
TextureType.Texture2DArray => TextureTarget.Texture2DArray,
TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
TextureType.Texture3D => TextureTarget.Texture3D,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendEquation eq)
{
public BlendEquationMode OpenGL => eq switch
{
BlendEquation.Add => BlendEquationMode.FuncAdd,
BlendEquation.Subtract => BlendEquationMode.FuncSubtract,
BlendEquation.Max => BlendEquationMode.Max,
BlendEquation.Min => BlendEquationMode.Min,
BlendEquation.ReverseSubtract => BlendEquationMode.FuncReverseSubtract,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendFunction mode)
{
public BlendingFactor OpenGL => mode switch
{
BlendFunction.One => BlendingFactor.One,
BlendFunction.Zero => BlendingFactor.Zero,
BlendFunction.SourceAlpha => BlendingFactor.SrcAlpha,
BlendFunction.OneMinusSourceAlpha => BlendingFactor.OneMinusSrcAlpha,
BlendFunction.DestinationAlpha => BlendingFactor.DstAlpha,
BlendFunction.OneMinusDestinationAlpha => BlendingFactor.OneMinusDstAlpha,
BlendFunction.SourceColor => BlendingFactor.SrcColor,
BlendFunction.OneMinusSourceColor => BlendingFactor.OneMinusSrcColor,
BlendFunction.DestinationColor => BlendingFactor.DstColor,
BlendFunction.OneMinusDestinationColor => BlendingFactor.OneMinusDstColor,
BlendFunction.ConstantColor => BlendingFactor.OneMinusConstantColor,
BlendFunction.OneMinusConstantColor => BlendingFactor.OneMinusConstantColor,
BlendFunction.ConstantAlpha => BlendingFactor.OneMinusConstantAlpha,
BlendFunction.OneMinusConstantAlpha => BlendingFactor.OneMinusConstantAlpha,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(TestFunction test)
{
public All OpenGL => test switch
{
TestFunction.Always => All.Always,
TestFunction.Equal => All.Equal,
TestFunction.Greater => All.Greater,
TestFunction.GreaterThanOrEqual => All.Gequal,
TestFunction.LessThan => All.Less,
TestFunction.LessThanOrEqual => All.Lequal,
TestFunction.Never => All.Never,
TestFunction.NotEqual => All.Notequal,
_ => throw new ArgumentOutOfRangeException(),
};
public DepthFunction DepthFunction => (DepthFunction)test.OpenGL;
public StencilFunction StencilFunction => (StencilFunction)test.OpenGL;
}
extension(StencilOperation op)
{
public StencilOp OpenGL => op switch
{
StencilOperation.Keep => StencilOp.Keep,
StencilOperation.Zero => StencilOp.Zero,
StencilOperation.Replace => StencilOp.Replace,
StencilOperation.Increment => StencilOp.Incr,
StencilOperation.UncheckedIncrement => StencilOp.IncrWrap,
StencilOperation.Decrement => StencilOp.Decr,
StencilOperation.UncheckedDecrement => StencilOp.DecrWrap,
StencilOperation.Invert => StencilOp.Invert,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(MeshPrimitive primitive)
{
public PrimitiveType OpenGL => primitive switch
{
MeshPrimitive.Line => PrimitiveType.Lines,
MeshPrimitive.Point => PrimitiveType.Points,
MeshPrimitive.Triangle => PrimitiveType.Triangles,
MeshPrimitive.TriangleFan => PrimitiveType.TriangleFan,
MeshPrimitive.TriangleStrip => PrimitiveType.TriangleStrip,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(IndexType index)
{
public DrawElementsType OpenGL => index switch
{
IndexType.Int or IndexType.UnsignedInt => DrawElementsType.UnsignedInt,
IndexType.Short or IndexType.UnsignedShort => DrawElementsType.UnsignedShort,
IndexType.UnsignedLong => throw new NotSupportedException(),
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendMode mode)
{
public void SetEnabled()
{
if (mode.Enabled)
GL.Enable(EnableCap.Blend);
else
GL.Disable(EnableCap.Blend);
}
public void SetMode()
{
if (mode.IsSeparate)
{
GL.BlendEquationSeparate(mode.ColorEquation.OpenGL, mode.AlphaEquation.OpenGL);
GL.BlendFuncSeparate(
mode.Color.Source.OpenGL, mode.Color.Destination.OpenGL,
mode.Alpha.Source.OpenGL, mode.Alpha.Destination.OpenGL);
}
else
{
GL.BlendEquation(mode.UnifiedEquation.Value.OpenGL);
GL.BlendFunc(mode.Unified.Value.Source.OpenGL, mode.Unified.Value.Destination.OpenGL);
}
}
public void SetConstant()
{
GL.BlendColor(mode.Constant.R / 255f, mode.Constant.G / 255f,mode.Constant.B / 255f,mode.Constant.A / 255f);
}
public void SetAll()
{
mode.SetEnabled();
mode.SetConstant();
mode.SetMode();
}
}
extension(DepthMode mode)
{
public void SetEnabled()
{
if (mode.Test)
GL.Enable(EnableCap.DepthTest);
else
GL.Disable(EnableCap.DepthTest);
}
public void SetMask() => GL.DepthMask(mode.Mask);
public void SetFunction() => GL.DepthFunc(mode.Function.DepthFunction);
public void SetAll()
{
mode.SetEnabled();
mode.SetMask();
mode.SetFunction();
}
}
extension(StencilMode mode)
{
public void SetEnabled()
{
if (mode.Enabled)
GL.Enable(EnableCap.StencilTest);
else
GL.Disable(EnableCap.StencilTest);
}
public void SetFunction() => GL.StencilFunc(mode.Function.StencilFunction, mode.Reference, (uint)mode.Mask);
public void SetOperations() => GL.StencilOp(mode.Fail.OpenGL, mode.DepthFail.OpenGL, mode.Pass.OpenGL);
public void SetAll()
{
mode.SetEnabled();
mode.SetFunction();
mode.SetOperations();
}
}
extension(PipelineState state)
{
public void SetViewport(Vector2 size)
{
Vector2 min = Vector2.Max(Vector2.Zero, state.ViewportRegion.Min);
Vector2 max = Vector2.Min(size, state.ViewportRegion.Max);
GL.Viewport(
(int)Math.Round(min.X),
(int)Math.Round(size.Y - max.Y),
(int)Math.Round(max.X - min.X),
(int)Math.Round(max.Y - min.Y));
}
// TODO: clamp to viewport size
public void SetScissor(Vector2 size)
{
if (state.ScissorRegion == new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity))
{
GL.Disable(EnableCap.ScissorTest);
return;
}
GL.Enable(EnableCap.ScissorTest);
GL.Scissor(
(int)Math.Round(state.ViewportRegion.Left),
(int)Math.Round(size.Y - state.ViewportRegion.Top),
(int)Math.Round(state.ViewportRegion.Right - state.ViewportRegion.Left),
(int)Math.Round(state.ViewportRegion.Bottom - state.ViewportRegion.Top));
}
public void SetFrontFace()
{
GL.FrontFace(state.FrontFace switch
{
WindingOrder.Clockwise => FrontFaceDirection.Cw,
WindingOrder.Counterclockwise => FrontFaceDirection.Ccw,
_ => throw new ArgumentOutOfRangeException(),
});
}
public void SetCullMode()
{
if (state.CullMode == FaceCulling.None)
{
GL.Disable(EnableCap.CullFace);
return;
}
GL.Enable(EnableCap.CullFace);
GL.CullFace(state.CullMode switch
{
FaceCulling.Both => TriangleFace.FrontAndBack,
FaceCulling.Front => TriangleFace.Front,
FaceCulling.Back => TriangleFace.Back,
_ => throw new ArgumentOutOfRangeException(),
});
}
public void SetBlendMode() => state.BlendMode.SetAll();
public void SetDepthMode() => state.DepthMode.SetAll();
public void SetStencilMode() => state.StencilMode.SetAll();
public void SetColorMask()
{
GL.ColorMask(state.RedMask, state.GreenMask, state.BlueMask, state.AlphaMask);
}
public void SetPointSize()
{
GL.PointSize(state.PointSize);
}
public void SetLineWidth()
{
GL.LineWidth(state.LineWidth);
}
}
extension(DrawCall call)
{
public void UsePipeline() => GL.UseProgram((call.ShaderPipeline as GLShader)?.Handle ?? 0);
public void SetViewport(Vector2 size) => call.PipelineState.SetViewport(size);
public void SetScissor(Vector2 size) => call.PipelineState.SetViewport(size);
public void SetFrontFace() => call.PipelineState.SetFrontFace();
public void SetCullMode() => call.PipelineState.SetCullMode();
public void SetBlendMode() => call.PipelineState.BlendMode.SetAll();
public void SetDepthMode() => call.PipelineState.DepthMode.SetAll();
public void SetStencilMode() => call.PipelineState.StencilMode.SetAll();
public void SetColorMask() => call.PipelineState.SetColorMask();
public void SetPointSize() => call.PipelineState.SetPointSize();
public void SetLineWidth() => call.PipelineState.SetLineWidth();
public void SetPrimitiveRestart()
{
if (!call.EnableRestart)
{
GL.Disable(EnableCap.PrimitiveRestart);
return;
}
GL.Enable(EnableCap.PrimitiveRestart);
GL.PrimitiveRestartIndex((uint)call.RestartIndex);
}
public void UseTextures()
{
int i = 0;
foreach (ITexture? texture in call.Textures)
{
GL.ActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + i));
if (texture is GLTexture glTexture)
{
GL.BindTexture(glTexture.Type.OpenGL, glTexture.Handle);
}
else
{
GL.BindTexture(TextureTarget.Texture2D, 0);
}
}
}
public void UpdateVertexArrays(int vao)
{
GL.BindVertexArray(vao);
VertexSpecification spec = call.VertexSpecification;
if (spec.ElementBuffer != null)
{
GL.BindBuffer(BufferTarget.ElementArrayBuffer, (spec.ElementBuffer as GLBuffer)?.Handle ?? 0);
}
for (int i = 0; i < spec.Attributes.Count; i++)
{
VertexAttribute attrib = spec.Attributes[i];
IBuffer? buffer = spec.VertexBuffers[i];
GL.BindBuffer(BufferTarget.ArrayBuffer, (buffer as GLBuffer)?.Handle ?? 0);
GL.VertexAttribPointer(
(uint)attrib.Location,
attrib.Components,
attrib.Type.VertexAttribPointerType,
false,
(int)attrib.Stride,
(nint)attrib.Offset);
GL.EnableVertexAttribArray((uint)attrib.Location);
}
}
public void UpdateUniforms(bool transpose = false)
{
int bufferIndex = 0;
foreach (UniformDescriptor uniform in call.Uniforms)
{
ReadOnlySpan<byte> uniformData = call.UniformData.Span;
switch (uniform.Type)
{
case UniformType.I1:
{
ReadOnlySpan<int> span = CastSpan<int>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform1i(uniform.Location, span.Length, span);
break;
}
case UniformType.I2:
{
ReadOnlySpan<Vector2i> span = CastSpan<Vector2i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform2i(uniform.Location, span.Length, span);
break;
}
case UniformType.I3:
{
ReadOnlySpan<Vector3i> span = CastSpan<Vector3i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform3i(uniform.Location, span.Length, span);
break;
}
case UniformType.I4:
{
ReadOnlySpan<Vector4i> span = CastSpan<Vector4i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform4i(uniform.Location, span.Length, span);
break;
}
case UniformType.F1:
{
ReadOnlySpan<float> span = CastSpan<float>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform1f(uniform.Location, span.Length, span);
break;
}
case UniformType.F2:
{
ReadOnlySpan<Vector2> span = CastSpan<Vector2>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform2f(uniform.Location, span.Length, span);
break;
}
case UniformType.F3:
{
ReadOnlySpan<Vector3> span = CastSpan<Vector3>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform3f(uniform.Location, span.Length, span);
break;
}
case UniformType.F4:
{
ReadOnlySpan<Vector4> span = CastSpan<Vector4>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform4f(uniform.Location, span.Length, span);
break;
}
case UniformType.Mat2:
{
ReadOnlySpan<Matrix2> span = CastSpan<Matrix2>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix2f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Mat3:
{
ReadOnlySpan<Matrix3> span = CastSpan<Matrix3>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix3f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Mat4:
{
ReadOnlySpan<Matrix4> span = CastSpan<Matrix4>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix4f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Buffer:
{
int buffer = (call.UniformBuffer as GLBuffer)?.Handle ?? 0;
int index = bufferIndex++;
GL.BindBufferRange(BufferTarget.UniformBuffer, (uint)index, buffer, (nint)uniform.Offset, (int)uniform.Size);
break;
}
default: throw new NotSupportedException();
}
}
return;
ReadOnlySpan<T> CastSpan<T>(ReadOnlySpan<byte> bytes, long offset, long size)
where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(bytes.Slice((int)offset, (int)size));
}
}
public void SetAll(Vector2 size, int vao, bool transpose = false)
{
call.UsePipeline();
call.SetViewport(size); call.SetScissor(size);
call.SetFrontFace();
call.SetCullMode();
call.SetBlendMode();
call.SetDepthMode();
call.SetStencilMode();
call.SetPrimitiveRestart();
call.SetColorMask();
call.SetPointSize();
call.SetLineWidth();
call.UseTextures();
call.UpdateUniforms(transpose);
call.UpdateVertexArrays(vao);
}
}
}
}
-257
View File
@@ -1,257 +0,0 @@
using System.Drawing;
using Dashboard.Drawing;
using Dashboard.OpenGL.Drawing;
using OpenTK.Graphics.OpenGL;
using OGL = OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public class GLTexture(DirectRendering extension, TextureType type) : ITexture
{
public int Handle { get; private set; } = 0;
public bool IsValid => Handle != 0;
public TextureType Type { get; } = type;
public PixelFormat Format { get; private set; } = PixelFormat.Rgba8I;
public ColorSwizzle Swizzle { get; set; } = ColorSwizzle.Default;
public TextureFilter MinifyFilter { get; set; } = TextureFilter.Linear;
public TextureFilter MagnifyFilter { get; set; } = TextureFilter.Linear;
public Color BorderColor { get; set; } = Color.White;
public TextureRepeat RepeatS { get; set; } = TextureRepeat.Repeat;
public TextureRepeat RepeatT { get; set; } = TextureRepeat.Repeat;
public TextureRepeat RepeatR { get; set; } = TextureRepeat.Repeat;
public int Anisotropy { get; set; } = 0;
public int Width { get; private set; } = 0;
public int Height { get; private set; } = 0;
public int Depth { get; private set; } = 0;
public int Levels { get; private set; } = 0;
public bool Premultiplied { get; set; } = false;
private TextureTarget Target { get; } = type switch
{
TextureType.Texture1D => TextureTarget.Texture1D,
TextureType.Texture2D => TextureTarget.Texture2D,
TextureType.Texture3D => TextureTarget.Texture3D,
TextureType.Texture2DArray => TextureTarget.Texture2DArray,
TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
_ => throw new NotSupportedException()
};
private DirectRendering Extension { get; } = extension;
private GLDeviceContext Context => Extension.Context;
~GLTexture()
{
Dispose(false);
}
public void SetStorage(PixelFormat format, int width, int height, int depth, int levels)
{
if (!Context.IsRenderThread)
{
Context.InvokeBeforeDraw(() => SetStorage(format, width, height, depth, levels)).Wait();
return;
}
if (levels == 0)
{
levels = Math.Max(Math.ILogB(width), Math.ILogB(height));
}
Bind();
SizedInternalFormat glFormat = GetFormat(format);
if (Extension.SupportsArbTextureStorage)
{
switch (Type)
{
case TextureType.Texture1D:
GL.TexStorage1D(Target, levels, glFormat, width);
break;
case TextureType.Texture2D:
GL.TexStorage2D(Target, levels, glFormat, width, height);
break;
case TextureType.Texture3D:
case TextureType.Texture2DArray:
case TextureType.Texture2DCube:
GL.TexStorage3D(Target, levels, glFormat, width, height, depth);
break;
}
}
else
{
switch (Type)
{
case TextureType.Texture1D:
GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
case TextureType.Texture2D:
GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
case TextureType.Texture3D:
case TextureType.Texture2DArray:
case TextureType.Texture2DCube:
GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
}
}
Width = width;
Height = height;
Depth = depth;
Levels = levels;
}
public void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged
{
throw new NotImplementedException();
}
void ITexture.Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level, int align) => Write(format, buffer, level, align, null);
public unsafe void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4, TimeSpan? timeout = null) where T : unmanaged
{
if (!Context.IsRenderThread)
{
T[] bufferArray = buffer.ToArray();
Task task = Context.InvokeBeforeDraw(() => Write<T>(format, bufferArray, level, align));
if (timeout.HasValue)
task.Wait(timeout.Value);
else
task.Wait();
}
Bind();
OGL::PixelFormat glFormat = format switch
{
PixelFormat.R8I or PixelFormat.R16F => OGL.PixelFormat.Red,
PixelFormat.Rg8I or PixelFormat.Rg16F => OGL.PixelFormat.Rg,
PixelFormat.Rgb8I or PixelFormat.Rgb16F => OGL.PixelFormat.Rgb,
PixelFormat.Rgba8I or PixelFormat.Rgba16F => OGL.PixelFormat.Rgba,
_ => throw new NotSupportedException()
};
PixelType glType = format switch
{
PixelFormat.R8I or PixelFormat.Rg8I or PixelFormat.Rgb8I or PixelFormat.Rgba8I => PixelType.UnsignedByte,
PixelFormat.R16F or PixelFormat.Rg16F or PixelFormat.Rgb16F or PixelFormat.Rgba16F => PixelType.HalfFloat,
_ => throw new NotSupportedException()
};
GL.PixelStorei(PixelStoreParameter.UnpackAlignment, align);
fixed (T* ptr = buffer)
{
switch (Type)
{
case TextureType.Texture1D:
GL.TexSubImage1D(Target, level, 0, Width, glFormat, glType, ptr);
break;
case TextureType.Texture2D:
GL.TexSubImage2D(Target, level, 0, 0, Width, Height, glFormat, glType, ptr);
break;
case TextureType.Texture2DCube:
case TextureType.Texture3D:
case TextureType.Texture2DArray:
GL.TexSubImage3D(Target, level, 0, 0, 0, Width, Height, Depth, glFormat, glType, ptr);
break;
}
}
}
public void Premultiply()
{
throw new NotImplementedException();
}
public void Unmultiply()
{
throw new NotImplementedException();
}
public void GenerateMipmaps()
{
if (!Context.IsRenderThread)
{
Context.InvokeBeforeDraw(GenerateMipmaps).Wait();
return;
}
Bind();
GL.GenerateMipmap(Target);
}
private void Dispose(bool disposing)
{
if (IsDisposed)
return;
IsDisposed = true;
if (disposing)
{
if (Thread.CurrentThread != Context.RendererThread)
{
Context.Collector.DeleteTexture(Handle);
}
else
{
GL.DeleteTexture(Handle);
}
Handle = 0;
GC.SuppressFinalize(this);
}
else
{
Context.Collector.DeleteTexture(Handle);
}
}
public bool IsDisposed { get; private set; }
public void Dispose() => Dispose(false);
private void Bind()
{
if (Handle == 0)
{
Handle = GL.GenTexture();
}
GL.BindTexture(Target, Handle);
}
private static SizedInternalFormat GetFormat(PixelFormat format)
{
return format switch
{
PixelFormat.R8I => SizedInternalFormat.R8,
PixelFormat.R16F => SizedInternalFormat.R16f,
PixelFormat.Rg8I => SizedInternalFormat.Rg8,
PixelFormat.Rg16F => SizedInternalFormat.Rg16f,
PixelFormat.Rgb8I => SizedInternalFormat.Rgb8,
PixelFormat.Rgb16F => SizedInternalFormat.Rgb16f,
PixelFormat.Rgba8I => SizedInternalFormat.Rgba8,
PixelFormat.Rgba16F => SizedInternalFormat.Rgba16f,
_ => throw new ArgumentOutOfRangeException()
};
}
private static PixelFormat GetFormat(SizedInternalFormat format)
{
return format switch
{
SizedInternalFormat.R8 => PixelFormat.R8I,
SizedInternalFormat.R16f => PixelFormat.R16F,
SizedInternalFormat.Rg8 => PixelFormat.Rg8I,
SizedInternalFormat.Rg16f => PixelFormat.Rg16F,
SizedInternalFormat.Rgb8 => PixelFormat.Rgb8I,
SizedInternalFormat.Rgb16f => PixelFormat.Rgb16F,
SizedInternalFormat.Rgba8 => PixelFormat.Rgba8I,
SizedInternalFormat.Rgba16f => PixelFormat.Rgba16F,
_ => throw new ArgumentOutOfRangeException()
};
}
}
}
-214
View File
@@ -1,214 +0,0 @@
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
]
);
}
}
}
@@ -1,156 +0,0 @@
using System.Text;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL.Drawing
{
public static class KhronosShaderHelper
{
public static int CompileStage(ShaderType type, string source, out string? log)
{
log = null;
int shader = GL.CreateShader(type);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
GL.GetShaderi(shader, ShaderParameterName.CompileStatus, out int flag);
if (flag != 0)
return shader;
GL.GetShaderInfoLog(shader, out log);
GL.DeleteShader(shader);
return 0;
}
public static int LinkStages(ReadOnlySpan<int> stages, out string? log)
{
log = null;
int program = GL.CreateProgram();
foreach (int stage in stages)
{
GL.AttachShader(program, stage);
}
GL.LinkProgram(program);
GL.GetProgrami(program, ProgramProperty.LinkStatus, out int flag);
if (flag != 0)
return program;
GL.GetProgramInfoLog(program, out log);
GL.DeleteProgram(program);
return 0;
}
public static int CreateProgram(this GlslShaderCreateInfo glsl)
{
Span<int> stages = stackalloc int[5];
int failed = 0;
StringBuilder builder = new StringBuilder();
int count = 0;
Compile(ShaderType.VertexShader, glsl.VertexShader, "Vertex", stages);
Compile(ShaderType.FragmentShader, glsl.FragmentShader, "Fragment", stages);
Compile(ShaderType.GeometryShader, glsl.GeometryShader, "Geometry", stages);
Compile(ShaderType.TessControlShader, glsl.TesselationControl, "Tesselation Control", stages);
Compile(ShaderType.TessEvaluationShader, glsl.TesselationEvaluation, "Tesselation Evaluation", stages);
if (failed > 0)
{
throw new Exception($"{failed} shader stage(s) failed to compile. See the exception data for details.")
{
Data =
{
["Log"] = builder.ToString(),
},
};
}
int program = KhronosShaderHelper.LinkStages(stages[..count], out string? log);
if (program == 0)
{
throw new Exception($"Shader program failed to link. See the exception data for details.")
{
Data =
{
["Log"] = log,
},
};
}
return program;
void Compile(ShaderType type, string? source, string name, Span<int> stages)
{
if (source == null)
return;
int stage = CompileStage(type, source, out string? log);
if (stage == 0)
{
failed++;
builder.Append($"=== {name} Shader Compile Log Begin ===\n{log}\n=== {name} Shader Compile Log End ===\n\n");
}
else
{
stages[count++] = stage;
}
}
}
public static int CreateProgram(this GlslComputeShaderCreateInfo glsl)
{
int compute = CompileStage(ShaderType.ComputeShader, glsl.Source, out string? log);
if (compute == 0)
{
throw new Exception("Failed to compile compute shader. See the exception data for details.")
{
Data =
{
["Log"] = log,
},
};
}
int program = KhronosShaderHelper.LinkStages([compute], out log);
if (program != 0)
return program;
throw new Exception("Failed to link compute shader. See the exception data for details.")
{
Data =
{
["Log"] = log,
},
};
}
public static int CreateProgram(this SpirvShaderCreateInfo spirv)
{
int program = GL.CreateProgram();
GL.ProgramBinary(program, (All)spirv.Format, spirv.Binary, spirv.Binary.Length);
GL.GetProgrami(program, ProgramProperty.LinkStatus, out int flag);
if (flag == 0)
return program;
GL.GetProgramInfoLog(program, out string? log);
GL.DeleteProgram(program);
throw new Exception("Failed to load program binary. See the exception data for details.")
{
Data =
{
["Log"] = log,
},
};
}
}
}
-12
View File
@@ -1,12 +0,0 @@
#version 130
uniform sampler2D image;
in vec2 vTexCoords;
in vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor * texture(image, vTexCoords);
}
-18
View File
@@ -1,18 +0,0 @@
#version 130
uniform mat4 transforms;
in vec3 aPos;
in vec2 aTexCoords;
in vec4 aColor;
out vec2 vTexCoords;
out vec4 vColor;
void main() {
vec4 position = transforms * vec4(aPos, 1.0);
gl_Position = position;
vTexCoords = aTexCoords;
vColor = aColor;
}
-98
View File
@@ -1,98 +0,0 @@
using System.Runtime.CompilerServices;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public class GLBuffer(GLDeviceContext dc, BufferAccessPattern pattern, int handle, long size, long offset) : IBuffer
{
// TODO: At the moment, this class does no pooling. Convert it to a pool for better API usage.
public BufferAccessPattern AccessPattern { get; } = pattern;
public long Size { get; private set; } = size;
public long Offset { get; private set; } = offset;
public int Handle { get; private set; } = handle;
public void Reallocate(long newSize)
{
GL.GenBuffer(out int handle);
GL.BindBuffer(BufferTarget.ArrayBuffer, handle);
GL.BufferData(BufferTarget.ArrayBuffer, (nint)newSize, (nint)0, AccessPattern switch
{
BufferAccessPattern.Stream => BufferUsage.StreamDraw,
BufferAccessPattern.Static => BufferUsage.StaticDraw,
BufferAccessPattern.Download => BufferUsage.DynamicCopy,
BufferAccessPattern.Upload => BufferUsage.DynamicCopy,
_ => BufferUsage.DynamicDraw,
});
if (Handle != -1)
GL.DeleteBuffer(Handle);
Handle = handle;
Size = newSize;
Offset = 0;
}
public unsafe Span<T> Map<T>(long offset, int count = -1)
where T : unmanaged
{
ArgumentOutOfRangeException.ThrowIfNegative(offset);
if (count < 0)
count = (int)(Size / Unsafe.SizeOf<T>());
long absOffset = Offset + offset;
long absCount = count * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
nint ptr = (nint)GL.MapBuffer(BufferTarget.ArrayBuffer, BufferAccess.ReadWrite);
return new Span<T>((T*)(ptr + absOffset), count);
}
public void Unmap()
{
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.UnmapBuffer(BufferTarget.ArrayBuffer);
}
public void Read<T>(long offset, Span<T> span) where T : unmanaged
{
long absCount = span.Length * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.GetBufferSubData(BufferTarget.ArrayBuffer, (nint)(Offset + offset), (nint)absCount, span);
}
public void Write<T>(long offset, ReadOnlySpan<T> span) where T : unmanaged
{
long absCount = span.Length * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.BufferSubData(BufferTarget.ArrayBuffer, (nint)(Offset + offset), (nint)absCount, span);
}
public void Dispose()
{
if (Handle == -1)
return;
dc.Collector.DeleteBufffer(Handle);
Handle = -1;
GC.SuppressFinalize(this);
}
public static GLBuffer Create(GLDeviceContext dc, BufferAccessPattern pattern, long size)
{
GLBuffer buffer = new GLBuffer(dc, pattern, -1, 0, 0);
buffer.Reallocate(size);
return buffer;
}
}
}
-175
View File
@@ -1,175 +0,0 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.OpenGL.Drawing;
using Dashboard.Pal;
using Dashboard.Windowing;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
internal class GLContextBindingsContext(IGLContext context) : IBindingsContext
{
public IntPtr GetProcAddress(string procName)
{
return context.GetProcAddress(procName);
}
}
public class GLDeviceContext : DeviceContext
{
public IGLContext GLContext { get; }
public ContextCollector Collector { get; } = new ContextCollector();
public override string DriverName => "Dashboard OpenGL Device Context";
public override string DriverVendor => "Dashboard";
public override Version DriverVersion => new Version(0, 1, 0);
public override ISwapGroup SwapGroup { get; }
public override Vector2 FramebufferSize => GLContext.FramebufferSize;
public override bool DoubleBuffered { get; } = true;
public Version GLVersion { get; }
public string GLRenderer { get; }
public string GLVendor { get; }
public ImmutableHashSet<string> Extensions { get; }
public Thread RendererThread { get; } = Thread.CurrentThread;
public bool IsRenderThread => RendererThread == Thread.CurrentThread;
private readonly ConcurrentQueue<Task> _beforeDrawActions = new ConcurrentQueue<Task>();
private readonly ConcurrentQueue<Task> _afterDrawActions = new ConcurrentQueue<Task>();
public GLDeviceContext(Application app, IWindow? window, IGLContext context, ISwapGroup swap) : base(app, window)
{
GLContext = context;
SwapGroup = swap;
context.MakeCurrent();
GLLoader.LoadBindings(new GLContextBindingsContext(context));
context.Disposed += Dispose;
GL.GetInteger(GetPName.MajorVersion, out int major);
GL.GetInteger(GetPName.MinorVersion, out int minor);
GLVersion = new Version(major, minor);
GLRenderer = GL.GetString(StringName.Renderer) ?? string.Empty;
GLVendor = GL.GetString(StringName.Vendor) ?? string.Empty;
HashSet<string> extensions = new HashSet<string>();
GL.GetInteger(GetPName.NumExtensions, out int extensionCount);
for (uint i = 0; i < extensionCount; i++)
{
string? ext = GL.GetStringi(StringName.Extensions, i);
if (ext != null)
extensions.Add(ext);
}
Extensions = extensions.ToImmutableHashSet();
ExtensionPreload<DeviceContextBase>();
ExtensionPreload<ImmediateMode>();
ExtensionPreload<DirectRendering>();
}
public bool IsGLExtensionAvailable(string name)
{
return Extensions.Contains(name);
}
public void AssertGLExtension(string name)
{
if (IsGLExtensionAvailable(name))
return;
throw new NotSupportedException($"The OpenGL extension \"{name}\" is not supported by this context.");
}
public void InvokeBeforeDraw(Task task) => _beforeDrawActions.Enqueue(task);
public void InvokeAfterDraw(Task task) => _afterDrawActions.Enqueue(task);
public Task InvokeBeforeDraw(Action action)
{
Task task = new Task(action);
_beforeDrawActions.Enqueue(task);
return task;
}
public Task InvokeAfterDraw(Action action)
{
Task task = new Task(action);
_afterDrawActions.Enqueue(task);
return task;
}
public Task<T> InvokeBeforeDraw<T>(Func<T> function)
{
Task<T> task = new Task<T>(function);
_beforeDrawActions.Enqueue(task);
return task;
}
public Task<T> InvokeAfterDraw<T>(Func<T> function)
{
Task<T> task = new Task<T>(function);
_afterDrawActions.Enqueue(task);
return task;
}
public Task InvokeOnRenderThread(Action action)
{
if (IsRenderThread)
{
action();
return Task.CompletedTask;
}
return InvokeBeforeDraw(action);
}
public Task<T> InvokeOnRenderThread<T>(Func<T> function)
{
return IsRenderThread ? Task.FromResult(function()) : InvokeBeforeDraw(function);
}
public override void Begin()
{
base.Begin();
GLContext.MakeCurrent();
IDeviceContextBase dc = ExtensionRequire<IDeviceContextBase>();
dc.ResetClip();
dc.ResetTransforms();
while (_beforeDrawActions.TryDequeue(out Task? action))
{
action.RunSynchronously();
}
}
public override void End()
{
while (_afterDrawActions.TryDequeue(out Task? action))
{
action.RunSynchronously();
}
base.End();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
GLContext.Disposed -= Dispose;
}
}
}
}
-29
View File
@@ -1,29 +0,0 @@
using System.Numerics;
using Dashboard.Windowing;
namespace Dashboard.OpenGL
{
/// <summary>
/// Interface for GL context operations
/// </summary>
public interface IGLContext : IDeviceContext
{
/// <summary>
/// The associated group for context sharing.
/// </summary>
/// <remarks>-1 assigns no group.</remarks>
public int ContextGroup { get; }
/// <summary>
/// Called when the context is disposed.
/// </summary>
event Action Disposed;
/// <summary>
/// Activate this OpenGL Context.
/// </summary>
void MakeCurrent();
IntPtr GetProcAddress(string procName);
}
}
-16
View File
@@ -1,16 +0,0 @@
namespace Dashboard.OpenGL
{
/// <summary>
/// Interface much like <see cref="IDisposable"/> except GL resources are dropped.
/// </summary>
/// <remarks>
/// The main reason this interface exists is that you need a way to differentiate
/// when a context is deleted, versus when the context is to remain present.
/// </remarks>
public interface IGLDisposable : IDisposable
{
/// <inheritdoc cref="IDisposable.Dispose"/>
/// <param name="safeExit">Set to true to spend the time clearing out GL objects.</param>
void Dispose(bool safeExit);
}
}
-60
View File
@@ -1,60 +0,0 @@
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public static class ShaderUtil
{
public static int CompileShader(ShaderType type, string source)
{
int shader = GL.CreateShader(type);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
int compileStatus = 0;
GL.GetShaderi(shader, ShaderParameterName.CompileStatus, out compileStatus);
if (compileStatus == 0)
{
GL.GetShaderInfoLog(shader, out string log);
GL.DeleteShader(shader);
throw new Exception($"{type} Shader compilation failed: " + log);
}
return shader;
}
public static int CompileShader(ShaderType type, Stream stream)
{
using StreamReader reader = new StreamReader(stream, leaveOpen: true);
return CompileShader(type, reader.ReadToEnd());
}
public static int LinkProgram(int s1, int s2, IReadOnlyList<string>? attribLocations = null)
{
int program = GL.CreateProgram();
GL.AttachShader(program, s1);
GL.AttachShader(program, s2);
for (int i = 0; i < attribLocations?.Count; i++)
{
GL.BindAttribLocation(program, (uint)i, attribLocations[i]);
}
GL.LinkProgram(program);
int linkStatus = 0;
GL.GetProgrami(program, ProgramProperty.LinkStatus, out linkStatus);
if (linkStatus == 0)
{
GL.GetProgramInfoLog(program, out string log);
GL.DeleteProgram(program);
throw new Exception("Shader program linking failed: " + log);
}
return program;
}
}
}
+17
View File
@@ -0,0 +1,17 @@
<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>
+105
View File
@@ -0,0 +1,105 @@
using Dashboard.ImmediateDraw;
using Dashboard.Media;
using Dashboard.PAL;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using System;
using System.Collections.Generic;
namespace Dashboard.OpenTK
{
public class OpenTKPlatform : IDbPlatform
{
private readonly List<OpenTKPort> _ports = new List<OpenTKPort>();
// These shall remain a sad nop for now.
public string? Title { get; set; }
public Media.Image? 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(new GLFWBindingsContext());
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 Vector2 PortGetSize(IDashHandle port) => ((OpenTKPort)port).Size;
public void PortSetSize(IDashHandle port, Vector2 size) => ((OpenTKPort)port).Size = size;
public Vector2 PortGetPosition(IDashHandle port) => ((OpenTKPort)port).Position;
public void PortSetPosition(IDashHandle port, Vector2 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, DrawList commands) => ((OpenTKPort)port).Paint(commands);
public void GetMaximumImage(out int width, out int height)
{
GL.GetInteger(GetPName.MaxTextureSize, out int value);
width = height = value;
}
public void GetMaximumImage(out int width, out int height, out int depth)
{
GetMaximumImage(out width, out height);
GL.GetInteger(GetPName.MaxArrayTextureLayers, out int value);
depth = value;
}
}
}
+108
View File
@@ -0,0 +1,108 @@
using System;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using OpenTK.Graphics.OpenGL4;
using Dashboard.OpenGL;
using Dashboard.ImmediateDraw;
using Dashboard.PAL;
using Dashboard.VertexGenerator;
namespace Dashboard.OpenTK
{
public class OpenTKPort : IDashHandle
{
private readonly NativeWindow _window;
private readonly GL21Driver _glDriver;
private readonly VertexDrawingEngine _vertexEngine;
public string Title
{
get => _window.Title;
set => _window.Title = value;
}
public Vector2 Size
{
get
{
Vector2i size = _window.ClientSize;
return new Vector2(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 Vector2 Position
{
get
{
Vector2i location = _window.Location;
return new Vector2(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 VertexDrawingEngine();
}
public void Focus()
{
_window.Focus();
}
public void Paint(DrawList queue)
{
Rectangle view = new Rectangle(Size, new Vector2(0, 0));
_vertexEngine.Reset();
_vertexEngine.ProcessCommands(view, queue);
if (!_window.Context.IsCurrent)
_window.Context.MakeCurrent();
if (!_glDriver.IsInit)
_glDriver.Init();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
_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);
}
}
+29 -56
View File
@@ -3,76 +3,49 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard", "Dashboard\Dashboard.csproj", "{4FE772DD-F424-4EAC-BF88-CB8F751B4926}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Media.Defaults", "Dashboard.Media.Defaults\Dashboard.Media.Defaults.csproj", "{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}"
EndProject
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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{2013470A-915C-46F2-BDD3-FCAA39C845EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{40F3B724-88A1-4D4F-93AB-FE0DC07A347E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{9B62A92D-ABF5-4704-B831-FD075515A82F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Demo", "tests\Dashboard.Demo\Dashboard.Demo.csproj", "{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}"
EndProject
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}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText", "Dashboard.BlurgText\Dashboard.BlurgText.csproj", "{D05A9DEA-A5D1-43DC-AB41-36B07598B749}"
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
{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}
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC} = {40F3B724-88A1-4D4F-93AB-FE0DC07A347E}
EndGlobalSection
EndGlobal
-69
View File
@@ -1,69 +0,0 @@
using System;
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Layout;
using Dashboard.Pal;
namespace Dashboard.Controls
{
public class Button : Control
{
private Vector2 _intrinsicSize = Vector2.Zero;
public bool AutoSize { get; set; } = true;
public string Text { get; set; } = "Click!";
public Font Font { get; set; } = Font.Create(new FontInfo("Rec Mono Linear"));
public float TextSize { get; set; } = 12f;
public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black);
public Brush ButtonBrush { get; set; } = new SolidColorBrush(Color.DarkSlateGray);
public Vector2 Padding { get; set; } = new Vector2(4, 4);
public event EventHandler? Clicked;
public override Vector2 CalculateIntrinsicSize()
{
return _intrinsicSize + 2 * Padding;
}
protected void CalculateSize(DeviceContext dc)
{
Box2d box = dc.ExtensionRequire<ITextRenderer>().MeasureText(Font.Base, TextSize, Text);
_intrinsicSize = box.Size;
// Layout.Size = box.Size;
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size);
}
public override void OnPaint(DeviceContext dc)
{
base.OnPaint(dc);
if (AutoSize)
CalculateSize(dc);
bool hidden = Layout.OverflowMode == OverflowMode.Hidden;
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
if (hidden)
dcb.PushScissor(ClientArea);
dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0));
var imm = dc.ExtensionRequire<IImmediateMode>();
Color color = (ButtonBrush as SolidColorBrush)?.Color ?? Color.Black;
Vector4 colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
imm.Rectangle(ClientArea, 0, colorVector);
var text = dc.ExtensionRequire<ITextRenderer>();
color = (TextBrush as SolidColorBrush)?.Color ?? Color.Black;
colorVector = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
text.DrawText(Vector2.Zero, colorVector, TextSize, Font.Base, Text);
if (hidden)
dcb.PopScissor();
dcb.PopTransforms();
}
}
}
+56
View File
@@ -0,0 +1,56 @@
using System;
namespace Dashboard.Controls
{
public enum Dock
{
None,
Top,
Left,
Bottom,
Right,
Center
}
[Flags]
public enum Anchor
{
None = 0,
Top = 1 << 0,
Left = 1 << 1,
Bottom = 1 << 2,
Right = 1 << 3,
All = Top | Left | Bottom | Right
}
public enum Direction
{
Vertical,
Horizontal
}
public enum TextAlignment
{
Left,
Center,
Right,
Justify,
}
public enum VerticalAlignment
{
Top,
Center,
Bottom,
Justify,
}
public enum HorizontalAlignment
{
Left,
Center,
Right,
Justify
}
}
-145
View File
@@ -1,145 +0,0 @@
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;
}
}
+86
View File
@@ -0,0 +1,86 @@
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 event EventHandler<ChildEventArgs>? ChildAdded;
public event EventHandler<ChildEventArgs>? ChildRemoved;
public void Add(Control item)
{
children.Add(item);
OnChildAdded(new ChildEventArgs(item));
}
public void Clear()
{
foreach (Control child in this)
{
OnChildRemoved(new ChildEventArgs(child));
}
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)
{
if (children.Remove(item))
{
OnChildRemoved(new ChildEventArgs(item));
return true;
}
return false;
}
public virtual void OnChildAdded(ChildEventArgs ea)
{
Adopt(ea.Child, this);
ChildAdded?.Invoke(this, ea);
}
public virtual void OnChildRemoved(ChildEventArgs ea)
{
Adopt(ea.Child, null);
ChildRemoved?.Invoke(this, ea);
}
IEnumerator IEnumerable.GetEnumerator()
{
return children.GetEnumerator();
}
}
public class ChildEventArgs : EventArgs
{
public Control Child { get; }
public ChildEventArgs(Control child)
{
Child = child;
}
}
}
+112 -151
View File
@@ -1,181 +1,142 @@
using System;
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;
using System.Collections.Generic;
using Dashboard.ImmediateDraw;
using OpenTK.Mathematics;
namespace Dashboard.Controls
{
public class Control : IEventListener, ILayoutItem, IDisposable
public abstract class Control : UIBase
{
private Form? _owner = null;
private readonly DrawList drawCommands = new DrawList();
public string? Id { get; set; }
public Form Owner
public override Vector2 Position
{
get => _owner ?? throw NoOwnerException;
protected set
get => base.Position;
set
{
_owner = value;
OnOwnerChanged(value);
base.Position = value;
InvalidateLayout();
}
}
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;
public Brush Background { get; set; } = new SolidColorBrush(Color.Transparent);
public Brush BorderBrush { get; set; } = new SolidColorBrush(Color.Black);
public LayoutInfo Layout { get; } = new LayoutInfo();
public bool IsLayoutEnabled { get; private set; } = true;
protected bool IsLayoutValid { get; set; } = false;
public event EventHandler<DeviceContext>? Painting;
public event EventHandler<TickEventArgs>? AnimationTick;
public event EventHandler? OwnerChanged;
public event EventHandler? ParentChanged;
public event EventHandler? FocusGained;
public event EventHandler? FocusLost;
public event EventHandler? Disposing;
public event EventHandler? Resized;
public virtual Vector2 CalculateIntrinsicSize()
public Style Style { get; set; } = new Style();
public float Padding
{
return Vector2.Zero;
// return Vector2.Max(Vector2.Zero, Vector2.Max(Layout.Size, Layout.MinimumSize));
get => (float)(Style["padding"] ?? 0.0f);
set => Style["padding"] = value;
}
public Vector2 CalculateSize(Vector2 limits)
public bool IsVisualsValid { get; private set; } = false;
public bool IsLayoutValid { get; private set; } = false;
protected bool IsLayoutSuspended { get; private set; } = false;
protected ResizedEventArgs? LastResizeEvent { get; private set; }
public void InvalidateVisual()
{
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);
IsVisualsValid = false;
OnVisualsInvalidated(this, EventArgs.Empty);
}
public void InvalidateLayout()
{
IsLayoutValid = false;
}
protected virtual void ValidateLayout()
{
IsLayoutValid = true;
}
public void ResumeLayout()
{
IsLayoutEnabled = true;
OnLayoutInvalidated(this, EventArgs.Empty);
}
public void SuspendLayout()
{
IsLayoutEnabled = false;
IsLayoutValid = false;
IsLayoutSuspended = true;
}
protected static Exception NoOwnerException => new Exception("No form owns this control");
public event PropertyChangedEventHandler? PropertyChanged;
public void ResumeLayout()
{
IsLayoutSuspended = false;
InvalidateLayout();
}
protected abstract void ValidateVisual(DrawList cmd);
protected abstract void ValidateLayout();
protected override void PaintBegin(DrawList cmd)
{
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);
if (this is IEnumerable<Control> children)
{
foreach (Control child in children)
{
child.Paint(cmd);
}
}
cmd.PopViewport();
cmd.PopStyle();
}
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);
}
public override void OnResized(object sender, ResizedEventArgs ea)
{
base.OnResized(sender, ea);
LastResizeEvent = ea;
InvalidateLayout();
}
}
}
+100
View File
@@ -0,0 +1,100 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dashboard.ImmediateDraw;
using OpenTK.Mathematics;
namespace Dashboard.Controls
{
public class FlowBox : ContainerControl
{
public Direction FlowDirection { get; set; }
public bool AllowWrap { get; set; }
public VerticalAlignment VerticalAlignment { get; set; }
public HorizontalAlignment HorizontalAlignment { get; set; }
public float ItemPadding { get; set; } = 4f;
protected override void ValidateLayout()
{
}
protected override void ValidateVisual(DrawList cmd)
{
throw new NotImplementedException();
}
private void FlowHorizontal()
{
IEnumerator<Control> controls = this.GetEnumerator();
List<Control> row;
}
// Enumerate a row.
private bool EnumerateRows(IEnumerator<Control> iterator, List<Control> row)
{
float width = 0;
do
{
if (width + iterator.Current.Size.X < Size.X)
{
row.Add(iterator.Current);
width += iterator.Current.Size.X + ItemPadding;
}
else
{
return true;
}
} while (iterator.MoveNext());
return false;
}
// Flows a row of children.
private void FlowRow(List<Control> line, Vector2 offset, Vector2 size, float packedWidth)
{
Vector2 pointer = offset;
pointer.X += hstart();
foreach (Control child in line)
{
child.Position = pointer;
pointer += new Vector2(child.Size.X + hoffset(child), voffset(child));
}
float hstart()
{
return HorizontalAlignment switch {
HorizontalAlignment.Center => (size.Y - packedWidth) / 2,
HorizontalAlignment.Right => size.Y - packedWidth,
_ => 0f
};
}
float hoffset(Control child)
{
if (line.Count == 1)
return 0;
else if (HorizontalAlignment == HorizontalAlignment.Justify)
{
return ItemPadding + ((size.Y - packedWidth) / (line.Count - 1));
}
else
{
return ItemPadding;
}
}
float voffset(Control child)
{
return VerticalAlignment switch {
VerticalAlignment.Top => 0f,
VerticalAlignment.Bottom => size.Y - child.Size.Y,
_ => (size.Y - child.Size.Y) / 2,
};
}
}
}
}
-99
View File
@@ -1,99 +0,0 @@
using System;
using System.Drawing;
using Dashboard.Drawing;
using Dashboard.Events;
using Dashboard.Pal;
using Dashboard.Windowing;
namespace Dashboard.Controls
{
public class Form : Container, IForm
{
private string? _title = "Untitled Form";
public IWindow Window { get; }
public Image? WindowIcon { get; set; }
public string? Title
{
get => _title;
set
{
_title = value;
Window.Title = _title ?? "";
}
}
public Brush Background { get; set; } = new SolidColorBrush(Color.SlateGray);
public Control? FocusedControl { get; private set; } = null;
public override Box2d ClientArea
{
get => new Box2d(0, 0, Window.ClientSize.Width, Window.ClientSize.Height);
set { }
}
public event EventHandler<WindowCloseEvent>? Closing;
public Form(IWindow window)
{
Window = window;
window.Form = this;
Window.Title = _title;
}
public void Focus(Control control)
{
if (FocusedControl != null)
InvokeFocusLost(this, FocusedControl);
FocusedControl = control;
InvokeFocusGained(this, control);
}
public override void OnPaint(DeviceContext dc)
{
dc.Begin();
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
dcb.ResetClip();
dcb.ResetScissor();
dcb.ResetTransforms();
dcb.ClearDepth();
if (Background is SolidColorBrush solidColorBrush)
dcb.ClearColor(solidColorBrush.Color);
foreach (Control child in this)
child.SendEvent(this, new PaintEventArgs(dc));
base.OnPaint(dc);
dc.End();
}
protected virtual void OnClosing(WindowCloseEvent ea)
{
Closing?.Invoke(this, ea);
if (ea.Cancel)
return;
Dispose();
Window.Dispose();
}
protected override void OnEventRaised(object? sender, EventArgs args)
{
base.OnEventRaised(sender, args);
switch (args)
{
case WindowCloseEvent close:
OnClosing(close);
break;
}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
using Dashboard.ImmediateDraw;
using Dashboard.Layout;
using OpenTK.Mathematics;
namespace Dashboard.Controls
{
public class GridBox : ContainerControl
{
protected override void ValidateLayout()
{
if (LastResizeEvent != null)
{
foreach (Control child in this)
{
GridLayoutAttribute attribute = GridLayoutAttribute.GetGridLayout(child);
attribute.Evaluate(LastResizeEvent, child.Bounds, out Rectangle newBounds);
child.Bounds = newBounds;
child.InvalidateLayout();
}
}
}
protected override void ValidateVisual(DrawList cmd)
{
cmd.Rectangle(new Rectangle(Size, Vector2.Zero));
}
}
}
-27
View File
@@ -1,27 +0,0 @@
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Pal;
namespace Dashboard.Controls
{
public class ImageBox : Control
{
public Image? Image { get; set; }
public override Vector2 CalculateIntrinsicSize()
{
return new Vector2(Image?.Width ?? 0, Image?.Height ?? 0);
}
public override void OnPaint(DeviceContext dc)
{
if (Image == null)
return;
// Layout.Size = CalculateIntrinsicSize();
// dc.ExtensionRequire<IImmediateMode>().Image(new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size), new Box2d(0, 0, 1, 1), 0, Image.InternTexture(dc));
base.OnPaint(dc);
}
}
}
+18 -48
View File
@@ -1,62 +1,32 @@
using System;
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Layout;
using Dashboard.Pal;
using Dashboard.ImmediateDraw;
using Dashboard.Media;
using Dashboard.Typography;
using OpenTK.Mathematics;
namespace Dashboard.Controls
{
public class Label : Control
{
private Vector2 _intrinsicSize = Vector2.Zero;
public string Text { get; set; } = string.Empty;
public Font? Font { get; set; }
public float TextSize { get; set; }
public bool AutoSize { get; set; } = true;
public string Text { get; set; } = "";
public event EventHandler? TextChanged;
// protected IBrush TextBrush => throw new NotImplementedException();
public Font Font { get; set; } = Drawing.Font.Create(new FontInfo("Rec Mono Linear"));
public float TextSize { get; set; } = 12f;
public Brush TextBrush { get; set; } = new SolidColorBrush(Color.Black);
public override Vector2 CalculateIntrinsicSize()
protected override void ValidateLayout()
{
return _intrinsicSize;
}
protected void CalculateSize(DeviceContext dc)
{
Box2d box = dc.ExtensionRequire<ITextRenderer>().MeasureText(Font.Base, TextSize, Text);
// _intrinsicSize = box.Size;
// Layout.Size = box.Size;
// ClientArea = new Box2d(ClientArea.Min, ClientArea.Min + Layout.Size);
}
public override void OnPaint(DeviceContext dc)
{
base.OnPaint(dc);
if (AutoSize)
CalculateSize(dc);
{
Vector2 size = Typesetter.MeasureHorizontal(Text, TextSize, Font!);
Size = size;
}
}
bool hidden = Layout.OverflowMode == OverflowMode.Hidden;
var dcb = dc.ExtensionRequire<IDeviceContextBase>();
if (hidden)
dcb.PushScissor(ClientArea);
protected override void ValidateVisual(DrawList cmd)
{
float padding = Padding;
Vector2 origin = new Vector2(padding, padding);
dcb.PushTransforms(Matrix4x4.CreateTranslation(ClientArea.Left, ClientArea.Top, 0));
var text = dc.ExtensionRequire<ITextRenderer>();
Color color = (TextBrush as SolidColorBrush)?.Color ?? Color.Black;
Vector4 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();
cmd.TypesetHorizontalDirect(Text, origin, TextSize, Font!);
}
}
}
-242
View File
@@ -1,242 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using Dashboard.Drawing;
using Dashboard.Windowing;
namespace Dashboard.Controls
{
public enum MessageBoxIcon
{
Info,
Question,
Warning,
Error,
Custom,
}
public enum MessageBoxButtons
{
AbortRetryIgnore,
CancelRetryContinue,
Ok,
OkCancel,
RetryCancel,
YesNo,
YesNoCancel,
Custom,
}
/// <summary>
/// A simple message box dialog.
/// </summary>
public class MessageBox : Form
{
private MessageBoxIcon _icon;
private MessageBoxButtons _buttons;
private ImageBox _iconBox = new ImageBox();
private Label _label = new Label();
private Container _main = new Container();
private Container _buttonsContainer = new Container();
private Image? IconImage
{
get => _iconBox.Image;
set => _iconBox.Image = value;
}
public MessageBoxIcon Icon
{
get => _icon;
set
{
IconImage = value switch
{
MessageBoxIcon.Question => s_questionIcon,
MessageBoxIcon.Info => s_infoIcon,
MessageBoxIcon.Warning => s_warningIcon,
MessageBoxIcon.Error => s_errorIcon,
_ => null,
};
_icon = value;
}
}
public Image? CustomImage
{
get => Icon == MessageBoxIcon.Custom ? IconImage : null;
set
{
if (IconImage == null)
return;
Icon = MessageBoxIcon.Custom;
IconImage = value;
}
}
public string? Message
{
get => _label.Text;
set => _label.Text = value ?? String.Empty;
}
public MessageBoxButtons Buttons
{
get => _buttons;
set
{
_buttons = value;
UpdateButtons();
}
}
public ObservableCollection<string> CustomButtons { get; } = new ObservableCollection<string>();
public int Result { get; private set; } = -1;
public MessageBox(IWindow window) : base(window)
{
// Layout.Rows.Clear();
// Layout.Rows.Add(-1);
// Layout.Rows.Add(48);
//
// Add(_main);
// _main.Layout.Columns.Clear();
// _main.Layout.Columns.Add(48);
// _main.Layout.Columns.Add(-1);
_main.Add(_iconBox);
_main.Add(_label);
_label.Layout.Column = 1;
Add(_buttonsContainer);
_buttonsContainer.Layout.Row = 1;
CustomButtons.CollectionChanged += (sender, ea) => UpdateButtons();
UpdateButtons();
}
private void UpdateButtons()
{
foreach (Control button in _buttonsContainer)
{
Remove(button);
}
IList<string> list = Buttons switch
{
MessageBoxButtons.Custom => CustomButtons,
MessageBoxButtons.AbortRetryIgnore => s_abortRetryContinue,
MessageBoxButtons.CancelRetryContinue => s_cancelRetryContinue,
MessageBoxButtons.OkCancel => s_okCancel,
MessageBoxButtons.RetryCancel => s_retryCancel,
MessageBoxButtons.YesNo => s_yesNo,
MessageBoxButtons.YesNoCancel => s_yesNoCancel,
_ => s_ok,
};
// _buttonsContainer.Clear();
// _buttonsContainer.Layout.Columns.Clear();
// for (int i = 0; i < list.Count; i++)
// {
// _buttonsContainer.Layout.Columns.Add(-1);
// string str = list[i];
//
// Button button = new Button() { Text = str };
// button.Clicked += (sender, ea) => ButtonClicked(sender, ea, i);
// button.Layout.Column = i;
// _buttonsContainer.Add(button);
// Add(button);
// }
}
private void ButtonClicked(object? sender, EventArgs ea, int i)
{
Result = i;
Dispose();
}
public static readonly Image s_questionIcon;
public static readonly Image s_infoIcon;
public static readonly Image s_warningIcon;
public static readonly Image s_errorIcon;
private static readonly ImmutableList<string> s_abortRetryContinue = ["Abort", "Retry", "Continue"];
private static readonly ImmutableList<string> s_cancelRetryContinue = ["Cancel", "Retry", "Continue"];
private static readonly ImmutableList<string> s_ok = ["OK"];
private static readonly ImmutableList<string> s_okCancel = ["OK", "Cancel"];
private static readonly ImmutableList<string> s_retryCancel = ["Retry", "Cancel"];
private static readonly ImmutableList<string> s_yesNo = ["Yes", "No"];
private static readonly ImmutableList<string> s_yesNoCancel = ["Yes", "No", "Cancel"];
static MessageBox()
{
Assembly asm = typeof(MessageBox).Assembly;
using (Stream str = asm.GetManifestResourceStream("Dashboard.Resources.question.png")!)
s_questionIcon = Image.Load(str);
using (Stream str = asm.GetManifestResourceStream("Dashboard.Resources.info.png")!)
s_infoIcon = Image.Load(str);
using (Stream str = asm.GetManifestResourceStream("Dashboard.Resources.warning.png")!)
s_warningIcon = Image.Load(str);
using (Stream str = asm.GetManifestResourceStream("Dashboard.Resources.error.png")!)
s_errorIcon = Image.Load(str);
}
public static MessageBox Create(IWindow window, string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons)
{
return new MessageBox(window)
{
Message = message,
Title = title,
Icon = icon,
Buttons = buttons,
};
}
// public static MessageBox Create(IWindow window, string message, string title, Image icon, IEnumerable<string> buttons)
// {
// throw new NotImplementedException();
// }
private static string GetDefaultTitle(MessageBoxIcon icon)
{
return icon switch
{
MessageBoxIcon.Error => "Error",
MessageBoxIcon.Info => "Info",
MessageBoxIcon.Question => "Question",
MessageBoxIcon.Warning => "Warning",
_ => "Message Box",
};
}
private static Image GetDefaultIcon(MessageBoxIcon icon)
{
return icon switch
{
MessageBoxIcon.Error => s_errorIcon,
MessageBoxIcon.Question => s_questionIcon,
MessageBoxIcon.Warning => s_warningIcon,
_ => s_infoIcon,
};
}
private static ImmutableList<string> GetDefaultButtons(MessageBoxButtons buttons)
{
return buttons switch
{
MessageBoxButtons.AbortRetryIgnore => s_abortRetryContinue,
MessageBoxButtons.CancelRetryContinue => s_cancelRetryContinue,
MessageBoxButtons.OkCancel => s_okCancel,
MessageBoxButtons.RetryCancel => s_retryCancel,
MessageBoxButtons.YesNo => s_yesNo,
MessageBoxButtons.YesNoCancel => s_yesNoCancel,
_ => s_ok,
};
}
}
}

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