Push all uncommitted changes.
I have had a long break from this project due to other higher priority things going on in my life. Big changes inbound.
This commit is contained in:
parent
98b1c1a277
commit
9339295378
83
Quik.FreeType/FT.cs
Normal file
83
Quik.FreeType/FT.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Quik.FreeType
|
||||
{
|
||||
public static class FT
|
||||
{
|
||||
private const string freetype2 = "freetype";
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Init_FreeType")]
|
||||
public static extern FTError InitFreeType(out FTLibrary library);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Done_FreeType")]
|
||||
public static extern FTError DoneFreeType(FTLibrary library);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_New_Face")]
|
||||
public static extern FTError NewFace(
|
||||
FTLibrary library,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string path,
|
||||
long faceIndex,
|
||||
out FTFace face);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_New_Memory_Face")]
|
||||
public static extern FTError NewMemoryFace(
|
||||
FTLibrary library,
|
||||
IntPtr buffer,
|
||||
long size,
|
||||
long faceIndex,
|
||||
out FTFace face);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_New_Memory_Face")]
|
||||
public static extern FTError NewMemoryFace(
|
||||
FTLibrary library,
|
||||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] buffer,
|
||||
long size,
|
||||
long faceIndex,
|
||||
out FTFace face);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_New_Memory_Face")]
|
||||
public static extern FTError NewMemoryFace(
|
||||
FTLibrary library,
|
||||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] Span<byte> buffer,
|
||||
long size,
|
||||
long faceIndex,
|
||||
out FTFace face);
|
||||
|
||||
// public static extern FTError OpenFace(FTLibrary library, in FTOpenArgs args, long faceIndex, out FTFace face);
|
||||
|
||||
// [DllImport(freetype2, EntryPoint = "FT_Attach_File")]
|
||||
// public static extern FTError AttachFile(FTFace face, [MarshalAs(UnmanagedType.LPStr)] string filePathName);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Set_Char_Size")]
|
||||
public static extern FTError SetCharSize(
|
||||
FTFace library,
|
||||
long charWidth,
|
||||
long charHeight,
|
||||
uint horizontalResolution,
|
||||
uint verticalResolution);
|
||||
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Get_Char_Index")]
|
||||
public static extern uint GetCharIndex(FTFace face, ulong charCode);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Load_Glyph")]
|
||||
public static extern FTError LoadGlyph(FTFace face, uint charIndex, FTLoadFlags flags);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Render_Glyph")]
|
||||
public static extern FTError RenderGlyph(FTGlyphSlot slot, FTRenderMode mode);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Done_Face")]
|
||||
public static extern FTError DoneFace(FTFace face);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Bitmap_Init")]
|
||||
public static extern void BitmapInit(ref FTBitmap bitmap);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Bitmap_Convert")]
|
||||
public static extern void BitmapConvert(FTLibrary library, in FTBitmap source, ref FTBitmap target,
|
||||
int alignment);
|
||||
|
||||
[DllImport(freetype2, EntryPoint = "FT_Bitmap_Done")]
|
||||
public static extern void BitmapDone(FTLibrary library, ref FTBitmap bitmap);
|
||||
}
|
||||
}
|
7
Quik.FreeType/FTError.cs
Normal file
7
Quik.FreeType/FTError.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Quik.FreeType
|
||||
{
|
||||
public enum FTError : int
|
||||
{
|
||||
None = 0,
|
||||
}
|
||||
}
|
28
Quik.FreeType/FTLoadFlags.cs
Normal file
28
Quik.FreeType/FTLoadFlags.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace Quik.FreeType
|
||||
{
|
||||
[Flags]
|
||||
public enum FTLoadFlags
|
||||
{
|
||||
Default = 0,
|
||||
NoScale = 1 << 0,
|
||||
NoHinting = 1 << 1,
|
||||
Render = 1 << 2,
|
||||
NoBitmap = 1 << 3,
|
||||
VerticalLayout = 1 << 4,
|
||||
ForceAutoHint = 1 << 5,
|
||||
CropBitmap = 1 << 6,
|
||||
Pedantic = 1 << 7,
|
||||
IgnoreGlobalAdvanceWidth = 1 << 9,
|
||||
NoRecurse = 1 << 10,
|
||||
IgnoreTransform = 1 << 11,
|
||||
Monochrome= 1 << 12,
|
||||
LinearDesign = 1 << 13,
|
||||
SbitsOnly = 1 << 14,
|
||||
NoAutoHint = 1 << 15,
|
||||
Color = 1 << 20,
|
||||
ComputeMetrics = 1 << 21,
|
||||
BitmapMetricsOnly = 1 << 22
|
||||
}
|
||||
}
|
12
Quik.FreeType/FTRenderMode.cs
Normal file
12
Quik.FreeType/FTRenderMode.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Quik.FreeType
|
||||
{
|
||||
public enum FTRenderMode
|
||||
{
|
||||
Normal = 0,
|
||||
Light,
|
||||
Mono,
|
||||
Lcd,
|
||||
LcdVertical,
|
||||
Sdf
|
||||
}
|
||||
}
|
28
Quik.FreeType/FaceFlag.cs
Normal file
28
Quik.FreeType/FaceFlag.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace Quik.FreeType
|
||||
{
|
||||
[Flags]
|
||||
public enum FaceFlag : int
|
||||
{
|
||||
Scalable = 1 << 0,
|
||||
FixedSizes = 1 << 1,
|
||||
FixedWidth = 1 << 2,
|
||||
Sfnt = 1 << 3,
|
||||
Horizontal = 1 << 4,
|
||||
Vertical = 1 << 5,
|
||||
Kerning = 1 << 6,
|
||||
FastGlyphs = 1 << 7,
|
||||
MultipleMasters = 1 << 8,
|
||||
GlyphNames = 1 << 9,
|
||||
ExternalStream = 1 << 10,
|
||||
Hinter = 1 << 11,
|
||||
CidKeyed = 1 << 12,
|
||||
Tricky = 1 << 13,
|
||||
Color = 1 << 14,
|
||||
Variation = 1 << 15,
|
||||
Svg = 1 << 16,
|
||||
Sbix = 1 << 17,
|
||||
SbixOverlay = 1 << 18
|
||||
}
|
||||
}
|
181
Quik.FreeType/FreeTypeFont.cs
Normal file
181
Quik.FreeType/FreeTypeFont.cs
Normal file
@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Quik.Typography;
|
||||
|
||||
namespace Quik.FreeType
|
||||
{
|
||||
public class FreeTypeFont : QuikFont, IDisposable
|
||||
{
|
||||
|
||||
private FreeTypeFontManager _owner;
|
||||
private FTLibrary _library;
|
||||
private FTFace _face;
|
||||
private List<Atlas> _textures = new List<Atlas>();
|
||||
private Dictionary<int, GlyphEntry> _entries = new Dictionary<int, GlyphEntry>();
|
||||
|
||||
public override float Ascender => _face.ScaledSize.Ascender / 64f;
|
||||
public override float Descender => _face.ScaledSize.Descender / 64f;
|
||||
|
||||
internal FreeTypeFont(FreeTypeFontManager owner, FileInfo file, QuikFontStyle style)
|
||||
{
|
||||
Style = style;
|
||||
_owner = owner;
|
||||
_library = owner._library;
|
||||
|
||||
if (FT.NewFace(_library, file.FullName, 0, out _face) != FTError.None)
|
||||
{
|
||||
throw new Exception("Could not load font file.");
|
||||
}
|
||||
|
||||
FT.SetCharSize(_face, 0, (long)(style.Size * 64), 96, 96);
|
||||
|
||||
_textures.Add(new Atlas(owner.Context.TextureManager, _library));
|
||||
}
|
||||
|
||||
public override QuikFontStyle Style { get; }
|
||||
|
||||
public override bool HasCharacter(int character)
|
||||
{
|
||||
return FT.GetCharIndex(_face, (ulong)character) != 0;
|
||||
}
|
||||
|
||||
public override void GetCharacter(int character, out IQuikTexture texture, out QuikGlyph glyph)
|
||||
{
|
||||
GlyphEntry entry;
|
||||
|
||||
if (_entries.TryGetValue(character, out entry))
|
||||
{
|
||||
texture = entry.Atlas.Texture;
|
||||
glyph = entry.Metrics;
|
||||
return;
|
||||
}
|
||||
|
||||
entry = new GlyphEntry();
|
||||
entry.Character = character;
|
||||
|
||||
uint index = FT.GetCharIndex(_face, (ulong) character);
|
||||
FT.LoadGlyph(_face, index, FTLoadFlags.Default);
|
||||
FT.RenderGlyph(_face.Glyph, FTRenderMode.Normal);
|
||||
|
||||
entry.Atlas = _textures[_textures.Count - 1];
|
||||
if (!entry.Atlas.CanFit(_face.Glyph))
|
||||
{
|
||||
entry.Atlas = new Atlas(_owner.Context.TextureManager, _library);
|
||||
_textures.Add(entry.Atlas);
|
||||
}
|
||||
entry.Atlas.AttachGlyph(_face.Glyph, out QuikRectangle uvs);
|
||||
|
||||
entry.Metrics = new QuikGlyph(
|
||||
character,
|
||||
uvs,
|
||||
new QuikVec2(_face.Glyph.Metrics.Width / 64f, _face.Glyph.Metrics.Height / 64f),
|
||||
new QuikVec2(_face.Glyph.Metrics.HorizontalBearingX / 64f, _face.Glyph.Metrics.HorizontalBearingY / 64f),
|
||||
new QuikVec2(_face.Glyph.Metrics.VerticalBearingX / 64f , _face.Glyph.Metrics.VerticalBearingY / 64f),
|
||||
new QuikVec2(_face.Glyph.Metrics.HorizontalAdvance / 64f, _face.Glyph.Metrics.VerticalAdvance / 64f));
|
||||
|
||||
_entries[character] = entry;
|
||||
|
||||
texture = entry.Atlas.Texture;
|
||||
glyph = entry.Metrics;
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; private set; } = false;
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
foreach (Atlas atlas in _textures)
|
||||
{
|
||||
atlas.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
FT.DoneFace(_face);
|
||||
IsDisposed = true;
|
||||
}
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
private class Atlas : IDisposable
|
||||
{
|
||||
public IQuikTexture Texture;
|
||||
|
||||
private QuikVec2 _pointer = new QuikVec2();
|
||||
private float _verticalAdvance = 0;
|
||||
private FTLibrary _ft;
|
||||
private FTBitmap _bitmap;
|
||||
|
||||
public Atlas(IQuikTextureManager textureManager, FTLibrary ft)
|
||||
{
|
||||
Texture = textureManager.CreateTexture(
|
||||
new QuikVec2(4096, 4096),
|
||||
false,
|
||||
QuikImageFormat.RgbaU8);
|
||||
|
||||
FT.BitmapInit(ref _bitmap);
|
||||
_ft = ft;
|
||||
}
|
||||
|
||||
public bool CanFit(in FTGlyphSlot slot)
|
||||
{
|
||||
// FIXME: the atlas will overflow.
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AttachGlyph(in FTGlyphSlot slot, out QuikRectangle UVs)
|
||||
{
|
||||
FT.BitmapConvert(_ft, slot.Bitmap, ref _bitmap, 1);
|
||||
|
||||
QuikRectangle position =
|
||||
new QuikRectangle(
|
||||
_pointer + new QuikVec2(_bitmap.Width + 1, _bitmap.Rows + 1),
|
||||
_pointer + new QuikVec2(1, 1));
|
||||
|
||||
Texture.SubImage(
|
||||
_bitmap.Buffer,
|
||||
QuikImageFormat.AlphaU8,
|
||||
position,
|
||||
0,
|
||||
1);
|
||||
|
||||
_pointer.X += _bitmap.Width + 2;
|
||||
_verticalAdvance = Math.Max(_verticalAdvance, slot.Bitmap.Rows + 2);
|
||||
|
||||
UVs = new QuikRectangle(
|
||||
position.Right / 4096,
|
||||
position.Bottom / 4096,
|
||||
position.Left / 4096,
|
||||
position.Top / 4096
|
||||
);
|
||||
}
|
||||
|
||||
private bool _isDisposed = false;
|
||||
|
||||
public void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
Texture.Dispose();
|
||||
}
|
||||
FT.BitmapDone(_ft, ref _bitmap);
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class GlyphEntry
|
||||
{
|
||||
public int Character;
|
||||
public Atlas Atlas;
|
||||
public QuikGlyph Metrics;
|
||||
}
|
||||
}
|
||||
}
|
177
Quik.FreeType/FreeTypeFontManager.cs
Normal file
177
Quik.FreeType/FreeTypeFontManager.cs
Normal file
@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Quik.Typography;
|
||||
|
||||
namespace Quik.FreeType
|
||||
{
|
||||
public class FreeTypeFontManager : IQuikFontManager, IDisposable
|
||||
{
|
||||
public QuikContext Context { get; set; }
|
||||
|
||||
internal FTLibrary _library;
|
||||
private Dictionary<string, FontCacheEntry> _cache = new Dictionary<string, FontCacheEntry>();
|
||||
private Dictionary<QuikFontStyle, FreeTypeFont> _fonts= new Dictionary<QuikFontStyle, FreeTypeFont>();
|
||||
|
||||
public event FreeTypeFontManagerFontFinder FontNotFound;
|
||||
|
||||
public FreeTypeFontManager()
|
||||
{
|
||||
FT.InitFreeType(out _library);
|
||||
|
||||
// FIXME: There are operating system specific ways to achieve this. This is
|
||||
// definitely not the best way to do this.
|
||||
|
||||
// Scan the fonts folder and build up a font cache.
|
||||
string path = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
path = "/usr/share/fonts";
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
path = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Windows),
|
||||
"Fonts");
|
||||
}
|
||||
// For macOS I don't know. Too bad.
|
||||
}
|
||||
|
||||
DirectoryInfo directory = new DirectoryInfo(path);
|
||||
ScanDirectoryForFonts(directory);
|
||||
}
|
||||
|
||||
private void ScanDirectoryForFonts(DirectoryInfo directory)
|
||||
{
|
||||
foreach (FileSystemInfo node in directory.GetFileSystemInfos())
|
||||
{
|
||||
if (node.Attributes.HasFlag(FileAttributes.Directory))
|
||||
{
|
||||
ScanDirectoryForFonts(node as DirectoryInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScanFileForFonts(node as FileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanFileForFonts(FileInfo file)
|
||||
{
|
||||
if (file is null) return;
|
||||
|
||||
if (FT.NewFace(_library, file.FullName, 0, out FTFace face) == FTError.None)
|
||||
{
|
||||
FontCacheEntry entry;
|
||||
string name = face.FamilyName;
|
||||
string style = face.StyleName;
|
||||
|
||||
if (name is null)
|
||||
goto done_face;
|
||||
|
||||
if (_cache.ContainsKey(name))
|
||||
{
|
||||
entry = _cache[name];
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = new FontCacheEntry();
|
||||
entry.Family = name;
|
||||
_cache[name] = entry;
|
||||
}
|
||||
|
||||
switch (style.ToLowerInvariant())
|
||||
{
|
||||
case "regular":
|
||||
entry.Regular = file;
|
||||
break;
|
||||
case "bold":
|
||||
entry.Bold = file;
|
||||
break;
|
||||
case "italic":
|
||||
entry.Italic = file;
|
||||
break;
|
||||
}
|
||||
|
||||
done_face:
|
||||
FT.DoneFace(face);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
public QuikFont GetFont(QuikFontStyle fontStyle)
|
||||
{
|
||||
FreeTypeFont font;
|
||||
|
||||
if (_fonts.TryGetValue(fontStyle, out font))
|
||||
return font;
|
||||
|
||||
FileInfo file = FindFont(fontStyle);
|
||||
if (file == null)
|
||||
{
|
||||
FontNotFound?.Invoke(fontStyle, ref file);
|
||||
if (file == null)
|
||||
{
|
||||
throw new Exception("Could not find the font you are looking for.");
|
||||
}
|
||||
}
|
||||
|
||||
font = new FreeTypeFont(this, file, fontStyle);
|
||||
_fonts.Add(fontStyle, font);
|
||||
return font;
|
||||
}
|
||||
|
||||
private FileInfo FindFont(QuikFontStyle fontStyle)
|
||||
{
|
||||
FontCacheEntry entry;
|
||||
if (_cache.TryGetValue(fontStyle.Family, out entry))
|
||||
{
|
||||
switch (fontStyle.Type)
|
||||
{
|
||||
case QuikFontType.Normal:
|
||||
return entry.Regular;
|
||||
case QuikFontType.Bold:
|
||||
return entry.Bold;
|
||||
case QuikFontType.Italic:
|
||||
return entry.Italic;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// IDisposable
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
FT.DoneFreeType(_library);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~FreeTypeFontManager()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
}
|
||||
|
||||
private class FontCacheEntry
|
||||
{
|
||||
public string Family;
|
||||
public FileInfo Regular;
|
||||
public FileInfo Bold;
|
||||
public FileInfo Italic;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void FreeTypeFontManagerFontFinder(QuikFontStyle style, ref FileInfo info);
|
||||
}
|
13
Quik.FreeType/Quik.FreeType.csproj
Normal file
13
Quik.FreeType/Quik.FreeType.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Quik\Quik.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
180
Quik.FreeType/Structures.cs
Normal file
180
Quik.FreeType/Structures.cs
Normal file
@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Quik.FreeType
|
||||
{
|
||||
public struct FTLibrary
|
||||
{
|
||||
private IntPtr _handle;
|
||||
public IntPtr Handle => _handle;
|
||||
}
|
||||
|
||||
public unsafe struct FTFace
|
||||
{
|
||||
private IntPtr _handle;
|
||||
public IntPtr Handle => _handle;
|
||||
private unsafe FTFaceInternal* Ptr => (FTFaceInternal*)_handle;
|
||||
|
||||
public long NumberOfGlyphs => Ptr->NumberOfGlyphs;
|
||||
public long FaceIndex => Ptr->FaceIndex;
|
||||
public FaceFlag FaceFlags => (FaceFlag)Ptr->FaceFlags;
|
||||
public long StyleFlags => Ptr->StyleFlags;
|
||||
public string FamilyName => Marshal.PtrToStringUTF8(Ptr->FamilyName);
|
||||
public string StyleName => Marshal.PtrToStringUTF8(Ptr->StyleName);
|
||||
public int NumberOfFixedSizes => Ptr->NumberOfFixedSizes;
|
||||
public int NumberOfCharMaps => Ptr->NumberOfCharMaps;
|
||||
public FTGlyphSlot Glyph => Ptr->Glyph;
|
||||
public short Ascender => Ptr->Ascender;
|
||||
public short Descender => Ptr->Descender;
|
||||
public ref readonly FTSizeMetrics ScaledSize => ref ((FTSize*)Ptr->Size)->Metrics;
|
||||
}
|
||||
|
||||
public struct FTBox
|
||||
{
|
||||
public long XMin;
|
||||
public long YMin;
|
||||
public long XMax;
|
||||
public long YMax;
|
||||
}
|
||||
|
||||
internal struct FTFaceInternal
|
||||
{
|
||||
public long NumberOfFaces;
|
||||
public long FaceIndex;
|
||||
public long FaceFlags;
|
||||
public long StyleFlags;
|
||||
public long NumberOfGlyphs;
|
||||
public IntPtr FamilyName;
|
||||
public IntPtr StyleName;
|
||||
public int NumberOfFixedSizes;
|
||||
public IntPtr AvailableSizes;
|
||||
public int NumberOfCharMaps;
|
||||
public IntPtr Charmaps;
|
||||
public FTGeneric Generic;
|
||||
public FTBox BoundingBox;
|
||||
public ushort UnitsPerEm;
|
||||
public short Ascender;
|
||||
public short Descender;
|
||||
public short Height;
|
||||
public short MaxAdvanceWidth;
|
||||
public short MaxAdvanceHeight;
|
||||
public short UnderlinePosition;
|
||||
public short UnderlineThickness;
|
||||
public FTGlyphSlot Glyph;
|
||||
public IntPtr Size;
|
||||
public IntPtr Charmap;
|
||||
|
||||
// Rest of the struct is private to implementation.
|
||||
}
|
||||
|
||||
public struct FTGeneric
|
||||
{
|
||||
public IntPtr Data;
|
||||
public IntPtr Finalizer;
|
||||
}
|
||||
|
||||
public struct FTVector
|
||||
{
|
||||
public long X;
|
||||
public long Y;
|
||||
}
|
||||
|
||||
public struct FTBitmap
|
||||
{
|
||||
public uint Rows;
|
||||
public uint Width;
|
||||
public int Pitch;
|
||||
public IntPtr Buffer;
|
||||
public ushort NumberOfGrays;
|
||||
public byte PixelMode;
|
||||
public byte PaletteMode;
|
||||
public IntPtr Palette;
|
||||
}
|
||||
|
||||
public struct FTOutline
|
||||
{
|
||||
public short NumberOfContours;
|
||||
public short NumberOfPoints;
|
||||
public IntPtr Points;
|
||||
public IntPtr Tags;
|
||||
public IntPtr Contours;
|
||||
public int Flags;
|
||||
}
|
||||
|
||||
internal struct FTGlyphSlotInternal
|
||||
{
|
||||
public FTLibrary Library;
|
||||
public FTFace Face;
|
||||
public FTGlyphSlot Next;
|
||||
public uint GlyphIndex;
|
||||
public FTGeneric Generic;
|
||||
public FTGlyphMetrics Metrics;
|
||||
public long LinearHorizontalAdvance;
|
||||
public long LinearVerticalAdvance;
|
||||
public FTVector Advance;
|
||||
public int Format;
|
||||
public FTBitmap Bitmap;
|
||||
public int BitmapLeft;
|
||||
public int BitmapTop;
|
||||
public FTOutline Outline;
|
||||
public uint NumberOfSubGlyphs;
|
||||
public IntPtr SubGlyphs;
|
||||
public IntPtr ControlData;
|
||||
public long ControlLength;
|
||||
public long LsbDelta;
|
||||
public long RsbDelta;
|
||||
public IntPtr Other;
|
||||
public IntPtr Internal;
|
||||
}
|
||||
|
||||
public unsafe struct FTGlyphSlot
|
||||
{
|
||||
private IntPtr _handle;
|
||||
public IntPtr Handle => _handle;
|
||||
private FTGlyphSlotInternal* Ptr => (FTGlyphSlotInternal*) _handle;
|
||||
|
||||
public FTLibrary Library => Ptr->Library;
|
||||
public FTFace Face => Ptr->Face;
|
||||
public FTGlyphSlot Next => Ptr->Next;
|
||||
public uint GlyphIndex => Ptr->GlyphIndex;
|
||||
public ref readonly FTGlyphMetrics Metrics => ref Ptr->Metrics;
|
||||
public long LinearHorizontalAdvance => Ptr->LinearHorizontalAdvance;
|
||||
public long LinearVerticalAdvance => Ptr->LinearVerticalAdvance;
|
||||
public FTVector Advance => Ptr->Advance;
|
||||
public ref readonly FTBitmap Bitmap => ref Ptr->Bitmap;
|
||||
public long BitmapLeft => Ptr->BitmapLeft;
|
||||
public long BitmapTop => Ptr->BitmapTop;
|
||||
}
|
||||
|
||||
public struct FTGlyphMetrics
|
||||
{
|
||||
public long Width;
|
||||
public long Height;
|
||||
public long HorizontalBearingX;
|
||||
public long HorizontalBearingY;
|
||||
public long HorizontalAdvance;
|
||||
public long VerticalBearingX;
|
||||
public long VerticalBearingY;
|
||||
public long VerticalAdvance;
|
||||
}
|
||||
|
||||
public struct FTSizeMetrics
|
||||
{
|
||||
public short Xppem;
|
||||
public short Yppem;
|
||||
public long XScale;
|
||||
public long YScale;
|
||||
public long Ascender;
|
||||
public long Descender;
|
||||
public long Height;
|
||||
public long MaxAdvance;
|
||||
}
|
||||
|
||||
public struct FTSize
|
||||
{
|
||||
public IntPtr Face;
|
||||
public FTGeneric Generic;
|
||||
public FTSizeMetrics Metrics;
|
||||
private IntPtr Privates;
|
||||
}
|
||||
}
|
147
Quik.OpenTK/GL30Driver.cs
Normal file
147
Quik.OpenTK/GL30Driver.cs
Normal file
@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace Quik.OpenTK
|
||||
{
|
||||
public class GL30Driver : IDisposable
|
||||
{
|
||||
public GL30Driver()
|
||||
{
|
||||
Assembly asm = typeof(GL30Driver).Assembly;
|
||||
|
||||
using (StreamReader vert = new StreamReader(asm.GetManifestResourceStream("Quik.OpenTK.glsl.glsl130.vert")))
|
||||
using (StreamReader frag = new StreamReader(asm.GetManifestResourceStream("Quik.OpenTK.glsl.glsl130.frag")))
|
||||
{
|
||||
int vs;
|
||||
int fs;
|
||||
|
||||
vs = GL.CreateShader(ShaderType.VertexShader);
|
||||
fs = GL.CreateShader(ShaderType.FragmentShader);
|
||||
|
||||
_sp = GL.CreateProgram();
|
||||
|
||||
GL.ShaderSource(vs, vert.ReadToEnd());
|
||||
GL.ShaderSource(fs, frag.ReadToEnd());
|
||||
|
||||
GL.CompileShader(vs);
|
||||
GL.CompileShader(fs);
|
||||
|
||||
#if DEBUG
|
||||
int status;
|
||||
|
||||
GL.GetShader(vs, ShaderParameter.CompileStatus, out status);
|
||||
if (status == 0)
|
||||
{
|
||||
throw new Exception(GL.GetShaderInfoLog(vs));
|
||||
}
|
||||
|
||||
GL.GetShader(fs, ShaderParameter.CompileStatus, out status);
|
||||
if (status == 0)
|
||||
{
|
||||
throw new Exception(GL.GetShaderInfoLog(fs));
|
||||
}
|
||||
#endif
|
||||
|
||||
GL.AttachShader(_sp, vs);
|
||||
GL.AttachShader(_sp, fs);
|
||||
|
||||
GL.LinkProgram(_sp);
|
||||
|
||||
#if DEBUG
|
||||
GL.GetProgram(_sp, GetProgramParameterName.LinkStatus, out status);
|
||||
if (status == 0)
|
||||
{
|
||||
throw new Exception(GL.GetProgramInfoLog(_sp));
|
||||
}
|
||||
#endif
|
||||
|
||||
GL.DetachShader(_sp, vs);
|
||||
GL.DetachShader(_sp, fs);
|
||||
GL.DeleteShader(vs);
|
||||
GL.DeleteShader(fs);
|
||||
}
|
||||
|
||||
LoadUniform(_nameM4View, out _locM4View);
|
||||
LoadUniform(_nameM4Model, out _locM4Model);
|
||||
LoadUniform(_nameIFlags, out _locIFlags);
|
||||
LoadUniform(_nameFSdfThreshold, out _locFSdfThreshold);
|
||||
LoadUniform(_nameFSdfAuxilliaryThreshold, out _locFSdfAuxilliaryThreshold);
|
||||
LoadUniform(_nameV4SdfAuxilliaryColor, out _locV4SdfAuxilliaryColor);
|
||||
LoadUniform(_nameX2Texture, out _locX2Texture);
|
||||
LoadUniform(_nameV2TextureOffset, out _locV2TextureOffset);
|
||||
|
||||
LoadAttribute(_nameV2Postion, out _locV2Position);
|
||||
LoadAttribute(_nameV2Texture, out _locV2Texture);
|
||||
LoadAttribute(_nameV4Color, out _locV4Color);
|
||||
|
||||
void LoadUniform(string name, out int location)
|
||||
{
|
||||
location = GL.GetUniformLocation(_sp, name);
|
||||
}
|
||||
|
||||
void LoadAttribute(string name, out int location)
|
||||
{
|
||||
location = GL.GetAttribLocation(_sp, name);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly int _sp;
|
||||
|
||||
private const string _nameM4View = "m4View";
|
||||
private readonly int _locM4View;
|
||||
private const string _nameM4Model = "m4Model";
|
||||
private readonly int _locM4Model;
|
||||
private const string _nameV2Postion = "v2Position";
|
||||
private readonly int _locV2Position;
|
||||
private const string _nameV2Texture = "v2Texture";
|
||||
private readonly int _locV2Texture;
|
||||
private const string _nameV4Color = "v4Color";
|
||||
private readonly int _locV4Color;
|
||||
private const string _nameIFlags = "iFlags";
|
||||
private readonly int _locIFlags;
|
||||
private const string _nameFSdfThreshold = "fSdfThreshold";
|
||||
private readonly int _locFSdfThreshold;
|
||||
private const string _nameFSdfAuxilliaryThreshold = "fSdfAuxilliaryThreshold";
|
||||
private readonly int _locFSdfAuxilliaryThreshold;
|
||||
private const string _nameV4SdfAuxilliaryColor = "v4SdfAuxilliaryColor";
|
||||
private readonly int _locV4SdfAuxilliaryColor;
|
||||
private const string _nameX2Texture = "x2Texture";
|
||||
private readonly int _locX2Texture;
|
||||
private const string _nameV2TextureOffset = "v2TextureOffset";
|
||||
private readonly int _locV2TextureOffset;
|
||||
|
||||
[Flags]
|
||||
private enum Flags : int
|
||||
{
|
||||
Texture = 1 << 0,
|
||||
DiscardEnable = 1 << 1,
|
||||
Sdf = 1 << 2,
|
||||
SdfAuxEnable = 1 << 3,
|
||||
}
|
||||
|
||||
private bool _isDisposed = false;
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
GL.DeleteProgram(_sp);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("OpenGL resource is leaked. Dispose unreferenced OpenGL objects in the context thread.");
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
~GL30Driver()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,9 +16,9 @@ namespace Quik.OpenTK
|
||||
Height = (int)size.Y;
|
||||
|
||||
TextureId = GL.GenTexture();
|
||||
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, TextureId);
|
||||
|
||||
|
||||
GL.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureMinFilter,
|
||||
@ -62,16 +62,18 @@ namespace Quik.OpenTK
|
||||
public bool Mipmaps { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Image(IntPtr data, QuikImageFormat format, QuikVec2 size, int level)
|
||||
public void Image(IntPtr data, QuikImageFormat format, QuikVec2 size, int level, int alignment = 4)
|
||||
{
|
||||
GL.BindTexture(TextureTarget.Texture2D, TextureId);
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, alignment);
|
||||
GL.TexSubImage2D(TextureTarget.Texture2D, level, 0, 0, Width, Height, GetGlImageFormat(format), GetGlDataFormat(format), data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SubImage(IntPtr data, QuikImageFormat format, QuikRectangle location, int level)
|
||||
public void SubImage(IntPtr data, QuikImageFormat format, QuikRectangle location, int level, int alignment = 4)
|
||||
{
|
||||
GL.BindTexture(TextureTarget.Texture2D, TextureId);
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, alignment);
|
||||
GL.TexSubImage2D(
|
||||
TextureTarget.Texture2D,
|
||||
level,
|
||||
@ -101,6 +103,8 @@ namespace Quik.OpenTK
|
||||
return PixelFormat.Rgb;
|
||||
case QuikImageFormat.RgbaF: case QuikImageFormat.RgbaU8:
|
||||
return PixelFormat.Rgba;
|
||||
case QuikImageFormat.AlphaF: case QuikImageFormat.AlphaU8:
|
||||
return PixelFormat.Alpha;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
@ -113,11 +117,14 @@ namespace Quik.OpenTK
|
||||
case QuikImageFormat.RedF:
|
||||
case QuikImageFormat.RgbaF:
|
||||
case QuikImageFormat.RgbF:
|
||||
case QuikImageFormat.AlphaF:
|
||||
return PixelType.Float;
|
||||
case QuikImageFormat.RedU8:
|
||||
case QuikImageFormat.RgbaU8:
|
||||
case QuikImageFormat.RgbU8:
|
||||
case QuikImageFormat.AlphaU8:
|
||||
return PixelType.UnsignedByte;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Quik\Quik.csproj" />
|
||||
<EmbeddedResource Include="glsl\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
61
Quik.OpenTK/glsl/glsl130.frag
Normal file
61
Quik.OpenTK/glsl/glsl130.frag
Normal file
@ -0,0 +1,61 @@
|
||||
#version 130
|
||||
|
||||
#define F_TEXTURE (1 << 0)
|
||||
#define F_DISCARD_EN (1 << 1)
|
||||
#define F_SDF (1 << 2)
|
||||
#define F_SDF_AUX_EN (1 << 3)
|
||||
|
||||
uniform int iFlags;
|
||||
uniform float fSdfThreshold;
|
||||
uniform float fSdfAuxilliaryThreshold;
|
||||
uniform vec4 v4SdfAuxilliaryColor;
|
||||
uniform sampler2D x2Texture;
|
||||
uniform vec2 v2TextureOffset;
|
||||
|
||||
in vec2 fv2Texture;
|
||||
out vec4 fv4Color;
|
||||
|
||||
vec4 v4Color()
|
||||
{
|
||||
vec4 color = fv4Color;
|
||||
|
||||
if ((iFlags & F_TEXTURE) != 0)
|
||||
{
|
||||
if ((iFlags & F_SDF) != 0)
|
||||
{
|
||||
float a = texture(x2Texture, fv2Texture + v2TextureOffset).a;
|
||||
|
||||
if ((iFlags & F_SDF_AUX_EN) != 0)
|
||||
{
|
||||
color =
|
||||
(a > fSdfThreshold)
|
||||
? color
|
||||
: (a > fSdfAuxilliaryThreshold)
|
||||
? v4SdfAuxilliaryColor
|
||||
: vec4(0, 0, 0, 0);
|
||||
}
|
||||
else if (a < fSdfThreshold)
|
||||
{
|
||||
color = vec4(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
color *= texture(x2Texture, fv2Texture + v2TextureOffset);
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = v4Color();
|
||||
|
||||
if ((iFlags & F_DISCARD_EN) != 0 && color.a <= 0)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
fv4Color = color;
|
||||
}
|
19
Quik.OpenTK/glsl/glsl130.vert
Normal file
19
Quik.OpenTK/glsl/glsl130.vert
Normal file
@ -0,0 +1,19 @@
|
||||
#version 130
|
||||
|
||||
uniform mat4 m4View;
|
||||
uniform mat4 m4Model;
|
||||
|
||||
in vec2 v2Position;
|
||||
in vec2 v2Texture;
|
||||
in vec4 v4Color;
|
||||
|
||||
out vec2 fv2Texture;
|
||||
out vec4 fv4Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
fv4Color = v4Color;
|
||||
fv2Texture = v2Texture;
|
||||
|
||||
gl_Position = m4View * m4Model * vec4(v2Position.xy, 1, 1);
|
||||
}
|
14
Quik.sln
14
Quik.sln
@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quik.OpenTK", "Quik.OpenTK\
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuikTestApplication", "QuikTestApplication\QuikTestApplication.csproj", "{49AEF502-692A-48A4-8076-EF2228925280}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quik.FreeType", "Quik.FreeType\Quik.FreeType.csproj", "{53B95098-F304-47E6-A08C-DAFA589F5BCF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -53,5 +55,17 @@ Global
|
||||
{49AEF502-692A-48A4-8076-EF2228925280}.Release|x64.Build.0 = Release|Any CPU
|
||||
{49AEF502-692A-48A4-8076-EF2228925280}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{49AEF502-692A-48A4-8076-EF2228925280}.Release|x86.Build.0 = Release|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{53B95098-F304-47E6-A08C-DAFA589F5BCF}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
105
Quik/Controls/Button.cs
Normal file
105
Quik/Controls/Button.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using Quik.Typography;
|
||||
|
||||
namespace Quik.Controls
|
||||
{
|
||||
public class Button : Control
|
||||
{
|
||||
public string Text { get; set; } = "Button";
|
||||
public float Padding { get; set; } = 4.0f;
|
||||
|
||||
public QuikFont Font { get; set; }
|
||||
|
||||
public QuikStrokeStyle NormalStroke { get; set; }
|
||||
public QuikFillStyle NormalFill { get; set; }
|
||||
public QuikStrokeStyle HoverStroke { get; set; }
|
||||
public QuikFillStyle HoverFill { get; set; }
|
||||
public QuikStrokeStyle ActiveStroke { get; set; }
|
||||
public QuikFillStyle ActiveFill { get; set; }
|
||||
|
||||
private ButtonClass _class = ButtonClass.Normal;
|
||||
|
||||
private enum ButtonClass
|
||||
{
|
||||
Normal,
|
||||
Hover,
|
||||
Active
|
||||
}
|
||||
|
||||
|
||||
protected override void OnMouseEnter(MouseMoveEventArgs args)
|
||||
{
|
||||
base.OnMouseEnter(args);
|
||||
|
||||
if (_class == ButtonClass.Normal)
|
||||
_class = ButtonClass.Hover;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(MouseMoveEventArgs args)
|
||||
{
|
||||
base.OnMouseLeave(args);
|
||||
|
||||
if (_class == ButtonClass.Hover)
|
||||
_class = ButtonClass.Normal;
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseButtonEventArgs args)
|
||||
{
|
||||
base.OnMouseDown(args);
|
||||
|
||||
if (_class == ButtonClass.Hover)
|
||||
_class = ButtonClass.Active;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseButtonEventArgs args)
|
||||
{
|
||||
base.OnMouseUp(args);
|
||||
|
||||
if (_class == ButtonClass.Active)
|
||||
{
|
||||
_class = ButtonClass.Hover;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaint(QuikDraw draw)
|
||||
{
|
||||
QuikRectangle bounds = AbsoluteBounds;
|
||||
|
||||
switch (_class)
|
||||
{
|
||||
default:
|
||||
case ButtonClass.Normal:
|
||||
draw.Commands.Enqueue(new QuikCommandRectangle(bounds) {
|
||||
StrokeStyle = NormalStroke,
|
||||
FillStyle = NormalFill
|
||||
});
|
||||
break;
|
||||
case ButtonClass.Hover:
|
||||
draw.Commands.Enqueue(new QuikCommandRectangle(bounds) {
|
||||
StrokeStyle = HoverStroke,
|
||||
FillStyle = HoverFill
|
||||
});
|
||||
break;
|
||||
case ButtonClass.Active:
|
||||
draw.Commands.Enqueue(new QuikCommandRectangle(bounds) {
|
||||
StrokeStyle = ActiveStroke,
|
||||
FillStyle = ActiveFill
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// Position the text so that it is centered.
|
||||
float ascender = Root.Context.DefaultFont.Ascender;
|
||||
float descender = -Root.Context.DefaultFont.Descender;
|
||||
QuikVec2 position =
|
||||
bounds.Min +
|
||||
new QuikVec2(
|
||||
Padding,
|
||||
(
|
||||
(bounds.Size.Y - ascender - descender) /
|
||||
2)
|
||||
+ descender);
|
||||
|
||||
draw.PutText(Text, position);
|
||||
}
|
||||
}
|
||||
}
|
139
Quik/Controls/Container.cs
Normal file
139
Quik/Controls/Container.cs
Normal file
@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Quik.Controls
|
||||
{
|
||||
public class Container : Control, IList<Control>
|
||||
{
|
||||
private List<Control> _children = new List<Control>();
|
||||
public IEnumerator<Control> GetEnumerator() => _children.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public virtual void Add(Control item)
|
||||
{
|
||||
_children.Add(item);
|
||||
item.NotifyParentChanged(new ParentChangedEventArgs(this));
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
foreach (Control child in _children)
|
||||
{
|
||||
child.NotifyParentChanged(ParentChangedEventArgs.Disowned);
|
||||
}
|
||||
_children.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(Control item) => _children.Contains(item);
|
||||
|
||||
public void CopyTo(Control[] array, int arrayIndex) => _children.CopyTo(array, arrayIndex);
|
||||
|
||||
public virtual bool Remove(Control item)
|
||||
{
|
||||
if (_children.Remove(item))
|
||||
{
|
||||
item.NotifyParentChanged(ParentChangedEventArgs.Disowned);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _children.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public int IndexOf(Control item) =>_children.IndexOf(item);
|
||||
|
||||
public virtual void Insert(int index, Control item)
|
||||
{
|
||||
_children.Insert(index, item);
|
||||
NotifyParentChanged(new ParentChangedEventArgs(this));
|
||||
}
|
||||
|
||||
public virtual void RemoveAt(int index)
|
||||
{
|
||||
_children.RemoveAt(index);
|
||||
_children[index].NotifyParentChanged(ParentChangedEventArgs.Disowned);
|
||||
}
|
||||
|
||||
public virtual Control this[int index]
|
||||
{
|
||||
get => _children[index];
|
||||
set
|
||||
{
|
||||
_children[index].NotifyParentChanged(ParentChangedEventArgs.Disowned);
|
||||
_children[index] = value;
|
||||
value.NotifyParentChanged(new ParentChangedEventArgs(this));
|
||||
}
|
||||
}
|
||||
|
||||
public Control this[string name] => _children.Find(x => x.Name == name);
|
||||
|
||||
internal override void NotifyUpdate()
|
||||
{
|
||||
base.NotifyUpdate();
|
||||
|
||||
foreach (Control child in _children)
|
||||
{
|
||||
child.NotifyUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void NotifyPaint(QuikDraw draw)
|
||||
{
|
||||
base.NotifyPaint(draw);
|
||||
|
||||
foreach (Control child in _children)
|
||||
{
|
||||
child.NotifyPaint(draw);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void NotifyRootChanged(RootChangedEventArgs args)
|
||||
{
|
||||
base.NotifyRootChanged(args);
|
||||
|
||||
foreach (Control child in _children)
|
||||
{
|
||||
child.NotifyRootChanged(args);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void NotifyMouseMove(MouseMoveEventArgs args)
|
||||
{
|
||||
base.NotifyMouseMove(args);
|
||||
|
||||
foreach (Control child in _children)
|
||||
{
|
||||
child.NotifyMouseMove(args);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void NotifyMouseDown(MouseButtonEventArgs args)
|
||||
{
|
||||
base.NotifyMouseDown(args);
|
||||
|
||||
foreach (Control child in _children)
|
||||
{
|
||||
child.NotifyMouseDown(args);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void NotifyMouseUp(MouseButtonEventArgs args)
|
||||
{
|
||||
base.NotifyMouseUp(args);
|
||||
|
||||
foreach (Control child in _children)
|
||||
{
|
||||
child.NotifyMouseUp(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
153
Quik/Controls/Control.cs
Normal file
153
Quik/Controls/Control.cs
Normal file
@ -0,0 +1,153 @@
|
||||
using System;
|
||||
|
||||
namespace Quik.Controls
|
||||
{
|
||||
public class Control
|
||||
{
|
||||
public string Name { get; set; } = null;
|
||||
|
||||
public Control Parent { get; set; } = null;
|
||||
|
||||
protected RootControl Root { get; set; } = null;
|
||||
|
||||
public QuikRectangle Bounds { get; set; }
|
||||
|
||||
public bool Focused { get => Root.FocusedControl == this; }
|
||||
|
||||
public QuikRectangle AbsoluteBounds
|
||||
{
|
||||
get => Parent is null
|
||||
? Bounds
|
||||
: new QuikRectangle(Parent.Bounds.Min + Bounds.Max, Parent.Bounds.Min + Bounds.Min);
|
||||
set => Bounds = Parent is null
|
||||
? value
|
||||
: new QuikRectangle(value.Min - Parent.Bounds.Min, value.Max - Parent.Bounds.Min);
|
||||
}
|
||||
|
||||
private MouseButton DownButtons;
|
||||
|
||||
// Hierarchy events.
|
||||
|
||||
public event EventHandler Update;
|
||||
public event EventHandler<QuikDraw> Paint;
|
||||
public event EventHandler<RootChangedEventArgs> RootChanging;
|
||||
public event EventHandler<ParentChangedEventArgs> ParentChanging;
|
||||
public event EventHandler<FocusChangedEventArgs> FocusLost;
|
||||
public event EventHandler<FocusChangedEventArgs> FocusAcquired;
|
||||
|
||||
// Mouse events.
|
||||
|
||||
public event EventHandler<MouseButtonEventArgs> Clicked;
|
||||
public event EventHandler<MouseButtonEventArgs> MouseDown;
|
||||
public event EventHandler<MouseButtonEventArgs> MouseUp;
|
||||
public event EventHandler<MouseMoveEventArgs> MouseEnter;
|
||||
public event EventHandler<MouseMoveEventArgs> MouseMove;
|
||||
// public event EventHandler<MouseMoveEventArgs> MouseHover;
|
||||
public event EventHandler<MouseMoveEventArgs> MouseLeave;
|
||||
|
||||
protected virtual void OnUpdate() => Update?.Invoke(this, EventArgs.Empty);
|
||||
protected virtual void OnPaint(QuikDraw draw) => Paint?.Invoke(this, draw);
|
||||
protected virtual void OnParentChanging(ParentChangedEventArgs args) => ParentChanging?.Invoke(this, args);
|
||||
protected virtual void OnRootChanging(RootChangedEventArgs args) => RootChanging?.Invoke(this, args);
|
||||
protected virtual void OnFocusLost(FocusChangedEventArgs args) => FocusLost?.Invoke(this, args);
|
||||
protected virtual void OnFocusAcquired(FocusChangedEventArgs args) => FocusAcquired?.Invoke(this, args);
|
||||
|
||||
protected virtual void OnClicked(MouseButtonEventArgs args) => Clicked?.Invoke(this, args);
|
||||
protected virtual void OnMouseDown(MouseButtonEventArgs args) => MouseDown?.Invoke(this, args);
|
||||
protected virtual void OnMouseUp(MouseButtonEventArgs args) => MouseUp?.Invoke(this, args);
|
||||
protected virtual void OnMouseEnter(MouseMoveEventArgs args) => MouseEnter?.Invoke(this, args);
|
||||
protected virtual void OnMouseMove(MouseMoveEventArgs args) => MouseMove?.Invoke(this, args);
|
||||
// protected virtual void OnMouseHover(MouseMoveEventArgs args) => MouseHover?.Invoke(this, args);
|
||||
protected virtual void OnMouseLeave(MouseMoveEventArgs args) => MouseLeave?.Invoke(this, args);
|
||||
|
||||
public void Focus()
|
||||
{
|
||||
Root?.Focus(this);
|
||||
}
|
||||
|
||||
internal virtual void NotifyUpdate()
|
||||
{
|
||||
OnUpdate();
|
||||
}
|
||||
|
||||
internal virtual void NotifyPaint(QuikDraw draw)
|
||||
{
|
||||
OnPaint(draw);
|
||||
}
|
||||
|
||||
internal void NotifyFocusChanged(FocusChangedEventArgs args)
|
||||
{
|
||||
if (args.Focused == this)
|
||||
{
|
||||
OnFocusAcquired(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnFocusLost(args);
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void NotifyParentChanged(ParentChangedEventArgs args)
|
||||
{
|
||||
OnParentChanging(args);
|
||||
Parent = args.NewParent;
|
||||
}
|
||||
|
||||
internal virtual void NotifyRootChanged(RootChangedEventArgs args)
|
||||
{
|
||||
OnRootChanging(args);
|
||||
Root = args.NewRoot;
|
||||
}
|
||||
|
||||
internal virtual void NotifyMouseMove(MouseMoveEventArgs args)
|
||||
{
|
||||
QuikRectangle bounds = AbsoluteBounds;
|
||||
|
||||
if (bounds.Contains(args.AbsolutePosition))
|
||||
{
|
||||
if (!bounds.Contains(args.AbsolutePosition - args.Motion))
|
||||
{
|
||||
OnMouseEnter(args);
|
||||
}
|
||||
|
||||
OnMouseMove(args);
|
||||
}
|
||||
else if (bounds.Contains(args.AbsolutePosition - args.Motion))
|
||||
{
|
||||
OnMouseLeave(args);
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void NotifyMouseDown(MouseButtonEventArgs args)
|
||||
{
|
||||
if (AbsoluteBounds.Contains(args.AbsolutePosition))
|
||||
{
|
||||
OnMouseDown(args);
|
||||
DownButtons = args.Buttons;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void NotifyMouseUp(MouseButtonEventArgs args)
|
||||
{
|
||||
if (AbsoluteBounds.Contains(args.AbsolutePosition))
|
||||
{
|
||||
MouseButton mask;
|
||||
|
||||
OnMouseUp(args);
|
||||
|
||||
if ((mask = DownButtons & args.Buttons) > 0)
|
||||
{
|
||||
MouseButtonEventArgs nargs = new MouseButtonEventArgs(args.AbsolutePosition, mask);
|
||||
Focus();
|
||||
OnClicked(nargs);
|
||||
}
|
||||
|
||||
DownButtons &= ~mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
DownButtons = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
Quik/Controls/HierarchyEvents.cs
Normal file
38
Quik/Controls/HierarchyEvents.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Quik.Controls
|
||||
{
|
||||
public class ParentChangedEventArgs : EventArgs
|
||||
{
|
||||
public Control NewParent { get; }
|
||||
|
||||
public ParentChangedEventArgs(Control newParent)
|
||||
{
|
||||
NewParent = newParent;
|
||||
}
|
||||
|
||||
public static ParentChangedEventArgs Disowned { get; } = new ParentChangedEventArgs(null);
|
||||
}
|
||||
|
||||
public class RootChangedEventArgs : EventArgs
|
||||
{
|
||||
public RootControl NewRoot { get; }
|
||||
|
||||
public RootChangedEventArgs(RootControl newRoot)
|
||||
{
|
||||
NewRoot = newRoot;
|
||||
}
|
||||
|
||||
public static RootChangedEventArgs Disowned { get; } = new RootChangedEventArgs(null);
|
||||
}
|
||||
|
||||
public class FocusChangedEventArgs : EventArgs
|
||||
{
|
||||
public Control Focused { get; }
|
||||
|
||||
public FocusChangedEventArgs(Control focused)
|
||||
{
|
||||
Focused = focused;
|
||||
}
|
||||
}
|
||||
}
|
36
Quik/Controls/Label.cs
Normal file
36
Quik/Controls/Label.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using Quik.Typography;
|
||||
|
||||
namespace Quik.Controls
|
||||
{
|
||||
public class Label : Control
|
||||
{
|
||||
public bool MultiLine { get; set; } = false;
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
public float Padding { get; set; } = 4.0f;
|
||||
|
||||
public QuikFont Font { get; set; }
|
||||
|
||||
protected override void OnPaint(QuikDraw draw)
|
||||
{
|
||||
if (MultiLine)
|
||||
{
|
||||
QuikRectangle absolute = AbsoluteBounds;
|
||||
QuikVec2 paddingVector = new QuikVec2(Padding, Padding);
|
||||
QuikRectangle rectangle;
|
||||
rectangle.Min = absolute.Min + paddingVector;
|
||||
rectangle.Max = absolute.Min - paddingVector;
|
||||
|
||||
// FIXME: For now use a puttext command.
|
||||
draw.PutText(Text, rectangle.Min + new QuikVec2(0, rectangle.Size.Y / 3f));
|
||||
|
||||
// draw.FlowText(Text, rectangle);
|
||||
}
|
||||
else
|
||||
{
|
||||
QuikVec2 position = AbsoluteBounds.Min + new QuikVec2(Padding, Padding + AbsoluteBounds.Size.Y / 3f);
|
||||
draw.PutText(Text, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
Quik/Controls/RootControl.cs
Normal file
108
Quik/Controls/RootControl.cs
Normal file
@ -0,0 +1,108 @@
|
||||
|
||||
namespace Quik.Controls
|
||||
{
|
||||
public sealed class RootControl : Container
|
||||
{
|
||||
public QuikContext Context { get; }
|
||||
public Control FocusedControl { get; private set; }
|
||||
|
||||
public RootControl(QuikContext context)
|
||||
{
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public void Focus(Control which)
|
||||
{
|
||||
FocusChangedEventArgs args = new FocusChangedEventArgs(which);
|
||||
|
||||
FocusedControl?.NotifyFocusChanged(args);
|
||||
(FocusedControl = which).NotifyFocusChanged(args);
|
||||
}
|
||||
|
||||
public new void NotifyUpdate()
|
||||
{
|
||||
base.NotifyUpdate();
|
||||
}
|
||||
|
||||
public new void NotifyPaint(QuikDraw draw)
|
||||
{
|
||||
base.NotifyPaint(draw);
|
||||
}
|
||||
|
||||
private MouseState LastMouseState;
|
||||
public void NotifyMouse(in MouseState state)
|
||||
{
|
||||
MouseMoveEventArgs move = new MouseMoveEventArgs(
|
||||
state.AbsolutePosition,
|
||||
LastMouseState.AbsolutePosition);
|
||||
MouseButtonEventArgs up = new MouseButtonEventArgs(
|
||||
state.AbsolutePosition,
|
||||
LastMouseState.ButtonsDown & ~state.ButtonsDown);
|
||||
MouseButtonEventArgs down = new MouseButtonEventArgs(
|
||||
state.AbsolutePosition,
|
||||
~LastMouseState.ButtonsDown & state.ButtonsDown);
|
||||
|
||||
if (move.Motion.Magnitude > 0)
|
||||
NotifyMouseMove(move);
|
||||
|
||||
if (up.Buttons != 0)
|
||||
NotifyMouseUp(up);
|
||||
|
||||
if (down.Buttons != 0)
|
||||
NotifyMouseDown(down);
|
||||
|
||||
LastMouseState = state;
|
||||
}
|
||||
|
||||
public override void Add(Control item)
|
||||
{
|
||||
base.Add(item);
|
||||
item.NotifyRootChanged(new RootChangedEventArgs(this));
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
foreach (Control child in this)
|
||||
{
|
||||
child.NotifyRootChanged(RootChangedEventArgs.Disowned);
|
||||
}
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
public override void Insert(int index, Control item)
|
||||
{
|
||||
base.Insert(index, item);
|
||||
item.NotifyRootChanged(new RootChangedEventArgs(this));
|
||||
}
|
||||
|
||||
public override bool Remove(Control item)
|
||||
{
|
||||
if(base.Remove(item))
|
||||
{
|
||||
item.NotifyRootChanged(RootChangedEventArgs.Disowned);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveAt(int index)
|
||||
{
|
||||
this[index].NotifyRootChanged(RootChangedEventArgs.Disowned);
|
||||
base.RemoveAt(index);
|
||||
}
|
||||
|
||||
public override Control this[int index]
|
||||
{
|
||||
get => base[index];
|
||||
set
|
||||
{
|
||||
base[index].NotifyRootChanged(RootChangedEventArgs.Disowned);
|
||||
base[index] = value;
|
||||
value.NotifyRootChanged(new RootChangedEventArgs(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
Quik/Mouse.cs
Normal file
74
Quik/Mouse.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
|
||||
namespace Quik
|
||||
{
|
||||
public enum MouseButton
|
||||
{
|
||||
Primary = 1 << 0,
|
||||
Secondary = 1 << 1,
|
||||
Tertiary = 1 << 2,
|
||||
Auxilliary1 = 1 << 3,
|
||||
Auxilliary2 = 1 << 4,
|
||||
Auxilliary3 = 1 << 5,
|
||||
Auxilliary4 = 1 << 6,
|
||||
Auxilliary5 = 1 << 8,
|
||||
}
|
||||
|
||||
public struct MouseState
|
||||
{
|
||||
public readonly QuikVec2 AbsolutePosition;
|
||||
public readonly MouseButton ButtonsDown;
|
||||
|
||||
public MouseState(QuikVec2 position, MouseButton down)
|
||||
{
|
||||
AbsolutePosition = position;
|
||||
ButtonsDown = down;
|
||||
}
|
||||
}
|
||||
|
||||
public class MouseButtonEventArgs : EventArgs
|
||||
{
|
||||
public QuikVec2 AbsolutePosition { get; }
|
||||
public MouseButton Buttons { get; }
|
||||
|
||||
public MouseButtonEventArgs(QuikVec2 position, MouseButton buttons)
|
||||
{
|
||||
AbsolutePosition = position;
|
||||
Buttons = buttons;
|
||||
}
|
||||
|
||||
public QuikVec2 RelativePosition(QuikVec2 origin)
|
||||
{
|
||||
return AbsolutePosition - origin;
|
||||
}
|
||||
|
||||
public QuikVec2 RelativePosition(Controls.Control control)
|
||||
{
|
||||
return AbsolutePosition - control.AbsoluteBounds.Min;
|
||||
}
|
||||
}
|
||||
|
||||
public class MouseMoveEventArgs : EventArgs
|
||||
{
|
||||
public QuikVec2 AbsolutePosition { get; }
|
||||
public QuikVec2 LastPosition { get; }
|
||||
public QuikVec2 Motion { get; }
|
||||
|
||||
public MouseMoveEventArgs(QuikVec2 position, QuikVec2 lastPosition)
|
||||
{
|
||||
AbsolutePosition = position;
|
||||
LastPosition = lastPosition;
|
||||
Motion = position - lastPosition;
|
||||
}
|
||||
|
||||
public QuikVec2 RelativePosition(QuikVec2 origin)
|
||||
{
|
||||
return AbsolutePosition - origin;
|
||||
}
|
||||
|
||||
public QuikVec2 RelativePosition(Controls.Control control)
|
||||
{
|
||||
return AbsolutePosition - control.AbsoluteBounds.Min;
|
||||
}
|
||||
}
|
||||
}
|
@ -82,6 +82,8 @@ namespace Quik
|
||||
/// </summary>
|
||||
FlowText,
|
||||
|
||||
EmitTypeset,
|
||||
|
||||
/// <summary>
|
||||
/// Clear the image mask.
|
||||
/// </summary>
|
||||
@ -568,6 +570,43 @@ namespace Quik
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit previously typeset text.
|
||||
/// </summary>
|
||||
public sealed class QuikCommandEmitText : QuikCommand
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override QuikCommandType Type => QuikCommandType.EmitTypeset;
|
||||
|
||||
/// <summary>
|
||||
/// The typeset group to emit.
|
||||
/// </summary>
|
||||
public Typography.TypesetGroup Group { get; }
|
||||
|
||||
public QuikVec2 Offset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an emit typeset text command.
|
||||
/// </summary>
|
||||
/// <param name="group">The typeset group to emit.</param>
|
||||
public QuikCommandEmitText(Typography.TypesetGroup group)
|
||||
{
|
||||
Group = group;
|
||||
Offset = new QuikVec2(0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an emit typeset text command.
|
||||
/// </summary>
|
||||
/// <param name="group">The typeset group to emit.</param>
|
||||
/// <param name="offset">The offset to emit at.</param>
|
||||
public QuikCommandEmitText(Typography.TypesetGroup group, QuikVec2 offset)
|
||||
{
|
||||
Group = group;
|
||||
Offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the stencil buffer.
|
||||
/// </summary>
|
||||
|
@ -8,6 +8,9 @@ namespace Quik.Typography
|
||||
public float FontSize => Style.Size;
|
||||
public QuikFontStyle FontStyle => Style;
|
||||
|
||||
public abstract float Ascender { get; }
|
||||
public abstract float Descender { get; }
|
||||
|
||||
public abstract bool HasCharacter(int character);
|
||||
public abstract void GetCharacter(int character, out IQuikTexture texture, out QuikGlyph glyph);
|
||||
}
|
||||
|
@ -12,6 +12,33 @@ namespace Quik.Typography
|
||||
Size = size;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return
|
||||
Family.GetHashCode() ^
|
||||
(Type.GetHashCode() * 1303) ^
|
||||
(Size.GetHashCode() * 2447);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return
|
||||
obj is QuikFontStyle other &&
|
||||
other.Family == Family &&
|
||||
other.Size == Size &&
|
||||
other.Type == Type;
|
||||
}
|
||||
|
||||
public static bool operator==(QuikFontStyle a, QuikFontStyle b)
|
||||
{
|
||||
return a.Size == b.Size && a.Type == b.Type && a.Family == b.Family;
|
||||
}
|
||||
|
||||
public static bool operator !=(QuikFontStyle a, QuikFontStyle b)
|
||||
{
|
||||
return a.Size != b.Size || a.Type != b.Type || a.Family != b.Family;
|
||||
}
|
||||
}
|
||||
|
||||
public enum QuikFontType
|
||||
|
508
Quik/Typography/TextLayout.cs
Normal file
508
Quik/Typography/TextLayout.cs
Normal file
@ -0,0 +1,508 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Quik.Typography
|
||||
{
|
||||
/// <summary>
|
||||
/// An atomic horizontal block of text which cannot be further divided.
|
||||
/// </summary>
|
||||
public struct HorizontalTextBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// The font associated with the text block.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public QuikFont Font { get; }
|
||||
/// <summary>
|
||||
/// Textual contents of the text block.
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
/// <summary>
|
||||
/// Indicates this text block should be layed out right to left.
|
||||
/// </summary>
|
||||
public bool IsRTL { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates this is a whitespace block.
|
||||
/// </summary>
|
||||
public bool IsWhitespace => string.IsNullOrWhiteSpace(Text);
|
||||
|
||||
public float Width { get; }
|
||||
public float Ascend { get; }
|
||||
public float Descend { get; }
|
||||
public float Height => Ascend - Descend;
|
||||
|
||||
public HorizontalTextBlock(QuikFont font, string text, bool rtl = false)
|
||||
{
|
||||
Font = font;
|
||||
Text = text;
|
||||
IsRTL = rtl;
|
||||
|
||||
float width = 0.0f;
|
||||
float ascend = 0.0f;
|
||||
float descend = 0.0f;
|
||||
|
||||
foreach (char chr in text)
|
||||
{
|
||||
font.GetCharacter(chr, out _, out QuikGlyph glyph);
|
||||
width += glyph.Advance.X;
|
||||
ascend = Math.Max(ascend, glyph.HorizontalBearing.Y);
|
||||
descend = Math.Min(descend, glyph.HorizontalBearing.Y - glyph.Size.Y);
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Ascend = ascend;
|
||||
Descend = descend;
|
||||
}
|
||||
|
||||
public HorizontalTextBlock(float width)
|
||||
{
|
||||
Font = null;
|
||||
Text = string.Empty;
|
||||
IsRTL = false;
|
||||
Width = width;
|
||||
Ascend = Descend = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An atomic vertical block of text which cannot be further divided.
|
||||
/// </summary>
|
||||
public struct VerticalTextBlock
|
||||
{
|
||||
public QuikFont Font { get; }
|
||||
public string Text { get; }
|
||||
public bool IsWhitespace => string.IsNullOrWhiteSpace(Text);
|
||||
public float Width { get; }
|
||||
public float Height { get; }
|
||||
|
||||
public VerticalTextBlock(QuikFont font, string text)
|
||||
{
|
||||
Font = font;
|
||||
Text = text;
|
||||
|
||||
float width = 0.0f;
|
||||
float height = 0.0f;
|
||||
|
||||
foreach(char chr in text)
|
||||
{
|
||||
font.GetCharacter(chr, out _, out QuikGlyph glyph);
|
||||
width = Math.Max(width, - glyph.VerticalBearing.X * 2);
|
||||
height += glyph.Advance.Y;
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public VerticalTextBlock(float height)
|
||||
{
|
||||
Font = null;
|
||||
Text = string.Empty;
|
||||
Width = 0.0f;
|
||||
Height = height;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Paragraph
|
||||
{
|
||||
public abstract bool IsVertical { get; }
|
||||
|
||||
public float JustifyLimit { get; set; } = 30.0f;
|
||||
public TextAlignment Alignment { get; set; } = TextAlignment.Default;
|
||||
public float PreSpace { get; set; } = 0.0f;
|
||||
public float PostSpace { get; set; } = 0.0f;
|
||||
public float FirstLineInset { get; set; } = 0.0f;
|
||||
public float LineGap { get; set; } = 12.0f;
|
||||
|
||||
public abstract void Typeset(TypesetGroup group, float width);
|
||||
|
||||
protected abstract void AppendBlock(QuikFont font, string text, bool rtl = false);
|
||||
|
||||
public void ConsumeText(QuikFont font, string text)
|
||||
{
|
||||
StringBuilder segment = new StringBuilder();
|
||||
bool rtl = false;
|
||||
bool ws = false;
|
||||
|
||||
foreach(char chr in text)
|
||||
{
|
||||
UnicodeCategory cat = char.GetUnicodeCategory(chr);
|
||||
// FIXME: don't ignore control characters like a barbarian.
|
||||
// TODO: how do I detect text flow direction???
|
||||
if (char.IsWhiteSpace(chr) && chr != '\u00a0')
|
||||
{
|
||||
if (ws)
|
||||
{
|
||||
segment.Append(chr);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendBlock(font, segment.ToString());
|
||||
segment.Clear();
|
||||
segment.Append(chr);
|
||||
ws = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ws)
|
||||
{
|
||||
AppendBlock(font, segment.ToString(), rtl);
|
||||
segment.Clear();
|
||||
segment.Append(chr);
|
||||
ws = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
segment.Append(chr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (segment.Length > 0)
|
||||
{
|
||||
AppendBlock(font, segment.ToString(), rtl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class HorizontalParagraph : Paragraph
|
||||
{
|
||||
public override bool IsVertical => false;
|
||||
public List<HorizontalTextBlock> Blocks { get; } = new List<HorizontalTextBlock>();
|
||||
|
||||
public override void Typeset(TypesetGroup group, float width)
|
||||
{
|
||||
Queue<HorizontalTextBlock> line = new Queue<HorizontalTextBlock>();
|
||||
int index = 0;
|
||||
bool firstLine = true;
|
||||
|
||||
QuikVec2 pen = new QuikVec2(0, -PreSpace);
|
||||
|
||||
while (index < Blocks.Count)
|
||||
{
|
||||
index
|
||||
+= GatherLine(
|
||||
index,
|
||||
width - (firstLine ? FirstLineInset : 0),
|
||||
line,
|
||||
out float excess,
|
||||
out float ascend,
|
||||
out float descend);
|
||||
|
||||
firstLine = false;
|
||||
|
||||
pen.Y -= ascend;
|
||||
|
||||
float interblockWs =
|
||||
Alignment.HasFlag(TextAlignment.Justify) && excess < JustifyLimit
|
||||
? excess / (line.Count - 1)
|
||||
: 0.0f;
|
||||
|
||||
switch (Alignment & TextAlignment.HorizontalMask)
|
||||
{
|
||||
default:
|
||||
case TextAlignment.AlignLeft:
|
||||
if (firstLine) pen.X += FirstLineInset;
|
||||
break;
|
||||
case TextAlignment.AlignCenterH:
|
||||
pen.X += excess / 2;
|
||||
break;
|
||||
case TextAlignment.AlignRight:
|
||||
pen.X += excess;
|
||||
break;
|
||||
}
|
||||
|
||||
PutBlock(group, line, interblockWs, ref pen);
|
||||
|
||||
pen.Y -= LineGap - descend;
|
||||
pen.X = 0.0f;
|
||||
}
|
||||
|
||||
pen.Y -= PostSpace;
|
||||
|
||||
group.BoundingBox = new QuikRectangle(width, 0, 0, pen.Y);
|
||||
group.Translate(-pen);
|
||||
}
|
||||
|
||||
private int GatherLine(
|
||||
int index,
|
||||
float width,
|
||||
Queue<HorizontalTextBlock> line,
|
||||
out float excess,
|
||||
out float ascend,
|
||||
out float descend)
|
||||
{
|
||||
float currentWidth = 0.0f;
|
||||
ascend = descend = 0.0f;
|
||||
|
||||
for (int i = index; i < Blocks.Count; i++)
|
||||
{
|
||||
HorizontalTextBlock block = Blocks[i];
|
||||
|
||||
if (currentWidth + block.Width > width)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ascend = Math.Max(ascend, block.Ascend);
|
||||
descend = Math.Min(descend, block.Descend);
|
||||
currentWidth += block.Width;
|
||||
line.Enqueue(block);
|
||||
}
|
||||
|
||||
excess = width - currentWidth;
|
||||
return line.Count;
|
||||
}
|
||||
|
||||
public void PutBlock(
|
||||
TypesetGroup group,
|
||||
Queue<HorizontalTextBlock> line,
|
||||
float interblockWs,
|
||||
ref QuikVec2 pen)
|
||||
{
|
||||
QuikVec2 penpal = pen;
|
||||
|
||||
while (line.TryDequeue(out HorizontalTextBlock block))
|
||||
{
|
||||
if (block.IsWhitespace)
|
||||
{
|
||||
penpal.X += block.Width + interblockWs;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (block.IsRTL)
|
||||
{
|
||||
for (int i = block.Text.Length - 1; i >= 0; i--)
|
||||
{
|
||||
char chr = block.Text[i];
|
||||
block.Font.GetCharacter(chr, out IQuikTexture texture, out QuikGlyph metrics);
|
||||
group.Add(
|
||||
new TypesetCharacter(
|
||||
chr,
|
||||
texture,
|
||||
new QuikRectangle(
|
||||
penpal.X + metrics.Advance.X,
|
||||
penpal.Y + metrics.HorizontalBearing.Y,
|
||||
penpal.X + metrics.HorizontalBearing.X,
|
||||
penpal.Y - metrics.Size.Y + metrics.HorizontalBearing.Y),
|
||||
metrics.Location
|
||||
)
|
||||
);
|
||||
penpal.X += metrics.Advance.X;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < block.Text.Length; i++)
|
||||
{
|
||||
char chr = block.Text[i];
|
||||
block.Font.GetCharacter(chr, out IQuikTexture texture, out QuikGlyph metrics);
|
||||
group.Add(
|
||||
new TypesetCharacter(
|
||||
chr,
|
||||
texture,
|
||||
new QuikRectangle(
|
||||
penpal.X + metrics.Advance.X,
|
||||
penpal.Y + metrics.HorizontalBearing.Y,
|
||||
penpal.X + metrics.HorizontalBearing.X,
|
||||
penpal.Y - metrics.Size.Y + metrics.HorizontalBearing.Y),
|
||||
metrics.Location
|
||||
)
|
||||
);
|
||||
|
||||
penpal.X += metrics.Advance.X;
|
||||
}
|
||||
}
|
||||
|
||||
penpal.X += interblockWs;
|
||||
}
|
||||
|
||||
penpal.X -= interblockWs;
|
||||
|
||||
pen = penpal;
|
||||
}
|
||||
|
||||
protected override void AppendBlock(QuikFont font, string text, bool rtl = false)
|
||||
{
|
||||
Blocks.Add(new HorizontalTextBlock(font, text, rtl));
|
||||
}
|
||||
}
|
||||
|
||||
public class VerticalParagraph : Paragraph
|
||||
{
|
||||
public override bool IsVertical => true;
|
||||
public List<VerticalTextBlock> Blocks { get; } = new List<VerticalTextBlock>();
|
||||
|
||||
public override void Typeset(TypesetGroup group, float width)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override void AppendBlock(QuikFont font, string text, bool rtl = false)
|
||||
{
|
||||
Blocks.Add(new VerticalTextBlock(font, text));
|
||||
}
|
||||
}
|
||||
|
||||
public struct TypesetCharacter
|
||||
{
|
||||
public int Character;
|
||||
public IQuikTexture Texture;
|
||||
public QuikRectangle Position;
|
||||
public QuikRectangle UV;
|
||||
|
||||
public TypesetCharacter(
|
||||
int chr,
|
||||
IQuikTexture texture,
|
||||
in QuikRectangle position,
|
||||
in QuikRectangle uv)
|
||||
{
|
||||
Character = chr;
|
||||
Texture = texture;
|
||||
Position = position;
|
||||
UV = uv;
|
||||
}
|
||||
}
|
||||
|
||||
public class TypesetGroup : ICollection<TypesetCharacter>
|
||||
{
|
||||
private int _count = 0;
|
||||
private TypesetCharacter[] _array = Array.Empty<TypesetCharacter>();
|
||||
|
||||
public QuikRectangle BoundingBox;
|
||||
|
||||
public int Count => _count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(TypesetCharacter item)
|
||||
{
|
||||
if (_count == _array.Length)
|
||||
{
|
||||
Array.Resize(ref _array, _array.Length + 256);
|
||||
}
|
||||
|
||||
_array[_count++] = item;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public void Translate(QuikVec2 offset)
|
||||
{
|
||||
BoundingBox.Translate(offset);
|
||||
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
_array[i].Position.Translate(offset);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(TypesetCharacter item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void CopyTo(TypesetCharacter[] array, int arrayIndex)
|
||||
{
|
||||
_array.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<TypesetCharacter> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
yield return _array[i];
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(TypesetCharacter item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return (IEnumerator)GetEnumerator();
|
||||
}
|
||||
|
||||
public void SortBy(IComparer<TypesetCharacter> comparer)
|
||||
{
|
||||
Array.Sort(_array, 0, _count, comparer);
|
||||
}
|
||||
|
||||
public static IComparer<TypesetCharacter> SortByTexture { get; } = new SortByTextureComparer();
|
||||
|
||||
private class SortByTextureComparer : IComparer<TypesetCharacter>
|
||||
{
|
||||
public int Compare(TypesetCharacter x, TypesetCharacter y)
|
||||
{
|
||||
return y.Texture.GetHashCode() - x.Texture.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An enumeration of possible text alignments.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TextAlignment
|
||||
{
|
||||
/// <summary>
|
||||
/// Align to the left margin, horizontally.
|
||||
/// </summary>
|
||||
AlignLeft = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Align text to center of left and right margins.
|
||||
/// </summary>
|
||||
AlignCenterH = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// Align to the right margin, horizontally.
|
||||
/// </summary>
|
||||
AlignRight = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// A bitmask for values relating to horizontal alignment.
|
||||
/// </summary>
|
||||
HorizontalMask = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// Align text to the top margin.
|
||||
/// </summary>
|
||||
AlignTop = 0x00,
|
||||
|
||||
/// <summary>
|
||||
/// Align text between the top and bottom margins.
|
||||
/// <summary>
|
||||
AlignCenterV = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Align text to the bottom margin.
|
||||
/// </summary>
|
||||
AlignBottom = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// A bitmask for values relating to the vertical alignment.
|
||||
/// </summary>
|
||||
VerticalMask = 0x0C,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute characters uniformly on the line, when possible.
|
||||
/// <summary>
|
||||
Justify = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// The default text alignment value.
|
||||
/// </summary>
|
||||
Default = AlignTop | AlignLeft
|
||||
}
|
||||
}
|
108
Quik/Typography/UnicodeUtil.cs
Normal file
108
Quik/Typography/UnicodeUtil.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Quik.Typography
|
||||
{
|
||||
public static class UnicodeUtil
|
||||
{
|
||||
|
||||
public static bool IsWhiteSpace(int chr)
|
||||
{
|
||||
switch (chr)
|
||||
{
|
||||
case '\t': case '\n': case '\v': case '\f': case '\r': case ' ':
|
||||
case '\u0085': case '\u00A0': case '\u1680': case '\u2000':
|
||||
case '\u2001': case '\u2002': case '\u2003': case '\u2004':
|
||||
case '\u2005': case '\u2006': case '\u2007': case '\u2008':
|
||||
case '\u2009': case '\u200A': case '\u2028': case '\u2029':
|
||||
case '\u202F': case '\u205F': case '\u3000': case '\u180E':
|
||||
case '\u200B': case '\u200C': case '\u200D': case '\u2060':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsUnicodeWhitespace(int chr)
|
||||
{
|
||||
switch (chr)
|
||||
{
|
||||
case '\t': case '\n': case '\v': case '\f': case '\r': case ' ':
|
||||
case '\u0085': case '\u00A0': case '\u1680': case '\u2000':
|
||||
case '\u2001': case '\u2002': case '\u2003': case '\u2004':
|
||||
case '\u2005': case '\u2006': case '\u2007': case '\u2008':
|
||||
case '\u2009': case '\u200A': case '\u2028': case '\u2029':
|
||||
case '\u202F': case '\u205F': case '\u3000':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsNewLine(int chr)
|
||||
{
|
||||
switch (chr)
|
||||
{
|
||||
case '\n': case '\v': case '\f': case '\r':
|
||||
case '\u0085': case '\u2028': case '\u2029':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static WhitespaceInfo GetWhitespaceInfo(int chr)
|
||||
{
|
||||
switch (chr)
|
||||
{
|
||||
// CHAR ------------BOILERPLATE----------- BREAK NEWLINE
|
||||
case '\t': return new WhitespaceInfo('\t', true, false);
|
||||
case '\n': return new WhitespaceInfo('\n', false, true);
|
||||
case '\v': return new WhitespaceInfo('\v', false, true);
|
||||
case '\f': return new WhitespaceInfo('\f', false, true);
|
||||
case '\r': return new WhitespaceInfo('\r', false, true);
|
||||
case ' ': return new WhitespaceInfo(' ', true, false);
|
||||
case '\u0085': return new WhitespaceInfo('\u0085', false, true);
|
||||
case '\u00A0': return new WhitespaceInfo('\u00A0', false, false);
|
||||
case '\u1680': return new WhitespaceInfo('\u1689', true, false);
|
||||
case '\u2000': return new WhitespaceInfo('\u2000', true, false);
|
||||
case '\u2001': return new WhitespaceInfo('\u2001', true, false);
|
||||
case '\u2002': return new WhitespaceInfo('\u2002', true, false);
|
||||
case '\u2003': return new WhitespaceInfo('\u2003', true, false);
|
||||
case '\u2004': return new WhitespaceInfo('\u2004', true, false);
|
||||
case '\u2005': return new WhitespaceInfo('\u2005', true, false);
|
||||
case '\u2006': return new WhitespaceInfo('\u2006', true, false);
|
||||
case '\u2007': return new WhitespaceInfo('\u2007', false, false);
|
||||
case '\u2008': return new WhitespaceInfo('\u2008', true, false);
|
||||
case '\u2009': return new WhitespaceInfo('\u2009', true, false);
|
||||
case '\u200A': return new WhitespaceInfo('\u200A', true, false);
|
||||
case '\u2028': return new WhitespaceInfo('\u2028', false, true);
|
||||
case '\u2029': return new WhitespaceInfo('\u2029', false, true);
|
||||
case '\u202F': return new WhitespaceInfo('\u202F', false, false);
|
||||
case '\u205F': return new WhitespaceInfo('\u205F', true, false);
|
||||
case '\u3000': return new WhitespaceInfo('\u3000', true, false);
|
||||
case '\u180E': return new WhitespaceInfo('\u180E', true, false);
|
||||
case '\u200B': return new WhitespaceInfo('\u200B', true, false);
|
||||
case '\u200C': return new WhitespaceInfo('\u200C', true, false);
|
||||
case '\u200D': return new WhitespaceInfo('\u200D', true, false);
|
||||
case '\u2060': return new WhitespaceInfo('\u2060', false, false);
|
||||
default:
|
||||
throw new ArgumentException("Character is not a whitespace character.", nameof(chr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct WhitespaceInfo
|
||||
{
|
||||
public int Character { get; }
|
||||
public bool IsBreaking { get; }
|
||||
public bool IsNewline { get; }
|
||||
|
||||
public WhitespaceInfo(int chr, bool breaking, bool nl)
|
||||
{
|
||||
Character = chr;
|
||||
IsBreaking = breaking;
|
||||
IsNewline = nl;
|
||||
}
|
||||
}
|
||||
}
|
@ -79,7 +79,7 @@ namespace Quik.VertexGenerator
|
||||
|
||||
public List<QuikDrawCall> DrawCalls { get; } = new List<QuikDrawCall>();
|
||||
|
||||
public float CurveGranularity { get; set; } = 0.2f;
|
||||
public float CurveGranularity { get; set; } = 1f;
|
||||
|
||||
public QuikContext Context { get; }
|
||||
|
||||
@ -233,6 +233,10 @@ namespace Quik.VertexGenerator
|
||||
RenderTextPut(command as QuikCommandPutText);
|
||||
goto exit_with_call;
|
||||
|
||||
case QuikCommandType.EmitTypeset:
|
||||
RenderTextTypeset(command as QuikCommandEmitText);
|
||||
goto exit_with_call;
|
||||
|
||||
default:
|
||||
{
|
||||
if (OnHandleCommand(this, command))
|
||||
@ -1468,7 +1472,7 @@ namespace Quik.VertexGenerator
|
||||
|
||||
a.Position = pointer + new QuikVec2(0, metrics.HorizontalBearing.Y - metrics.Size.Y);
|
||||
a.TextureCoordinates = metrics.Location.Min;
|
||||
|
||||
|
||||
b.Position = a.Position + new QuikVec2(metrics.Size.X, 0);
|
||||
c.Position = a.Position + metrics.Size;
|
||||
d.Position = a.Position + new QuikVec2(0, metrics.Size.Y);
|
||||
@ -1478,7 +1482,7 @@ namespace Quik.VertexGenerator
|
||||
d.TextureCoordinates = new QuikVec2(metrics.Location.Left, metrics.Location.Top);
|
||||
|
||||
pointer.X += metrics.Advance.X;
|
||||
|
||||
|
||||
short startVertex = (short)_vertexBufferPointer;
|
||||
AddVertex(a, b, c, d);
|
||||
AddElement(startVertex, (short)(startVertex + 1), (short)(startVertex + 2), startVertex, (short)(startVertex + 2), (short)(startVertex + 3));
|
||||
@ -1492,6 +1496,61 @@ namespace Quik.VertexGenerator
|
||||
DrawCalls.Add(call);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderTextTypeset(QuikCommandEmitText text)
|
||||
{
|
||||
short startElement = (short)_elementBufferPointer;
|
||||
TypesetGroup group = text.Group;
|
||||
QuikVertex vertex = new QuikVertex() { Color = new QuikColor(0x000000ff) };
|
||||
IQuikTexture texture = null;
|
||||
|
||||
group.SortBy(TypesetGroup.SortByTexture);
|
||||
foreach (TypesetCharacter chr in group)
|
||||
{
|
||||
if (texture == null)
|
||||
{
|
||||
texture = chr.Texture;
|
||||
}
|
||||
else if (texture != chr.Texture)
|
||||
{
|
||||
EmitCall();
|
||||
|
||||
startElement = (short)_elementBufferPointer;
|
||||
texture = chr.Texture;
|
||||
|
||||
CallTemplate.ClearStencil = false;
|
||||
}
|
||||
|
||||
QuikVertex a, b, c, d;
|
||||
a = b = c = d = vertex;
|
||||
|
||||
a.Position = new QuikVec2(chr.Position.Left, chr.Position.Bottom) + text.Offset;
|
||||
b.Position = new QuikVec2(chr.Position.Right, chr.Position.Bottom) + text.Offset;
|
||||
c.Position = new QuikVec2(chr.Position.Right, chr.Position.Top) + text.Offset;
|
||||
d.Position = new QuikVec2(chr.Position.Left, chr.Position.Top) + text.Offset;
|
||||
|
||||
a.TextureCoordinates = new QuikVec2(chr.UV.Left, chr.UV.Bottom);
|
||||
b.TextureCoordinates = new QuikVec2(chr.UV.Right, chr.UV.Bottom);
|
||||
c.TextureCoordinates = new QuikVec2(chr.UV.Right, chr.UV.Top);
|
||||
d.TextureCoordinates = new QuikVec2(chr.UV.Left, chr.UV.Top);
|
||||
|
||||
short startVertex = (short)_vertexBufferPointer;
|
||||
AddVertex(a, b, c, d);
|
||||
AddElement(startVertex, (short)(startVertex + 1), (short)(startVertex + 2), startVertex, (short)(startVertex + 2), (short)(startVertex + 3));
|
||||
}
|
||||
|
||||
EmitCall();
|
||||
|
||||
void EmitCall()
|
||||
{
|
||||
QuikDrawCall call = CallTemplate;
|
||||
|
||||
call.Texture = texture;
|
||||
call.Offset = (short)(startElement * 2);
|
||||
call.Count = (short)(_elementBufferPointer - startElement);
|
||||
DrawCalls.Add(call);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Quik;
|
||||
using Quik.VertexGenerator;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
@ -7,8 +8,10 @@ using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using Quik.FreeType;
|
||||
using Quik.OpenTK;
|
||||
using Quik.Typography;
|
||||
using Quik.Controls;
|
||||
|
||||
namespace QuikTestApplication
|
||||
{
|
||||
@ -38,25 +41,58 @@ in vec4 fcolor;
|
||||
out vec4 outcolor;
|
||||
|
||||
uniform sampler2D texture0;
|
||||
uniform vec2 texture0offset;
|
||||
|
||||
void main()
|
||||
{
|
||||
outcolor = fcolor * texture(texture0, ftexcoord);
|
||||
outcolor = fcolor * texture(texture0, ftexcoord + texture0offset);
|
||||
}
|
||||
";
|
||||
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
NativeWindowSettings windowSettings = NativeWindowSettings.Default;
|
||||
windowSettings.NumberOfSamples = 4;
|
||||
NativeWindow window = new NativeWindow(windowSettings);
|
||||
|
||||
|
||||
window.Context.MakeCurrent();
|
||||
GL.LoadBindings(new GLFWBindingsContext());
|
||||
|
||||
QuikContext context = new QuikContext(new OpenGLTextureManager(), new TextFontManager());
|
||||
FreeTypeFontManager fontManager = new FreeTypeFontManager();
|
||||
QuikContext context = new QuikContext(new OpenGLTextureManager(), fontManager);
|
||||
QuikVertexGenerator gen = new QuikVertexGenerator(context);
|
||||
|
||||
RootControl root = new RootControl(context);
|
||||
Button button = new Button()
|
||||
{
|
||||
Bounds = new QuikRectangle(120, 60, 20, 20),
|
||||
Text = "button",
|
||||
Padding = 8,
|
||||
NormalStroke = new QuikStrokeStyle(new QuikColor(0xccccccff), 4f),
|
||||
HoverStroke = new QuikStrokeStyle(new QuikColor(0x1010ccff), 4f),
|
||||
ActiveStroke = new QuikStrokeStyle(new QuikColor(0x999999ff), 4f),
|
||||
};
|
||||
button.Clicked += (sender, args) => {
|
||||
if (!args.Buttons.HasFlag(Quik.MouseButton.Primary))
|
||||
return;
|
||||
|
||||
Button xbutton = (Button)sender;
|
||||
|
||||
if (xbutton.Text == "button")
|
||||
{
|
||||
xbutton.Text = "le button";
|
||||
}
|
||||
else
|
||||
{
|
||||
xbutton.Text = "button";
|
||||
}
|
||||
};
|
||||
root.MouseEnter += (_,_) => Console.WriteLine("enter");
|
||||
root.MouseLeave += (_,_) => Console.WriteLine("leave");
|
||||
root.MouseMove += (_,_) => Console.WriteLine("move");
|
||||
root.MouseDown += (_,_) => Console.WriteLine("down");
|
||||
root.MouseUp += (_,_) => Console.WriteLine("up");
|
||||
root.Add(button);
|
||||
|
||||
GL.Enable(EnableCap.Multisample);
|
||||
|
||||
int sp;
|
||||
@ -79,6 +115,8 @@ void main()
|
||||
|
||||
GL.UseProgram(sp);
|
||||
}
|
||||
|
||||
new GL30Driver();
|
||||
|
||||
int vbo, ebo, vao;
|
||||
vbo = GL.GenBuffer();
|
||||
@ -118,7 +156,7 @@ void main()
|
||||
|
||||
loc = GL.GetUniformLocation(sp, "matrix");
|
||||
|
||||
int i = 0;
|
||||
int offsetLoc = GL.GetUniformLocation(sp, "texture0offset");
|
||||
|
||||
QuikStrokeStyle strokeBorder = new QuikStrokeStyle()
|
||||
{
|
||||
@ -137,6 +175,10 @@ void main()
|
||||
Color = new QuikColor(0xeeeeeeff)
|
||||
};
|
||||
|
||||
QuikFillStyle magenta = new QuikFillStyle
|
||||
{
|
||||
Color = new QuikColor(0xff00ffff)
|
||||
};
|
||||
|
||||
int whiteTexture = GL.GenTexture();
|
||||
uint[] whitePixel = {0xFFFFFFFF};
|
||||
@ -145,13 +187,46 @@ void main()
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest);
|
||||
|
||||
context.DefaultFont = context.FontManager.GetFont(null);
|
||||
context.DefaultFont = context.FontManager.GetFont(new QuikFontStyle("Arial", 16, QuikFontType.Normal));
|
||||
|
||||
const string testString =
|
||||
"The quick brown fox jumps over the lazy dog. " +
|
||||
"Sphinx of black quartz judge my vow. " +
|
||||
"Have some japanese for fun: これが読めるかな?";
|
||||
|
||||
var para = new HorizontalParagraph();
|
||||
para.ConsumeText(context.DefaultFont, testString);
|
||||
var typeset = new TypesetGroup();
|
||||
para.Typeset(typeset, 200);
|
||||
|
||||
window.Context.SwapInterval = 0;
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
float lastMs = stopwatch.ElapsedMilliseconds;
|
||||
int frames = 0;
|
||||
for (;!window.IsExiting;)
|
||||
{
|
||||
NativeWindow.ProcessWindowEvents(false);
|
||||
window.ProcessEvents(0.0f);
|
||||
window.TryGetCurrentMonitorDpi(out float dpi, out _);
|
||||
|
||||
if (window.IsFocused)
|
||||
{
|
||||
var mouseState = window.MouseState;
|
||||
QuikVec2 postion = new QuikVec2(mouseState.Position.X, window.ClientSize.Y - mouseState.Position.Y) * (dpi/72f);
|
||||
Quik.MouseButton buttons =
|
||||
(mouseState.IsButtonDown(OpenTK.Windowing.GraphicsLibraryFramework.MouseButton.Button1) ? Quik.MouseButton.Primary : 0) |
|
||||
(mouseState.IsButtonDown(OpenTK.Windowing.GraphicsLibraryFramework.MouseButton.Button2) ? Quik.MouseButton.Secondary : 0) |
|
||||
(mouseState.IsButtonDown(OpenTK.Windowing.GraphicsLibraryFramework.MouseButton.Button3) ? Quik.MouseButton.Tertiary : 0);
|
||||
|
||||
root.NotifyMouse(new Quik.MouseState(postion, buttons));
|
||||
}
|
||||
|
||||
root.Bounds = new QuikRectangle(
|
||||
window.ClientSize.X,
|
||||
window.ClientSize.Y,
|
||||
0,
|
||||
0);
|
||||
root.NotifyUpdate();
|
||||
|
||||
GL.Viewport(0, 0, window.Size.X, window.Size.Y);
|
||||
|
||||
@ -162,9 +237,9 @@ void main()
|
||||
|
||||
Matrix4 matrix = Matrix4.CreateOrthographicOffCenter(
|
||||
0,
|
||||
(float)window.Size.X,
|
||||
(float)window.Size.X*dpi/72f,
|
||||
0,
|
||||
(float)window.Size.Y,
|
||||
(float)window.Size.Y*dpi/72f,
|
||||
1,
|
||||
-1);
|
||||
GL.UniformMatrix4(loc, false, ref matrix);
|
||||
@ -216,8 +291,11 @@ void main()
|
||||
FillStyle = fill,
|
||||
StrokeStyle = strokeBorder
|
||||
});
|
||||
|
||||
context.Draw.PutText("Aaah! the cat turned into a cat!", new QuikVec2(25,30));
|
||||
|
||||
// context.Draw.PutText("これが読めるかな?", new QuikVec2(25,30));
|
||||
root.NotifyPaint(context.Draw);
|
||||
|
||||
context.Draw.Commands.Enqueue(new QuikCommandEmitText(typeset, new QuikVec2(200, 200)));
|
||||
|
||||
QuikCommand command;
|
||||
while (context.Draw.Commands.TryDequeue(out command))
|
||||
@ -228,23 +306,33 @@ void main()
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, gen.VertexCount * QuikVertex.Stride, ref gen.VertexBuffer[0], BufferUsageHint.StreamDraw);
|
||||
GL.BufferData(BufferTarget.ElementArrayBuffer, gen.ElementCount * 2, ref gen.ElementBuffer[0], BufferUsageHint.StreamDraw);
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
foreach (QuikDrawCall call in gen.DrawCalls)
|
||||
{
|
||||
GL.BindTexture(
|
||||
TextureTarget.Texture2D,
|
||||
call.Texture == null ? whiteTexture : (call.Texture as OpenGLTexture).TextureId);
|
||||
if (call.Texture != null)
|
||||
GL.Uniform2(offsetLoc, 0.5f / call.Texture.Width, 0.5f / call.Texture.Height);
|
||||
GL.DrawElements(BeginMode.Triangles, call.Count, DrawElementsType.UnsignedShort, call.Offset);
|
||||
}
|
||||
GL.Disable(EnableCap.Blend);
|
||||
|
||||
int callCount = gen.DrawCalls.Count;
|
||||
gen.Clear();
|
||||
System.Threading.Thread.Sleep(1);
|
||||
window.Context.SwapBuffers();
|
||||
|
||||
if (++i % 60 == 0)
|
||||
frames++;
|
||||
float ms = stopwatch.ElapsedMilliseconds;
|
||||
if (ms - lastMs > 1000)
|
||||
{
|
||||
Console.WriteLine("Frames: {0}", frames*(ms - lastMs)/1000);
|
||||
frames = 0;
|
||||
lastMs = ms;
|
||||
Console.WriteLine("Vertex Usage: {0} ; Element Usage: {1} Calls: {2}", gen.VertexUsage, gen.ElementUsage, callCount);
|
||||
}
|
||||
|
||||
window.Context.SwapBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Quik.FreeType\Quik.FreeType.csproj" />
|
||||
<ProjectReference Include="..\Quik.OpenTK\Quik.OpenTK.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -22,6 +22,10 @@ namespace QuikTestApplication
|
||||
|
||||
public IQuikTexture Texture { get; }
|
||||
|
||||
public override float Ascender => throw new NotImplementedException();
|
||||
|
||||
public override float Descender => throw new NotImplementedException();
|
||||
|
||||
public TestFont(QuikContext context)
|
||||
{
|
||||
Texture = context.TextureManager.CreateTexture(TextureSize, false, QuikImageFormat.RgbaU8);
|
||||
|
Loading…
Reference in New Issue
Block a user