78 Commits

Author SHA1 Message Date
c6a9dd8008 Change the way container controls work and add Grid layout. 2024-08-04 13:21:00 +03:00
ff3b3ee961 Rename UserData to Attribute. 2024-08-02 21:20:35 +03:00
9b6a15c6ec Add userdata fields to all control hierarchy. 2024-07-30 21:54:18 +03:00
50552914c1 Rename various types. 2024-07-28 14:19:13 +03:00
d959c42e99 Rename various objects. 2024-07-28 14:11:23 +03:00
9b2f0859e5 Replace own matrix type with OpenTK. 2024-07-28 14:07:42 +03:00
831c93b916 Replace QColor and QColorF with OpenTK.Mathematics.Color4. 2024-07-28 13:59:35 +03:00
42782b8a71 Replace QVec2 with OpenTK.Mathematics.Vector2. 2024-07-28 13:50:33 +03:00
1301868269 Remove old OpenGL bindings and just pull in OpenTK. 2024-07-28 13:34:53 +03:00
21591c3d11 Go over vertex generator. 2024-07-28 12:37:33 +03:00
3856b3c66e Refactoring some names I don't like. 2024-07-28 12:34:22 +03:00
92fb8f06a7 Rename QuikCommandHandler. 2024-07-28 12:24:24 +03:00
468648de95 Rename a few files and classes. 2024-07-28 12:21:18 +03:00
3f1875252e Merge branch 'master' into blurg-test 2024-07-28 11:36:20 +03:00
65609bf685 Fix buffer size in QImageBuffer.cs 2024-07-28 11:33:12 +03:00
a95ddb46ad Add some blurg test code. 2024-07-28 11:16:07 +03:00
dadd9f137a Add utility for custom commands into command lists. 2024-07-26 23:48:48 +03:00
23fc485b2a Create Dashboard.BlurgText 2024-07-26 22:20:44 +03:00
c6c76aa8be Update dependencies. 2024-07-18 21:54:44 +03:00
6670667ff6 Fix some broken dependencies. 2024-07-18 20:34:39 +03:00
d06264e424 Rename some classes to fit the new names. 2024-07-18 20:28:02 +03:00
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
470475092a Rename namespace to Dashboard. 2024-07-17 23:18:20 +03:00
1ee492ccd4 Push WIP changes before rename. 2024-07-17 23:12:07 +03:00
3b52649ad2 Change over to the new nuget package and change namespaces. 2024-06-24 22:28:41 +03:00
f8a4c73367 Fix load order in FallbackFontDatabase. 2024-06-21 11:59:04 +03:00
88e060a92b Update Stbi to new rebranded package. 2024-06-21 09:52:29 +03:00
1ccab1c85a Converted project to ^nullable enable. 2024-06-09 22:54:33 +03:00
55e9d775cd Some changes to the UI elements. 2024-06-09 22:06:13 +03:00
8fd00257d3 Fix GL21 driver bug. 2024-06-09 19:55:13 +03:00
82d2027cf3 Add measure string function to Typesetter. 2024-06-09 19:20:35 +03:00
9b16e015f6 Fix depth test issue in demo. 2024-06-06 22:33:55 +03:00
ff4a158cdb Update stbi. 2024-06-01 14:33:20 +03:00
29829fd299 Made font rendering easier. 2024-05-27 21:49:25 +03:00
19cc765955 Implement Label control. 2024-05-16 22:58:22 +03:00
ce2a569a20 Add texture swizzle parameters. 2024-05-16 22:57:55 +03:00
ab1849a891 Add missing commands. 2024-05-16 22:57:38 +03:00
3ae107c83e Fix translation matrix to be column major and implement multiplication. 2024-05-16 22:57:14 +03:00
bd69c0d93f Preliminary typesetting work. 2024-05-15 23:17:01 +03:00
279e619c3b Remove extra accidental copy of one file. 2024-05-15 23:16:37 +03:00
7cb47c721b Fix 90 degree rotation error in Image2D 2024-05-05 16:45:43 +03:00
9105b16df8 Add singleton support for QuikApplication. 2024-05-05 16:45:25 +03:00
3b73090f79 Fix error in the mouse button enum. 2024-05-05 16:45:12 +03:00
3484dce8c5 Create a font atlaser. 2024-05-01 16:53:30 +03:00
3418537b43 Add image copy functions to QImage. 2024-05-01 16:52:20 +03:00
d831c9b72d Added better font search stuff. 2024-05-01 15:34:21 +03:00
66644be699 Remove popz command in UIBase.cs 2024-05-01 14:15:47 +03:00
39dfca18f2 Remove the readme under lib/ 2024-05-01 14:13:16 +03:00
75a11adbe7 Remove old submodules from the repository. 2024-05-01 14:12:12 +03:00
21233c8173 Add fontconfig support. 2024-04-28 15:39:12 +03:00
ccb0c6ffe7 Add new font search criterea. 2024-04-28 13:45:46 +03:00
bc3dcff3ea Remove the old Quik texture API. 2024-04-28 13:44:40 +03:00
2aa1066a9d Fix one of the image command lists. 2024-04-14 23:21:31 +03:00
fe3c2d0550 Image support for vertex generator. 2024-04-14 23:21:08 +03:00
5aa9a2f4e6 Support for 3d images in the renderer. 2024-04-14 22:51:10 +03:00
4d5e0dd8f2 Add unit vectors to QVec2. 2024-04-14 22:35:54 +03:00
f07208ebe9 Replace textures with QImage. 2024-04-14 22:35:30 +03:00
b57d7bed41 Texture 3d entry points and enums. 2024-04-14 22:34:51 +03:00
a1573d3786 Merge branch 'master' of https://git.mixedup.dev/QUIK/Quik 2024-04-11 19:35:30 +03:00
20c126fb88 New Image3D command. 2024-04-11 19:33:42 +03:00
7ce474d92a Changes to platform abstraction layer. 2024-04-11 19:09:00 +03:00
2eb5663ee9 Renamed the classes in the command engine. 2024-04-11 19:06:58 +03:00
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
4dff6eba91 Math library changes and fixes. 2024-04-08 23:51:03 +03:00
9c9efc6eeb Convert command queue to a command list. 2024-04-08 23:50:05 +03:00
51e9018a22 These dependencies have been moved into their own repositories. 2024-04-06 23:36:32 +03:00
032a38e13b Small fixes on OpenTK support. 2024-03-04 21:41:16 +03:00
cea243a3b8 Finish backporting Freetype from in development quik. 2023-11-10 20:57:33 +03:00
6240f5921b Add new assets for the quik logo. 2023-10-17 22:26:14 +03:00
959788563f Fix small errors in driver. 2023-09-22 21:23:59 +03:00
118b50cee2 Add font rendering. 2023-09-22 19:30:17 +03:00
1f6a3a55e1 Ressurrect old freetype library. 2023-09-16 09:59:51 +03:00
ac0a70cefc Remove auto-generated freetype bindings. 2023-09-16 09:50:14 +03:00
845ed1c27a Rename Quik.Media.Stb to *.Defaults 2023-09-16 09:24:57 +03:00
4a35f18737 Add partial FreeType bindings. 2023-09-15 20:40:30 +03:00
166 changed files with 6207 additions and 5857 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@ obj/
riderModule.iml riderModule.iml
/_ReSharper.Caches/ /_ReSharper.Caches/
.idea .idea
.vs
.vscode .vscode
nuget_repo nuget_repo
**/out **/out

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "lib/stb"]
path = lib/stb
url = https://github.com/nothings/stb.git
[submodule "lib/freetype"]
path = lib/freetype
url = https://gitlab.freedesktop.org/freetype/freetype.git

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);
}
}
}
}

View File

@@ -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>

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);
}
}

View File

@@ -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>

View File

@@ -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";
}
}

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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}
}

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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -1,25 +1,25 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using Dashboard.Media.Color;
using Quik.Media; using ReFuel.Stb;
using Quik.Media.Color;
using Quik.Stb;
namespace Quik.Media.Stb namespace Dashboard.Media.Defaults
{ {
public unsafe class QImageStbi : QImage public unsafe class ImageStbi : Image
{ {
private readonly StbImage image; private readonly StbImage image;
private ImageBuffer buffer; private QImageBuffer buffer;
private bool isSdf = false;
public override int Width => image.Width; public override int Width => image.Width;
public override int Height => image.Height; public override int Height => image.Height;
public override int Depth => 1; public override int Depth => 1;
public override QImageFormat InternalFormat => Stb2QImageFormat(image.Format); public override bool IsSdf => isSdf;
public override ImageFormat InternalFormat => Stb2QImageFormat(image.Format);
public QImageStbi(Stream source) public ImageStbi(Stream source)
{ {
// According to the stbi documentation, only a specific type of PNG // According to the stbi documentation, only a specific type of PNG
// files are premultiplied out of the box (iPhone PNG). Take the // files are premultiplied out of the box (iPhone PNG). Take the
@@ -30,15 +30,15 @@ namespace Quik.Media.Stb
image = StbImage.Load(source); image = StbImage.Load(source);
} }
public static QImageFormat Stb2QImageFormat(StbiImageFormat src) public static ImageFormat Stb2QImageFormat(StbiImageFormat src)
{ {
switch (src) switch (src)
{ {
case StbiImageFormat.Grey: return QImageFormat.RedU8; case StbiImageFormat.Grey: return ImageFormat.RedU8;
case StbiImageFormat.Rgb: return QImageFormat.RgbU8; case StbiImageFormat.Rgb: return ImageFormat.RgbU8;
case StbiImageFormat.Rgba: return QImageFormat.RgbaU8; case StbiImageFormat.Rgba: return ImageFormat.RgbaU8;
case StbiImageFormat.GreyAlpha: return QImageFormat.RaU8; case StbiImageFormat.GreyAlpha: return ImageFormat.RaU8;
default: return QImageFormat.Undefined; default: return ImageFormat.Undefined;
} }
} }
@@ -47,8 +47,8 @@ namespace Quik.Media.Stb
if (options.MipLevel > 0) throw new Exception("This image has no mip levels."); if (options.MipLevel > 0) throw new Exception("This image has no mip levels.");
buffer?.Dispose(); buffer?.Dispose();
buffer = new ImageBuffer(options.Format, Width, Height); buffer = new QImageBuffer(options.Format, Width, Height);
QImageLock dst = buffer.Lock(); buffer.LockBits2d(out QImageLock dst, QImageLockOptions.Default);
byte *srcPtr = (byte*)image.ImagePointer; byte *srcPtr = (byte*)image.ImagePointer;
QImageLock src = new QImageLock(InternalFormat, Width, Height, 1, (IntPtr)srcPtr); QImageLock src = new QImageLock(InternalFormat, Width, Height, 1, (IntPtr)srcPtr);
@@ -76,7 +76,12 @@ namespace Quik.Media.Stb
public override void UnlockBits() public override void UnlockBits()
{ {
buffer.Unlock(); buffer.UnlockBits();
}
public void SdfHint(bool value = true)
{
isSdf = value;
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)

View File

@@ -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;
}
}

View File

@@ -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];
}
}
}

View File

@@ -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 = "";
}
}

View File

@@ -4,14 +4,14 @@ using System.Collections;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using Quik.Media; using Dashboard.Media.Fonts;
// WebRequest is obsolete but runs on .NET framework. // WebRequest is obsolete but runs on .NET framework.
#pragma warning disable SYSLIB0014 #pragma warning disable SYSLIB0014
namespace Quik.Media.Stb namespace Dashboard.Media.Defaults
{ {
public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontInfo> public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontFace>
{ {
public bool AllowRemoteTransfers { get; set; } = false; public bool AllowRemoteTransfers { get; set; } = false;
private readonly ArrayPool<byte> ByteArrays = ArrayPool<byte>.Create(); private readonly ArrayPool<byte> ByteArrays = ArrayPool<byte>.Create();
@@ -31,9 +31,9 @@ namespace Quik.Media.Stb
{ {
return GetMedia((FileInfo)key, hint); return GetMedia((FileInfo)key, hint);
} }
else if (t == typeof(FontInfo)) else if (t == typeof(FontFace))
{ {
return GetMedia((FontInfo)key, hint); return GetMedia((FontFace)key, hint);
} }
else else
{ {
@@ -56,7 +56,7 @@ namespace Quik.Media.Stb
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IDisposable GetMedia(FontInfo key, MediaHint hint) public IDisposable GetMedia(FontFace key, MediaHint hint)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@@ -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

View File

@@ -1,18 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable> <Nullable>enable</Nullable>
<LangVersion>7.3</LangVersion> </PropertyGroup>
</PropertyGroup>
<ItemGroup>
<ItemGroup> <PackageReference Include="OpenTK" Version="4.8.0" />
<PackageReference Include="OpenTK" Version="4.7.4" /> </ItemGroup>
</ItemGroup>
<ItemGroup>
<ItemGroup> <ProjectReference Include="..\Dashboard\Dashboard.csproj" />
<ProjectReference Include="..\Quik\Quik.csproj" /> <EmbeddedResource Include="glsl\**"/>
<EmbeddedResource Include="glsl\**"/> </ItemGroup>
</ItemGroup>
</Project>
</Project>

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;
}
}
}

View File

@@ -1,18 +1,19 @@
using System; using System;
using OpenTK.Mathematics; using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop; using OpenTK.Windowing.Desktop;
using Quik.OpenGL; using OpenTK.Graphics.OpenGL4;
using Quik.CommandMachine; using Dashboard.OpenGL;
using Quik.PAL; using Dashboard.ImmediateDraw;
using Quik.VertexGenerator; using Dashboard.PAL;
using Dashboard.VertexGenerator;
namespace Quik.OpenTK namespace Dashboard.OpenTK
{ {
public class OpenTKPort : IQuikPort public class OpenTKPort : IDashHandle
{ {
private readonly NativeWindow _window; private readonly NativeWindow _window;
private readonly GL21Driver _glDriver; private readonly GL21Driver _glDriver;
private readonly VertexGeneratorEngine _vertexEngine; private readonly VertexDrawingEngine _vertexEngine;
public string Title public string Title
{ {
@@ -20,12 +21,12 @@ namespace Quik.OpenTK
set => _window.Title = value; set => _window.Title = value;
} }
public QVec2 Size public Vector2 Size
{ {
get get
{ {
Vector2i size = _window.ClientSize; Vector2i size = _window.ClientSize;
return new QVec2(size.X, size.Y); return new Vector2(size.X, size.Y);
} }
set set
{ {
@@ -35,12 +36,12 @@ namespace Quik.OpenTK
_window.Size = size; _window.Size = size;
} }
} }
public QVec2 Position public Vector2 Position
{ {
get get
{ {
Vector2i location = _window.Location; Vector2i location = _window.Location;
return new QVec2(location.X, location.Y); return new Vector2(location.X, location.Y);
} }
set set
{ {
@@ -51,13 +52,13 @@ namespace Quik.OpenTK
public bool IsValid => !isDisposed; public bool IsValid => !isDisposed;
public event EventHandler EventRaised; public event EventHandler? EventRaised;
public OpenTKPort(NativeWindow window) public OpenTKPort(NativeWindow window)
{ {
_window = window; _window = window;
_glDriver = new GL21Driver(); _glDriver = new GL21Driver();
_vertexEngine = new VertexGeneratorEngine(); _vertexEngine = new VertexDrawingEngine();
} }
public void Focus() public void Focus()
@@ -65,18 +66,20 @@ namespace Quik.OpenTK
_window.Focus(); _window.Focus();
} }
public void Paint(CommandQueue queue) public void Paint(DrawList queue)
{ {
QRectangle view = new QRectangle(Size, new QVec2(0, 0)); Rectangle view = new Rectangle(Size, new Vector2(0, 0));
_vertexEngine.Reset(); _vertexEngine.Reset();
_vertexEngine.ProcessCommands(new QRectangle(), queue); _vertexEngine.ProcessCommands(view, queue);
_window.Context.MakeCurrent(); if (!_window.Context.IsCurrent)
_window.Context.MakeCurrent();
if (!_glDriver.IsInit) if (!_glDriver.IsInit)
_glDriver.Init(); _glDriver.Init();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
_glDriver.Draw(_vertexEngine.DrawQueue, view); _glDriver.Draw(_vertexEngine.DrawQueue, view);
_window.Context.SwapBuffers(); _window.Context.SwapBuffers();

51
Dashboard.sln Normal file
View File

@@ -0,0 +1,51 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard", "Dashboard\Dashboard.csproj", "{4FE772DD-F424-4EAC-BF88-CB8F751B4926}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Media.Defaults", "Dashboard.Media.Defaults\Dashboard.Media.Defaults.csproj", "{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{2013470A-915C-46F2-BDD3-FCAA39C845EE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{40F3B724-88A1-4D4F-93AB-FE0DC07A347E}"
EndProject
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.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(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Release|Any CPU.Build.0 = Release|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Release|Any CPU.Build.0 = Release|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Release|Any CPU.Build.0 = Release|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Release|Any CPU.Build.0 = Release|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC} = {40F3B724-88A1-4D4F-93AB-FE0DC07A347E}
EndGlobalSection
EndGlobal

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
}
}

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;
}
}
}

View File

@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using Dashboard.ImmediateDraw;
using OpenTK.Mathematics;
namespace Dashboard.Controls
{
public abstract class Control : UIBase
{
private readonly DrawList drawCommands = new DrawList();
public string? Id { get; set; }
public override Vector2 Position
{
get => base.Position;
set
{
base.Position = value;
InvalidateLayout();
}
}
public Style Style { get; set; } = new Style();
public float Padding
{
get => (float)(Style["padding"] ?? 0.0f);
set => Style["padding"] = value;
}
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()
{
IsVisualsValid = false;
OnVisualsInvalidated(this, EventArgs.Empty);
}
public void InvalidateLayout()
{
IsLayoutValid = false;
OnLayoutInvalidated(this, EventArgs.Empty);
}
public void SuspendLayout()
{
IsLayoutSuspended = true;
}
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();
}
}
}

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,
};
}
}
}
}

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));
}
}
}

View File

@@ -0,0 +1,32 @@
using Dashboard.ImmediateDraw;
using Dashboard.Media;
using Dashboard.Typography;
using OpenTK.Mathematics;
namespace Dashboard.Controls
{
public class Label : Control
{
public string Text { get; set; } = string.Empty;
public Font? Font { get; set; }
public float TextSize { get; set; }
public bool AutoSize { get; set; } = true;
protected override void ValidateLayout()
{
if (AutoSize)
{
Vector2 size = Typesetter.MeasureHorizontal(Text, TextSize, Font!);
Size = size;
}
}
protected override void ValidateVisual(DrawList cmd)
{
float padding = Padding;
Vector2 origin = new Vector2(padding, padding);
cmd.TypesetHorizontalDirect(Text, origin, TextSize, Font!);
}
}
}

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using Dashboard.ImmediateDraw;
using OpenTK.Mathematics;
namespace Dashboard.Controls
{
/// <summary>
/// Bases for all UI elements.
/// </summary>
public abstract class UIBase : IDbAttribute
{
private Vector2 size;
public UIBase? Parent { get; protected set; }
public Rectangle Bounds
{
get => new Rectangle(Position + Size, Position);
set
{
Size = value.Size;
Position = value.Min;
}
}
public virtual Vector2 Position { get; set; }
public Vector2 Size
{
get => size;
set
{
Vector2 oldSize = size;
size = value;
OnResized(this, new ResizedEventArgs(size, oldSize));
}
}
public Rectangle AbsoluteBounds
{
get
{
if (Parent == null)
{
return Bounds;
}
else
{
return new Rectangle(Bounds.Max + Parent.Position, Bounds.Min + Parent.Position);
}
}
}
public Vector2 MaximumSize { get; set; } = new Vector2(-1, -1);
public Vector2 MinimumSize { get; set; } = new Vector2(-1, -1);
public bool IsMaximumSizeSet => MaximumSize != new Vector2(-1, -1);
public bool IsMinimumSizeSet => MinimumSize != new Vector2(-1, -1);
public Dictionary<string, object> Attributes { get; } = new Dictionary<string, object>();
public virtual void NotifyEvent(object? sender, EventArgs args)
{
}
protected virtual void PaintBegin(DrawList cmd)
{
cmd.PushViewport();
cmd.StoreViewport(AbsoluteBounds);
cmd.PushZ();
}
protected virtual void PaintEnd(DrawList cmd)
{
cmd.PopViewport();
}
public void Paint(DrawList cmd)
{
PaintBegin(cmd);
PaintEnd(cmd);
}
public event EventHandler<ResizedEventArgs>? Resized;
public event EventHandler<AdoptedEventArgs>? Adopted;
public virtual void OnResized(object sender, ResizedEventArgs ea)
{
Resized?.Invoke(sender, ea);
}
protected virtual void OnAdopted(UIBase? parent)
{
Adopted?.Invoke(this, new AdoptedEventArgs(parent));
}
public bool IsDisposed { get; private set; } = false;
protected virtual void Dispose(bool disposing)
{
foreach (object userdata in Attributes.Values)
{
if (userdata is IDisposable disposable)
disposable.Dispose();
}
}
protected void DisposeInvoker(bool disposing)
{
if (IsDisposed)
return;
IsDisposed = true;
Dispose(disposing);
}
public void Dispose() => DisposeInvoker(true);
protected static void Adopt(UIBase child, UIBase? parent)
{
child.Parent = parent;
child.OnAdopted(parent);
}
}
public class ResizedEventArgs : EventArgs
{
public Vector2 NewSize { get; }
public Vector2 OldSize { get; }
public ResizedEventArgs(Vector2 newSize, Vector2 oldSize)
{
NewSize = newSize;
OldSize = oldSize;
}
}
public class AdoptedEventArgs : EventArgs
{
public UIBase? Parent { get; }
public AdoptedEventArgs(UIBase? parent = null)
{
Parent = parent;
}
}
}

View File

@@ -1,6 +1,6 @@
using System; using System;
namespace Quik.Controls namespace Dashboard.Controls
{ {
public class View : UIBase public class View : UIBase
{ {

View File

@@ -2,8 +2,8 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable> <Nullable>enable</Nullable>
<LangVersion>7.3</LangVersion> <LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
@@ -11,4 +11,9 @@
<EmbeddedResource Include="res/**" /> <EmbeddedResource Include="res/**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Graphics" Version="4.8.2" />
<PackageReference Include="OpenTK.Mathematics" Version="4.8.2" />
</ItemGroup>
</Project> </Project>

133
Dashboard/DbApplication.cs Normal file
View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Dashboard.ImmediateDraw;
using Dashboard.Controls;
using Dashboard.Media;
using Dashboard.PAL;
using Dashboard.Typography;
namespace Dashboard
{
/// <summary>
/// Main class for Dashboard applications.
/// </summary>
public class DbApplication
{
/// <summary>
/// The application platform driver.
/// </summary>
public IDbPlatform Platform { get; }
/// <summary>
/// Title of the application.
/// </summary>
public string? Title
{
get => Platform.Title;
set => Platform.Title = value;
}
/// <summary>
/// Application icon.
/// </summary>
public Image? Icon
{
get => Platform.Icon;
set => Platform.Icon = value;
}
public PAL.Dash? MainPort { get; private set; } = null;
public FontProvider FontProvider { get; }
/// <summary>
/// List of media loaders, drivers that load media such as images and fonts.
/// </summary>
public List<MediaLoader> MediaLoaders { get; } = new List<MediaLoader>();
public DbApplication(IDbPlatform platform)
{
Platform = platform;
FontProvider = new FontProvider(this);
Current = this;
}
public IDisposable? GetMedia(object key, MediaHint hint)
{
IDisposable? disposable = null;
foreach (MediaLoader loader in MediaLoaders)
{
disposable = loader.GetMedia(key, hint);
if (disposable != null)
break;
}
return disposable;
}
public IDisposable? GetMedia<T>(T key, MediaHint hint)
{
IDisposable? disposable = null;
foreach (MediaLoader loader in MediaLoaders)
{
if (loader is MediaLoader<T> typedLoader)
{
disposable = typedLoader.GetMedia(key, hint);
if (disposable != null)
break;
}
}
return disposable;
}
private DrawList cmd = new DrawList();
public void Run(View mainView, bool yield = true)
{
Init(mainView);
while (RunSync())
{
if (yield)
{
Thread.Yield();
}
}
}
public void Init(View mainView)
{
MainPort = new PAL.Dash(Platform) { UIElement = mainView };
MainPort.EventRaised += (sender, ea) => mainView.NotifyEvent(sender, ea);
}
public bool RunSync()
{
if (!MainPort!.IsValid)
return false;
Platform.ProcessEvents(false);
if (MainPort.IsValid)
{
cmd.Clear();
MainPort.Paint(cmd);
}
return true;
}
public static DbApplication Current { get; private set; } = null!;
public static void SetCurrentApplication(DbApplication application)
{
Current = application;
}
}
}

268
Dashboard/Geometry.cs Normal file
View File

@@ -0,0 +1,268 @@
using OpenTK.Mathematics;
using System;
using System.Diagnostics;
namespace Dashboard
{
/// <summary>
/// A bezier curve segment.
/// </summary>
[DebuggerDisplay("{Start} -- {ControlA} -- {ControlB} -- {End}")]
public struct Bezier
{
/// <summary>
/// Segment start point.
/// </summary>
public Vector2 Start;
/// <summary>
/// Start point control point.
/// </summary>
public Vector2 ControlA;
/// <summary>
/// End point control point.
/// </summary>
public Vector2 ControlB;
/// <summary>
/// Segment end point.
/// </summary>
public Vector2 End;
/// <summary>
/// An approximation of the arc length of the bezier curve, for calculating rasterization resolution.
/// </summary>
public float RasterizationArc =>
0.5f * (End - Start).Length +
0.5f * ((ControlA - Start).Length + (ControlB - ControlA).Length + (End - ControlB).Length);
public Bezier(Vector2 start, Vector2 controlA, Vector2 controlB, Vector2 end)
{
Start = start;
ControlA = controlA;
ControlB = controlB;
End = end;
}
public Bezier(
float startX,
float startY,
float controlAx,
float controlAy,
float controlBx,
float controlBy,
float endX,
float endY)
: this(
new Vector2(startX, startY),
new Vector2(controlAx, controlAy),
new Vector2(controlBx, controlBy),
new Vector2(endX, endY))
{
}
/// <summary>
/// Get a point in the curve segment.
/// </summary>
/// <param name="t">Control parameter (between 0 and 1)</param>
/// <returns>The point on the curve.</returns>
public Vector2 GetBezierPoint(float t)
{
float T = 1 - t;
return
T * T * T * Start +
3 * T * T * t * ControlA +
3 * T * t * t * ControlB +
t * t * t * End;
}
/// <summary>
/// Get the tangent on the curve.
/// </summary>
/// <param name="t">Control parameter (between 0 and 1)</param>
/// <returns>The tangent curve.</returns>
public Vector2 GetBezierTangent(float t)
{
float T = 1 - t;
return
(
3 * T * T * (ControlA - Start) +
6 * T * t * (ControlB - ControlA) +
3 * t * t * (End - ControlB)
).Normalized();
}
internal Vector2 GetBezierNormal(float t)
{
Vector2 tangent = GetBezierTangent(t);
return new Vector2(-tangent.Y, tangent.X);
}
}
/// <summary>
/// A line segment.
/// </summary>
[DebuggerDisplay("{Start} -- {End}")]
public struct Line
{
/// <summary>
/// Start point.
/// </summary>
public Vector2 Start;
/// <summary>
/// End point.
/// </summary>
public Vector2 End;
public Line(Vector2 start, Vector2 end)
{
Start = start;
End = end;
}
public Line(float startX, float startY, float endX, float endY)
{
Start.X = startX;
Start.Y = startY;
End.X = endX;
End.Y = endY;
}
public Vector2 Normal()
{
Vector2 tangent = Tangent();
return new Vector2(-tangent.Y, tangent.X);
}
public Vector2 Tangent()
{
return (End - Start).Normalized();
}
}
/// <summary>
/// A rectangle.
/// </summary>
[DebuggerDisplay("({Left}, {Top}, {Right}, {Bottom})")]
public struct Rectangle
{
/// <summary>
/// Position maximum point.
/// </summary>
public Vector2 Max;
/// <summary>
/// Position minimum point.
/// </summary>
public Vector2 Min;
public float Left
{
get => Min.X;
set => Min.X = value;
}
public float Right
{
get => Max.X;
set => Max.X = value;
}
public float Top
{
get => Min.Y;
set => Min.Y = value;
}
public float Bottom
{
get => Max.Y;
set => Max.Y = value;
}
public Vector2 Size
{
get => Max - Min;
set => Max = Min + value;
}
public Rectangle(Vector2 max, Vector2 min)
{
Max = max;
Min = min;
}
public Rectangle(float r, float b, float l, float t)
{
Max = new Vector2(r, b);
Min = new Vector2(l, t);
}
public bool Contains(Vector2 point)
{
return
point.X > Left && point.X < Right &&
point.Y > Bottom && point.Y < Top;
}
internal void Translate(in Vector2 offset)
{
Min += offset;
Max += offset;
}
public static Rectangle Intersect(in Rectangle a, in Rectangle b) =>
new Rectangle(
Math.Max(a.Right, b.Right),
Math.Max(a.Bottom, b.Bottom)
,
Math.Min(a.Left, b.Left),
Math.Min(a.Top, b.Top));
}
/// <summary>
/// An ellipse.
/// </summary>
/// <remarks>It is undefined to have an ellipse with non-orthogonal axes.</remarks>
[DebuggerDisplay("{Center} ellipse {AxisA}; {AxisB}")]
public struct Ellipse
{
/// <summary>
/// Ellipse center point.
/// </summary>
public Vector2 Center;
/// <summary>
/// First ellipse axis.
/// </summary>
public Vector2 AxisA;
/// <summary>
/// Second ellipse axis.
/// </summary>
public Vector2 AxisB;
}
/// <summary>
/// A triangle.
/// </summary>
[DebuggerDisplay("{A} -- {B} -- {C}")]
public struct Triangle
{
/// <summary>
/// First vertex.
/// </summary>
public Vector2 A;
/// <summary>
/// Second vertex.
/// </summary>
public Vector2 B;
/// <summary>
/// Third vertex.
/// </summary>
public Vector2 C;
}
}

21
Dashboard/IDbAttribute.cs Normal file
View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace Dashboard
{
/// <summary>
/// Common interface for Dashboard objects that accept attributes.
/// </summary>
/// <remarks>
/// Dashboard will call dispose on any and all objects which implement IDisposable.
/// If this is an issue, please guard your object against this using a wrapper.
/// </remarks
public interface IDbAttribute : IDisposable
{
/// <summary>
/// Attribute dictionary.
/// </summary>
public Dictionary<string, object> Attributes { get; }
}
}

View File

@@ -1,4 +1,4 @@
namespace Quik.CommandMachine namespace Dashboard.ImmediateDraw
{ {
/// <summary> /// <summary>
/// Enumeration of built-in Quik commands. /// Enumeration of built-in Quik commands.

View File

@@ -0,0 +1,8 @@
namespace Dashboard.ImmediateDraw
{
/// <summary>
/// A delegate for a Dashboard engine command.
/// </summary>
/// <param name="stack">The current stack.</param>
public delegate void CommandHandler(DrawingEngine state, DrawQueue queue);
}

View File

@@ -0,0 +1,233 @@
using System;
using System.Collections.Generic;
using OpenTK.Mathematics;
namespace Dashboard.ImmediateDraw
{
public class DrawingEngine
{
private int _zIndex = 0;
private readonly Stack<int> _zStack = new Stack<int>();
public int ZIndex => _zIndex;
private Rectangle _viewport;
private readonly Stack<Rectangle> _viewportStack = new Stack<Rectangle>();
private readonly Stack<Matrix4> _matrixStack = new Stack<Matrix4>();
private Command _customCommandBase = Command.CustomCommandBase;
private readonly List<CommandHandler> _customCommands = new List<CommandHandler>();
public Rectangle Viewport => _viewport;
public Matrix4 ActiveTransforms { get; }
public StyleStack Style { get; } = new StyleStack(new Style());
protected DrawingEngine()
{
Reset();
}
public Command RegisterCustomCommand(CommandHandler handler)
{
Command id = _customCommandBase++;
_customCommands.Insert(id - Command.CustomCommandBase, handler);
return id;
}
public void ProcessCommands(Rectangle bounds, DrawList queue)
{
DrawQueue iterator = queue.GetEnumerator();
if (!iterator.Peek().IsCommand)
throw new ArgumentException("The first element in the iterator must be a command frame.");
Reset();
_viewport = bounds;
_viewportStack.Push(_viewport);
Frame frame;
while (iterator.TryDequeue(out frame))
{
Command cmd = (Command)frame;
switch (cmd)
{
default:
if (cmd > Command.CustomCommandBase)
{
_customCommands[cmd - Command.CustomCommandBase].Invoke(this, iterator);
}
else
{
ChildProcessCommand(cmd, iterator);
}
break;
case Command.ConditionalBegin: ConditionalHandler(iterator); break;
case Command.ConditionalEnd: /* nop */ break;
case Command.Invoke:
iterator.Dequeue().As<CommandHandler>().Invoke(this, iterator);
break;
case Command.PushViewport:
_viewportStack.Push(_viewport);
break;
case Command.IntersectViewport:
_viewport = Rectangle.Intersect((Rectangle)iterator.Dequeue(), _viewport);
break;
case Command.StoreViewport:
_viewport = (Rectangle)iterator.Dequeue();
break;
case Command.PopViewport:
_viewport = _viewportStack.TryPop(out Rectangle viewport) ? viewport : bounds;
break;
case Command.PushZ:
_zStack.Push(_zIndex);
break;
case Command.IncrementZ:
_zIndex++;
break;
case Command.AddZ:
_zIndex += (int)iterator.Dequeue();
break;
case Command.StoreZ:
_zIndex = (int)iterator.Dequeue();
break;
case Command.DecrementZ:
_zIndex--;
break;
case Command.PopZ:
_zIndex = _zStack.TryPop(out int zindex) ? zindex : 0;
break;
case Command.PushStyle:
Style.Push(iterator.Dequeue().As<Style>());
break;
case Command.StoreStyle:
Style.Pop();
Style.Push(iterator.Dequeue().As<Style>());
break;
case Command.PopStyle:
Style.Pop();
break;
}
}
}
protected virtual void ChildProcessCommand(Command name, DrawQueue queue)
{
}
public virtual void Reset()
{
_zIndex = 0;
_zStack.Clear();
_viewport = new Rectangle(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue);
_viewportStack.Clear();
_matrixStack.Clear();
_matrixStack.Push(Matrix4.Identity);
}
private void ConditionalHandler(DrawQueue iterator)
{
Frame frame = iterator.Dequeue();
if (
frame.IsInteger && (int)frame != 0 ||
frame.As<Func<bool>>().Invoke())
{
// Take this branch.
return;
}
// Skip this branch.
int depth = 1;
while (iterator.TryPeek(out frame))
{
if (!frame.IsCommand)
{
iterator.Dequeue();
continue;
}
switch ((Command)frame)
{
case Command.ConditionalBegin:
// Increment conditional depth.
depth++;
break;
case Command.ConditionalEnd:
// Decrement condional depth, exit if zero.
if (--depth == 0)
{
iterator.Dequeue();
return;
}
break;
}
iterator.Dequeue();
}
}
private static readonly Dictionary<Type, IDrawListSerializer> s_serializers = new Dictionary<Type, IDrawListSerializer>();
/// <summary>
/// Add a custom serializer to the command engine.
/// </summary>
/// <typeparam name="T">Type object type.</typeparam>
/// <param name="serializer">The serializer.</param>
/// <param name="overwrite">True to allow overwriting.</param>
public static void AddSerializer<T>(IDrawListSerializer serializer, bool overwrite = false)
{
if (overwrite)
{
s_serializers[typeof(T)] = serializer;
}
else
{
s_serializers.Add(typeof(T), serializer);
}
}
/// <summary>
/// Add a custom serializer to the command engine.
/// </summary>
/// <typeparam name="T">Type object type.</typeparam>
/// <param name="serializer">The serializer.</param>
/// <param name="overwrite">True to allow overwriting.</param>
public static void AddSerializer<T>(IDrawListSerializer<T> serializer, bool overwrite = false)
=> AddSerializer<T>((IDrawListSerializer)serializer, overwrite);
/// <summary>
/// Get a serializer for the given object.
/// </summary>
/// <typeparam name="T">The object type.</typeparam>
/// <param name="value">Required parameter for the C# type inference to work.</param>
/// <returns>The serializer.</returns>
public static IDrawListSerializer<T> GetSerializer<T>(IDrawListSerializable<T>? value)
where T : IDrawListSerializable<T>, new()
{
if (!s_serializers.TryGetValue(typeof(T), out var serializer))
{
serializer = new DrawListSerializableSerializer<T>();
AddSerializer<T>(serializer);
}
return (IDrawListSerializer<T>)serializer;
}
/// <summary>
/// Get a serializer for the given object.
/// </summary>
/// <typeparam name="T">The object type.</typeparam>
/// <param name="value">Required parameter for the C# type inference to work.</param>
/// <returns>The serializer.</returns>
public static IDrawListSerializer<T> GetSerializer<T>(T? value)
{
return (IDrawListSerializer<T>)s_serializers[typeof(T)];
}
}
}

View File

@@ -0,0 +1,434 @@
using Dashboard.Media;
using OpenTK.Mathematics;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Dashboard.ImmediateDraw
{
public class DrawList : IEnumerable<Frame>
{
private readonly List<Frame> _frames = new List<Frame>();
public void Clear()
{
_frames.Clear();
}
protected void Enqueue(in Frame frame)
{
_frames.Add(frame);
}
public void Invoke(CommandHandler handler)
{
Enqueue(Command.Invoke);
Enqueue(new Frame(handler));
}
public void ConditionalBegin(bool value)
{
Enqueue(Command.ConditionalBegin);
Enqueue((Frame)(value ? 1 : 0));
}
public void ConditionalBegin(Func<bool> condition)
{
Enqueue(Command.ConditionalBegin);
Enqueue(new Frame(condition));
}
public void ConditionalEnd()
{
Enqueue(Command.ConditionalEnd);
}
public void PushViewport()
{
Enqueue(Command.PushViewport);
}
public void IntersectViewport(in Rectangle viewport)
{
Enqueue(Command.IntersectViewport);
Enqueue(viewport);
}
public void StoreViewport(in Rectangle viewport)
{
Enqueue(Command.StoreViewport);
Enqueue(viewport);
}
public void PopViewport()
{
Enqueue(Command.PopViewport);
}
public void PushZ()
{
Enqueue(Command.PushZ);
}
public void IncrementZ()
{
Enqueue(Command.IncrementZ);
}
public void AddZ(int value)
{
if (value == 1)
{
IncrementZ();
}
else if (value == -1)
{
DecrementZ();
}
else
{
Enqueue(Command.AddZ);
Enqueue((Frame)value);
}
}
public void StoreZ(int value)
{
Enqueue(Command.StoreZ);
Enqueue((Frame)value);
}
public void DecrementZ()
{
Enqueue(Command.DecrementZ);
}
public void PopZ()
{
Enqueue(Command.PopZ);
}
public void PushStyle(Style style)
{
Enqueue(Command.PushStyle);
Enqueue(new Frame(style));
}
public void StoreStyle(Style style)
{
Enqueue(Command.StoreStyle);
Enqueue(new Frame(style));
}
public void PopStyle()
{
Enqueue(Command.PopStyle);
}
public void Line(in Line line)
{
Enqueue(Command.Line);
Enqueue(line);
}
public void Line(params Line[] lines)
{
Enqueue(Command.Line);
Enqueue((Frame)lines.Length);
foreach (Line line in lines)
Enqueue(line);
}
public void Bezier(in Bezier bezier)
{
Frame a, b;
Frame.Create(bezier, out a, out b);
Enqueue(Command.Bezier);
Enqueue(a);
Enqueue(b);
}
public void Bezier(params Bezier[] beziers)
{
Frame a, b;
Enqueue(Command.Bezier);
Enqueue((Frame)beziers.Length);
foreach (Bezier bezier in beziers)
{
Frame.Create(bezier, out a, out b);
Enqueue(a);
Enqueue(b);
}
}
public void Rectangle(in Rectangle rectangle)
{
Enqueue(Command.Rectangle);
Enqueue(rectangle);
}
public void Rectangle(Rectangle[] rectangles)
{
Enqueue(Command.Rectangle);
Enqueue((Frame)rectangles.Length);
foreach (Rectangle rectangle in rectangles)
Enqueue(rectangle);
}
public void Ellipse(in Ellipse ellipse)
{
Frame a, b;
Frame.Create(ellipse, out a, out b);
Enqueue(Command.Ellipse);
Enqueue(a);
Enqueue(b);
}
public void Ellipse(params Ellipse[] ellipses)
{
Frame a, b;
Enqueue(Command.Ellipse);
Enqueue((Frame)ellipses.Length);
foreach (Ellipse ellipse in ellipses)
{
Frame.Create(ellipse, out a, out b);
Enqueue(a);
Enqueue(b);
}
}
public void Triangle(in Triangle triangle)
{
Enqueue(Command.Triangle);
Enqueue(triangle.A);
Enqueue(triangle.B);
Enqueue(triangle.C);
}
public void Triangle(params Triangle[] triangles)
{
Enqueue(Command.Triangle);
Enqueue((Frame)triangles.Length);
foreach (Triangle triangle in triangles)
{
Enqueue(triangle.A);
Enqueue(triangle.B);
Enqueue(triangle.C);
}
}
public void Polygon(params Vector2[] polygon)
{
Enqueue(Command.Polygon);
Enqueue((Frame)polygon.Length);
foreach (Vector2 vertex in polygon)
{
Enqueue(vertex);
}
}
public void Image(Image texture, in Rectangle rectangle)
{
Enqueue(Command.Image);
Enqueue((Frame)(int)ImageCommandFlags.Single);
Enqueue(new Frame(texture));
Enqueue(rectangle);
}
public void Image(Image texture, in Rectangle rectangle, in Rectangle uv)
{
Enqueue(Command.Image);
Enqueue((Frame)(int)(ImageCommandFlags.Single | ImageCommandFlags.UVs));
Enqueue(new Frame(texture));
Enqueue(rectangle);
Enqueue(uv);
}
public void Image(Image texture, ReadOnlySpan<Rectangle> rectangles, bool interleavedUV = false)
{
int count = rectangles.Length;
ImageCommandFlags flags = ImageCommandFlags.None;
if (interleavedUV)
{
count /= 2;
flags |= ImageCommandFlags.UVs;
}
Enqueue(Command.Image);
Enqueue(new Frame((int)flags, count));
Enqueue(new Frame(texture));
foreach (Rectangle rectangle in rectangles)
{
Enqueue(rectangle);
}
}
public void Image(Image texture, ReadOnlySpan<Rectangle> rectangles, ReadOnlySpan<Rectangle> uvs)
{
int count = Math.Min(rectangles.Length, uvs.Length);
Enqueue(Command.Image);
Enqueue(new Frame((int)ImageCommandFlags.UVs, count));
Enqueue(new Frame(texture));
for (int i = 0; i < count; i++)
{
Enqueue(rectangles[i]);
Enqueue(uvs[i]);
}
}
public void Image3D(Image texture, in Image3DCall call)
{
Enqueue(Command.Image);
Enqueue(new Frame(ImageCommandFlags.Image3d | ImageCommandFlags.Single));
Enqueue(new Frame(texture));
Enqueue(call.Rectangle);
Enqueue(call.UVs);
Enqueue(new Frame(call.Layer));
}
public void Image3D(Image texture, ReadOnlySpan<Image3DCall> calls)
{
Enqueue(Command.Image);
Enqueue(new Frame((int)ImageCommandFlags.Image3d, calls.Length));
Enqueue(new Frame(texture));
foreach (Image3DCall call in calls)
{
Enqueue(call.Rectangle);
Enqueue(call.UVs);
Enqueue(new Frame(call.Layer));
}
}
public void Splice(DrawList list)
{
foreach (Frame frame in list)
{
Enqueue(frame);
}
}
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <typeparam name="T">The type of the value to serialize.</typeparam>
/// <param name="value">What to write into the command list.</param>
public void Write<T>(T value)
{
DrawingEngine.GetSerializer(value).Serialize(value, this);
}
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <typeparam name="T">The type of the value to serialize.</typeparam>
/// <param name="value">What to write into the command list.</param>
public void Write<T>(IDrawListSerializable<T> value)
where T : IDrawListSerializable<T>, new()
{
DrawingEngine.GetSerializer(value).Serialize((T)value, this);
}
public DrawQueue GetEnumerator() => new DrawQueue(_frames);
IEnumerator<Frame> IEnumerable<Frame>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class DrawQueue : IEnumerator<Frame>
{
private readonly IReadOnlyList<Frame> _frames;
private int _current;
public Frame Current => _frames[_current];
object IEnumerator.Current => Current;
public DrawQueue(IReadOnlyList<Frame> frames)
{
_current = -1;
_frames = frames;
}
public void Dispose()
{
}
public bool TryDequeue([NotNullWhen(true)] out Frame frame)
{
if (MoveNext())
{
frame = Current;
return true;
}
else
{
frame = default;
return false;
}
}
public Frame Dequeue() => TryDequeue(out Frame frame) ? frame : throw new Exception("No more frames left.");
public bool TryPeek([NotNullWhen(true)] out Frame frame)
{
if (_current + 1 < _frames.Count)
{
frame = _frames[_current + 1];
return true;
}
else
{
frame = default;
return false;
}
}
public Frame Peek() => TryPeek(out Frame frame) ? frame : throw new Exception("No more frames left.");
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
/// <param name="value">The deserialized value.</param>
public void Read<T>([NotNull] out T? value)
{
value = DrawingEngine.GetSerializer(default(T)).Deserialize(this);
}
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
/// <param name="value">The deserialized value.</param>
public void Read<T>([NotNull] out IDrawListSerializable<T>? value)
where T : IDrawListSerializable<T>, new()
{
value = DrawingEngine.GetSerializer(value = null).Deserialize(this);
}
/// <inheritdoc/>
public bool MoveNext()
{
if (_current + 1 < _frames.Count)
{
_current++;
return true;
}
return false;
}
/// <inheritdoc/>
public void Reset()
{
_current = -1;
}
}
}

View File

@@ -0,0 +1,362 @@
using System;
using System.Runtime.InteropServices;
using OpenTK.Mathematics;
namespace Dashboard.ImmediateDraw
{
[StructLayout(LayoutKind.Explicit)]
public struct Frame
{
[FieldOffset(0)]
private FrameType _type;
[FieldOffset(sizeof(FrameType) + 0 * sizeof(int))]
private int _i1;
[FieldOffset(sizeof(FrameType) + 1 * sizeof(int))]
private int _i2;
[FieldOffset(sizeof(FrameType) + 2 * sizeof(int))]
private int _i3;
[FieldOffset(sizeof(FrameType) + 3 * sizeof(int))]
private int _i4;
[FieldOffset(sizeof(FrameType) + 0 * sizeof(float))]
private float _f1;
[FieldOffset(sizeof(FrameType) + 1 * sizeof(float))]
private float _f2;
[FieldOffset(sizeof(FrameType) + 2 * sizeof(float))]
private float _f3;
[FieldOffset(sizeof(FrameType) + 3 * sizeof(float))]
private float _f4;
[FieldOffset(24)]
private object? _object = null;
public bool IsCommand => _type == FrameType.Command;
public bool IsInteger =>
_type == FrameType.IVec1 ||
_type == FrameType.IVec2 ||
_type == FrameType.IVec3 ||
_type == FrameType.IVec4;
public bool IsFloat =>
_type == FrameType.Vec1 ||
_type == FrameType.Vec2 ||
_type == FrameType.Vec3 ||
_type == FrameType.Vec4;
public int VectorSize
{
get
{
switch (_type)
{
case FrameType.None:
return 0;
default:
return 1;
case FrameType.Vec2: case FrameType.IVec2:
return 2;
case FrameType.Vec3: case FrameType.IVec3:
return 3;
case FrameType.Vec4: case FrameType.IVec4:
return 4;
}
}
}
public FrameType Type => _type;
public int I1 => _i1;
public int I2 => _i2;
public int I3 => _i3;
public int I4 => _i4;
public float F1 => _f1;
public float F2 => _f2;
public float F3 => _f3;
public float F4 => _f4;
public static Frame None { get; } = new Frame() {
_type = FrameType.None
};
#region Constructors
public Frame(Command command) : this()
{
_type = FrameType.Command;
_i1 = (int)command;
}
public Frame(object o)
{
_type = FrameType.Object;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_object = o;
}
public Frame(int i1)
{
_type = FrameType.IVec1;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_i1 = i1;
}
public Frame(int i1, int i2)
{
_type = FrameType.IVec2;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_i1 = i1;
_i2 = i2;
}
public Frame(int i1, int i2, int i3)
{
_type = FrameType.IVec3;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_i1 = i1;
_i2 = i2;
_i3 = i3;
}
public Frame(int i1, int i2, int i3, int i4)
{
_type = FrameType.IVec4;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_i1 = i1;
_i2 = i2;
_i3 = i3;
_i4 = i4;
}
public Frame(float f1)
{
_type = FrameType.Vec1;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_f1 = f1;
}
public Frame(float f1, float f2)
{
_type = FrameType.Vec2;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_f1 = f1;
_f2 = f2;
}
public Frame(float f1, float f2, float f3)
{
_type = FrameType.Vec3;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_f1 = f1;
_f2 = f2;
_f3 = f3;
}
public Frame(float f1, float f2, float f3, float f4)
{
_type = FrameType.Vec4;
_i1 = _i2 = _i3 = _i4 = default;
_f1 = _f2 = _f3 = _f4 = default;
_object = null;
_f1 = f1;
_f2 = f2;
_f3 = f3;
_f4 = f4;
}
#endregion
public T As<T>()
{
return (T)_object!;
}
public float GetF(int i)
{
switch (i)
{
case 0: return _f1;
case 1: return _f2;
case 2: return _f3;
case 3: return _f4;
default:
throw new ArgumentOutOfRangeException();
}
}
public int GetI(int i)
{
switch (i)
{
case 0: return _i1;
case 1: return _i2;
case 2: return _i3;
case 3: return _i4;
default:
throw new ArgumentOutOfRangeException();
}
}
#region Frame->T Conversion
public static explicit operator int(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.Command:
case FrameType.IVec1:
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return frame._i1;
case FrameType.Vec1:
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return (int)frame._f1;
}
}
public static explicit operator float(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec1:
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return frame._i1;
case FrameType.Vec1:
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return frame._f1;
}
}
public static explicit operator Command(in Frame frame)
{
if (frame.Type != FrameType.Command)
{
throw new InvalidCastException("Not a command frame.");
}
return (Command)frame._i1;
}
public static explicit operator Vector2(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return new Vector2(frame._i1, frame._i2);
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return new Vector2(frame._f1, frame._f2);
}
}
public static explicit operator Color4(in Frame frame)
{
switch (frame.Type)
{
case FrameType.IVec4:
return new Color4((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4);
case FrameType.Vec4:
return new Color4(frame._f1, frame._f2, frame._f3, frame._f4);
default:
throw new InvalidCastException();
}
}
public static explicit operator Rectangle(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec4:
return new Rectangle(frame._i1, frame._i2, frame._i3, frame._i4);
case FrameType.Vec4:
return new Rectangle(frame._f1, frame._f2, frame._f3, frame._f4);
}
}
public static explicit operator Line(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec4:
return new Line(frame._i1, frame._i2, frame._i3, frame._i4);
case FrameType.Vec4:
return new Line(frame._f1, frame._f2, frame._f3, frame._f4);
}
}
#endregion
public static explicit operator Frame(int i) => new Frame(i);
public static explicit operator Frame(float f) => new Frame(f);
public static implicit operator Frame(Command cmd) => new Frame(cmd);
public static implicit operator Frame(in Vector2 vector) => new Frame(vector.X, vector.Y);
public static implicit operator Frame(in Color4 color) => new Frame(color.R, color.G, color.B, color.A);
public static implicit operator Frame(in Rectangle rect) => new Frame(rect.Max.X, rect.Max.Y, rect.Min.X, rect.Min.Y);
public static implicit operator Frame(in Line line) => new Frame(line.Start.X, line.Start.Y, line.End.X, line.Start.Y);
public static void Create(in Bezier bezier, out Frame a, out Frame b)
{
a = new Frame(bezier.Start.X, bezier.Start.Y, bezier.End.X, bezier.End.Y);
b = new Frame(bezier.ControlA.X, bezier.ControlA.Y, bezier.ControlB.X, bezier.ControlB.Y);
}
public static void Create(in Ellipse ellipse, out Frame a, out Frame b)
{
a = new Frame(ellipse.Center.X, ellipse.Center.Y);
b = new Frame(ellipse.AxisA.X, ellipse.AxisA.Y, ellipse.AxisB.X, ellipse.AxisB.Y);
}
}
}

View File

@@ -0,0 +1,68 @@
namespace Dashboard.ImmediateDraw
{
/// <summary>
/// Enumeration of command types in the Dashboard command lists.
/// </summary>
public enum FrameType
{
/// <summary>
/// A null value.
/// </summary>
None,
/// <summary>
/// A command frame.
/// </summary>
Command,
/// <summary>
/// An integer frame.
/// </summary>
IVec1,
/// <summary>
/// A two dimensional integer vector frame.
/// </summary>
IVec2,
/// <summary>
/// A three dimensional integer vector frame.
/// </summary>
IVec3,
/// <summary>
/// A four dimensional integer vector frame.
/// </summary>
IVec4,
/// <summary>
/// A floating point frame.
/// </summary>
Vec1,
/// <summary>
/// A two dimensional floating point vector frame.
/// </summary>
Vec2,
/// <summary>
/// A three dimensional floating point vector frame.
/// </summary>
Vec3,
/// <summary>
/// A four dimensional floating point vector frame.
/// </summary>
Vec4,
/// <summary>
/// A serialized object frame.
/// </summary>
Serialized,
/// <summary>
/// A .Net object frame.
/// </summary>
Object,
}
}

View File

@@ -0,0 +1,18 @@
namespace Dashboard.ImmediateDraw
{
public enum ImageCommandFlags
{
None = 0,
Single = 1 << 0,
UVs = 1 << 1,
Image3d = 1 << 2,
}
public struct Image3DCall
{
public Rectangle Rectangle;
public Rectangle UVs;
public int Layer;
}
}

View File

@@ -0,0 +1,68 @@
using System.Diagnostics.CodeAnalysis;
namespace Dashboard.ImmediateDraw
{
public interface IDrawListSerializable { }
/// <summary>
/// Interface for objects that can be serialized into the Dashboard command stream.
/// </summary>
public interface IDrawListSerializable<T> : IDrawListSerializable
{
/// <summary>
/// Seralize object.
/// </summary>
/// <param name="list">The object to serialize into.</param>
void Serialize(DrawList list);
/// <summary>
/// Deserialize object.
/// </summary>
/// <param name="queue">The command queue to deserialize from.</param>
void Deserialize(DrawQueue queue);
}
/// <summary>
/// Base interface for all Command List serializers.
/// </summary>
public interface IDrawListSerializer { }
public interface IDrawListSerializer<T> : IDrawListSerializer
{
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <param name="value">The object to serialize.</param>
/// <param name="list">The command list to serialize into.</param>
void Serialize(T value, DrawList list);
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <param name="queue">The command queue.</param>
/// <returns>The object deserialized from the command queue.</returns>
[return: NotNull]
T Deserialize(DrawQueue queue);
}
/// <summary>
/// Class for automatic serialization of <see cref="IDrawListSerializable"/> objects.
/// </summary>
/// <typeparam name="T">The object type to convert.</typeparam>
internal class DrawListSerializableSerializer<T> : IDrawListSerializer<T>
where T : IDrawListSerializable<T>, new()
{
public T Deserialize(DrawQueue queue)
{
T value = new T();
value.Deserialize(queue);
return value;
}
public void Serialize(T value, DrawList list)
{
value.Serialize(list);
}
}
}

View File

@@ -0,0 +1,116 @@
using OpenTK.Mathematics;
using Dashboard.Controls;
using System.Reflection.Metadata;
namespace Dashboard.Layout
{
/// <summary>
/// Control attributes for grid layout.
/// </summary>
public class GridLayoutAttribute
{
/// <summary>
/// An anchor will keep the relative distance of a control's edges the same during resizes.
/// </summary>
/// <remarks><see cref="Dock"/> has higher precedence.</remarks>
public Anchor Anchor { get; set; } = Anchor.Left | Anchor.Top;
/// <summary>
/// Dock will strongly attach a control to its container or its edges.
/// </summary>
/// <remarks>Has more precedence than <see cref="Anchor"/></remarks>
public Dock Dock { get; set; } = Dock.None;
public void Evaluate(
ResizedEventArgs parentResizeEvent,
in Rectangle oldBounds,
out Rectangle newBounds
)
{
switch (Dock)
{
default:
case Dock.None:
break;
case Dock.Top:
newBounds = new Rectangle(
parentResizeEvent.NewSize.X,
oldBounds.Size.Y,
0,
0
);
return;
case Dock.Bottom:
newBounds = new Rectangle(
parentResizeEvent.NewSize.X,
parentResizeEvent.NewSize.Y,
0,
oldBounds.Size.Y
);
return;
case Dock.Left:
newBounds = new Rectangle(
oldBounds.Size.X,
parentResizeEvent.NewSize.Y,
0,
0
);
return;
case Dock.Right:
newBounds = new Rectangle(
parentResizeEvent.NewSize.X,
parentResizeEvent.NewSize.Y,
parentResizeEvent.NewSize.Y - oldBounds.Size.Y,
0
);
return;
}
Vector2 scale = parentResizeEvent.NewSize / parentResizeEvent.OldSize;
newBounds = oldBounds;
if (Anchor.HasFlag(Anchor.Top))
{
newBounds.Top = scale.Y * oldBounds.Top;
}
if (Anchor.HasFlag(Anchor.Left))
{
newBounds.Left = scale.X * oldBounds.Left;
}
if (Anchor.HasFlag(Anchor.Bottom))
{
float margin = scale.Y * (parentResizeEvent.OldSize.Y - newBounds.Bottom);
newBounds.Bottom = parentResizeEvent.NewSize.Y - margin;
}
if (Anchor.HasFlag(Anchor.Right))
{
float margin = scale.X * (parentResizeEvent.OldSize.X - newBounds.Right);
newBounds.Right = parentResizeEvent.NewSize.X - margin;
}
}
/// <summary>
/// Get the grid layout attribute associated with the given UI-base.
/// </summary>
/// <param name="uiBase">The UI-base to query.</param>
/// <returns>It's grid layout attribute, if any.</returns>
public static GridLayoutAttribute GetGridLayout(UIBase uiBase)
{
const string GRID_LAYOUT_ATTRIBUTE_KEY = "486ddf8c-b75f-4ad4-a51d-5ba20db9bd0e";
if (
!uiBase.Attributes.TryGetValue(GRID_LAYOUT_ATTRIBUTE_KEY, out object? attribute)
|| attribute is not GridLayoutAttribute)
{
attribute = new GridLayoutAttribute();
uiBase.Attributes[GRID_LAYOUT_ATTRIBUTE_KEY] = attribute;
}
return (GridLayoutAttribute)attribute;
}
}
}

View File

@@ -1,6 +1,7 @@
using System; using System;
using OpenTK.Mathematics;
namespace Quik.Media.Color namespace Dashboard.Media.Color
{ {
public static class FormatConvert public static class FormatConvert
{ {
@@ -8,12 +9,12 @@ namespace Quik.Media.Color
{ {
switch (image.Format) switch (image.Format)
{ {
case QImageFormat.RaF: case ImageFormat.RaF:
case QImageFormat.RaU8: case ImageFormat.RaU8:
case QImageFormat.RgbF: case ImageFormat.RgbF:
case QImageFormat.RgbU8: case ImageFormat.RgbU8:
case QImageFormat.RgbaF: case ImageFormat.RgbaF:
case QImageFormat.RgbaU8: case ImageFormat.RgbaU8:
break; break;
default: default:
return; return;
@@ -27,7 +28,7 @@ namespace Quik.Media.Color
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
QColorF color = io[i]; Color4 color = io[i];
color.R *= color.A; color.R *= color.A;
color.G *= color.A; color.G *= color.A;
@@ -42,8 +43,8 @@ namespace Quik.Media.Color
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
QColor color = io[i]; Color4 color = io[i];
float a = color.A/255.0f; float a = color.A;
color.R = (byte)(color.R * a); color.R = (byte)(color.R * a);
color.G = (byte)(color.G * a); color.G = (byte)(color.G * a);
@@ -75,7 +76,7 @@ namespace Quik.Media.Color
int count = dst.Width * dst.Height * dst.Depth; int count = dst.Width * dst.Height * dst.Depth;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
dstIO[i] = (QColor)srcIO[i]; dstIO[i] = srcIO[i];
} }
} }
else if (dst.Format.IsFloat() && src.Format.IsU8()) else if (dst.Format.IsFloat() && src.Format.IsU8())
@@ -86,7 +87,7 @@ namespace Quik.Media.Color
int count = dst.Width * dst.Height * dst.Depth; int count = dst.Width * dst.Height * dst.Depth;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
dstIO[i] = (QColorF)srcIO[i]; dstIO[i] = srcIO[i];
} }
} }
else if (dst.Format.IsFloat() && src.Format.IsFloat()) else if (dst.Format.IsFloat() && src.Format.IsFloat())

View File

@@ -0,0 +1,69 @@
using System;
using System.Runtime.InteropServices;
namespace Dashboard.Media.Color
{
public class QImageBuffer : Image
{
private byte[] buffer;
GCHandle handle;
private bool isSdf = false;
public override ImageFormat InternalFormat { get; }
public override int Width { get; }
public override int Height { get; }
public override int Depth { get; }
public override bool IsSdf => isSdf;
public QImageBuffer(ImageFormat format, int width, int height, int depth = 1)
{
InternalFormat = format;
Width = width;
Height = height;
Depth = depth;
buffer = new byte[(long)width * height * depth * format.BytesPerPixel()];
}
~QImageBuffer()
{
Dispose(false);
}
private QImageLock Lock()
{
if (handle.IsAllocated) handle.Free();
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
return new QImageLock(InternalFormat, Width, Height, Depth, ptr);
}
protected override void Dispose(bool disposing)
{
if (handle.IsAllocated) handle.Free();
GC.SuppressFinalize(this);
}
public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
{
imageLock = Lock();
}
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
{
imageLock = Lock();
}
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
{
imageLock = Lock();
}
public override void UnlockBits()
{
handle.Free();
}
public void SetSdf(bool value = true) => isSdf = value;
}
}

View File

@@ -1,6 +1,7 @@
using System; using System;
using OpenTK.Mathematics;
namespace Quik.Media.Color namespace Dashboard.Media.Color
{ {
public unsafe struct LockIO public unsafe struct LockIO
{ {
@@ -8,7 +9,7 @@ namespace Quik.Media.Color
public int Width => Lock.Width; public int Width => Lock.Width;
public int Height => Lock.Height; public int Height => Lock.Height;
public int Depth => Depth; public int Depth => Depth;
public QImageFormat Format => Lock.Format; public ImageFormat Format => Lock.Format;
public LockIO(QImageLock imageLock) public LockIO(QImageLock imageLock)
{ {
@@ -18,7 +19,7 @@ namespace Quik.Media.Color
Lock = imageLock; Lock = imageLock;
} }
public QColor this[int index] public Color4 this[int index]
{ {
get get
{ {
@@ -28,11 +29,11 @@ namespace Quik.Media.Color
switch (Format) switch (Format)
{ {
default: default:
case QImageFormat.RedU8: return new QColor(ptr[0], 0, 0, 255); case ImageFormat.RedU8: return new Color4(ptr[0], 0, 0, 255);
case QImageFormat.AlphaU8: return new QColor(0, 0, 0, ptr[0]); case ImageFormat.AlphaU8: return new Color4(0, 0, 0, ptr[0]);
case QImageFormat.RaU8: return new QColor(ptr[0], 0, 0, ptr[1]); case ImageFormat.RaU8: return new Color4(ptr[0], 0, 0, ptr[1]);
case QImageFormat.RgbU8: return new QColor(ptr[0], ptr[1], ptr[2], 255); case ImageFormat.RgbU8: return new Color4(ptr[0], ptr[1], ptr[2], 255);
case QImageFormat.RgbaU8: return new QColor(ptr[0], ptr[1], ptr[2], ptr[3]); case ImageFormat.RgbaU8: return new Color4(ptr[0], ptr[1], ptr[2], ptr[3]);
} }
} }
@@ -44,31 +45,31 @@ namespace Quik.Media.Color
switch (Format) switch (Format)
{ {
default: default:
case QImageFormat.RedU8: case ImageFormat.RedU8:
ptr[0] = value.R; ptr[0] = (byte)(value.R * 255);
break; break;
case QImageFormat.AlphaU8: case ImageFormat.AlphaU8:
ptr[0] = value.A; ptr[0] = (byte)(value.A * 255);
break; break;
case QImageFormat.RaU8: case ImageFormat.RaU8:
ptr[0] = value.R; ptr[0] = (byte)(value.R * 255);
ptr[1] = value.A; ptr[1] = (byte)(value.A * 255);
break; break;
case QImageFormat.RgbU8: case ImageFormat.RgbU8:
ptr[0] = value.R; ptr[0] = (byte)(value.R * 255);
ptr[1] = value.G; ptr[1] = (byte)(value.G * 255);
ptr[2] = value.B; ptr[2] = (byte)(value.B * 255);
break; break;
case QImageFormat.RgbaU8: case ImageFormat.RgbaU8:
ptr[0] = value.R; ptr[0] = (byte)(value.R * 255);
ptr[1] = value.G; ptr[1] = (byte)(value.G * 255);
ptr[2] = value.B; ptr[2] = (byte)(value.B * 255);
ptr[3] = value.A; ptr[3] = (byte)(value.A * 255);
break; break;
} }
} }
} }
public QColor this[int x, int y, int z = 0] public Color4 this[int x, int y, int z = 0]
{ {
get => this[x + y * Width + z * Width * Height]; get => this[x + y * Width + z * Width * Height];
set => this[x + y * Width + z * Width * Height] = value; set => this[x + y * Width + z * Width * Height] = value;
@@ -81,7 +82,7 @@ namespace Quik.Media.Color
public int Width => Lock.Width; public int Width => Lock.Width;
public int Height => Lock.Height; public int Height => Lock.Height;
public int Depth => Depth; public int Depth => Depth;
public QImageFormat Format => Lock.Format; public ImageFormat Format => Lock.Format;
public LockIOF(QImageLock imageLock) public LockIOF(QImageLock imageLock)
{ {
@@ -91,7 +92,7 @@ namespace Quik.Media.Color
Lock = imageLock; Lock = imageLock;
} }
public QColorF this[int index] public Color4 this[int index]
{ {
get get
{ {
@@ -101,11 +102,11 @@ namespace Quik.Media.Color
switch (Format) switch (Format)
{ {
default: default:
case QImageFormat.RedU8: return new QColorF(ptr[0], 0, 0, 255); case ImageFormat.RedU8: return new Color4(ptr[0], 0, 0, 1);
case QImageFormat.AlphaU8: return new QColorF(0, 0, 0, ptr[0]); case ImageFormat.AlphaU8: return new Color4(0, 0, 0, ptr[0]);
case QImageFormat.RaU8: return new QColorF(ptr[0], 0, 0, ptr[1]); case ImageFormat.RaU8: return new Color4(ptr[0], 0, 0, ptr[1]);
case QImageFormat.RgbU8: return new QColorF(ptr[0], ptr[1], ptr[2], 255); case ImageFormat.RgbU8: return new Color4(ptr[0], ptr[1], ptr[2], 1);
case QImageFormat.RgbaU8: return new QColorF(ptr[0], ptr[1], ptr[2], ptr[3]); case ImageFormat.RgbaU8: return new Color4(ptr[0], ptr[1], ptr[2], ptr[3]);
} }
} }
@@ -117,22 +118,22 @@ namespace Quik.Media.Color
switch (Format) switch (Format)
{ {
default: default:
case QImageFormat.RedU8: case ImageFormat.RedU8:
ptr[0] = value.R; ptr[0] = value.R;
break; break;
case QImageFormat.AlphaU8: case ImageFormat.AlphaU8:
ptr[0] = value.A; ptr[0] = value.A;
break; break;
case QImageFormat.RaU8: case ImageFormat.RaU8:
ptr[0] = value.R; ptr[0] = value.R;
ptr[1] = value.A; ptr[1] = value.A;
break; break;
case QImageFormat.RgbU8: case ImageFormat.RgbU8:
ptr[0] = value.R; ptr[0] = value.R;
ptr[1] = value.G; ptr[1] = value.G;
ptr[2] = value.B; ptr[2] = value.B;
break; break;
case QImageFormat.RgbaU8: case ImageFormat.RgbaU8:
ptr[0] = value.R; ptr[0] = value.R;
ptr[1] = value.G; ptr[1] = value.G;
ptr[2] = value.B; ptr[2] = value.B;
@@ -141,7 +142,7 @@ namespace Quik.Media.Color
} }
} }
} }
public QColorF this[int x, int y, int z = 0] public Color4 this[int x, int y, int z = 0]
{ {
get => this[x + y * Width + z * Width * Height]; get => this[x + y * Width + z * Width * Height];
set => this[x + y * Width + z * Width * Height] = value; set => this[x + y * Width + z * Width * Height] = value;

View File

@@ -0,0 +1,71 @@
namespace Dashboard.Media
{
public static class Extensions
{
public static bool IsU8(this ImageFormat format)
{
switch (format)
{
case ImageFormat.AlphaU8:
case ImageFormat.RedU8:
case ImageFormat.RaU8:
case ImageFormat.RgbU8:
case ImageFormat.RgbaU8:
return true;
default:
return false;
}
}
public static bool IsFloat(this ImageFormat format)
{
switch (format)
{
case ImageFormat.AlphaF:
case ImageFormat.RedF:
case ImageFormat.RaF:
case ImageFormat.RgbF:
case ImageFormat.RgbaF:
return true;
default:
return false;
}
}
public static int BytesPerPixel(this ImageFormat format)
{
switch (format)
{
case ImageFormat.AlphaU8: return sizeof(byte);
case ImageFormat.RedU8: return sizeof(byte);
case ImageFormat.RaU8: return 2 * sizeof(byte);
case ImageFormat.RgbU8: return 3 * sizeof(byte);
case ImageFormat.RgbaU8: return 4 * sizeof(byte);
case ImageFormat.AlphaF: return sizeof(float);
case ImageFormat.RedF: return sizeof(float);
case ImageFormat.RaF: return 2 * sizeof(float);
case ImageFormat.RgbF: return 3 * sizeof(float);
case ImageFormat.RgbaF: return 4 * sizeof(float);
default: return 0;
}
}
public static int Channels(this ImageFormat format)
{
switch (format)
{
case ImageFormat.AlphaU8: return 1;
case ImageFormat.RedU8: return 1;
case ImageFormat.RaU8: return 2;
case ImageFormat.RgbU8: return 3;
case ImageFormat.RgbaU8: return 4;
case ImageFormat.AlphaF: return 1;
case ImageFormat.RedF: return 1;
case ImageFormat.RaF: return 2;
case ImageFormat.RgbF: return 3;
case ImageFormat.RgbaF: return 4;
default: return 0;
}
}
}
}

129
Dashboard/Media/Font.cs Normal file
View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Dashboard.Media;
using Dashboard.Media.Fonts;
namespace Dashboard.Media
{
/// <summary>
/// Abstract class that represents a font.
/// </summary>
public abstract class Font : IDisposable
{
public abstract FontFace Face { get; }
public string Family => Face.Family;
public FontSlant Slant => Face.Slant;
public FontWeight Weight => Face.Weight;
public FontStretch Stretch => Face.Stretch;
public abstract bool HasRune(int rune);
protected abstract Image Render(out GlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options);
private readonly Dictionary<float, SizedFontCollection> _atlasses = new Dictionary<float, SizedFontCollection>();
public void Get(int codepoint, float size, out FontGlyph glyph)
{
SizedFontCollection? collection;
if (!_atlasses.TryGetValue(size, out collection))
{
collection = new SizedFontCollection(size);
_atlasses.Add(size, collection);
}
collection.Get(codepoint, out glyph, this);
}
// IDisposable
private bool isDisposed = false;
private void DisposePrivate(bool disposing)
{
if (isDisposed) return;
Dispose(disposing);
isDisposed = true;
}
protected virtual void Dispose(bool disposing) { }
public void Dispose() => DisposePrivate(true);
private class SizedFontCollection
{
public float Size { get; }
private readonly Dictionary<int, FontGlyph> glyphs = new Dictionary<int, FontGlyph>();
private readonly FontAtlas atlas;
public SizedFontCollection(float size)
{
Size = size;
DbApplication.Current.Platform.GetMaximumImage(out int height, out int width);
// Do no allow to create a texture that is greater than 16 square characters at 200 DPI.
width = Math.Min(width, (int)(size * 200 * 16));
height = Math.Min(height, (int)(size * 200 * 16));
// width = height = 256;
atlas = new FontAtlas(width, height, DbApplication.Current.FontProvider.RasterizerOptions.Sdf);
}
public void Get(int codepoint, out FontGlyph glyph, Font font)
{
if (glyphs.TryGetValue(codepoint, out glyph))
return;
Image image = font.Render(
out GlyphMetrics metrics,
codepoint,
Size,
DbApplication.Current.FontProvider.RasterizerOptions);
if (image != null)
{
image.LockBits2d(out QImageLock l, QImageLockOptions.Default);
atlas.PutGlyph(codepoint, l, out FontAtlasGlyphInfo glyphInfo);
image.UnlockBits();
image.Dispose();
glyph = new FontGlyph(codepoint, glyphInfo.Image, metrics, glyphInfo.UVs);
}
else
{
glyph = new FontGlyph(codepoint, null, metrics, default);
}
glyphs[codepoint] = glyph;
}
}
}
public readonly struct FontGlyph
{
public readonly int CodePoint;
public readonly Image? Image;
public readonly GlyphMetrics Metrics;
public readonly Rectangle UVs;
public FontGlyph(int codepoint, Image? image, in GlyphMetrics metrics, in Rectangle uvs)
{
CodePoint = codepoint;
Image = image;
Metrics = metrics;
UVs = uvs;
}
}
public struct FontRasterizerOptions
{
public float Resolution { get; set; }
public bool Sdf { get; set; }
public static readonly FontRasterizerOptions Default = new FontRasterizerOptions()
{
Resolution = 96.0f,
Sdf = false
};
}
}

View File

@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using Dashboard.Media.Color;
using OpenTK.Mathematics;
namespace Dashboard.Media.Fonts
{
public struct FontAtlasGlyphInfo
{
public int Codepoint;
public Image Image;
public Rectangle UVs;
}
public class FontAtlas
{
private readonly int width, height;
private readonly List<AtlasPage> atlases = new List<AtlasPage>();
private readonly Dictionary<int, FontAtlasGlyphInfo> glyphs = new Dictionary<int, FontAtlasGlyphInfo>();
private int index = 0;
private AtlasPage? last = null;
private bool isSdf = false;
private int expansion;
public bool IsSdf
{
get => isSdf;
set
{
foreach (AtlasPage page in atlases)
{
((QImageBuffer)page.Image).SetSdf(value);
}
isSdf = value;
}
}
public FontAtlas(int width, int height, bool isSdf, int expansion = 4)
{
this.width = width;
this.height = height;
IsSdf = isSdf;
this.expansion = expansion;
}
public bool GetGlyph(int codepoint, out FontAtlasGlyphInfo info)
{
return glyphs.TryGetValue(codepoint, out info);
}
public void PutGlyph(int codepoint, QImageLock source, out FontAtlasGlyphInfo info)
{
info = new FontAtlasGlyphInfo() { Codepoint = codepoint };
if (last == null || !last.WouldFit(source))
{
AddPage();
}
last!.PutGlyph(source, ref info);
}
private void AddPage()
{
index++;
if (index < atlases.Count)
{
last = atlases[index];
}
else
{
last = new AtlasPage(width, height, expansion);
((QImageBuffer)last.Image).SetSdf(IsSdf);
atlases.Add(last);
}
}
public void Clear()
{
// Trim any pages that were not used yet.
for (int i = atlases.Count -1; i >= 0; i--)
{
if (atlases[i].PointerX != 0 && atlases[i].PointerY != 0)
{
for (int j = i + 1; j < atlases.Count; j++)
{
atlases[j].Dispose();
}
if (i != atlases.Count - 1)
atlases.RemoveRange(i+1, atlases.Count - i - 1);
break;
}
}
if (atlases.Count > 0)
{
last = atlases[0];
}
else
{
last = null;
}
index = -1;
glyphs.Clear();
}
private class AtlasPage : IDisposable
{
public Image Image;
public int PointerX, PointerY;
public int RowHeight;
public int Expansion;
public bool IsFull => PointerX > Image.Width || PointerY > Image.Height;
public AtlasPage(int width, int height, int expansion)
{
Image = new QImageBuffer(ImageFormat.AlphaU8, width, height);
Expansion = expansion;
Reset();
}
public void PutGlyph(QImageLock src, ref FontAtlasGlyphInfo prototype)
{
if (IsFull)
throw new Exception("Page is full!");
Image.LockBits2d(out QImageLock dst, QImageLockOptions.Default);
src.CopyTo(dst, PointerX, PointerY);
Image.UnlockBits();
Vector2 min = new Vector2((float)PointerX/Image.Width, (float)PointerY/Image.Height);
Vector2 size = new Vector2((float)src.Width/Image.Width, (float)src.Height/Image.Height);
prototype.Image = Image;
prototype.UVs = new Rectangle(min + size, min);
AdvanceColumn(src.Width, src.Height);
}
public void Reset()
{
RowHeight = PointerX = PointerY = 0;
}
public void AdvanceRow()
{
PointerX = 0;
PointerY += RowHeight + Expansion;
RowHeight = 0;
}
public void AdvanceColumn(int width, int height)
{
RowHeight = Math.Max(RowHeight, height);
PointerX += width + Expansion;
if (PointerX > Image.Width)
{
AdvanceRow();
}
}
private bool isDisposed = false;
public void Dispose()
{
if (isDisposed)
return;
Image?.Dispose();
isDisposed = true;
}
internal bool WouldFit(QImageLock source)
{
return !IsFull || PointerX + source.Width > Image.Width || PointerY + source.Height > Image.Height;
}
}
}
}

View File

@@ -0,0 +1,238 @@
using System;
using System.Text;
namespace Dashboard.Media.Fonts
{
public readonly struct FontFace : IEquatable<FontFace>
{
public string Family { get; }
public FontSlant Slant { get; }
public FontWeight Weight { get; }
public FontStretch Stretch { get; }
public FontFace(string family, FontSlant slant, FontWeight weight, FontStretch stretch)
{
Family = family;
Slant = slant;
Weight = weight;
Stretch = stretch;
}
public override string ToString()
{
StringBuilder builder = new StringBuilder(Family);
if (Slant != FontSlant.Normal)
{
builder.Append(' ');
builder.Append(Slant);
}
if (Stretch != FontStretch.Normal)
{
builder.Append(' ');
builder.Append(Stretch);
}
if (Weight != FontWeight.Normal)
{
builder.Append(' ');
builder.Append(Weight);
}
if (Slant == FontSlant.Normal &&
Stretch == FontStretch.Normal &&
Weight == FontWeight.Normal)
{
builder.Append(" Regular");
}
return builder.ToString();
}
public override int GetHashCode()
{
return HashCode.Combine(Family, Slant, Weight, Stretch);
}
public static bool operator==(FontFace a, FontFace b)
{
return (a.Slant == b.Slant) &&
(a.Weight == b.Weight) &&
(a.Stretch == b.Stretch) &&
(a.Family == a.Family);
}
public static bool operator!=(FontFace a, FontFace b)
{
return (a.Slant != b.Slant) ||
(a.Weight != b.Weight) ||
(a.Stretch != b.Stretch) ||
(a.Family != b.Family);
}
public bool Equals(FontFace other)
{
return this == other;
}
public override bool Equals(object? obj)
{
return (obj?.GetType() == typeof(FontFace)) &&
this == (FontFace)obj;
}
public static FontFace Parse(string family, string style)
{
FontSlant slant = FontSlant.Normal;
FontWeight weight = FontWeight.Normal;
FontStretch stretch = FontStretch.Normal;
string[] tokens = style.Split(' ');
foreach (string token in tokens)
{
/**/ if (TryParseSlant(token, out FontSlant xslant)) slant = xslant;
else if (TryParseWeight(token, out FontWeight xweight)) weight = xweight;
else if (TryParseStretch(token, out FontStretch xstretch)) stretch = xstretch;
}
return new FontFace(family, slant, weight, stretch);
}
public static FontFace Parse(string face)
{
StringBuilder family = new StringBuilder();
FontSlant slant = FontSlant.Normal;
FontWeight weight = FontWeight.Normal;
FontStretch stretch = FontStretch.Normal;
string[] tokens = face.Split(' ');
foreach (string token in tokens)
{
string xtoken = token.ToLower();
if (xtoken == "regular" || xtoken == "normal")
{
continue;
}
else if (TryParseSlant(xtoken, out FontSlant xslant)) slant = xslant;
else if (TryParseWeight(xtoken, out FontWeight xweight)) weight = xweight;
else if (TryParseStretch(xtoken, out FontStretch xstretch)) stretch = xstretch;
else
{
family.Append(token);
}
}
return new FontFace(family.ToString(), slant, weight, stretch);
}
/// <summary>
/// Try to convert a token that represents a font slant into its enum.
/// </summary>
/// <param name="token">The token to interpret.</param>
/// <param name="slant">The resulting slant.</param>
/// <returns>True if it matched any.</returns>
public static bool TryParseSlant(string token, out FontSlant slant)
{
switch (token.ToLower())
{
case "italic":
slant = FontSlant.Italic;
return true;
case "oblique":
slant = FontSlant.Oblique;
return true;
default:
slant = FontSlant.Normal;
return false;
}
}
/// <summary>
/// Try to convert a token that represents a font weight into its enum.
/// </summary>
/// <param name="token">The token to interpret.</param>
/// <param name="weight">The resulting weight.</param>
/// <returns>True if it matched any.</returns>
public static bool TryParseWeight(string token, out FontWeight weight)
{
switch (token.ToLower())
{
case "thin":
weight = FontWeight.Thin;
return true;
case "extralight":
case "ultralight":
weight = FontWeight._200;
return true;
case "light":
case "demilight":
case "semilight":
weight = FontWeight._300;
return true;
case "demibold":
case "semibold":
weight = FontWeight._600;
return true;
case "bold":
weight = FontWeight._700;
return true;
case "extrabold":
case "ultrabold":
weight = FontWeight._800;
return true;
case "heavy":
case "extrablack":
case "black":
case "ultrablack":
weight = FontWeight._900;
return true;
default:
weight = FontWeight.Normal;
return false;
}
}
/// <summary>
/// Try to convert a token that represents a font stretch into its enum.
/// </summary>
/// <param name="token">The token to interpret.</param>
/// <param name="stretch">The resulting stretch.</param>
/// <returns>True if it matched any.</returns>
public static bool TryParseStretch(string token, out FontStretch stretch)
{
switch (token.ToLower())
{
case "ultracondensed":
stretch = FontStretch.UltraCondensed;
return true;
case "extracondensed":
stretch = FontStretch.ExtraCondensed;
return true;
case "condensed":
stretch = FontStretch.Condensed;
return true;
case "semicondensed":
case "demicondensed":
stretch = FontStretch.SemiCondensed;
return true;
case "semiexpanded":
case "demiexpanded":
stretch = FontStretch.SemiExpanded;
return true;
case "expanded":
stretch = FontStretch.Expanded;
return true;
case "extraexpanded":
stretch = FontStretch.ExtraExpanded;
return true;
case "ultraexpanded":
stretch = FontStretch.UltraExpanded;
return true;
default:
stretch = FontStretch.Normal;
return false;
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Dashboard.Media.Fonts
{
public enum FontSlant
{
Normal = 0,
Italic = 1,
Oblique = 2,
}
}

View File

@@ -0,0 +1,18 @@
namespace Dashboard.Media.Fonts
{
/// <summary>
/// Enumeration of font stretch values.
/// </summary>
public enum FontStretch
{
UltraCondensed = 500,
ExtraCondensed = 625,
Condensed = 750,
SemiCondensed = 875,
Normal = 1000,
SemiExpanded = 1125,
Expanded = 1250,
ExtraExpanded = 1500,
UltraExpanded = 2000,
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace Dashboard.Media.Fonts
{
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 = _700,
Heavy = _900,
}
}

View File

@@ -0,0 +1,26 @@
namespace Dashboard.Media.Fonts
{
public enum SystemFontFamily
{
/// <summary>
/// A font with serifs, like Times New Roman.
/// </summary>
Serif,
/// <summary>
/// A font without serifs, like Helvetica or Arial.
/// </summary>
Sans,
/// <summary>
/// A monospace font like Courier New.
/// </summary>
Monospace,
/// <summary>
/// A cursive font like Lucida Handwriting.
/// </summary>
Cursive,
/// <summary>
/// An immature font like Comic Sans or Papyrus, nghehehehe.
/// </summary>
Fantasy
}
}

View File

@@ -1,50 +1,45 @@
namespace Quik.Media using OpenTK.Mathematics;
namespace Dashboard.Media
{ {
/// <summary> /// <summary>
/// Glyph properties with metrics based on FreeType glyph metrics. /// Glyph properties with metrics based on FreeType glyph metrics.
/// </summary> /// </summary>
public struct QGlyphMetrics public struct GlyphMetrics
{ {
/// <summary> /// <summary>
/// The code point for the character. /// The code point for the character.
/// </summary> /// </summary>
public int Rune { get; } public int Rune { get; }
/// <summary>
/// Location of the glyph on the atlas.
/// </summary>
public QRectangle Location { get; }
/// <summary> /// <summary>
/// Size of the glyph in units. /// Size of the glyph in units.
/// </summary> /// </summary>
public QVec2 Size { get; } public Vector2 Size { get; }
/// <summary> /// <summary>
/// Bearing vector for horizontal layout. /// Bearing vector for horizontal layout.
/// </summary> /// </summary>
public QVec2 HorizontalBearing { get; } public Vector2 HorizontalBearing { get; }
/// <summary> /// <summary>
/// Bearing vector for vertical layout. /// Bearing vector for vertical layout.
/// </summary> /// </summary>
public QVec2 VerticalBearing { get; } public Vector2 VerticalBearing { get; }
/// <summary> /// <summary>
/// Advance vector for vertical and horizontal layouts. /// Advance vector for vertical and horizontal layouts.
/// </summary> /// </summary>
public QVec2 Advance { get; } public Vector2 Advance { get; }
public QGlyphMetrics( public GlyphMetrics(
int character, int character,
QRectangle location, Vector2 size,
QVec2 size, Vector2 horizontalBearing,
QVec2 horizontalBearing, Vector2 verticalBearing,
QVec2 verticalBearing, Vector2 advance)
QVec2 advance)
{ {
Rune = character; Rune = character;
Location = location;
Size = size; Size = size;
HorizontalBearing = horizontalBearing; HorizontalBearing = horizontalBearing;
VerticalBearing = verticalBearing; VerticalBearing = verticalBearing;

120
Dashboard/Media/Image.cs Normal file
View File

@@ -0,0 +1,120 @@
using System;
namespace Dashboard.Media
{
public abstract class Image : IDisposable
{
public abstract int Width { get; }
public abstract int Height { get; }
public abstract int Depth { get; }
public abstract ImageFormat InternalFormat { get; }
public virtual int MipMapLevels => 0;
public virtual bool Premultiplied => false;
public virtual bool IsSdf => false;
public abstract void LockBits2d(out QImageLock imageLock, QImageLockOptions options);
public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options);
public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth);
public abstract void UnlockBits();
// IDisposable
private bool isDisposed = false;
private void DisposePrivate(bool disposing)
{
if (isDisposed) return;
Dispose(disposing);
isDisposed = true;
}
protected virtual void Dispose(bool disposing) { }
public void Dispose() => DisposePrivate(true);
}
public struct QImageLockOptions
{
public ImageFormat Format { get; }
public bool Premultiply { get; }
public int MipLevel { get; }
public static QImageLockOptions Default { get; } = new QImageLockOptions(ImageFormat.RgbaU8, true, 0);
public QImageLockOptions(ImageFormat format, bool premultiply, int level)
{
Format = format;
Premultiply = premultiply;
MipLevel = level;
}
}
public struct QImageLock
{
public ImageFormat Format { get; }
public int Width { get; }
public int Height { get; }
public int Depth { get; }
public IntPtr ImagePtr { get; }
public QImageLock(ImageFormat format, int width, int height, int depth, IntPtr ptr)
{
Format = format;
Width = width;
Height = height;
Depth = depth;
ImagePtr = ptr;
}
public unsafe void CopyTo(QImageLock destination, int x, int y)
{
if (
Width + x >= destination.Width ||
Height + y >= destination.Height)
{
throw new Exception("Image falls outside the bounds of the destination.");
}
else if (Format != destination.Format)
{
throw new Exception("Image formats must be the same.");
}
int bpp = Format.BytesPerPixel();
for (int i = 0; i < Height; i++)
{
IntPtr srcPtr = (IntPtr)((long)ImagePtr + i * Width * bpp);
long dstPos = x + i * destination.Width;
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
Buffer.MemoryCopy((void*)srcPtr, (void*)dstPtr, Width * bpp, Width * bpp);
}
}
public unsafe void ExtractFrom(QImageLock destination, int x, int y, int width, int height)
{
if (
width != destination.Width ||
height != destination.Height)
{
throw new Exception("Destination is not the same size as the subregion.");
}
else if (x + width > Width || y + height > Height)
{
throw new Exception("The subregion is larger than this image.");
}
else if (Format != destination.Format)
{
throw new Exception("Image formats must be the same.");
}
int bpp = Format.BytesPerPixel();
for (int i = 0; i < height; i++)
{
long srcPos = x + y * i;
IntPtr srcPtr = (IntPtr)((long)ImagePtr + srcPos * bpp);
long dstPos = i * destination.Width;
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
Buffer.MemoryCopy((void*)srcPtr, (void*)dstPtr, width * bpp, width * bpp);
}
}
}
}

View File

@@ -1,8 +1,8 @@
using System; using System;
namespace Quik.Media namespace Dashboard.Media
{ {
public enum QImageFormat public enum ImageFormat
{ {
Undefined, Undefined,
RedU8, RedU8,

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.IO; using System.IO;
namespace Quik.Media namespace Dashboard.Media
{ {
public enum MediaHint public enum MediaHint
{ {

View File

@@ -1,8 +1,9 @@
using OpenTK.Mathematics;
using System; using System;
namespace Quik namespace Dashboard
{ {
public enum MouseButton public enum MouseButton : byte
{ {
Primary = 1 << 0, Primary = 1 << 0,
Secondary = 1 << 1, Secondary = 1 << 1,
@@ -11,15 +12,15 @@ namespace Quik
Auxilliary2 = 1 << 4, Auxilliary2 = 1 << 4,
Auxilliary3 = 1 << 5, Auxilliary3 = 1 << 5,
Auxilliary4 = 1 << 6, Auxilliary4 = 1 << 6,
Auxilliary5 = 1 << 8, Auxilliary5 = 1 << 7,
} }
public struct MouseState public struct MouseState
{ {
public readonly QVec2 AbsolutePosition; public readonly Vector2 AbsolutePosition;
public readonly MouseButton ButtonsDown; public readonly MouseButton ButtonsDown;
public MouseState(QVec2 position, MouseButton down) public MouseState(Vector2 position, MouseButton down)
{ {
AbsolutePosition = position; AbsolutePosition = position;
ButtonsDown = down; ButtonsDown = down;
@@ -28,16 +29,16 @@ namespace Quik
public class MouseButtonEventArgs : EventArgs public class MouseButtonEventArgs : EventArgs
{ {
public QVec2 AbsolutePosition { get; } public Vector2 AbsolutePosition { get; }
public MouseButton Buttons { get; } public MouseButton Buttons { get; }
public MouseButtonEventArgs(QVec2 position, MouseButton buttons) public MouseButtonEventArgs(Vector2 position, MouseButton buttons)
{ {
AbsolutePosition = position; AbsolutePosition = position;
Buttons = buttons; Buttons = buttons;
} }
public QVec2 RelativePosition(QVec2 origin) public Vector2 RelativePosition(Vector2 origin)
{ {
return AbsolutePosition - origin; return AbsolutePosition - origin;
} }
@@ -50,18 +51,18 @@ namespace Quik
public class MouseMoveEventArgs : EventArgs public class MouseMoveEventArgs : EventArgs
{ {
public QVec2 AbsolutePosition { get; } public Vector2 AbsolutePosition { get; }
public QVec2 LastPosition { get; } public Vector2 LastPosition { get; }
public QVec2 Motion { get; } public Vector2 Motion { get; }
public MouseMoveEventArgs(QVec2 position, QVec2 lastPosition) public MouseMoveEventArgs(Vector2 position, Vector2 lastPosition)
{ {
AbsolutePosition = position; AbsolutePosition = position;
LastPosition = lastPosition; LastPosition = lastPosition;
Motion = position - lastPosition; Motion = position - lastPosition;
} }
public QVec2 RelativePosition(QVec2 origin) public Vector2 RelativePosition(Vector2 origin)
{ {
return AbsolutePosition - origin; return AbsolutePosition - origin;
} }

View File

@@ -0,0 +1,460 @@
using Dashboard.VertexGenerator;
using Dashboard.Media;
using OpenTK.Graphics.OpenGL4;
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using OpenTK.Mathematics;
namespace Dashboard.OpenGL
{
public class GL21Driver : IDisposable
{
private int program;
private int v2Position;
private int fZIndex;
private int v2TexPos;
private int fTexLayer;
private int v4Color;
private int m4Transforms;
private int fMaxZ;
private int iEnableSdf;
private int iEnableTexture;
private int iAlphaDiscard;
private int fSdfThreshold;
private int tx2d;
private int tx2darray;
private bool isDiposed;
private readonly Dictionary<DrawCallQueue, DrawData> data = new Dictionary<DrawCallQueue, DrawData>();
private readonly TextureManager textures = new TextureManager();
public bool IsInit { get; private set; } = false;
public event Action<GL21Driver>? OnGCDispose;
public GL21Driver()
{
}
~GL21Driver()
{
Dispose(false);
}
public void Init()
{
if (IsInit) return;
int vs = CreateShader(ShaderType.VertexShader, "Dashboard.res.gl21.vert");
int fs = CreateShader(ShaderType.FragmentShader, "Dashboard.res.gl21.frag");
program = GL.CreateProgram();
GL.AttachShader(program, vs);
GL.AttachShader(program, fs);
GL.LinkProgram(program);
if (CheckProgram(program, out string msg) == false)
{
GraphicsException ex = new GraphicsException("Could not link shader program.");
ex.Data.Add("Program Info Log", msg);
}
GL.DeleteShader(vs);
GL.DeleteShader(fs);
v2Position = GL.GetAttribLocation(program, nameof(v2Position));
fZIndex = GL.GetAttribLocation(program, nameof(fZIndex));
v2TexPos = GL.GetAttribLocation(program, nameof(v2TexPos));
fTexLayer = GL.GetAttribLocation(program, nameof(fTexLayer));
v4Color = GL.GetAttribLocation(program, nameof(v4Color));
m4Transforms = GL.GetUniformLocation(program, nameof(m4Transforms));
fMaxZ = GL.GetUniformLocation(program, nameof(fMaxZ));
fSdfThreshold = GL.GetUniformLocation(program, nameof(fSdfThreshold));
iEnableSdf = GL.GetUniformLocation(program, nameof(iEnableSdf));
iEnableTexture = GL.GetUniformLocation(program, nameof(iEnableTexture));
iAlphaDiscard = GL.GetUniformLocation(program, nameof(iAlphaDiscard));
tx2d = GL.GetUniformLocation(program, nameof(tx2d));
tx2darray = GL.GetUniformLocation(program, nameof(tx2darray));
IsInit = true;
}
private void AssertInit()
{
if (!IsInit) throw new InvalidOperationException("Initialize the driver first.");
}
public void Draw(DrawCallQueue queue, in Rectangle view)
{
AssertInit();
if (!data.TryGetValue(queue, out DrawData? draw))
{
draw = new DrawData(this, queue);
data.Add(queue, draw);
}
// This already binds the vertex array for me.
draw.PrepareFrame();
Vector2 size = view.Size;
Matrix4 viewMatrix = Matrix4.CreateOrthographicOffCenter(view.Left, view.Right, view.Bottom, view.Top, 1, -1);
GL.Viewport(0, 0, (int)view.Size.X, (int)view.Size.Y);
GL.UseProgram(program);
GL.Uniform1(fMaxZ, (float)(queue.ZDepth+1));
GL.Uniform1(fSdfThreshold, 0.5f);
GL.Uniform1(tx2d, 0);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Enable(EnableCap.ScissorTest);
GL.Enable(EnableCap.DepthTest);
GL.DepthFunc(DepthFunction.Less);
foreach (DrawCall call in queue)
{
GL.Scissor(
(int)MathF.Round(call.Bounds.Min.X),
(int)MathF.Round(size.Y - call.Bounds.Max.Y),
(int)MathF.Round(call.Bounds.Size.X),
(int)MathF.Round(call.Bounds.Size.Y));
Matrix4 modelMatrix = Matrix4.CreateTranslation(call.Bounds.Min.X, call.Bounds.Min.Y, 0);
Matrix4 modelView = viewMatrix * modelMatrix;
GL.UniformMatrix4(m4Transforms, false, ref modelView);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, 0);
if (call.Texture != null)
{
GL.Uniform1(iEnableSdf, call.Texture.IsSdf ? 1 : 0);
GL.Uniform1(iAlphaDiscard, 1);
if (call.Texture.Depth > 1)
{
GL.Uniform1(iEnableTexture, 3);
GL.ActiveTexture(TextureUnit.Texture1);
GL.BindTexture(TextureTarget.Texture2D, textures.GetTexture(call.Texture));
}
else
{
GL.Uniform1(iEnableTexture, 2);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, textures.GetTexture(call.Texture));
}
}
else
{
GL.Uniform1(iEnableTexture, 0);
}
GL.DrawElements(PrimitiveType.Triangles, call.Count, DrawElementsType.UnsignedInt, sizeof(int)*call.Start);
}
GL.Disable(EnableCap.ScissorTest);
GL.Disable(EnableCap.DepthTest);
GL.Disable(EnableCap.Blend);
}
public void ClearDrawQueue(DrawCallQueue queue)
{
AssertInit();
if (!data.TryGetValue(queue, out DrawData? draw))
return;
draw.Dispose();
data.Remove(queue);
}
private static int CreateShader(ShaderType type, string name)
{
StreamReader source = new StreamReader(typeof(GL21Driver).Assembly.GetManifestResourceStream(name) ?? throw new Exception("Resource not found."));
string text = source.ReadToEnd();
source.Dispose();
int shader = GL.CreateShader(type);
GL.ShaderSource(shader, text);
GL.CompileShader(shader);
if (CheckShader(shader, out string msg) == false)
{
GraphicsException ex = new GraphicsException($"Failed to compile {type} shader stage.");
ex.Data.Add("Shader Stage", type);
ex.Data.Add("Shader Info Log", msg);
ex.Data.Add("Shader Source", text);
throw ex;
}
return shader;
}
private static bool CheckShader(int shader, out string message)
{
message = string.Empty;
GL.GetShader(shader, ShaderParameter.CompileStatus, out int i);
if (i == 0)
{
message = GL.GetShaderInfoLog(shader);
return false;
}
return true;
}
private static bool CheckProgram(int program, out string message)
{
message = string.Empty;
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out int i);
if (i == 0)
{
message = GL.GetProgramInfoLog(program);
return false;
}
return true;
}
private void Dispose(bool disposing)
{
if (isDiposed) return;
if (!IsInit)
{
isDiposed = true;
return;
}
if (!disposing)
{
if (OnGCDispose == null)
{
throw new Exception("This object must strictly be disposed from the owning thread, not GC");
}
else
{
OnGCDispose(this);
return;
}
}
GL.DeleteProgram(program);
foreach (DrawData datum in data.Values)
{
datum.Dispose();
}
isDiposed = true;
GC.SuppressFinalize(this);
}
public void Dispose() => Dispose(true);
private class DrawData : IDisposable
{
public DrawCallQueue Queue { get; }
public int VertexArray { get; }
private readonly GL21Driver driver;
private int vbo1, vbo2;
private int ebo1, ebo2;
public DrawData(GL21Driver driver, DrawCallQueue queue)
{
Queue = queue;
this.driver = driver;
VertexArray = GL.GenVertexArray();
GL.GenBuffers(1, out vbo1);
GL.GenBuffers(1, out vbo2);
GL.GenBuffers(1, out ebo1);
GL.GenBuffers(1, out ebo2);
isDisposed = false;
}
public void PrepareFrame()
{
int vbo, ebo;
vbo = Swap(ref vbo1, ref vbo2);
ebo = Swap(ref ebo1, ref ebo2);
if (Queue.VertexCount == 0 || Queue.ElementCount == 0)
return;
GL.BindVertexArray(VertexArray);
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
GL.BufferData(BufferTarget.ArrayBuffer, DbVertex.Stride * Queue.VertexCount, Queue.VertexArray, BufferUsageHint.StreamDraw);
GL.VertexAttribPointer(driver.v2Position, 2, VertexAttribPointerType.Float, false, DbVertex.Stride, DbVertex.PositionOffset);
GL.VertexAttribPointer(driver.fZIndex, 1, VertexAttribPointerType.UnsignedInt, false, DbVertex.Stride, DbVertex.ZIndexOffset);
GL.VertexAttribPointer(driver.v2TexPos, 2, VertexAttribPointerType.Float, false, DbVertex.Stride, DbVertex.TextureCoordinatesOffset);
GL.VertexAttribPointer(driver.fTexLayer, 1, VertexAttribPointerType.Float, false, DbVertex.Stride, DbVertex.TextureLayerOffset);
GL.VertexAttribPointer(driver.v4Color, 4, VertexAttribPointerType.Float, true, DbVertex.Stride, DbVertex.ColorOffset);
GL.EnableVertexAttribArray(driver.v2Position);
GL.EnableVertexAttribArray(driver.fZIndex);
GL.EnableVertexAttribArray(driver.v2TexPos);
GL.EnableVertexAttribArray(driver.v4Color);
GL.EnableVertexAttribArray(driver.fTexLayer);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
GL.BufferData(BufferTarget.ElementArrayBuffer, Queue.ElementCount * sizeof(int), Queue.ElementArray, BufferUsageHint.StreamDraw);
int Swap(ref int a, ref int b)
{
a ^= b;
b ^= a;
a ^= b;
return a;
}
}
private bool isDisposed;
public void Dispose()
{
if (isDisposed) return;
GL.DeleteVertexArray(VertexArray);
GL.DeleteBuffer(vbo1);
GL.DeleteBuffer(vbo2);
GL.DeleteBuffer(ebo1);
GL.DeleteBuffer(ebo2);
}
}
}
internal class TextureManager : IDisposable
{
private readonly Dictionary<Image, int> textures = new Dictionary<Image, int>();
private readonly HashSet<Image> imagesNotUsed = new HashSet<Image>();
private bool isDisposed = false;
public void BeginFrame()
{
if (imagesNotUsed.Count > 0)
{
foreach (Image image in imagesNotUsed)
{
GL.DeleteTexture(textures[image]);
}
imagesNotUsed.Clear();
}
foreach (Image image in textures.Keys)
{
imagesNotUsed.Add(image);
}
}
public int GetTexture(Image image)
{
if (textures.TryGetValue(image, out int texture))
{
return texture;
}
if (image.Depth > 1)
{
texture = UploadTexture3d(image);
}
else
{
texture = UploadTexture2d(image);
}
return textures[image] = texture;
}
public int UploadTexture3d(Image image3d)
{
int texture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2DArray, texture);
image3d.LockBits3d(out QImageLock lck, QImageLockOptions.Default);
GL.TexImage3D(TextureTarget.Texture2DArray, 0, PixelInternalFormat.Rgba, lck.Width, lck.Height, lck.Depth, 0, s_InternalFormat[lck.Format], s_PixelType[lck.Format], lck.ImagePtr);
image3d.UnlockBits();
GL.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMagFilter, (int)TextureMinFilter.Linear);
return texture;
}
public int UploadTexture2d(Image image2d)
{
int texture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, texture);
image2d.LockBits2d(out QImageLock lck, QImageLockOptions.Default);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, lck.Width, lck.Height, 0, s_InternalFormat[lck.Format], s_PixelType[lck.Format], lck.ImagePtr);
image2d.UnlockBits();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMinFilter.Linear);
switch (image2d.InternalFormat)
{
case ImageFormat.RedU8:
case ImageFormat.RedF:
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int)All.Red);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int)All.Red);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int)All.Red);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int)All.One);
break;
case ImageFormat.AlphaU8:
case ImageFormat.AlphaF:
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int)All.One);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int)All.One);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int)All.One);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int)All.Alpha);
break;
}
return texture;
}
public void Dispose()
{
if (isDisposed)
return;
isDisposed = true;
int[] ids = textures.Values.ToArray();
GL.DeleteTextures(ids.Length, ref ids[0]);
}
private static readonly Dictionary<ImageFormat, PixelFormat> s_InternalFormat = new Dictionary<ImageFormat, PixelFormat>()
{
[ImageFormat.AlphaF] = PixelFormat.Alpha,
[ImageFormat.AlphaU8] = PixelFormat.Alpha,
[ImageFormat.RedF] = PixelFormat.Red,
[ImageFormat.RedU8] = PixelFormat.Red,
[ImageFormat.RgbF] = PixelFormat.Rgb,
[ImageFormat.RgbU8] = PixelFormat.Rgb,
[ImageFormat.RgbaU8] = PixelFormat.Rgba,
[ImageFormat.RgbaF] = PixelFormat.Rgba,
};
private static readonly Dictionary<ImageFormat, PixelType> s_PixelType = new Dictionary<ImageFormat, PixelType>()
{
[ImageFormat.AlphaF] = PixelType.Float,
[ImageFormat.RedF] = PixelType.Float,
[ImageFormat.RgbF] = PixelType.Float,
[ImageFormat.RgbaF] = PixelType.Float,
[ImageFormat.AlphaU8] = PixelType.UnsignedByte,
[ImageFormat.RedU8] = PixelType.UnsignedByte,
[ImageFormat.RgbU8] = PixelType.UnsignedByte,
[ImageFormat.RgbaU8] = PixelType.UnsignedByte,
};
}
}

View File

@@ -1,10 +1,10 @@
using System; using System;
using static Quik.OpenGL.GLEnum; using OpenTK.Graphics.OpenGL4;
namespace Quik.OpenGL namespace Dashboard.OpenGL
{ {
[System.Serializable] [System.Serializable]
public class GraphicsException : System.Exception public class GraphicsException : Exception
{ {
public GraphicsException() public GraphicsException()
{ {
@@ -16,7 +16,7 @@ namespace Quik.OpenGL
AddExtraData(); AddExtraData();
} }
public GraphicsException(string message, System.Exception inner) : base(message, inner) public GraphicsException(string message, Exception inner) : base(message, inner)
{ {
AddExtraData(); AddExtraData();
} }
@@ -30,12 +30,12 @@ namespace Quik.OpenGL
private void AddExtraData() private void AddExtraData()
{ {
GL.Get(GL_MAJOR_VERSION, out int major); GL.GetInteger(GetPName.MajorVersion, out int major);
GL.Get(GL_MINOR_VERSION, out int minor); GL.GetInteger(GetPName.MinorVersion, out int minor);
string version = GL.GetString(GL_VERSION); string version = GL.GetString(StringName.Version);
string vendor = GL.GetString(GL_VENDOR); string vendor = GL.GetString(StringName.Vendor);
string renderer = GL.GetString(GL_RENDERER); string renderer = GL.GetString(StringName.Renderer);
Data.Add("OpenGL Version", new Version(major, minor)); Data.Add("OpenGL Version", new Version(major, minor));
Data.Add("OpenGL Version String", version); Data.Add("OpenGL Version String", version);

88
Dashboard/PAL/Dash.cs Normal file
View File

@@ -0,0 +1,88 @@
using Dashboard.ImmediateDraw;
using Dashboard.Controls;
using OpenTK.Mathematics;
using System;
namespace Dashboard.PAL
{
/// <summary>
/// An abstraction layer over the UI input and output.
/// </summary>
public class Dash
{
private readonly IDashHandle handle;
private readonly IDbPlatform platform;
public string Title
{
get => platform.PortGetTitle(handle);
set => platform.PortSetTitle(handle, value);
}
public Vector2 Size
{
get => platform.PortGetSize(handle);
set => platform.PortSetSize(handle, value);
}
public Vector2 Position
{
get => platform.PortGetPosition(handle);
set => platform.PortSetPosition(handle, value);
}
public UIBase? UIElement { get; set; }
public bool IsValid => platform.PortIsValid(handle);
public event EventHandler EventRaised
{
add
{
platform.PortSubscribeEvent(handle, value);
}
remove
{
platform.PortUnsubscribeEvent(handle, value);
}
}
public Dash(IDbPlatform platform)
{
this.platform = platform;
handle = platform.CreatePort();
}
bool isDisposed = false;
public void Dispose()
{
if (isDisposed) return;
platform.DestroyPort(handle);
isDisposed = true;
}
public void Focus()
{
platform.PortFocus(handle);
}
public void Paint(DrawList? list = null)
{
if (UIElement == null)
return;
list ??= new DrawList();
list.Clear();
UIElement.Bounds = new Rectangle(Size, new Vector2(0,0));
UIElement.Paint(list);
platform.PortPaint(handle, list);
}
public void Show(bool shown = true)
{
platform.PortShow(handle, shown);
}
}
}

View File

@@ -0,0 +1,62 @@
using Dashboard.ImmediateDraw;
using Dashboard.Media;
using OpenTK.Mathematics;
using System;
namespace Dashboard.PAL
{
/// <summary>
/// An empty interface to statically type Quik port handles.
/// </summary>
public interface IDashHandle
{
}
/// <summary>
/// The primary primary platform abstraction interface for dashboard hosts.
/// </summary>
public interface IDbPlatform : IDisposable
{
/// <summary>
/// The title of the application.
/// </summary>
string? Title { get; set; }
/// <summary>
/// The default icon for the application.
/// </summary>
Image? Icon { get; set; }
/// <summary>
/// The event raised when an event is received.
/// </summary>
event EventHandler? EventRaised;
/// <summary>
/// Raise the events that have been enqueued.
/// </summary>
/// <param name="block">True to block until a new event arrives.</param>
void ProcessEvents(bool block);
/// <summary>
/// Create a window.
/// </summary>
/// <returns>The window instance.</returns>
IDashHandle CreatePort();
void DestroyPort(IDashHandle port);
string PortGetTitle(IDashHandle port);
void PortSetTitle(IDashHandle port, string title);
Vector2 PortGetSize(IDashHandle port);
void PortSetSize(IDashHandle port, Vector2 size);
Vector2 PortGetPosition(IDashHandle port);
void PortSetPosition(IDashHandle port, Vector2 position);
bool PortIsValid(IDashHandle port);
void PortSubscribeEvent(IDashHandle port, EventHandler handler);
void PortUnsubscribeEvent(IDashHandle port, EventHandler handler);
void PortFocus(IDashHandle port);
void PortShow(IDashHandle port, bool shown = true);
void PortPaint(IDashHandle port, DrawList commands);
void GetMaximumImage(out int width, out int height);
void GetMaximumImage(out int width, out int height, out int depth);
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.IO;
using Dashboard.Media.Fonts;
namespace Dashboard.PAL
{
/// <summary>
/// Flags that effect font search criterea.
/// </summary>
[Flags]
public enum FontMatchCriteria
{
None = 0,
Family = 1 << 0,
Slant = 1 << 1,
Weight = 1 << 2,
Stretch = 1 << 3,
All = Family | Slant | Weight | Stretch,
}
/// <summary>
/// An abstraction over the system font database.
/// </summary>
public interface IFontDataBase
{
/// <summary>
/// All the fonts installed in the system.
/// </summary>
IEnumerable<FontFace> All { get; }
public FontFace Serif => GetSystemFontFace(SystemFontFamily.Serif);
public FontFace Sans => GetSystemFontFace(SystemFontFamily.Sans);
public FontFace Monospace => GetSystemFontFace(SystemFontFamily.Monospace);
public FontFace Cursive => GetSystemFontFace(SystemFontFamily.Cursive);
public FontFace Fantasy => GetSystemFontFace(SystemFontFamily.Fantasy);
/// <summary>
/// Search for the given font face.
/// </summary>
/// <param name="prototype">The font face prototype.</param>
/// <param name="criteria">The match criteria</param>
/// <returns>A list of fonts sorted by the closest match first.</returns>
IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All);
/// <summary>
/// Get the font face file info if it exists.
/// </summary>
/// <param name="face">The face to look for.</param>
/// <returns>The file info if it exists.</returns>
FileInfo FontFileInfo(FontFace face);
/// <summary>
/// Open a font face.
/// </summary>
/// <param name="face">The font face to open.</param>
/// <returns>The stream to the font face.</returns>
Stream Open(FontFace face);
/// <summary>
/// Get a system font family.
/// </summary>
/// <param name="family">The family type to look up.</param>
/// <returns>The name of a font in this family.</returns>
FontFace GetSystemFontFace(SystemFontFamily family);
}
}

View File

@@ -0,0 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Dashboard.Media;
namespace Dashboard.PAL
{
public interface IFontFactory
{
bool TryOpen(Stream stream, [NotNullWhen(true)] out Font font);
}
}

View File

@@ -1,9 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Quik.Media; using Dashboard.Media;
using Quik.Typography; using Dashboard.Media.Fonts;
using OpenTK.Mathematics;
namespace Quik namespace Dashboard
{ {
public enum TextAlignment public enum TextAlignment
{ {
@@ -53,11 +54,11 @@ namespace Quik
public abstract class StyleBase public abstract class StyleBase
{ {
public abstract object this[string key] { get; set; } public abstract object? this[string key] { get; set; }
public QColor? Color public Color4? Color
{ {
get => (QColor?)this["color"]; get => (Color4?)this["color"];
set => this["color"] = value; set => this["color"] = value;
} }
@@ -109,9 +110,9 @@ namespace Quik
set => this["list-marker-position"] = value; set => this["list-marker-position"] = value;
} }
public QuikTexture ListMarkerImage public Image? ListMarkerImage
{ {
get => (QuikTexture)this["list-marker-image"]; get => (Image?)this["list-marker-image"];
set => this["list-marker-image"] = value; set => this["list-marker-image"] = value;
} }
@@ -121,15 +122,15 @@ namespace Quik
set => this["stroke-width"] = value; set => this["stroke-width"] = value;
} }
public QColor? StrokeColor public OpenTK.Mathematics.Color4? StrokeColor
{ {
get => (QColor?)this["stroke-color"]; get => (Color4?)this["stroke-color"];
set => this["stroke-color"] = value; set => this["stroke-color"] = value;
} }
public FontInfo Font public FontFace? Font
{ {
get => (FontInfo)this["font"]; get => (FontFace?)this["font"];
set => this["font"] = value; set => this["font"] = value;
} }
@@ -144,9 +145,9 @@ namespace Quik
{ {
private readonly Dictionary<string, object> _keys = new Dictionary<string, object>(); private readonly Dictionary<string, object> _keys = new Dictionary<string, object>();
public override object this[string styleKey] public override object? this[string styleKey]
{ {
get => _keys.TryGetValue(styleKey, out object value) ? value : null; get => _keys.TryGetValue(styleKey, out object? value) ? value : null;
set set
{ {
if (value == null) if (value == null)
@@ -163,11 +164,11 @@ namespace Quik
public Style BaseStyle { get; } public Style BaseStyle { get; }
public override object this[string key] public override object? this[string key]
{ {
get get
{ {
object value = null; object? value = null;
for (int i = _styles.Count; i != 0 && value == null; i--) for (int i = _styles.Count; i != 0 && value == null; i--)
{ {
@@ -208,7 +209,7 @@ namespace Quik
/// <summary> /// <summary>
/// A line stipple pattern. /// A line stipple pattern.
/// </summary> /// </summary>
public struct QuikStipplePattern public struct StipplePattern
{ {
/// <summary> /// <summary>
/// The stipple pitch value. /// The stipple pitch value.
@@ -220,24 +221,24 @@ namespace Quik
/// </summary> /// </summary>
public float DutyCycle; public float DutyCycle;
public QuikStipplePattern(float pitch, float dutyCycle) public StipplePattern(float pitch, float dutyCycle)
{ {
Pitch = pitch; Pitch = pitch;
DutyCycle = dutyCycle; DutyCycle = dutyCycle;
} }
public static QuikStipplePattern None => new QuikStipplePattern(0.0f, 1.0f); public static StipplePattern None => new StipplePattern(0.0f, 1.0f);
} }
/// <summary> /// <summary>
/// Stroke style for lines and borders. /// Stroke style for lines and borders.
/// </summary> /// </summary>
public class QuikStrokeStyle public class StrokeStyle
{ {
/// <summary> /// <summary>
/// Stroke color. /// Stroke color.
/// </summary> /// </summary>
public QColor Color { get; set; } public Color4 Color { get; set; }
/// <summary> /// <summary>
/// Stroke width. /// Stroke width.
@@ -249,11 +250,11 @@ namespace Quik
// /// </summary> // /// </summary>
// public QuikStipplePattern StipplePattern { get; set; } // public QuikStipplePattern StipplePattern { get; set; }
public QuikStrokeStyle() public StrokeStyle()
{ {
} }
public QuikStrokeStyle(QColor color, float width /*, QuikStipplePattern pattern*/) public StrokeStyle(Color4 color, float width /*, QuikStipplePattern pattern*/)
{ {
Color = color; Color = color;
Width = width; Width = width;
@@ -268,8 +269,8 @@ namespace Quik
/// <summary> /// <summary>
/// Fill style for rectangles and the like. /// Fill style for rectangles and the like.
/// </summary> /// </summary>
public class QuikFillStyle public class FillStyle
{ {
public QColor Color { get; set; } public Color4 Color { get; set; }
} }
} }

View File

@@ -0,0 +1,108 @@
using Dashboard.Media;
using Dashboard.Media.Fonts;
using Dashboard.PAL;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Dashboard.Typography
{
/// <summary>
/// The font provider is a caching object that provides fonts for typesetting classes.
/// </summary>
public class FontProvider : IDisposable
{
private Dictionary<FontFace, Font> Fonts { get; } = new Dictionary<FontFace, Font>();
private HashSet<Font> UsedFonts { get; } = new HashSet<Font>();
public readonly FontRasterizerOptions RasterizerOptions;
public IFontDataBase? Database { get; set; }
public IFontFactory? FontFactory { get; set; }
private readonly DbApplication App;
public Font this[FontFace info]
{
get
{
if (!Fonts.TryGetValue(info, out Font? font))
{
using Stream str = Database?.Open(info) ?? throw new Exception("Font could not be found.");
if (FontFactory?.TryOpen(str, out font) ?? false)
{
Fonts.Add(info, font);
}
else
{
throw new Exception("Font not found.");
}
}
UsedFonts.Add(font);
return font;
}
}
public Font this[SystemFontFamily family]
{
get
{
return this[Database?.GetSystemFontFace(family) ?? throw new Exception("No font database.")];
}
}
public FontProvider(DbApplication app, in FontRasterizerOptions options)
{
RasterizerOptions = options;
App = app;
Type? fdb = Type.GetType("Quik.Media.Defaults.FontDataBaseProvider, Quik.Media.Defaults");
if (fdb != null)
{
PropertyInfo? instanceProperty = fdb.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty);
if (instanceProperty != null)
{
Database = (IFontDataBase)instanceProperty.GetValue(null)!;
}
}
Type? ffact = Type.GetType("Quik.Media.Defaults.FreeTypeFontFactory, Quik.Media.Defaults");
if (ffact != null)
{
ConstructorInfo? ctor = ffact.GetConstructor(Array.Empty<Type>());
FontFactory = (IFontFactory?)ctor?.Invoke(null);
}
}
public FontProvider(DbApplication app)
: this(app, FontRasterizerOptions.Default)
{
}
/// <summary>
/// Tracks the use of fonts used by this typesetter and removes any that haven't been referenced since the last cycle.
/// </summary>
public void Collect()
{
// foreach (FontJar jar in Fonts.Values.ToArray())
// {
// if (!UsedFonts.Contains(jar))
// {
// Fonts.Remove(jar.Info);
// }
// }
// UsedFonts.Clear();
}
private bool isDisposed = false;
public void Dispose()
{
if (isDisposed) return;
isDisposed = true;
foreach (Font font in Fonts.Values)
{
font.Dispose();
}
}
}
}

View File

@@ -1,11 +1,12 @@
using OpenTK.Mathematics;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using Quik.Media; using Dashboard.Media;
namespace Quik.Typography namespace Dashboard.Typography
{ {
/// <summary> /// <summary>
/// An atomic horizontal block of text which cannot be further divided. /// An atomic horizontal block of text which cannot be further divided.
@@ -182,7 +183,7 @@ namespace Quik.Typography
int index = 0; int index = 0;
bool firstLine = true; bool firstLine = true;
QVec2 pen = new QVec2(0, -PreSpace); Vector2 pen = new Vector2(0, -PreSpace);
while (index < Blocks.Count) while (index < Blocks.Count)
{ {
@@ -226,7 +227,7 @@ namespace Quik.Typography
pen.Y -= PostSpace; pen.Y -= PostSpace;
group.BoundingBox = new QRectangle(width, 0, 0, pen.Y); group.BoundingBox = new Rectangle(width, pen.Y, 0, 0);
group.Translate(-pen); group.Translate(-pen);
} }
@@ -264,9 +265,9 @@ namespace Quik.Typography
TypesetGroup group, TypesetGroup group,
Queue<HorizontalTextBlock> line, Queue<HorizontalTextBlock> line,
float interblockWs, float interblockWs,
ref QVec2 pen) ref Vector2 pen)
{ {
QVec2 penpal = pen; Vector2 penpal = pen;
while (line.TryDequeue(out HorizontalTextBlock block)) while (line.TryDequeue(out HorizontalTextBlock block))
{ {
@@ -353,15 +354,15 @@ namespace Quik.Typography
public struct TypesetCharacter public struct TypesetCharacter
{ {
public int Character; public int Character;
public QuikTexture Texture; public Image Texture;
public QRectangle Position; public Rectangle Position;
public QRectangle UV; public Rectangle UV;
public TypesetCharacter( public TypesetCharacter(
int chr, int chr,
QuikTexture texture, Image texture,
in QRectangle position, in Rectangle position,
in QRectangle uv) in Rectangle uv)
{ {
Character = chr; Character = chr;
Texture = texture; Texture = texture;
@@ -375,7 +376,7 @@ namespace Quik.Typography
private int _count = 0; private int _count = 0;
private TypesetCharacter[] _array = Array.Empty<TypesetCharacter>(); private TypesetCharacter[] _array = Array.Empty<TypesetCharacter>();
public QRectangle BoundingBox; public Rectangle BoundingBox;
public int Count => _count; public int Count => _count;
@@ -396,7 +397,7 @@ namespace Quik.Typography
_count = 0; _count = 0;
} }
public void Translate(QVec2 offset) public void Translate(Vector2 offset)
{ {
BoundingBox.Translate(offset); BoundingBox.Translate(offset);

View File

@@ -0,0 +1,182 @@
using OpenTK.Mathematics;
using Dashboard.ImmediateDraw;
using Dashboard.Media;
using System;
using System.Collections.Generic;
using System.Text;
namespace Dashboard.Typography
{
public static class Typesetter
{
private ref struct LineEnumerator
{
private ReadOnlySpan<char> Entire, Segment;
private bool Final;
public ReadOnlySpan<char> Current => Segment;
public LineEnumerator(ReadOnlySpan<char> value)
{
Entire = value;
Segment = ReadOnlySpan<char>.Empty;
Final = false;
}
public void Reset()
{
Segment = ReadOnlySpan<char>.Empty;
Final = false;
}
public bool MoveNext()
{
if (Final)
{
return false;
}
else if (Segment == ReadOnlySpan<char>.Empty)
{
int index = Entire.IndexOf('\n');
if (index == -1)
{
Segment = Entire;
}
else
{
Segment = Entire.Slice(0, index);
}
return true;
}
else
{
Entire.Overlaps(Segment, out int offset);
if (offset + Segment.Length >= Entire.Length)
{
return false;
}
ReadOnlySpan<char> rest = Entire.Slice(offset + Segment.Length + 1);
int index = rest.IndexOf('\n');
if (index == -1)
{
Segment = rest;
Final = true;
}
else
{
Segment = rest.Slice(0, index);
}
return true;
}
}
}
public static Vector2 MeasureHorizontal(ReadOnlySpan<char> str, float size, Font font)
{
var enumerator = new LineEnumerator(str);
float width = 0.0f;
float height = 0.0f;
while (enumerator.MoveNext())
{
ReadOnlySpan<char> line = enumerator.Current;
float lineHeight = 0.0f;
foreach (Rune r in line.EnumerateRunes())
{
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
width += glyph.Metrics.Advance.X;
lineHeight = Math.Max(lineHeight, glyph.Metrics.Size.Y);
}
height += lineHeight;
}
return new Vector2(width, height);
}
public static void TypesetHorizontalDirect(this DrawList list, ReadOnlySpan<char> str, Vector2 origin, float size, Font font)
{
Dictionary<Image, FontDrawInfo> drawInfo = new Dictionary<Image, FontDrawInfo>();
var enumerator = new LineEnumerator(str);
Vector2 pen = origin;
while (enumerator.MoveNext())
{
ReadOnlySpan<char> line = enumerator.Current;
float rise = 0.0f;
float fall = 0.0f;
// Find out all the code pages required, and the line height.
foreach (Rune r in line.EnumerateRunes())
{
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
float crise = glyph.Metrics.HorizontalBearing.Y;
float cfall = glyph.Metrics.Size.Y - crise;
rise = Math.Max(crise, rise);
fall = Math.Max(cfall, fall);
}
pen += new Vector2(0, rise);
foreach (Rune r in line.EnumerateRunes())
{
FontDrawInfo info;
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
ref readonly GlyphMetrics metrics = ref glyph.Metrics;
Image? image = glyph.Image;
if (image == null)
{
pen += new Vector2(metrics.Advance.X, 0);
continue;
}
if (!drawInfo.TryGetValue(image, out info))
{
info = new FontDrawInfo();
info.Image = image;
info.rectangles = new List<Rectangle>();
drawInfo[image] = info;
}
Rectangle dest = new Rectangle(
pen + new Vector2(metrics.HorizontalBearing.X + metrics.Size.X, metrics.Size.Y - metrics.HorizontalBearing.Y),
pen + new Vector2(metrics.HorizontalBearing.X, -metrics.HorizontalBearing.Y));
info.rectangles.Add(dest);
info.rectangles.Add(glyph.UVs);
pen.X += metrics.Advance.X;
}
pen.X = origin.X;
pen.Y += fall;
}
// Now for each rectangle we can dispatch draw calls.
foreach (FontDrawInfo info in drawInfo.Values)
{
list.Image(info.Image, info.rectangles.ToArray(), true);
}
}
private struct FontDrawInfo
{
public Image Image;
public List<Rectangle> rectangles;
}
}
}

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Text; using System.Text;
namespace Quik.Typography namespace Dashboard.Typography
{ {
public static class UnicodeUtil public static class UnicodeUtil
{ {

View File

@@ -1,37 +1,44 @@
using OpenTK.Mathematics;
using System.Diagnostics; using System.Diagnostics;
namespace Quik.VertexGenerator namespace Dashboard.VertexGenerator
{ {
/// <summary> /// <summary>
/// Represents a GPU vertex. /// Represents a GPU vertex.
/// </summary> /// </summary>
[DebuggerDisplay("XY={Position} RGBA={Color}, UV={TextureCoordinates}")] [DebuggerDisplay("XY={Position} RGBA={Color}, UV={TextureCoordinates}")]
public struct QuikVertex public struct DbVertex
{ {
/// <summary> /// <summary>
/// Position value. /// Position value.
/// </summary> /// </summary>
public QVec2 Position; public Vector2 Position;
/// <summary> /// <summary>
/// Texture Coordinates. /// Texture Coordinates.
/// </summary> /// </summary>
public QVec2 TextureCoordinates; public Vector2 TextureCoordinates;
/// <summary> /// <summary>
/// Per vertex color value. /// Per vertex color value.
/// </summary> /// </summary>
public QColor Color; public Color4 Color;
/// <summary> /// <summary>
/// Per vertex depth index value. /// Per vertex depth index value.
/// </summary> /// </summary>
public int ZIndex; public int ZIndex;
/// <summary>
/// The texture layer to draw for 3d images.
/// </summary>
public float TextureLayer;
public static int PositionOffset => 0; public static int PositionOffset => 0;
public static unsafe int TextureCoordinatesOffset => sizeof(QVec2); public static unsafe int TextureCoordinatesOffset => sizeof(Vector2);
public static unsafe int ColorOffset => 2 * sizeof(QVec2); public static unsafe int ColorOffset => 2 * sizeof(Vector2);
public static unsafe int ZIndexOffset => ColorOffset + sizeof(QColor); public static unsafe int ZIndexOffset => ColorOffset + sizeof(Color4);
public static unsafe int Stride => sizeof(QuikVertex); public static unsafe int TextureLayerOffset => ZIndexOffset + sizeof(int);
public static unsafe int Stride => sizeof(DbVertex);
} }
} }

View File

@@ -1,22 +1,23 @@
using Dashboard.Media;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Quik.VertexGenerator namespace Dashboard.VertexGenerator
{ {
public class DrawQueue : IEnumerable<DrawCall> public class DrawCallQueue : IEnumerable<DrawCall>
{ {
private readonly RefList<QuikVertex> _vertices = new RefList<QuikVertex>(); private readonly RefList<DbVertex> _vertices = new RefList<DbVertex>();
private readonly RefList<int> _elements = new RefList<int>(); private readonly RefList<int> _elements = new RefList<int>();
private readonly List<DrawCall> _drawCalls = new List<DrawCall>(); private readonly List<DrawCall> _drawCalls = new List<DrawCall>();
private int _start; private int _start;
private int _baseOffset; private int _baseOffset;
private QRectangle _bounds; private Rectangle _bounds;
private QuikTexture _texture; private Image? _texture;
public int ZDepth { get; private set; } public int ZDepth { get; private set; }
public QuikVertex[] VertexArray => _vertices.InternalArray; public DbVertex[] VertexArray => _vertices.InternalArray;
public int VertexCount => _vertices.Count; public int VertexCount => _vertices.Count;
public int[] ElementArray => _elements.InternalArray; public int[] ElementArray => _elements.InternalArray;
public int ElementCount => _elements.Count; public int ElementCount => _elements.Count;
@@ -32,7 +33,7 @@ namespace Quik.VertexGenerator
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void StartDrawCall(in QRectangle bounds, QuikTexture texture, int baseOffset) public void StartDrawCall(in Rectangle bounds, Image? texture, int baseOffset)
{ {
_start = ElementCount; _start = ElementCount;
_texture = texture; _texture = texture;
@@ -41,16 +42,16 @@ namespace Quik.VertexGenerator
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void StartDrawCall(in QRectangle bounds) => StartDrawCall(bounds, null, _vertices.Count); public void StartDrawCall(in Rectangle bounds) => StartDrawCall(bounds, null, _vertices.Count);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void StartDrawCall(in QRectangle bounds, int baseOffset) => StartDrawCall(bounds, null, baseOffset); public void StartDrawCall(in Rectangle bounds, int baseOffset) => StartDrawCall(bounds, null, baseOffset);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void StartDrawCall(in QRectangle bounds, QuikTexture texture) => StartDrawCall(bounds, texture, _vertices.Count); public void StartDrawCall(in Rectangle bounds, Image texture) => StartDrawCall(bounds, texture, _vertices.Count);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddVertex(in QuikVertex vertex) public void AddVertex(in DbVertex vertex)
{ {
_vertices.Add(in vertex); _vertices.Add(in vertex);
} }
@@ -98,10 +99,10 @@ namespace Quik.VertexGenerator
{ {
public int Start { get; } public int Start { get; }
public int Count { get; } public int Count { get; }
public QRectangle Bounds { get; } public Rectangle Bounds { get; }
public QuikTexture Texture { get; } public Image? Texture { get; }
public DrawCall(int start, int count, in QRectangle bounds, QuikTexture texture) public DrawCall(int start, int count, in Rectangle bounds, Image? texture)
{ {
Start = start; Start = start;
Count = count; Count = count;

View File

@@ -1,12 +1,12 @@
using System; using System;
namespace Quik.VertexGenerator namespace Dashboard.VertexGenerator
{ {
/// <summary> /// <summary>
/// A small list which whose items can be used by reference. /// A small list which whose items can be used by reference.
/// </summary> /// </summary>
/// <typeparam name="T">Container type.</typeparam> /// <typeparam name="T">Container type.</typeparam>
public class RefList<T> internal class RefList<T>
{ {
private T[] _array = Array.Empty<T>(); private T[] _array = Array.Empty<T>();
private int _count = 0; private int _count = 0;

View File

@@ -1,27 +1,38 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Quik.CommandMachine; using Dashboard.ImmediateDraw;
using Dashboard.Media;
using OpenTK.Mathematics;
namespace Quik.VertexGenerator namespace Dashboard.VertexGenerator
{ {
public class VertexGeneratorEngine : CommandEngine public class VertexDrawingEngine : DrawingEngine
{ {
public DrawQueue DrawQueue { get; } = new DrawQueue(); public DrawCallQueue DrawQueue { get; } = new DrawCallQueue();
/// <summary> /// <summary>
/// Granularity for rounded geometry. /// Granularity for rounded geometry.
/// </summary> /// </summary>
protected float CurveGranularity => protected float CurveGranularity =>
(Style["-vertex-curve-granularity"] is float value) ? value : 1.0f; (Style["-vertex-curve-granularity"] is float value) ? value : 1.0f;
protected QuikVertex StrokeVertex => new QuikVertex() protected bool BlendTextures =>
(Style["-vertex-blend-textures"] is bool value) ? value : false;
protected DbVertex StrokeVertex => new DbVertex()
{ {
ZIndex = Style.ZIndex ?? this.ZIndex, ZIndex = Style.ZIndex ?? this.ZIndex,
Color = Style.StrokeColor ?? QColor.Black, Color = Style.StrokeColor ?? Color4.Black,
}; };
protected QuikVertex FillVertex => new QuikVertex() protected DbVertex FillVertex => new DbVertex()
{ {
ZIndex = Style.ZIndex ?? this.ZIndex, ZIndex = Style.ZIndex ?? this.ZIndex,
Color = Style.Color ?? QColor.White, Color = Style.Color ?? Color4.White,
};
protected DbVertex ImageVertex => new DbVertex()
{
ZIndex = Style.ZIndex ?? this.ZIndex,
Color = BlendTextures ? (Style.Color ?? Color4.White) : Color4.White,
}; };
public override void Reset() public override void Reset()
@@ -30,7 +41,7 @@ namespace Quik.VertexGenerator
DrawQueue.Clear(); DrawQueue.Clear();
} }
protected override void ChildProcessCommand(Command name, CommandQueue queue) protected override void ChildProcessCommand(Command name, DrawQueue queue)
{ {
base.ChildProcessCommand(name, queue); base.ChildProcessCommand(name, queue);
@@ -39,6 +50,7 @@ namespace Quik.VertexGenerator
case Command.Line: LineProc(queue); break; case Command.Line: LineProc(queue); break;
case Command.Bezier: BezierProc(queue); break; case Command.Bezier: BezierProc(queue); break;
case Command.Rectangle: RectangleProc(queue); break; case Command.Rectangle: RectangleProc(queue); break;
case Command.Image: ImageProc(queue); break;
default: break; default: break;
} }
} }
@@ -54,8 +66,8 @@ namespace Quik.VertexGenerator
return (int) Math.Ceiling(arc * radius * CurveGranularity); return (int) Math.Ceiling(arc * radius * CurveGranularity);
} }
private readonly List<QLine> LineList = new List<QLine>(); private readonly List<Line> LineList = new List<Line>();
private void LineProc(CommandQueue queue) private void LineProc(DrawQueue queue)
{ {
Frame frame = queue.Dequeue(); Frame frame = queue.Dequeue();
@@ -67,12 +79,12 @@ namespace Quik.VertexGenerator
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
frame = queue.Dequeue(); frame = queue.Dequeue();
LineList.Add((QLine)frame); LineList.Add((Line)frame);
} }
} }
else else
{ {
LineList.Add((QLine)frame); LineList.Add((Line)frame);
} }
float width = Style.StrokeWidth ?? 1; float width = Style.StrokeWidth ?? 1;
@@ -81,7 +93,7 @@ namespace Quik.VertexGenerator
LineInfo prevBase, nextBase = default; LineInfo prevBase, nextBase = default;
for (int i = 0; i < LineList.Count; i++) for (int i = 0; i < LineList.Count; i++)
{ {
QLine line = LineList[i]; Line line = LineList[i];
// A line segment needs a start cap if it is the first segment in // A line segment needs a start cap if it is the first segment in
// the list, or the last end point is not the current start point. // the list, or the last end point is not the current start point.
bool isStart = (i == 0 || line.Start != LineList[i - 1].End); bool isStart = (i == 0 || line.Start != LineList[i - 1].End);
@@ -112,11 +124,11 @@ namespace Quik.VertexGenerator
DrawQueue.EndDrawCall(); DrawQueue.EndDrawCall();
} }
private LineInfo GenerateLineSegment(in QLine line) private LineInfo GenerateLineSegment(in Line line)
{ {
QuikVertex vertex = StrokeVertex; DbVertex vertex = StrokeVertex;
QuikVertex a, b, c, d; DbVertex a, b, c, d;
QVec2 normal = line.Normal(); Vector2 normal = line.Normal();
float width = Style.StrokeWidth ?? 1; float width = Style.StrokeWidth ?? 1;
a = b = c = d = vertex; a = b = c = d = vertex;
@@ -136,20 +148,20 @@ namespace Quik.VertexGenerator
} }
private void GenerateJoint( private void GenerateJoint(
in QVec2 center, in Vector2 center,
in QVec2 prevNormal, in Vector2 prevNormal,
in QVec2 nextNormal, in Vector2 nextNormal,
in LineInfo prevInfo, in LineInfo prevInfo,
in LineInfo nextInfo) in LineInfo nextInfo)
{ {
// Figure out which side needs the joint. // Figure out which side needs the joint.
QVec2 meanNormal = 0.5f * (prevNormal + nextNormal); Vector2 meanNormal = 0.5f * (prevNormal + nextNormal);
QVec2 meanTangent = new QVec2(meanNormal.Y, -meanNormal.X); Vector2 meanTangent = new Vector2(meanNormal.Y, -meanNormal.X);
QVec2 positiveEdge = ((center + nextNormal) - (center + prevNormal)).Normalize(); Vector2 positiveEdge = ((center + nextNormal) - (center + prevNormal)).Normalized();
QVec2 negativeEdge = ((center - nextNormal) - (center - prevNormal)).Normalize(); Vector2 negativeEdge = ((center - nextNormal) - (center - prevNormal)).Normalized();
float positive, negative; float positive, negative;
positive = QVec2.Dot(meanTangent, positiveEdge); positive = Vector2.Dot(meanTangent, positiveEdge);
negative = QVec2.Dot(meanNormal, negativeEdge); negative = Vector2.Dot(meanNormal, negativeEdge);
if (positive == negative) if (positive == negative)
{ {
@@ -159,9 +171,9 @@ namespace Quik.VertexGenerator
return; return;
} }
QuikVertex vertex = StrokeVertex; DbVertex vertex = StrokeVertex;
float radius = Style.StrokeWidth/2 ?? 0.5f; float radius = Style.StrokeWidth/2 ?? 0.5f;
float arc = MathF.Acos(QVec2.Dot(prevNormal, nextNormal)); float arc = MathF.Acos(Vector2.Dot(prevNormal, nextNormal));
int resolution = GetRoundingResolution(radius, arc); int resolution = GetRoundingResolution(radius, arc);
bool isNegative = positive < negative; bool isNegative = positive < negative;
@@ -187,10 +199,10 @@ namespace Quik.VertexGenerator
float cos = MathF.Cos(angle); float cos = MathF.Cos(angle);
float sin = MathF.Sin(angle); float sin = MathF.Sin(angle);
QVec2 displacement; Vector2 displacement;
if (isNegative) if (isNegative)
{ {
displacement = new QVec2() displacement = new Vector2()
{ {
X = -prevNormal.X * cos + prevNormal.Y * sin, X = -prevNormal.X * cos + prevNormal.Y * sin,
Y = -prevNormal.X * sin - prevNormal.Y * cos Y = -prevNormal.X * sin - prevNormal.Y * cos
@@ -198,7 +210,7 @@ namespace Quik.VertexGenerator
} }
else else
{ {
displacement = new QVec2() displacement = new Vector2()
{ {
X = nextNormal.X * cos - nextNormal.Y * sin, X = nextNormal.X * cos - nextNormal.Y * sin,
Y = nextNormal.X * sin + nextNormal.Y * cos Y = nextNormal.X * sin + nextNormal.Y * cos
@@ -222,13 +234,13 @@ namespace Quik.VertexGenerator
} }
private void GenerateCap( private void GenerateCap(
in QVec2 center, in Vector2 center,
in QVec2 normal, in Vector2 normal,
in LineInfo info, in LineInfo info,
bool endCap) bool endCap)
{ {
int lastIndex, startIndex; int lastIndex, startIndex;
QuikVertex vertex = StrokeVertex; DbVertex vertex = StrokeVertex;
float radius = Style.StrokeWidth ?? 1.0f; float radius = Style.StrokeWidth ?? 1.0f;
int resolution = GetRoundingResolution(radius, MathF.PI); int resolution = GetRoundingResolution(radius, MathF.PI);
@@ -250,10 +262,10 @@ namespace Quik.VertexGenerator
float cos = MathF.Cos(angle); float cos = MathF.Cos(angle);
float sin = MathF.Sin(angle); float sin = MathF.Sin(angle);
QVec2 displacement; Vector2 displacement;
if (endCap) if (endCap)
{ {
displacement = new QVec2() displacement = new Vector2()
{ {
X = normal.X * cos + normal.Y * sin, X = normal.X * cos + normal.Y * sin,
Y = -normal.X * sin + normal.Y * cos Y = -normal.X * sin + normal.Y * cos
@@ -261,7 +273,7 @@ namespace Quik.VertexGenerator
} }
else else
{ {
displacement = new QVec2() displacement = new Vector2()
{ {
X = normal.X * cos - normal.Y * sin, X = normal.X * cos - normal.Y * sin,
Y = normal.X * sin + normal.Y * cos Y = normal.X * sin + normal.Y * cos
@@ -279,8 +291,8 @@ namespace Quik.VertexGenerator
} }
} }
private readonly List<QBezier> BezierList = new List<QBezier>(); private readonly List<Bezier> BezierList = new List<Bezier>();
private void BezierProc(CommandQueue queue) private void BezierProc(DrawQueue queue)
{ {
Frame a = queue.Dequeue(); Frame a = queue.Dequeue();
Frame b; Frame b;
@@ -296,11 +308,11 @@ namespace Quik.VertexGenerator
b = queue.Dequeue(); b = queue.Dequeue();
BezierList.Add( BezierList.Add(
new QBezier( new Bezier(
new QVec2(a.GetF(0), a.GetF(1)), new Vector2(a.GetF(0), a.GetF(1)),
new QVec2(b.GetF(0), b.GetF(1)), new Vector2(b.GetF(0), b.GetF(1)),
new QVec2(b.GetF(2), b.GetF(3)), new Vector2(b.GetF(2), b.GetF(3)),
new QVec2(a.GetF(2), a.GetF(3)) new Vector2(a.GetF(2), a.GetF(3))
) )
); );
} }
@@ -310,11 +322,11 @@ namespace Quik.VertexGenerator
b = queue.Dequeue(); b = queue.Dequeue();
BezierList.Add( BezierList.Add(
new QBezier( new Bezier(
new QVec2(a.GetF(0), a.GetF(1)), new Vector2(a.GetF(0), a.GetF(1)),
new QVec2(b.GetF(0), b.GetF(1)), new Vector2(b.GetF(0), b.GetF(1)),
new QVec2(b.GetF(2), b.GetF(3)), new Vector2(b.GetF(2), b.GetF(3)),
new QVec2(a.GetF(2), a.GetF(3)) new Vector2(a.GetF(2), a.GetF(3))
) )
); );
} }
@@ -325,7 +337,7 @@ namespace Quik.VertexGenerator
LineInfo prevBase, nextBase = default; LineInfo prevBase, nextBase = default;
for (int i = 0; i < LineList.Count; i++) for (int i = 0; i < LineList.Count; i++)
{ {
QBezier bezier = BezierList[i]; Bezier bezier = BezierList[i];
// A line segment needs a start cap if it is the first segment in // A line segment needs a start cap if it is the first segment in
// the list, or the last end point is not the current start point. // the list, or the last end point is not the current start point.
bool isStart = (i == 0 || bezier.Start != BezierList[i - 1].End); bool isStart = (i == 0 || bezier.Start != BezierList[i - 1].End);
@@ -356,19 +368,19 @@ namespace Quik.VertexGenerator
DrawQueue.EndDrawCall(); DrawQueue.EndDrawCall();
} }
private LineInfo GenerateBezierSegment(in QBezier bezier) private LineInfo GenerateBezierSegment(in Bezier bezier)
{ {
QVec2 startTangent = bezier.GetBezierTangent(0); Vector2 startTangent = bezier.GetBezierTangent(0);
QVec2 endTangent = bezier.GetBezierTangent(1); Vector2 endTangent = bezier.GetBezierTangent(1);
QVec2 startNormal = new QVec2(-startTangent.Y, startTangent.X).Normalize(); Vector2 startNormal = new Vector2(-startTangent.Y, startTangent.X).Normalized();
QVec2 endNormal = new QVec2(-endTangent.Y, endTangent.X).Normalize(); Vector2 endNormal = new Vector2(-endTangent.Y, endTangent.X).Normalized();
float width = Style.StrokeWidth ?? 1; float width = Style.StrokeWidth ?? 1;
float radius = 0.5f * width; float radius = 0.5f * width;
int resolution = GetRoundingResolution(radius, bezier.RasterizationArc); int resolution = GetRoundingResolution(radius, bezier.RasterizationArc);
DrawQueue.RestoreOffset(); DrawQueue.RestoreOffset();
QuikVertex v = StrokeVertex; DbVertex v = StrokeVertex;
int vbase = DrawQueue.BaseOffset; int vbase = DrawQueue.BaseOffset;
int index = 2; int index = 2;
@@ -380,9 +392,9 @@ namespace Quik.VertexGenerator
for (int i = 0; i < resolution; i++, index += 2) for (int i = 0; i < resolution; i++, index += 2)
{ {
float t = (i + 1.0f) / resolution; float t = (i + 1.0f) / resolution;
QVec2 at = bezier.GetBezierTangent(t).Normalize(); Vector2 at = bezier.GetBezierTangent(t).Normalized();
QVec2 a = bezier.GetBezierPoint(t); Vector2 a = bezier.GetBezierPoint(t);
QVec2 an = radius * new QVec2(-at.Y, at.X); Vector2 an = radius * new Vector2(-at.Y, at.X);
v.Position = a + an; v.Position = a + an;
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
@@ -396,8 +408,8 @@ namespace Quik.VertexGenerator
return new LineInfo(vbase, 0, 1, index - 2, index - 1); return new LineInfo(vbase, 0, 1, index - 2, index - 1);
} }
private readonly List<QRectangle> RectangleList = new List<QRectangle>(); private readonly List<Rectangle> RectangleList = new List<Rectangle>();
private void RectangleProc(CommandQueue queue) private void RectangleProc(DrawQueue queue)
{ {
Frame frame = queue.Dequeue(); Frame frame = queue.Dequeue();
RectangleList.Clear(); RectangleList.Clear();
@@ -407,12 +419,12 @@ namespace Quik.VertexGenerator
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
frame = queue.Dequeue(); frame = queue.Dequeue();
RectangleList.Add((QRectangle)frame); RectangleList.Add((Rectangle)frame);
} }
} }
else else
{ {
RectangleList.Add((QRectangle)frame); RectangleList.Add((Rectangle)frame);
} }
float stroke = Style.StrokeWidth ?? 1.0f; float stroke = Style.StrokeWidth ?? 1.0f;
@@ -420,10 +432,10 @@ namespace Quik.VertexGenerator
DrawQueue.StartDrawCall(Viewport); DrawQueue.StartDrawCall(Viewport);
for (int i = 0; i < RectangleList.Count; i++) for (int i = 0; i < RectangleList.Count; i++)
{ {
QRectangle outer = RectangleList[i]; Rectangle outer = RectangleList[i];
QRectangle inner = new QRectangle( Rectangle inner = new Rectangle(
outer.Right - stroke, outer.Top - stroke, outer.Right - stroke, outer.Bottom - stroke,
outer.Left + stroke, outer.Bottom + stroke); outer.Left + stroke, outer.Top + stroke);
GenerateRectangleBase(inner, Math.Max(radius - stroke, 0.0f)); GenerateRectangleBase(inner, Math.Max(radius - stroke, 0.0f));
@@ -445,7 +457,7 @@ namespace Quik.VertexGenerator
DrawQueue.EndDrawCall(); DrawQueue.EndDrawCall();
} }
private void GenerateRectangleBase(in QRectangle rectangle, float radius) private void GenerateRectangleBase(in Rectangle rectangle, float radius)
{ {
/* /*
+--j-------i--+ +--j-------i--+
@@ -466,16 +478,16 @@ namespace Quik.VertexGenerator
// Draw center rectangle. // Draw center rectangle.
QVec2 aPos, bPos, cPos, dPos; Vector2 aPos, bPos, cPos, dPos;
QuikVertex v = FillVertex; DbVertex v = FillVertex;
aPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius); aPos = v.Position = new Vector2(rectangle.Left + radius, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
bPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius); bPos = v.Position = new Vector2(rectangle.Right - radius, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
cPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - radius); cPos = v.Position = new Vector2(rectangle.Right - radius, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
dPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - radius); dPos = v.Position = new Vector2(rectangle.Left + radius, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2); DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2);
@@ -486,9 +498,9 @@ namespace Quik.VertexGenerator
// Draw south rectangle. // Draw south rectangle.
v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom); v.Position = new Vector2(rectangle.Left + radius, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom); v.Position = new Vector2(rectangle.Right - radius, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1); DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1);
@@ -496,9 +508,9 @@ namespace Quik.VertexGenerator
// Draw east rectangle. // Draw east rectangle.
v.Position = new QVec2(rectangle.Right, rectangle.Bottom + radius); v.Position = new Vector2(rectangle.Right, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top - radius); v.Position = new Vector2(rectangle.Right, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(7); DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(7);
@@ -506,9 +518,9 @@ namespace Quik.VertexGenerator
// Draw north rectangle. // Draw north rectangle.
v.Position = new QVec2(rectangle.Right - radius, rectangle.Top); v.Position = new Vector2(rectangle.Right - radius, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Top); v.Position = new Vector2(rectangle.Left + radius, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(3); DrawQueue.AddElement(2); DrawQueue.AddElement(8); DrawQueue.AddElement(3); DrawQueue.AddElement(2); DrawQueue.AddElement(8);
@@ -516,9 +528,9 @@ namespace Quik.VertexGenerator
// Draw west rectangle. // Draw west rectangle.
v.Position = new QVec2(rectangle.Left, rectangle.Top - radius); v.Position = new Vector2(rectangle.Left, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius); v.Position = new Vector2(rectangle.Left, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(11); DrawQueue.AddElement(0); DrawQueue.AddElement(3); DrawQueue.AddElement(11); DrawQueue.AddElement(0); DrawQueue.AddElement(3);
@@ -535,7 +547,7 @@ namespace Quik.VertexGenerator
float xoff = MathF.Cos(theta) * radius; float xoff = MathF.Cos(theta) * radius;
float yoff = MathF.Sin(theta) * radius; float yoff = MathF.Sin(theta) * radius;
v.Position = cPos + new QVec2(xoff, yoff); v.Position = cPos + new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(2); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); DrawQueue.AddElement(2); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
} }
@@ -550,7 +562,7 @@ namespace Quik.VertexGenerator
float xoff = -MathF.Sin(theta) * radius; float xoff = -MathF.Sin(theta) * radius;
float yoff = MathF.Cos(theta) * radius; float yoff = MathF.Cos(theta) * radius;
v.Position = dPos + new QVec2(xoff, yoff); v.Position = dPos + new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(3); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); DrawQueue.AddElement(3); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
} }
@@ -565,7 +577,7 @@ namespace Quik.VertexGenerator
float xoff = -MathF.Cos(theta) * radius; float xoff = -MathF.Cos(theta) * radius;
float yoff = -MathF.Sin(theta) * radius; float yoff = -MathF.Sin(theta) * radius;
v.Position = aPos + new QVec2(xoff, yoff); v.Position = aPos + new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(0); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); DrawQueue.AddElement(0); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
} }
@@ -580,14 +592,14 @@ namespace Quik.VertexGenerator
float xoff = -MathF.Sin(theta) * radius; float xoff = -MathF.Sin(theta) * radius;
float yoff = MathF.Cos(theta) * radius; float yoff = MathF.Cos(theta) * radius;
v.Position = bPos + new QVec2(xoff, yoff); v.Position = bPos + new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(1); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); DrawQueue.AddElement(1); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
} }
DrawQueue.AddElement(1); DrawQueue.AddElement(previous); DrawQueue.AddElement(6); DrawQueue.AddElement(1); DrawQueue.AddElement(previous); DrawQueue.AddElement(6);
} }
private void GenerateRectangleStripStraight(in QRectangle rectangle) private void GenerateRectangleStripStraight(in Rectangle rectangle)
{ {
/* /*
h---------g h---------g
@@ -604,27 +616,27 @@ namespace Quik.VertexGenerator
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
*/ */
QuikVertex v = StrokeVertex; DbVertex v = StrokeVertex;
float stroke = Style.StrokeWidth ?? 1.0f; float stroke = Style.StrokeWidth ?? 1.0f;
DrawQueue.RestoreOffset(); DrawQueue.RestoreOffset();
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom + stroke); v.Position = new Vector2(rectangle.Left + stroke, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + stroke); v.Position = new Vector2(rectangle.Right - stroke, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top - stroke); v.Position = new Vector2(rectangle.Right - stroke, rectangle.Top + stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - stroke); v.Position = new Vector2(rectangle.Left + stroke, rectangle.Top + stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom); v.Position = new Vector2(rectangle.Left, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Bottom); v.Position = new Vector2(rectangle.Right, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top); v.Position = new Vector2(rectangle.Right, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Top); v.Position = new Vector2(rectangle.Left, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1); // SSW DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1); // SSW
@@ -637,7 +649,7 @@ namespace Quik.VertexGenerator
DrawQueue.AddElement(4); DrawQueue.AddElement(3); DrawQueue.AddElement(7); // SWW DrawQueue.AddElement(4); DrawQueue.AddElement(3); DrawQueue.AddElement(7); // SWW
} }
private void GenerateRectangleStripNarrow(in QRectangle rectangle, float radius) private void GenerateRectangleStripNarrow(in Rectangle rectangle, float radius)
{ {
/* /*
v-j---i-u v-j---i-u
@@ -661,74 +673,74 @@ namespace Quik.VertexGenerator
u v w x u v w x
20: 0 1 2 3 20: 0 1 2 3
*/ */
QuikVertex v = StrokeVertex; DbVertex v = StrokeVertex;
QVec2 nPos, qPos, tPos, wPos; Vector2 nPos, qPos, tPos, wPos;
float stroke = Style.StrokeWidth ?? 1.0f; float stroke = Style.StrokeWidth ?? 1.0f;
DrawQueue.RestoreOffset(); DrawQueue.RestoreOffset();
// a-b-c-d // a-b-c-d
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom + stroke); v.Position = new Vector2(rectangle.Left + stroke, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + stroke); v.Position = new Vector2(rectangle.Right - stroke, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top - stroke); v.Position = new Vector2(rectangle.Right - stroke, rectangle.Top + stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - stroke); v.Position = new Vector2(rectangle.Left + stroke, rectangle.Top + stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
// ef-gh-ij-kl // ef-gh-ij-kl
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom); v.Position = new Vector2(rectangle.Left + stroke, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom); v.Position = new Vector2(rectangle.Left + stroke, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Bottom + stroke); v.Position = new Vector2(rectangle.Right, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top - stroke); v.Position = new Vector2(rectangle.Right, rectangle.Top + stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top); v.Position = new Vector2(rectangle.Right - stroke, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top); v.Position = new Vector2(rectangle.Right - stroke, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Top - stroke); v.Position = new Vector2(rectangle.Left, rectangle.Top + stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + stroke); v.Position = new Vector2(rectangle.Left, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
// mno // mno
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius); v.Position = new Vector2(rectangle.Left, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
nPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius); nPos = v.Position = new Vector2(rectangle.Left + radius, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom); v.Position = new Vector2(rectangle.Left + radius, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
// pqr // pqr
v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom); v.Position = new Vector2(rectangle.Right - radius, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
qPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius); qPos = v.Position = new Vector2(rectangle.Right - radius, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Bottom + radius); v.Position = new Vector2(rectangle.Right, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
// stu // stu
v.Position = new QVec2(rectangle.Right, rectangle.Top - radius); v.Position = new Vector2(rectangle.Right, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
tPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - radius); tPos = v.Position = new Vector2(rectangle.Right - radius, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Top); v.Position = new Vector2(rectangle.Right - radius, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
// vwx // vwx
v.Position = new QVec2(rectangle.Left + radius, rectangle.Top); v.Position = new Vector2(rectangle.Left + radius, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
wPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - radius); wPos = v.Position = new Vector2(rectangle.Left + radius, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Top - radius); v.Position = new Vector2(rectangle.Left, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
// E // E
@@ -779,7 +791,7 @@ namespace Quik.VertexGenerator
float xoff = MathF.Cos(theta) * radius; float xoff = MathF.Cos(theta) * radius;
float yoff = MathF.Sin(theta) * radius; float yoff = MathF.Sin(theta) * radius;
v.Position = tPos + new QVec2(xoff, yoff); v.Position = tPos + new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(19); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); DrawQueue.AddElement(19); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
} }
@@ -794,7 +806,7 @@ namespace Quik.VertexGenerator
float xoff = -MathF.Sin(theta) * radius; float xoff = -MathF.Sin(theta) * radius;
float yoff = MathF.Cos(theta) * radius; float yoff = MathF.Cos(theta) * radius;
v.Position = wPos + new QVec2(xoff, yoff); v.Position = wPos + new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(22); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); DrawQueue.AddElement(22); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
} }
@@ -809,7 +821,7 @@ namespace Quik.VertexGenerator
float xoff = -MathF.Cos(theta) * radius; float xoff = -MathF.Cos(theta) * radius;
float yoff = -MathF.Sin(theta) * radius; float yoff = -MathF.Sin(theta) * radius;
v.Position = nPos + new QVec2(xoff, yoff); v.Position = nPos + new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(23); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); DrawQueue.AddElement(23); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
} }
@@ -824,14 +836,14 @@ namespace Quik.VertexGenerator
float xoff = -MathF.Sin(theta) * radius; float xoff = -MathF.Sin(theta) * radius;
float yoff = MathF.Cos(theta) * radius; float yoff = MathF.Cos(theta) * radius;
v.Position = qPos + new QVec2(xoff, yoff); v.Position = qPos + new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(16); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); DrawQueue.AddElement(16); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++));
} }
DrawQueue.AddElement(16); DrawQueue.AddElement(previous); DrawQueue.AddElement(17); DrawQueue.AddElement(16); DrawQueue.AddElement(previous); DrawQueue.AddElement(17);
} }
private void GenerateRectangleStripWide(in QRectangle rectangle, float radius) private void GenerateRectangleStripWide(in Rectangle rectangle, float radius)
{ {
/* /*
l---k l---k
@@ -850,45 +862,45 @@ namespace Quik.VertexGenerator
10: 0 1 2 3 4 5 10: 0 1 2 3 4 5
*/ */
QuikVertex v = StrokeVertex; DbVertex v = StrokeVertex;
float stroke = Style.StrokeWidth ?? 1.0f; float stroke = Style.StrokeWidth ?? 1.0f;
float innerRadius = radius - stroke; float innerRadius = radius - stroke;
DrawQueue.RestoreOffset(); DrawQueue.RestoreOffset();
v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom); v.Position = new Vector2(rectangle.Left + radius, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom); v.Position = new Vector2(rectangle.Right - radius, rectangle.Bottom);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + stroke); v.Position = new Vector2(rectangle.Right - radius, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + stroke); v.Position = new Vector2(rectangle.Left + radius, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + radius); v.Position = new Vector2(rectangle.Right - stroke, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top - radius); v.Position = new Vector2(rectangle.Right, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top - radius); v.Position = new Vector2(rectangle.Right, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + radius); v.Position = new Vector2(rectangle.Right - stroke, rectangle.Bottom - radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - stroke); v.Position = new Vector2(rectangle.Left + radius, rectangle.Top + stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - stroke); v.Position = new Vector2(rectangle.Right - radius, rectangle.Top + stroke);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Top); v.Position = new Vector2(rectangle.Right - radius, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Top); v.Position = new Vector2(rectangle.Left + radius, rectangle.Top);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius); v.Position = new Vector2(rectangle.Left, rectangle.Bottom + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - radius); v.Position = new Vector2(rectangle.Left + stroke, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - radius); v.Position = new Vector2(rectangle.Left + stroke, rectangle.Top + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius); v.Position = new Vector2(rectangle.Left, rectangle.Bottom + radius);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
// S // S
@@ -908,7 +920,7 @@ namespace Quik.VertexGenerator
int resolution = GetRoundingResolution(radius, 0.5f * MathF.PI); int resolution = GetRoundingResolution(radius, 0.5f * MathF.PI);
int current = 16; int current = 16;
QVec2 center = new QVec2(rectangle.Right - radius, rectangle.Top - radius); Vector2 center = new Vector2(rectangle.Right - radius, rectangle.Top - radius);
int s1 = 7, s2 = 6; int s1 = 7, s2 = 6;
for (int i = 0; i < resolution - 1; i++) for (int i = 0; i < resolution - 1; i++)
{ {
@@ -916,9 +928,9 @@ namespace Quik.VertexGenerator
float xoff = MathF.Cos(theta); float xoff = MathF.Cos(theta);
float yoff = MathF.Sin(theta); float yoff = MathF.Sin(theta);
v.Position = center + radius * new QVec2(xoff, yoff); v.Position = center + radius * new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = center + innerRadius * new QVec2(xoff, yoff); v.Position = center + innerRadius * new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0);
@@ -931,7 +943,7 @@ namespace Quik.VertexGenerator
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(9); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(9);
// Draw NW arc // Draw NW arc
center = new QVec2(rectangle.Left + radius, rectangle.Top - radius); center = new Vector2(rectangle.Left + radius, rectangle.Top - radius);
s1 = 8; s2 = 11; s1 = 8; s2 = 11;
for (int i = 0; i < resolution - 1; i++) for (int i = 0; i < resolution - 1; i++)
{ {
@@ -939,9 +951,9 @@ namespace Quik.VertexGenerator
float xoff = -MathF.Sin(theta); float xoff = -MathF.Sin(theta);
float yoff = MathF.Cos(theta); float yoff = MathF.Cos(theta);
v.Position = center + radius * new QVec2(xoff, yoff); v.Position = center + radius * new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = center + innerRadius * new QVec2(xoff, yoff); v.Position = center + innerRadius * new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0);
@@ -954,7 +966,7 @@ namespace Quik.VertexGenerator
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(14); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(14);
// Draw SW arc // Draw SW arc
center = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius); center = new Vector2(rectangle.Left + radius, rectangle.Bottom + radius);
s1 = 13; s2 = 12; s1 = 13; s2 = 12;
for (int i = 0; i < resolution - 1; i++) for (int i = 0; i < resolution - 1; i++)
{ {
@@ -962,9 +974,9 @@ namespace Quik.VertexGenerator
float xoff = -MathF.Cos(theta); float xoff = -MathF.Cos(theta);
float yoff = -MathF.Sin(theta); float yoff = -MathF.Sin(theta);
v.Position = center + radius * new QVec2(xoff, yoff); v.Position = center + radius * new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = center + innerRadius * new QVec2(xoff, yoff); v.Position = center + innerRadius * new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0);
@@ -977,7 +989,7 @@ namespace Quik.VertexGenerator
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(3); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(3);
// Draw SW arc // Draw SW arc
center = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius); center = new Vector2(rectangle.Right - radius, rectangle.Bottom + radius);
s1 = 2; s2 = 1; s1 = 2; s2 = 1;
for (int i = 0; i < resolution - 1; i++) for (int i = 0; i < resolution - 1; i++)
{ {
@@ -985,9 +997,9 @@ namespace Quik.VertexGenerator
float xoff = MathF.Sin(theta); float xoff = MathF.Sin(theta);
float yoff = -MathF.Cos(theta); float yoff = -MathF.Cos(theta);
v.Position = center + radius * new QVec2(xoff, yoff); v.Position = center + radius * new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
v.Position = center + innerRadius * new QVec2(xoff, yoff); v.Position = center + innerRadius * new Vector2(xoff, yoff);
DrawQueue.AddVertex(v); DrawQueue.AddVertex(v);
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0);
@@ -1000,6 +1012,104 @@ namespace Quik.VertexGenerator
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(4); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(4);
} }
private void ImageProc(DrawQueue queue)
{
Frame frame = queue.Dequeue();
ImageCommandFlags flags = (ImageCommandFlags)frame.I1;
Image image = queue.Dequeue().As<Image>();
int count = flags.HasFlag(ImageCommandFlags.Single) ? 1 : frame.I2;
if (flags.HasFlag(ImageCommandFlags.Image3d))
{
Image3d(queue, image, count);
}
else
{
Image2d(queue, image, count, flags.HasFlag(ImageCommandFlags.UVs));
}
}
private void Image2d(DrawQueue queue, Image image, int count, bool uv)
{
DrawQueue.StartDrawCall(Viewport, image);
for (int i = 0; i < count; i++)
{
Rectangle rect = (Rectangle)queue.Dequeue();
Rectangle uvs;
if (uv)
{
uvs = (Rectangle)queue.Dequeue();
}
else
{
uvs = new Rectangle(1, 1, 0, 0);
}
DrawQueue.RestoreOffset();
DbVertex vertex = ImageVertex;
vertex.Position = new Vector2(rect.Left, rect.Top);
vertex.TextureCoordinates = new Vector2(uvs.Left, uvs.Top);
DrawQueue.AddVertex(vertex);
vertex.Position = new Vector2(rect.Left, rect.Bottom);
vertex.TextureCoordinates = new Vector2(uvs.Left, uvs.Bottom);
DrawQueue.AddVertex(vertex);
vertex.Position = new Vector2(rect.Right, rect.Bottom);
vertex.TextureCoordinates = new Vector2(uvs.Right, uvs.Bottom);
DrawQueue.AddVertex(vertex);
vertex.Position = new Vector2(rect.Right, rect.Top);
vertex.TextureCoordinates = new Vector2(uvs.Right, uvs.Top);
DrawQueue.AddVertex(vertex);
DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3);
DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2);
}
DrawQueue.EndDrawCall();
}
private void Image3d(DrawQueue queue, Image image, int count)
{
DrawQueue.StartDrawCall(Viewport, image);
for (int i = 0; i < count; i++)
{
Rectangle rect = (Rectangle)queue.Dequeue();
Rectangle uvs = (Rectangle)queue.Dequeue();
int layer = (int)queue.Dequeue();
DrawQueue.RestoreOffset();
DbVertex vertex = ImageVertex;
vertex.TextureLayer = layer;
vertex.Position = new Vector2(rect.Top, rect.Left);
vertex.TextureCoordinates = new Vector2(uvs.Top, uvs.Left);
DrawQueue.AddVertex(vertex);
vertex.Position = new Vector2(rect.Bottom, rect.Left);
vertex.TextureCoordinates = new Vector2(uvs.Bottom, uvs.Left);
DrawQueue.AddVertex(vertex);
vertex.Position = new Vector2(rect.Bottom, rect.Right);
vertex.TextureCoordinates = new Vector2(uvs.Bottom, uvs.Right);
DrawQueue.AddVertex(vertex);
vertex.Position = new Vector2(rect.Top, rect.Right);
vertex.TextureCoordinates = new Vector2(uvs.Top, uvs.Right);
DrawQueue.AddVertex(vertex);
DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3);
DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2);
}
DrawQueue.EndDrawCall();
}
private struct LineInfo private struct LineInfo
{ {
public int BaseOffset { get; } public int BaseOffset { get; }

61
Dashboard/res/gl21.frag Normal file
View File

@@ -0,0 +1,61 @@
/**
* QUIK: User Interface Kit
* Copyright (C) 2023 Halit Utku Maden, et al.
*/
#version 130
in vec2 fv2TexPos;
in vec4 fv4Color;
in float ffTexLayer;
out vec4 fragColor;
uniform int iEnableSdf;
uniform int iEnableTexture;
uniform int iAlphaDiscard;
uniform float fSdfThreshold = 0.5;
uniform sampler2D tx2d;
const float fAlphaThreshold = 0.01;
vec4 getTexture()
{
if (iEnableTexture == 3)
{
// return texture(tx2dArray, vec3(fv2TexPos, ffTexLayer));
}
else if (iEnableSdf == 1)
{
vec2 texelSz = 1.0/vec2(textureSize(tx2d, 0));
vec2 txCoord2 = fv2TexPos + texelSz * (1 - mod(fv2TexPos, texelSz));
return texture(tx2d, txCoord2);
}
else
{
return texture(tx2d, fv2TexPos);
}
}
void main(void)
{
vec4 albedo = fv4Color;
if (iEnableTexture != 0)
{
vec4 value = getTexture();
if (iEnableSdf != 0)
{
value = vec4(vec3(1.0), smoothstep(fSdfThreshold-0.1, fSdfThreshold+0.1, value.r));
}
if (iAlphaDiscard != 0 && value.a <= fAlphaThreshold)
{
discard;
}
albedo = albedo * value;
}
fragColor = albedo;
}

35
Dashboard/res/gl21.vert Normal file
View File

@@ -0,0 +1,35 @@
/**
* QUIK: User Interface Kit
* Copyright (C) 2023 Halit Utku Maden, et al.
*/
#version 130
in vec2 v2Position; /**< The vertex position.*/
in float fZIndex; /**< The z index. */
in vec2 v2TexPos; /**< The texture coorindates. */
in float fTexLayer; /**< The texture layer for 3D textures. */
in vec4 v4Color; /**< The vertex color. */
out vec2 fv2TexPos;
out float ffTexLayer;
out vec4 fv4Color;
uniform mat4 m4Transforms; /**< The view matrix. */
uniform float fMaxZ; /**< Highest Z coordinate. */
const mat4 m4BaseTransforms = mat4(
vec4( 2.0, 0.0, 0.0, 0.0),
vec4( 0.0, -2.0, 0.0, 0.0),
vec4( 0.0, 0.0, 1.0, 0.0),
vec4(-1.0, 1.0, 0.0, 1.0)
);
void main(void)
{
vec4 v = vec4(v2Position, fZIndex/fMaxZ, 1);
gl_Position = m4Transforms * v;
fv2TexPos = v2TexPos;
fv4Color = v4Color;
ffTexLayer = fTexLayer;
}

View File

@@ -1,60 +0,0 @@
# This is going to create an environment for you to cross compile all the
# packages needed to build this project.
#
# As always, debian > ubuntu <3
FROM debian:stable-slim
WORKDIR /root
# Download and Install dependencies.
# Install WGET
RUN apt-get update
RUN apt-get install -y sudo wget
# Add the .NET package repository to the repository listing.
RUN wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
RUN dpkg -i packages-microsoft-prod.deb
RUN rm packages-microsoft-prod.deb
# APT dependencies.
RUN apt-get update
RUN apt-get install -y \
build-essential \
bzip2 \
cmake \
clang \
cpio \
dotnet-sdk-6.0 \
gcc-arm-linux-gnueabihf \
gcc-aarch64-linux-gnu \
gcc-i686-linux-gnu \
git \
libssl-dev \
libxml2-dev \
lzma-dev \
mingw-w64 \
nuget \
ninja-build \
patch \
python3 \
xz-utils \
zlib1g-dev
# Clone osxcross
# Let's do this later.
# RUN git clone https://github.com/tpoechtrager/osxcross.git osxcross
# Setup interactive shell.
# Setup sudo. Remove password prompt for group "wheel".
RUN echo "%wheel ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/quik_sudo_conf
# Create a default user and switch.
RUN adduser --comment "" --disabled-password quik
USER quik
WORKDIR /home/quik
# Copy bashrc
RUN cp /etc/bash.bashrc ~/.bashrc
RUN echo source $HOME/src/sh/bashrc.sh >> ~/.bashrc
# Execute an interactive shell.
CMD bash

View File

@@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<LangVersion>7.3</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@@ -1,3 +0,0 @@
#!/bin/bash
cd $(dirname "$0")
../sh/quik_build_native.sh .

View File

@@ -1,36 +0,0 @@
using System;
using System.IO;
using Quik.Media.Color;
namespace Quik.Media.Stb
{
public class QFontStbtt : QFont
{
public override FontInfo Info => throw new NotImplementedException();
public QFontStbtt(Stream source)
{
}
public override QGlyphMetrics[] GetMetricsForPage(int codepage)
{
throw new NotImplementedException();
}
public override QGlyphMetrics GetMetricsForRune(int rune)
{
throw new NotImplementedException();
}
public override bool HasRune(int rune)
{
throw new NotImplementedException();
}
public override QImage RenderPage(int codepage, float resolution, bool sdf)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LanguageVersion>7.3</LanguageVersion>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Quik\Quik.csproj" />
<ProjectReference Include="..\Quik.StbImage\Quik.StbImage.csproj" />
<ProjectReference Include="..\Quik.StbTrueType\Quik.StbTrueType.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,59 +0,0 @@
using System;
using System.Collections.Generic;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using Quik.Media;
using Quik.OpenGL;
using Quik.PAL;
namespace Quik.OpenTK
{
public class OpenTKPlatform : IQuikPlatform
{
private readonly List<OpenTKPort> _ports = new List<OpenTKPort>();
// These shall remain a sad nop for now.
public string Title { get; set; }
public QImage Icon { get; set; }
public event EventHandler EventRaised;
public NativeWindowSettings DefaultSettings { get; set; } = NativeWindowSettings.Default;
public IReadOnlyList<OpenTKPort> Ports => _ports;
private bool IsGLInitialized = false;
public IQuikPort CreatePort()
{
NativeWindow window = new NativeWindow(DefaultSettings);
OpenTKPort port = new OpenTKPort(window);
_ports.Add(port);
if (!IsGLInitialized)
{
window.Context.MakeCurrent();
GL.LoadBindings((string proc) => GLFW.GetProcAddress(proc));
IsGLInitialized = true;
}
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);
}
}
}

View File

@@ -1,12 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(quik_stbi LANGUAGES C VERSION 1.0)
add_compile_options(-static-libgcc)
add_library(stbi SHARED "quik_stbi.c")
target_include_directories(stbi PRIVATE "../lib")
install(
TARGETS stbi
RUNTIME DESTINATION .
LIBRARY DESTINATION .)

View File

@@ -1,12 +0,0 @@
using System;
namespace Quik.Stb.Image
{
[AttributeUsage(System.AttributeTargets.All, Inherited = false, AllowMultiple = true)]
internal sealed class NativeTypeNameAttribute : System.Attribute
{
public NativeTypeNameAttribute(string typename)
{
}
}
}

View File

@@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<LangVersion>7.3</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<RuntimeIdentifiers>linux-arm;linux-arm64;linux-x64;win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup>
<!-- Nuget Properties. -->
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>Quik.StbImage</PackageId>
<Version>1.0.0</Version>
<Authors>STBI Authors, H. Utku Maden</Authors>
<Description>
A C# wrapper for the ubiquitous Stb Image library.
</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes/**">
<PackagePath>runtimes</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -1,144 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Quik.Stb
{
/// <summary>
/// A class that encompasses all features of stb_image.h in a safe way.
/// </summary>
public unsafe class StbImage : IDisposable
{
private bool isDisposed = false;
/// <summary>
/// Pointer to the image.
/// </summary>
public IntPtr ImagePointer { get; }
/// <summary>
/// Width of the image.
/// </summary>
public int Width { get; }
/// <summary>
/// Height of the image.
/// </summary>
/// <value></value>
public int Height { get; }
/// <summary>
/// Internal image format.
/// </summary>
public StbiImageFormat Format { get; }
public bool IsFloat { get; }
private StbImage(IntPtr image, int x, int y, StbiImageFormat format, bool isFloat)
{
ImagePointer = image;
Width = x;
Height = y;
Format = format;
IsFloat = isFloat;
}
~StbImage()
{
Dispose(false);
}
public void Dispose() => Dispose(true);
private void Dispose(bool disposing)
{
if (isDisposed) return;
if (disposing)
{
GC.SuppressFinalize(this);
}
Stbi.image_free(ImagePointer.ToPointer());
isDisposed = true;
}
/// <summary>
/// Set to flip the y-axis of loaded images on load.
/// </summary>
public static bool FlipVerticallyOnLoad { set => Stbi.set_flip_vertically_on_load(1); }
/// <summary>
/// Set to unpremultiply images on load.
/// </summary>
/// <remarks>
/// According to the stb_image documentation, only iPhone PNG images
/// can come with premultiplied alpha.
/// </remarks>
public static bool UnpremultiplyOnLoad { set => Stbi.set_unpremultiply_on_load(1); }
/// <summary>
/// Try loading an image, without raising exceptions.
/// </summary>
/// <param name="image">The resulting image.</param>
/// <param name="stream">Source stream.</param>
/// <param name="format">The desired image format.</param>
/// <returns>True on success.</returns>
public static bool TryLoad(out StbImage image, Stream stream, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false)
{
int x, y, iFormat;
StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true);
wrapper.CreateCallbacks(out stbi_io_callbacks cb);
stream.Position = 0;
IntPtr imagePtr;
if (isFloat)
{
imagePtr = (IntPtr)Stbi.loadf_from_callbacks(&cb, null, &x, &y, &iFormat, (int)format);
}
else
{
imagePtr = (IntPtr)Stbi.load_from_callbacks(&cb, null, &x, &y, &iFormat, (int)format);
}
if (imagePtr != IntPtr.Zero)
{
image = new StbImage(imagePtr, x, y, (StbiImageFormat)iFormat, isFloat);
return true;
}
else
{
image = null;
return false;
}
}
/// <summary>
/// Load an image.
/// </summary>
/// <param name="stream">The stream to load from.</param>
/// <param name="format">The desired image format.</param>
/// <returns>The image object.</returns>
public static StbImage Load(Stream stream, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false)
{
if (TryLoad(out StbImage image, stream, format, isFloat))
{
return image;
}
string reason = Marshal.PtrToStringUTF8((IntPtr)Stbi.failure_reason());
throw new Exception($"Failed to load image: {reason}");
}
public bool IsLoadable(Stream stream)
{
int x, y, iFormat;
StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true);
wrapper.CreateCallbacks(out stbi_io_callbacks cb);
stream.Position = 0;
int result = Stbi.info_from_callbacks(&cb, null, &x, &y, &iFormat);
return result != 0;
}
}
}

View File

@@ -1,65 +0,0 @@
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Reflection;
namespace Quik.Stb
{
public unsafe static partial class Stbi
{
private delegate void FailedAssertProc(byte *expression, byte *file, int line, byte *function);
private static readonly string[] LibraryNames = new string[]
{
//FIXME: This is wrong on so many levels, but, i need to do this
// in order to get a change of this running.
"runtimes/win-x64/native/libstbi.dll",
"runtimes/win-x86/native/libstbi.dll",
"runtimes/linux-arm/native/libstbi.so",
"runtimes/linux-arm64/native/libstbi.so",
"runtimes/linux-x64/native/libstbi.so",
"runtimes/native/libstbi.dylib",
"libstbi.dll",
"libstbi.so",
"libstbi.dylib",
};
static Stbi()
{
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), Resolver);
quik_stbi_failed_assert_store(Marshal.GetFunctionPointerForDelegate<FailedAssertProc>(FailedAssert));
}
private static IntPtr Resolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName != "stbi")
return IntPtr.Zero;
foreach (string name in LibraryNames)
{
if (NativeLibrary.TryLoad(name, assembly, searchPath, out IntPtr handle))
{
return handle;
}
}
return NativeLibrary.Load(libraryName);
}
private static void FailedAssert(byte *expression, byte *file, int line, byte *function)
{
string expr = expression == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)expression);
string f = file == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)file);
string func = function == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)function);
Exception ex = new Exception("Assert failed in native stbi code.");
ex.Data.Add("Expression", expr);
ex.Data.Add("File", f);
ex.Data.Add("Line", line);
ex.Data.Add("Function", func);
throw ex;
}
}
}

View File

@@ -1,177 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Quik.Stb.Image;
namespace Quik.Stb
{
[NativeTypeName("unsigned int")]
public enum StbiEnum : uint
{
STBI_default = 0,
STBI_grey = 1,
STBI_grey_alpha = 2,
STBI_rgb = 3,
STBI_rgb_alpha = 4,
}
public partial struct stbi_io_callbacks
{
[NativeTypeName("int (*)(void *, char *, int)")]
public IntPtr read;
[NativeTypeName("void (*)(void *, int)")]
public IntPtr skip;
[NativeTypeName("int (*)(void *)")]
public IntPtr eof;
}
public static unsafe partial class Stbi
{
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void quik_stbi_failed_assert_store([NativeTypeName("quik_failed_assert_cb_t")] IntPtr cb);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_from_memory", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_from_callbacks", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_from_file", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load_from_file([NativeTypeName("FILE *")] void* f, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_gif_from_memory", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load_gif_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int** delays, int* x, int* y, int* z, int* comp, int req_comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_16_from_memory", ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* load_16_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_16_from_callbacks", ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* load_16_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_16", ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* load_16([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_from_file_16", ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* load_from_file_16([NativeTypeName("FILE *")] void* f, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_loadf_from_memory", ExactSpelling = true)]
public static extern float* loadf_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_loadf_from_callbacks", ExactSpelling = true)]
public static extern float* loadf_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_loadf", ExactSpelling = true)]
public static extern float* loadf([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_loadf_from_file", ExactSpelling = true)]
public static extern float* loadf_from_file([NativeTypeName("FILE *")] void* f, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_hdr_to_ldr_gamma", ExactSpelling = true)]
public static extern void hdr_to_ldr_gamma(float gamma);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_hdr_to_ldr_scale", ExactSpelling = true)]
public static extern void hdr_to_ldr_scale(float scale);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_ldr_to_hdr_gamma", ExactSpelling = true)]
public static extern void ldr_to_hdr_gamma(float gamma);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_ldr_to_hdr_scale", ExactSpelling = true)]
public static extern void ldr_to_hdr_scale(float scale);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_hdr_from_callbacks", ExactSpelling = true)]
public static extern int is_hdr_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_hdr_from_memory", ExactSpelling = true)]
public static extern int is_hdr_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_hdr", ExactSpelling = true)]
public static extern int is_hdr([NativeTypeName("const char *")] sbyte* filename);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_hdr_from_file", ExactSpelling = true)]
public static extern int is_hdr_from_file([NativeTypeName("FILE *")] void* f);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_failure_reason", ExactSpelling = true)]
[return: NativeTypeName("const char *")]
public static extern sbyte* failure_reason();
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_image_free", ExactSpelling = true)]
public static extern void image_free(void* retval_from_stbi_load);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_info_from_memory", ExactSpelling = true)]
public static extern int info_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_info_from_callbacks", ExactSpelling = true)]
public static extern int info_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_16_bit_from_memory", ExactSpelling = true)]
public static extern int is_16_bit_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_16_bit_from_callbacks", ExactSpelling = true)]
public static extern int is_16_bit_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_info", ExactSpelling = true)]
public static extern int info([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_info_from_file", ExactSpelling = true)]
public static extern int info_from_file([NativeTypeName("FILE *")] void* f, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_16_bit", ExactSpelling = true)]
public static extern int is_16_bit([NativeTypeName("const char *")] sbyte* filename);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_16_bit_from_file", ExactSpelling = true)]
public static extern int is_16_bit_from_file([NativeTypeName("FILE *")] void* f);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_set_unpremultiply_on_load", ExactSpelling = true)]
public static extern void set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_convert_iphone_png_to_rgb", ExactSpelling = true)]
public static extern void convert_iphone_png_to_rgb(int flag_true_if_should_convert);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_set_flip_vertically_on_load", ExactSpelling = true)]
public static extern void set_flip_vertically_on_load(int flag_true_if_should_flip);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_set_unpremultiply_on_load_thread", ExactSpelling = true)]
public static extern void set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_convert_iphone_png_to_rgb_thread", ExactSpelling = true)]
public static extern void convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_set_flip_vertically_on_load_thread", ExactSpelling = true)]
public static extern void set_flip_vertically_on_load_thread(int flag_true_if_should_flip);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_malloc_guesssize", ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* zlib_decode_malloc_guesssize([NativeTypeName("const char *")] sbyte* buffer, int len, int initial_size, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_malloc_guesssize_headerflag", ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* zlib_decode_malloc_guesssize_headerflag([NativeTypeName("const char *")] sbyte* buffer, int len, int initial_size, int* outlen, int parse_header);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_malloc", ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* zlib_decode_malloc([NativeTypeName("const char *")] sbyte* buffer, int len, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_buffer", ExactSpelling = true)]
public static extern int zlib_decode_buffer([NativeTypeName("char *")] sbyte* obuffer, int olen, [NativeTypeName("const char *")] sbyte* ibuffer, int ilen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_noheader_malloc", ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* zlib_decode_noheader_malloc([NativeTypeName("const char *")] sbyte* buffer, int len, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_noheader_buffer", ExactSpelling = true)]
public static extern int zlib_decode_noheader_buffer([NativeTypeName("char *")] sbyte* obuffer, int olen, [NativeTypeName("const char *")] sbyte* ibuffer, int ilen);
}
}

View File

@@ -1,11 +0,0 @@
namespace Quik.Stb
{
public enum StbiImageFormat
{
Default = (int)StbiEnum.STBI_default,
Grey = (int)StbiEnum.STBI_grey,
GreyAlpha = (int)StbiEnum.STBI_grey_alpha,
Rgb = (int)StbiEnum.STBI_rgb,
Rgba = (int)StbiEnum.STBI_rgb_alpha
}
}

View File

@@ -1,60 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Quik.Stb
{
public unsafe class StbiStreamWrapper : IDisposable
{
private Stream _stream;
private bool _keepOpen;
private bool _isDisposed;
private delegate int ReadProc(void *userdata, byte* buffer, int count);
private delegate void SkipProc(void *userdata, int count);
private delegate int Eof(void *userdata);
public StbiStreamWrapper(Stream stream, bool keepOpen = false)
{
if (stream == null) throw new ArgumentNullException(nameof(stream));
_stream = stream;
_keepOpen = keepOpen;
}
public void CreateCallbacks(out stbi_io_callbacks cb)
{
cb = default;
cb.read = Marshal.GetFunctionPointerForDelegate<ReadProc>(ReadCb);
cb.skip = Marshal.GetFunctionPointerForDelegate<SkipProc>(SkipCb);
cb.eof = Marshal.GetFunctionPointerForDelegate<Eof>(EofCb);
}
private int ReadCb(void *userdata, byte* buffer, int count)
{
Span<byte> bytes = new Span<byte>(buffer, count);
return _stream.Read(bytes);
}
private void SkipCb(void *userdata, int count)
{
_stream.Seek(count, SeekOrigin.Current);
}
private int EofCb(void *userdata)
{
if (!_stream.CanRead || _stream.Position == _stream.Length)
return 1;
return 0;
}
public void Dispose()
{
if (_isDisposed) return;
if (!_keepOpen) _stream.Dispose();
_isDisposed = true;
}
}
}

View File

@@ -1,3 +0,0 @@
#!/bin/bash
cd $(dirname "$0")
../sh/quik_build_native.sh .

View File

@@ -1,30 +0,0 @@
-x
c
-l
stbi
--config
compatible-codegen
single-file
exclude-fnptr-codegen
generate-aggressive-inlining
generate-setslastsystemerror-attribute
unix-types
--include-directory
../lib
--include-directory
../Quik.StbImage
--include-directory
/usr/lib/llvm-14/lib/clang/14.0.6/include
--file
../Quik.StbImage.redist/quik_stbi.h
../lib/stb/stb_image.h
--methodClassName
Stbi
--namespace
Quik.Stb
--output
Stbi.cs
--prefixStrip
stbi_
--with-type
FILE=void

View File

@@ -1,6 +0,0 @@
#include "quik_stbi.h"
QUIK_DEFINE_LIB(quik_stbi);
#define STB_IMAGE_IMPLEMENTATION 1
#include "stb/stb_image.h"

View File

@@ -1,18 +0,0 @@
#ifndef _QUIK_STBI_H_
#define _QUIK_STBI_H_
#include "quik/quik_common.h"
QUIK_DECLARE_LIB(quik_stbi)
/* TODO: Change this declaration so we can export a DLL properly in windows. */
#define STBIDEF QEXTERN
#define STBI_ASSERT(EXPR) do { \
if (!(EXPR)) \
quik_stbi_failed_assert(#EXPR, __FILE__, __LINE__ - 2, __QUIK_FUNCTION__); \
} while(0)
#include "stb/stb_image.h"
#endif

View File

@@ -1,12 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(quik_stbtt LANGUAGES C VERSION 1.0)
add_compile_options(-static-libgcc)
add_library(stbtt SHARED "quik_stbtt.c")
target_include_directories(stbtt PRIVATE "../lib")
install(
TARGETS stbtt
RUNTIME DESTINATION .
LIBRARY DESTINATION .)

View File

@@ -1,12 +0,0 @@
using System;
namespace Quik.Stb.TrueType
{
[AttributeUsage(System.AttributeTargets.All, Inherited = false, AllowMultiple = true)]
internal sealed class NativeTypeNameAttribute : System.Attribute
{
public NativeTypeNameAttribute(string typename)
{
}
}
}

View File

@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<LangVersion>7.3</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Quik.StbTrueType.redist" Version="1.0" />
</ItemGroup>
<ItemGroup>
<Content Include="runtimes/**">
<PackagePath>runtimes</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -1,355 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace Quik.Stb
{
public unsafe class StbFont : IDisposable
{
IntPtr _buffer;
stbtt_fontinfo* _info;
List<stbtt_kerningentry> _kerningTable;
public IntPtr FontBuffer => _buffer;
public ref stbtt_fontinfo FontInfo => ref *_info;
public IReadOnlyList<stbtt_kerningentry> KerningTable
{
get
{
if (_kerningTable != null)
return _kerningTable;
int count = Stbtt.GetKerningTableLength(_info);
if (count == 0)
{
return _kerningTable = new List<stbtt_kerningentry>();
}
else
{
stbtt_kerningentry[] array = new stbtt_kerningentry[count];
fixed (stbtt_kerningentry *ptr = array)
Stbtt.GetKerningTable(_info, ptr, count);
return _kerningTable = new List<stbtt_kerningentry>(array);
}
}
}
public int Ascend { get; }
public int Descend { get; }
public int VerticalLineGap { get; }
public int AscendOS2 { get; }
public int DescendOS2 { get; }
public int VerticalLineGapOS2 { get; }
public Box BoundingBox { get; }
private StbFont(IntPtr buffer, stbtt_fontinfo* info)
{
_buffer = buffer;
_info = info;
int a, b, c, d;
Stbtt.GetFontVMetrics(_info, &a, &b, &c);
Ascend = a;
Descend = b;
VerticalLineGap = c;
Stbtt.GetFontVMetricsOS2(_info, &a, &b, &c);
AscendOS2 = a;
DescendOS2 = b;
VerticalLineGapOS2 = c;
Stbtt.GetFontBoundingBox(_info, &a, &b, &c, &d);
BoundingBox = new Box(a, b, c, d);
}
~StbFont()
{
Dispose(false);
}
public int FindGlyphIndex(int codepoint)
{
return Stbtt.FindGlyphIndex(_info, codepoint);
}
public int FindGlyphIndex(Rune codepoint) => FindGlyphIndex(codepoint.Value);
public float ScaleForPixelHeight(float pixels)
{
return Stbtt.ScaleForPixelHeight(_info, pixels);
}
public float ScaleForMappingEmToPixels(float pixels)
{
return Stbtt.ScaleForMappingEmToPixels(_info, pixels);
}
public void GetCodepointHMetrics(int codepoint, out int advance, out int bearing)
{
int a, b;
Stbtt.GetCodepointHMetrics(_info, codepoint, &a, &b);
advance = a;
bearing = b;
}
public void GetCodepointHMetrics(Rune codepoint, out int advance, out int bearing)
=> GetCodepointHMetrics(codepoint.Value, out advance, out bearing);
public int GetCodepointKernAdvance(int cp1, int cp2)
{
return Stbtt.GetCodepointKernAdvance(_info, cp1, cp2);
}
public int GetCodepointKernAdvance(Rune cp1, Rune cp2) => GetCodepointKernAdvance(cp1.Value, cp2.Value);
public int GetCodepointBox(int codepoint, out Box box)
{
int x0, y0;
int x1, y1;
int rval;
rval = Stbtt.GetCodepointBox(_info, codepoint, &x0, &y0, &x1, &y1);
box = new Box(x0, y0, x1, y1);
return rval;
}
public void GetGlyphHMetrics(int glyph, out int advance, out int bearing)
{
int a, b;
Stbtt.GetGlyphHMetrics(_info, glyph, &a, &b);
advance = a;
bearing = b;
}
public int GetGlyphKernAdvance(int gl1, int gl2)
{
return Stbtt.GetGlyphKernAdvance(_info, gl1, gl2);
}
public int GetGlyphBox(int glyph, out Box box)
{
int x0, y0;
int x1, y1;
int rval;
rval = Stbtt.GetGlyphBox(_info, glyph, &x0, &y0, &x1, &y1);
box = new Box(x0, y0, x1, y1);
return rval;
}
public bool IsGlyphEmpty(int glyph)
{
return Stbtt.IsGlyphEmpty(_info, glyph) != 0;
}
public Bitmap GetCodepointBitmap(float scaleX, float scaleY, int codepoint, out int offsetX, out int offsetY)
{
int w, h, x, y;
void* ptr = Stbtt.GetCodepointBitmap(_info, scaleX, scaleY, codepoint, &w, &h, &x, &y);
offsetX = x;
offsetY = y;
return new Bitmap((IntPtr)ptr, w, h, FreeBitmap);
}
public Bitmap GetCodepointBitmap(float scaleX, float scaleY, Rune codepoint, out int offsetX, out int offsetY)
=> GetCodepointBitmap(scaleX, scaleY, codepoint.Value, out offsetX, out offsetY);
public Bitmap GetCodepointBitmapSubpixel(float scaleX, float scaleY, float shiftX, float shiftY, int codepoint, out int offsetX, out int offsetY)
{
int w, h, x, y;
void* ptr = Stbtt.GetCodepointBitmapSubpixel(_info, scaleX, scaleY, shiftX, shiftY, codepoint, &w, &h, &x, &y);
offsetX = x;
offsetY = y;
return new Bitmap((IntPtr)ptr, w, h, FreeBitmap);
}
public Bitmap GetCodepointBitmapSubpixel(float scaleX, float scaleY, float shiftX, float shiftY, Rune codepoint, out int offsetX, out int offsetY)
=> GetCodepointBitmapSubpixel(scaleX, scaleY, shiftX, shiftY, codepoint.Value, out offsetX, out offsetY);
public Bitmap GetGlyphBitmap(float scaleX, float scaleY, int glyph, out int offsetX, out int offsetY)
{
int w, h, x, y;
void* ptr = Stbtt.GetGlyphBitmap(_info, scaleX, scaleY, glyph, &w, &h, &x, &y);
offsetX = x;
offsetY = y;
return new Bitmap((IntPtr)ptr, w, h, FreeBitmap);
}
public Bitmap GetGlyphBitmapSubpixel(float scaleX, float scaleY, float shiftX, float shiftY, int glyph, out int offsetX, out int offsetY)
{
int w, h, x, y;
void* ptr = Stbtt.GetGlyphBitmapSubpixel(_info, scaleX, scaleY, shiftX, shiftY, glyph, &w, &h, &x, &y);
offsetX = x;
offsetY = y;
return new Bitmap((IntPtr)ptr, w, h, FreeBitmap);
}
public Bitmap GetGlyphSdf(float scale, int glyph, int padding, byte edgeValue, float pixelDistScale, out int offsetX, out int offsetY)
{
int w, h, x, y;
void *ptr = Stbtt.GetGlyphSDF(_info, scale, glyph, padding, edgeValue, pixelDistScale, &w, &h, &x, &y);
offsetX = x;
offsetY = y;
return new Bitmap((IntPtr)ptr, w, h, FreeSdf);
}
public Bitmap GetCodepointSdf(float scale, int codepoint, int padding, byte edgeValue, float pixelDistScale, out int offsetX, out int offsetY)
{
int w, h, x, y;
void *ptr = Stbtt.GetCodepointSDF(_info, scale, codepoint, padding, edgeValue, pixelDistScale, &w, &h, &x, &y);
offsetX = x;
offsetY = y;
return new Bitmap((IntPtr)ptr, w, h, FreeSdf);
}
public Bitmap GetCodepointSdf(float scale, Rune codepoint, int padding, byte edgeValue, float pixelDistScale, out int offsetX, out int offsetY)
=> GetCodepointSdf(scale, codepoint.Value, padding, edgeValue, pixelDistScale, out offsetX, out offsetY);
public void Dispose()
{
Dispose(true);
}
bool isDisposed = false;
private void Dispose(bool disposing)
{
if (isDisposed) return;
if (disposing)
{
GC.SuppressFinalize(this);
}
Marshal.FreeHGlobal(_buffer);
Marshal.FreeHGlobal((IntPtr)_info);
isDisposed = true;
}
public static bool TryLoad(Stream stream, out StbFont font)
{
byte* buffer = (byte*)Marshal.AllocHGlobal((int)stream.Length);
stbtt_fontinfo* fontInfo = (stbtt_fontinfo*)Marshal.AllocHGlobal(sizeof(stbtt_fontinfo));
stream.Read(new Span<byte>(buffer, (int)stream.Length));
int nfont = Stbtt.GetNumberOfFonts(buffer);
if (nfont == 0)
{
font = null;
return false;
}
int offset = Stbtt.GetFontOffsetForIndex(buffer, 0);
if (Stbtt.InitFont(fontInfo, (byte*)buffer, offset) == 0)
{
Marshal.FreeHGlobal((IntPtr)buffer);
Marshal.FreeHGlobal((IntPtr)fontInfo);
font = null;
return false;
}
font = new StbFont((IntPtr)buffer, fontInfo);
return true;
}
public static StbFont Load(Stream stream)
{
if (TryLoad(stream, out StbFont font))
{
return font;
}
throw new Exception("Could not load the font.");
}
private static void FreeBitmap(IntPtr buffer)
{
Stbtt.FreeBitmap((byte*)buffer, null);
}
private static void FreeSdf(IntPtr buffer)
{
Stbtt.FreeSDF((byte*)buffer, null);
}
public struct Box
{
public int X0;
public int Y0;
public int X1;
public int Y1;
public Box(int x0, int y0, int x1, int y1)
{
X0 = x0; Y0 = y0;
X1 = x1; Y1 = y1;
}
}
public class Bitmap : IDisposable
{
public IntPtr Buffer { get; }
public int Width { get; }
public int Height { get; }
private readonly Action<IntPtr> Destroy;
public Bitmap(IntPtr buffer, int width, int height, Action<IntPtr> destroy)
{
Buffer = buffer;
Width = width;
Height = height;
Destroy = destroy;
}
~Bitmap()
{
Dispose(false);
}
public void Dispose() => Dispose(true);
private bool isDiposed = false;
public void Dispose(bool disposing)
{
if (isDiposed) return;
if (disposing)
{
GC.SuppressFinalize(this);
}
Destroy(Buffer);
isDiposed = true;
}
}
}
}

View File

@@ -1,64 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Reflection;
namespace Quik.Stb
{
public unsafe static partial class Stbtt
{
private delegate void FailedAssertProc(byte *expression, byte *file, int line, byte *function);
private static readonly string[] LibraryNames = new string[]
{
//FIXME: This is wrong on so many levels, but, i need to do this
// in order to get a change of this running.
"runtimes/win-x64/native/libstbtt.dll",
"runtimes/win-x86/native/libstbtt.dll",
"runtimes/linux-arm/native/libstbtt.so",
"runtimes/linux-arm64/native/libstbtt.so",
"runtimes/linux-x64/native/libstbtt.so",
"runtimes/native/libstbtt.dylib",
"libstbtt.dll",
"libstbtt.so",
"libstbtt.dylib",
};
static Stbtt()
{
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), Resolver);
quik_stbtt_failed_assert_store(Marshal.GetFunctionPointerForDelegate<FailedAssertProc>(FailedAssert));
}
private static IntPtr Resolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName != "stbtt")
return IntPtr.Zero;
foreach (string name in LibraryNames)
{
if (NativeLibrary.TryLoad(name, assembly, searchPath, out IntPtr handle))
{
return handle;
}
}
return NativeLibrary.Load(libraryName);
}
private static void FailedAssert(byte *expression, byte *file, int line, byte *function)
{
string expr = expression == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)expression);
string f = file == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)file);
string func = function == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)function);
Exception ex =
new Exception($"Assert failed in native stbtt code. ({System.IO.Path.GetFileName(f)}:{line})");
ex.Data.Add("Expression", expr);
ex.Data.Add("File", f);
ex.Data.Add("Line", line);
ex.Data.Add("Function", func);
throw ex;
}
}
}

View File

@@ -1,495 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Quik.Stb.TrueType;
namespace Quik.Stb
{
public unsafe partial struct stbtt__buf
{
[NativeTypeName("unsigned char *")]
public byte* data;
public int cursor;
public int size;
}
public partial struct stbtt_bakedchar
{
[NativeTypeName("unsigned short")]
public ushort x0;
[NativeTypeName("unsigned short")]
public ushort y0;
[NativeTypeName("unsigned short")]
public ushort x1;
[NativeTypeName("unsigned short")]
public ushort y1;
public float xoff;
public float yoff;
public float xadvance;
}
public partial struct stbtt_aligned_quad
{
public float x0;
public float y0;
public float s0;
public float t0;
public float x1;
public float y1;
public float s1;
public float t1;
}
public partial struct stbtt_packedchar
{
[NativeTypeName("unsigned short")]
public ushort x0;
[NativeTypeName("unsigned short")]
public ushort y0;
[NativeTypeName("unsigned short")]
public ushort x1;
[NativeTypeName("unsigned short")]
public ushort y1;
public float xoff;
public float yoff;
public float xadvance;
public float xoff2;
public float yoff2;
}
public partial struct stbrp_rect
{
}
public unsafe partial struct stbtt_pack_range
{
public float font_size;
public int first_unicode_codepoint_in_range;
public int* array_of_unicode_codepoints;
public int num_chars;
public stbtt_packedchar* chardata_for_range;
[NativeTypeName("unsigned char")]
public byte h_oversample;
[NativeTypeName("unsigned char")]
public byte v_oversample;
}
public unsafe partial struct stbtt_pack_context
{
public void* user_allocator_context;
public void* pack_info;
public int width;
public int height;
public int stride_in_bytes;
public int padding;
public int skip_missing;
[NativeTypeName("unsigned int")]
public uint h_oversample;
[NativeTypeName("unsigned int")]
public uint v_oversample;
[NativeTypeName("unsigned char *")]
public byte* pixels;
public void* nodes;
}
public unsafe partial struct stbtt_fontinfo
{
public void* userdata;
[NativeTypeName("unsigned char *")]
public byte* data;
public int fontstart;
public int numGlyphs;
public int loca;
public int head;
public int glyf;
public int hhea;
public int hmtx;
public int kern;
public int gpos;
public int svg;
public int index_map;
public int indexToLocFormat;
public stbtt__buf cff;
public stbtt__buf charstrings;
public stbtt__buf gsubrs;
public stbtt__buf subrs;
public stbtt__buf fontdicts;
public stbtt__buf fdselect;
}
public partial struct stbtt_kerningentry
{
public int glyph1;
public int glyph2;
public int advance;
}
[NativeTypeName("unsigned int")]
public enum StbttV : uint
{
STBTT_vmove = 1,
STBTT_vline,
STBTT_vcurve,
STBTT_vcubic,
}
public partial struct stbtt_vertex
{
public short x;
public short y;
public short cx;
public short cy;
public short cx1;
public short cy1;
[NativeTypeName("unsigned char")]
public byte type;
[NativeTypeName("unsigned char")]
public byte padding;
}
public unsafe partial struct stbtt__bitmap
{
public int w;
public int h;
public int stride;
[NativeTypeName("unsigned char *")]
public byte* pixels;
}
[NativeTypeName("unsigned int")]
public enum StbttPlatform : uint
{
STBTT_PLATFORM_ID_UNICODE = 0,
STBTT_PLATFORM_ID_MAC = 1,
STBTT_PLATFORM_ID_ISO = 2,
STBTT_PLATFORM_ID_MICROSOFT = 3,
}
[NativeTypeName("unsigned int")]
public enum StbttUnicode : uint
{
STBTT_UNICODE_EID_UNICODE_1_0 = 0,
STBTT_UNICODE_EID_UNICODE_1_1 = 1,
STBTT_UNICODE_EID_ISO_10646 = 2,
STBTT_UNICODE_EID_UNICODE_2_0_BMP = 3,
STBTT_UNICODE_EID_UNICODE_2_0_FULL = 4,
}
[NativeTypeName("unsigned int")]
public enum StbttMs : uint
{
STBTT_MS_EID_SYMBOL = 0,
STBTT_MS_EID_UNICODE_BMP = 1,
STBTT_MS_EID_SHIFTJIS = 2,
STBTT_MS_EID_UNICODE_FULL = 10,
}
[NativeTypeName("unsigned int")]
public enum StbttMac : uint
{
STBTT_MAC_EID_ROMAN = 0,
STBTT_MAC_EID_ARABIC = 4,
STBTT_MAC_EID_JAPANESE = 1,
STBTT_MAC_EID_HEBREW = 5,
STBTT_MAC_EID_CHINESE_TRAD = 2,
STBTT_MAC_EID_GREEK = 6,
STBTT_MAC_EID_KOREAN = 3,
STBTT_MAC_EID_RUSSIAN = 7,
}
[NativeTypeName("unsigned int")]
public enum StbttMsLang : uint
{
STBTT_MS_LANG_ENGLISH = 0x0409,
STBTT_MS_LANG_ITALIAN = 0x0410,
STBTT_MS_LANG_CHINESE = 0x0804,
STBTT_MS_LANG_JAPANESE = 0x0411,
STBTT_MS_LANG_DUTCH = 0x0413,
STBTT_MS_LANG_KOREAN = 0x0412,
STBTT_MS_LANG_FRENCH = 0x040c,
STBTT_MS_LANG_RUSSIAN = 0x0419,
STBTT_MS_LANG_GERMAN = 0x0407,
STBTT_MS_LANG_SPANISH = 0x0409,
STBTT_MS_LANG_HEBREW = 0x040d,
STBTT_MS_LANG_SWEDISH = 0x041D,
}
[NativeTypeName("unsigned int")]
public enum StbttMacLang : uint
{
STBTT_MAC_LANG_ENGLISH = 0,
STBTT_MAC_LANG_JAPANESE = 11,
STBTT_MAC_LANG_ARABIC = 12,
STBTT_MAC_LANG_KOREAN = 23,
STBTT_MAC_LANG_DUTCH = 4,
STBTT_MAC_LANG_RUSSIAN = 32,
STBTT_MAC_LANG_FRENCH = 1,
STBTT_MAC_LANG_SPANISH = 6,
STBTT_MAC_LANG_GERMAN = 2,
STBTT_MAC_LANG_SWEDISH = 5,
STBTT_MAC_LANG_HEBREW = 10,
STBTT_MAC_LANG_CHINESE_SIMPLIFIED = 33,
STBTT_MAC_LANG_ITALIAN = 3,
STBTT_MAC_LANG_CHINESE_TRAD = 19,
}
public static unsafe partial class Stbtt
{
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void quik_stbtt_failed_assert_store([NativeTypeName("quik_failed_assert_cb_t")] IntPtr cb);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_BakeFontBitmap", ExactSpelling = true)]
public static extern int BakeFontBitmap([NativeTypeName("const unsigned char *")] byte* data, int offset, float pixel_height, [NativeTypeName("unsigned char *")] byte* pixels, int pw, int ph, int first_char, int num_chars, stbtt_bakedchar* chardata);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetBakedQuad", ExactSpelling = true)]
public static extern void GetBakedQuad([NativeTypeName("const stbtt_bakedchar *")] stbtt_bakedchar* chardata, int pw, int ph, int char_index, float* xpos, float* ypos, stbtt_aligned_quad* q, int opengl_fillrule);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetScaledFontVMetrics", ExactSpelling = true)]
public static extern void GetScaledFontVMetrics([NativeTypeName("const unsigned char *")] byte* fontdata, int index, float size, float* ascent, float* descent, float* lineGap);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackBegin", ExactSpelling = true)]
public static extern int PackBegin(stbtt_pack_context* spc, [NativeTypeName("unsigned char *")] byte* pixels, int width, int height, int stride_in_bytes, int padding, void* alloc_context);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackEnd", ExactSpelling = true)]
public static extern void PackEnd(stbtt_pack_context* spc);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackFontRange", ExactSpelling = true)]
public static extern int PackFontRange(stbtt_pack_context* spc, [NativeTypeName("const unsigned char *")] byte* fontdata, int font_index, float font_size, int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar* chardata_for_range);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackFontRanges", ExactSpelling = true)]
public static extern int PackFontRanges(stbtt_pack_context* spc, [NativeTypeName("const unsigned char *")] byte* fontdata, int font_index, stbtt_pack_range* ranges, int num_ranges);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackSetOversampling", ExactSpelling = true)]
public static extern void PackSetOversampling(stbtt_pack_context* spc, [NativeTypeName("unsigned int")] uint h_oversample, [NativeTypeName("unsigned int")] uint v_oversample);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackSetSkipMissingCodepoints", ExactSpelling = true)]
public static extern void PackSetSkipMissingCodepoints(stbtt_pack_context* spc, int skip);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetPackedQuad", ExactSpelling = true)]
public static extern void GetPackedQuad([NativeTypeName("const stbtt_packedchar *")] stbtt_packedchar* chardata, int pw, int ph, int char_index, float* xpos, float* ypos, stbtt_aligned_quad* q, int align_to_integer);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackFontRangesGatherRects", ExactSpelling = true)]
public static extern int PackFontRangesGatherRects(stbtt_pack_context* spc, [NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, stbtt_pack_range* ranges, int num_ranges, stbrp_rect* rects);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackFontRangesPackRects", ExactSpelling = true)]
public static extern void PackFontRangesPackRects(stbtt_pack_context* spc, stbrp_rect* rects, int num_rects);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_PackFontRangesRenderIntoRects", ExactSpelling = true)]
public static extern int PackFontRangesRenderIntoRects(stbtt_pack_context* spc, [NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, stbtt_pack_range* ranges, int num_ranges, stbrp_rect* rects);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetNumberOfFonts", ExactSpelling = true)]
public static extern int GetNumberOfFonts([NativeTypeName("const unsigned char *")] byte* data);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetFontOffsetForIndex", ExactSpelling = true)]
public static extern int GetFontOffsetForIndex([NativeTypeName("const unsigned char *")] byte* data, int index);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_InitFont", ExactSpelling = true)]
public static extern int InitFont(stbtt_fontinfo* info, [NativeTypeName("const unsigned char *")] byte* data, int offset);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_FindGlyphIndex", ExactSpelling = true)]
public static extern int FindGlyphIndex([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int unicode_codepoint);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_ScaleForPixelHeight", ExactSpelling = true)]
public static extern float ScaleForPixelHeight([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, float pixels);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_ScaleForMappingEmToPixels", ExactSpelling = true)]
public static extern float ScaleForMappingEmToPixels([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, float pixels);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetFontVMetrics", ExactSpelling = true)]
public static extern void GetFontVMetrics([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int* ascent, int* descent, int* lineGap);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetFontVMetricsOS2", ExactSpelling = true)]
public static extern int GetFontVMetricsOS2([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int* typoAscent, int* typoDescent, int* typoLineGap);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetFontBoundingBox", ExactSpelling = true)]
public static extern void GetFontBoundingBox([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int* x0, int* y0, int* x1, int* y1);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointHMetrics", ExactSpelling = true)]
public static extern void GetCodepointHMetrics([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int codepoint, int* advanceWidth, int* leftSideBearing);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointKernAdvance", ExactSpelling = true)]
public static extern int GetCodepointKernAdvance([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int ch1, int ch2);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointBox", ExactSpelling = true)]
public static extern int GetCodepointBox([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int codepoint, int* x0, int* y0, int* x1, int* y1);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphHMetrics", ExactSpelling = true)]
public static extern void GetGlyphHMetrics([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int glyph_index, int* advanceWidth, int* leftSideBearing);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphKernAdvance", ExactSpelling = true)]
public static extern int GetGlyphKernAdvance([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int glyph1, int glyph2);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphBox", ExactSpelling = true)]
public static extern int GetGlyphBox([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int glyph_index, int* x0, int* y0, int* x1, int* y1);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetKerningTableLength", ExactSpelling = true)]
public static extern int GetKerningTableLength([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetKerningTable", ExactSpelling = true)]
public static extern int GetKerningTable([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, stbtt_kerningentry* table, int table_length);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_IsGlyphEmpty", ExactSpelling = true)]
public static extern int IsGlyphEmpty([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int glyph_index);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointShape", ExactSpelling = true)]
public static extern int GetCodepointShape([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int unicode_codepoint, stbtt_vertex** vertices);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphShape", ExactSpelling = true)]
public static extern int GetGlyphShape([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int glyph_index, stbtt_vertex** vertices);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_FreeShape", ExactSpelling = true)]
public static extern void FreeShape([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, stbtt_vertex* vertices);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_FindSVGDoc", ExactSpelling = true)]
[return: NativeTypeName("unsigned char *")]
public static extern byte* FindSVGDoc([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int gl);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointSVG", ExactSpelling = true)]
public static extern int GetCodepointSVG([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int unicode_codepoint, [NativeTypeName("const char **")] sbyte** svg);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphSVG", ExactSpelling = true)]
public static extern int GetGlyphSVG([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, int gl, [NativeTypeName("const char **")] sbyte** svg);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_FreeBitmap", ExactSpelling = true)]
public static extern void FreeBitmap([NativeTypeName("unsigned char *")] byte* bitmap, void* userdata);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointBitmap", ExactSpelling = true)]
[return: NativeTypeName("unsigned char *")]
public static extern byte* GetCodepointBitmap([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, float scale_x, float scale_y, int codepoint, int* width, int* height, int* xoff, int* yoff);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointBitmapSubpixel", ExactSpelling = true)]
[return: NativeTypeName("unsigned char *")]
public static extern byte* GetCodepointBitmapSubpixel([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int* width, int* height, int* xoff, int* yoff);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_MakeCodepointBitmap", ExactSpelling = true)]
public static extern void MakeCodepointBitmap([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, [NativeTypeName("unsigned char *")] byte* output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_MakeCodepointBitmapSubpixel", ExactSpelling = true)]
public static extern void MakeCodepointBitmapSubpixel([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, [NativeTypeName("unsigned char *")] byte* output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_MakeCodepointBitmapSubpixelPrefilter", ExactSpelling = true)]
public static extern void MakeCodepointBitmapSubpixelPrefilter([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, [NativeTypeName("unsigned char *")] byte* output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float* sub_x, float* sub_y, int codepoint);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointBitmapBox", ExactSpelling = true)]
public static extern void GetCodepointBitmapBox([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* font, int codepoint, float scale_x, float scale_y, int* ix0, int* iy0, int* ix1, int* iy1);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointBitmapBoxSubpixel", ExactSpelling = true)]
public static extern void GetCodepointBitmapBoxSubpixel([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int* ix0, int* iy0, int* ix1, int* iy1);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphBitmap", ExactSpelling = true)]
[return: NativeTypeName("unsigned char *")]
public static extern byte* GetGlyphBitmap([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, float scale_x, float scale_y, int glyph, int* width, int* height, int* xoff, int* yoff);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphBitmapSubpixel", ExactSpelling = true)]
[return: NativeTypeName("unsigned char *")]
public static extern byte* GetGlyphBitmapSubpixel([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int* width, int* height, int* xoff, int* yoff);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_MakeGlyphBitmap", ExactSpelling = true)]
public static extern void MakeGlyphBitmap([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, [NativeTypeName("unsigned char *")] byte* output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_MakeGlyphBitmapSubpixel", ExactSpelling = true)]
public static extern void MakeGlyphBitmapSubpixel([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, [NativeTypeName("unsigned char *")] byte* output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_MakeGlyphBitmapSubpixelPrefilter", ExactSpelling = true)]
public static extern void MakeGlyphBitmapSubpixelPrefilter([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, [NativeTypeName("unsigned char *")] byte* output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float* sub_x, float* sub_y, int glyph);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphBitmapBox", ExactSpelling = true)]
public static extern void GetGlyphBitmapBox([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* font, int glyph, float scale_x, float scale_y, int* ix0, int* iy0, int* ix1, int* iy1);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphBitmapBoxSubpixel", ExactSpelling = true)]
public static extern void GetGlyphBitmapBoxSubpixel([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* font, int glyph, float scale_x, float scale_y, float shift_x, float shift_y, int* ix0, int* iy0, int* ix1, int* iy1);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_Rasterize", ExactSpelling = true)]
public static extern void Rasterize(stbtt__bitmap* result, float flatness_in_pixels, stbtt_vertex* vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void* userdata);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_FreeSDF", ExactSpelling = true)]
public static extern void FreeSDF([NativeTypeName("unsigned char *")] byte* bitmap, void* userdata);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetGlyphSDF", ExactSpelling = true)]
[return: NativeTypeName("unsigned char *")]
public static extern byte* GetGlyphSDF([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, float scale, int glyph, int padding, [NativeTypeName("unsigned char")] byte onedge_value, float pixel_dist_scale, int* width, int* height, int* xoff, int* yoff);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetCodepointSDF", ExactSpelling = true)]
[return: NativeTypeName("unsigned char *")]
public static extern byte* GetCodepointSDF([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* info, float scale, int codepoint, int padding, [NativeTypeName("unsigned char")] byte onedge_value, float pixel_dist_scale, int* width, int* height, int* xoff, int* yoff);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_FindMatchingFont", ExactSpelling = true)]
public static extern int FindMatchingFont([NativeTypeName("const unsigned char *")] byte* fontdata, [NativeTypeName("const char *")] sbyte* name, int flags);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_CompareUTF8toUTF16_bigendian", ExactSpelling = true)]
public static extern int CompareUTF8toUTF16_bigendian([NativeTypeName("const char *")] sbyte* s1, int len1, [NativeTypeName("const char *")] sbyte* s2, int len2);
[DllImport("stbtt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbtt_GetFontNameString", ExactSpelling = true)]
[return: NativeTypeName("const char *")]
public static extern sbyte* GetFontNameString([NativeTypeName("const stbtt_fontinfo *")] stbtt_fontinfo* font, int* length, int platformID, int encodingID, int languageID, int nameID);
}
}

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