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;
|
Height = (int)size.Y;
|
||||||
|
|
||||||
TextureId = GL.GenTexture();
|
TextureId = GL.GenTexture();
|
||||||
|
|
||||||
GL.BindTexture(TextureTarget.Texture2D, TextureId);
|
GL.BindTexture(TextureTarget.Texture2D, TextureId);
|
||||||
|
|
||||||
GL.TexParameter(
|
GL.TexParameter(
|
||||||
TextureTarget.Texture2D,
|
TextureTarget.Texture2D,
|
||||||
TextureParameterName.TextureMinFilter,
|
TextureParameterName.TextureMinFilter,
|
||||||
@ -62,16 +62,18 @@ namespace Quik.OpenTK
|
|||||||
public bool Mipmaps { get; }
|
public bool Mipmaps { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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.BindTexture(TextureTarget.Texture2D, TextureId);
|
||||||
|
GL.PixelStore(PixelStoreParameter.UnpackAlignment, alignment);
|
||||||
GL.TexSubImage2D(TextureTarget.Texture2D, level, 0, 0, Width, Height, GetGlImageFormat(format), GetGlDataFormat(format), data);
|
GL.TexSubImage2D(TextureTarget.Texture2D, level, 0, 0, Width, Height, GetGlImageFormat(format), GetGlDataFormat(format), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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.BindTexture(TextureTarget.Texture2D, TextureId);
|
||||||
|
GL.PixelStore(PixelStoreParameter.UnpackAlignment, alignment);
|
||||||
GL.TexSubImage2D(
|
GL.TexSubImage2D(
|
||||||
TextureTarget.Texture2D,
|
TextureTarget.Texture2D,
|
||||||
level,
|
level,
|
||||||
@ -101,6 +103,8 @@ namespace Quik.OpenTK
|
|||||||
return PixelFormat.Rgb;
|
return PixelFormat.Rgb;
|
||||||
case QuikImageFormat.RgbaF: case QuikImageFormat.RgbaU8:
|
case QuikImageFormat.RgbaF: case QuikImageFormat.RgbaU8:
|
||||||
return PixelFormat.Rgba;
|
return PixelFormat.Rgba;
|
||||||
|
case QuikImageFormat.AlphaF: case QuikImageFormat.AlphaU8:
|
||||||
|
return PixelFormat.Alpha;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
@ -113,11 +117,14 @@ namespace Quik.OpenTK
|
|||||||
case QuikImageFormat.RedF:
|
case QuikImageFormat.RedF:
|
||||||
case QuikImageFormat.RgbaF:
|
case QuikImageFormat.RgbaF:
|
||||||
case QuikImageFormat.RgbF:
|
case QuikImageFormat.RgbF:
|
||||||
|
case QuikImageFormat.AlphaF:
|
||||||
return PixelType.Float;
|
return PixelType.Float;
|
||||||
case QuikImageFormat.RedU8:
|
case QuikImageFormat.RedU8:
|
||||||
case QuikImageFormat.RgbaU8:
|
case QuikImageFormat.RgbaU8:
|
||||||
case QuikImageFormat.RgbU8:
|
case QuikImageFormat.RgbU8:
|
||||||
|
case QuikImageFormat.AlphaU8:
|
||||||
return PixelType.UnsignedByte;
|
return PixelType.UnsignedByte;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Quik\Quik.csproj" />
|
<ProjectReference Include="..\Quik\Quik.csproj" />
|
||||||
|
<EmbeddedResource Include="glsl\**"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</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
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuikTestApplication", "QuikTestApplication\QuikTestApplication.csproj", "{49AEF502-692A-48A4-8076-EF2228925280}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuikTestApplication", "QuikTestApplication\QuikTestApplication.csproj", "{49AEF502-692A-48A4-8076-EF2228925280}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quik.FreeType", "Quik.FreeType\Quik.FreeType.csproj", "{53B95098-F304-47E6-A08C-DAFA589F5BCF}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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|x64.Build.0 = Release|Any CPU
|
||||||
{49AEF502-692A-48A4-8076-EF2228925280}.Release|x86.ActiveCfg = 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
|
{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
|
EndGlobalSection
|
||||||
EndGlobal
|
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>
|
/// </summary>
|
||||||
FlowText,
|
FlowText,
|
||||||
|
|
||||||
|
EmitTypeset,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clear the image mask.
|
/// Clear the image mask.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Clear the stencil buffer.
|
/// Clear the stencil buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -8,6 +8,9 @@ namespace Quik.Typography
|
|||||||
public float FontSize => Style.Size;
|
public float FontSize => Style.Size;
|
||||||
public QuikFontStyle FontStyle => Style;
|
public QuikFontStyle FontStyle => Style;
|
||||||
|
|
||||||
|
public abstract float Ascender { get; }
|
||||||
|
public abstract float Descender { get; }
|
||||||
|
|
||||||
public abstract bool HasCharacter(int character);
|
public abstract bool HasCharacter(int character);
|
||||||
public abstract void GetCharacter(int character, out IQuikTexture texture, out QuikGlyph glyph);
|
public abstract void GetCharacter(int character, out IQuikTexture texture, out QuikGlyph glyph);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,33 @@ namespace Quik.Typography
|
|||||||
Size = size;
|
Size = size;
|
||||||
Type = type;
|
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
|
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 List<QuikDrawCall> DrawCalls { get; } = new List<QuikDrawCall>();
|
||||||
|
|
||||||
public float CurveGranularity { get; set; } = 0.2f;
|
public float CurveGranularity { get; set; } = 1f;
|
||||||
|
|
||||||
public QuikContext Context { get; }
|
public QuikContext Context { get; }
|
||||||
|
|
||||||
@ -233,6 +233,10 @@ namespace Quik.VertexGenerator
|
|||||||
RenderTextPut(command as QuikCommandPutText);
|
RenderTextPut(command as QuikCommandPutText);
|
||||||
goto exit_with_call;
|
goto exit_with_call;
|
||||||
|
|
||||||
|
case QuikCommandType.EmitTypeset:
|
||||||
|
RenderTextTypeset(command as QuikCommandEmitText);
|
||||||
|
goto exit_with_call;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
if (OnHandleCommand(this, command))
|
if (OnHandleCommand(this, command))
|
||||||
@ -1468,7 +1472,7 @@ namespace Quik.VertexGenerator
|
|||||||
|
|
||||||
a.Position = pointer + new QuikVec2(0, metrics.HorizontalBearing.Y - metrics.Size.Y);
|
a.Position = pointer + new QuikVec2(0, metrics.HorizontalBearing.Y - metrics.Size.Y);
|
||||||
a.TextureCoordinates = metrics.Location.Min;
|
a.TextureCoordinates = metrics.Location.Min;
|
||||||
|
|
||||||
b.Position = a.Position + new QuikVec2(metrics.Size.X, 0);
|
b.Position = a.Position + new QuikVec2(metrics.Size.X, 0);
|
||||||
c.Position = a.Position + metrics.Size;
|
c.Position = a.Position + metrics.Size;
|
||||||
d.Position = a.Position + new QuikVec2(0, metrics.Size.Y);
|
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);
|
d.TextureCoordinates = new QuikVec2(metrics.Location.Left, metrics.Location.Top);
|
||||||
|
|
||||||
pointer.X += metrics.Advance.X;
|
pointer.X += metrics.Advance.X;
|
||||||
|
|
||||||
short startVertex = (short)_vertexBufferPointer;
|
short startVertex = (short)_vertexBufferPointer;
|
||||||
AddVertex(a, b, c, d);
|
AddVertex(a, b, c, d);
|
||||||
AddElement(startVertex, (short)(startVertex + 1), (short)(startVertex + 2), startVertex, (short)(startVertex + 2), (short)(startVertex + 3));
|
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);
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using Quik;
|
using Quik;
|
||||||
using Quik.VertexGenerator;
|
using Quik.VertexGenerator;
|
||||||
using OpenTK.Graphics.OpenGL4;
|
using OpenTK.Graphics.OpenGL4;
|
||||||
@ -7,8 +8,10 @@ using OpenTK.Mathematics;
|
|||||||
using OpenTK.Windowing.Common;
|
using OpenTK.Windowing.Common;
|
||||||
using OpenTK.Windowing.Desktop;
|
using OpenTK.Windowing.Desktop;
|
||||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
|
using Quik.FreeType;
|
||||||
using Quik.OpenTK;
|
using Quik.OpenTK;
|
||||||
using Quik.Typography;
|
using Quik.Typography;
|
||||||
|
using Quik.Controls;
|
||||||
|
|
||||||
namespace QuikTestApplication
|
namespace QuikTestApplication
|
||||||
{
|
{
|
||||||
@ -38,25 +41,58 @@ in vec4 fcolor;
|
|||||||
out vec4 outcolor;
|
out vec4 outcolor;
|
||||||
|
|
||||||
uniform sampler2D texture0;
|
uniform sampler2D texture0;
|
||||||
|
uniform vec2 texture0offset;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
outcolor = fcolor * texture(texture0, ftexcoord);
|
outcolor = fcolor * texture(texture0, ftexcoord + texture0offset);
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
NativeWindowSettings windowSettings = NativeWindowSettings.Default;
|
NativeWindowSettings windowSettings = NativeWindowSettings.Default;
|
||||||
windowSettings.NumberOfSamples = 4;
|
windowSettings.NumberOfSamples = 4;
|
||||||
NativeWindow window = new NativeWindow(windowSettings);
|
NativeWindow window = new NativeWindow(windowSettings);
|
||||||
|
|
||||||
window.Context.MakeCurrent();
|
window.Context.MakeCurrent();
|
||||||
GL.LoadBindings(new GLFWBindingsContext());
|
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);
|
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);
|
GL.Enable(EnableCap.Multisample);
|
||||||
|
|
||||||
int sp;
|
int sp;
|
||||||
@ -79,6 +115,8 @@ void main()
|
|||||||
|
|
||||||
GL.UseProgram(sp);
|
GL.UseProgram(sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new GL30Driver();
|
||||||
|
|
||||||
int vbo, ebo, vao;
|
int vbo, ebo, vao;
|
||||||
vbo = GL.GenBuffer();
|
vbo = GL.GenBuffer();
|
||||||
@ -118,7 +156,7 @@ void main()
|
|||||||
|
|
||||||
loc = GL.GetUniformLocation(sp, "matrix");
|
loc = GL.GetUniformLocation(sp, "matrix");
|
||||||
|
|
||||||
int i = 0;
|
int offsetLoc = GL.GetUniformLocation(sp, "texture0offset");
|
||||||
|
|
||||||
QuikStrokeStyle strokeBorder = new QuikStrokeStyle()
|
QuikStrokeStyle strokeBorder = new QuikStrokeStyle()
|
||||||
{
|
{
|
||||||
@ -137,6 +175,10 @@ void main()
|
|||||||
Color = new QuikColor(0xeeeeeeff)
|
Color = new QuikColor(0xeeeeeeff)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QuikFillStyle magenta = new QuikFillStyle
|
||||||
|
{
|
||||||
|
Color = new QuikColor(0xff00ffff)
|
||||||
|
};
|
||||||
|
|
||||||
int whiteTexture = GL.GenTexture();
|
int whiteTexture = GL.GenTexture();
|
||||||
uint[] whitePixel = {0xFFFFFFFF};
|
uint[] whitePixel = {0xFFFFFFFF};
|
||||||
@ -145,13 +187,46 @@ void main()
|
|||||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear);
|
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear);
|
||||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest);
|
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;
|
window.Context.SwapInterval = 0;
|
||||||
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||||
|
float lastMs = stopwatch.ElapsedMilliseconds;
|
||||||
|
int frames = 0;
|
||||||
for (;!window.IsExiting;)
|
for (;!window.IsExiting;)
|
||||||
{
|
{
|
||||||
NativeWindow.ProcessWindowEvents(false);
|
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);
|
GL.Viewport(0, 0, window.Size.X, window.Size.Y);
|
||||||
|
|
||||||
@ -162,9 +237,9 @@ void main()
|
|||||||
|
|
||||||
Matrix4 matrix = Matrix4.CreateOrthographicOffCenter(
|
Matrix4 matrix = Matrix4.CreateOrthographicOffCenter(
|
||||||
0,
|
0,
|
||||||
(float)window.Size.X,
|
(float)window.Size.X*dpi/72f,
|
||||||
0,
|
0,
|
||||||
(float)window.Size.Y,
|
(float)window.Size.Y*dpi/72f,
|
||||||
1,
|
1,
|
||||||
-1);
|
-1);
|
||||||
GL.UniformMatrix4(loc, false, ref matrix);
|
GL.UniformMatrix4(loc, false, ref matrix);
|
||||||
@ -216,8 +291,11 @@ void main()
|
|||||||
FillStyle = fill,
|
FillStyle = fill,
|
||||||
StrokeStyle = strokeBorder
|
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;
|
QuikCommand command;
|
||||||
while (context.Draw.Commands.TryDequeue(out 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.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.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)
|
foreach (QuikDrawCall call in gen.DrawCalls)
|
||||||
{
|
{
|
||||||
GL.BindTexture(
|
GL.BindTexture(
|
||||||
TextureTarget.Texture2D,
|
TextureTarget.Texture2D,
|
||||||
call.Texture == null ? whiteTexture : (call.Texture as OpenGLTexture).TextureId);
|
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.DrawElements(BeginMode.Triangles, call.Count, DrawElementsType.UnsignedShort, call.Offset);
|
||||||
}
|
}
|
||||||
|
GL.Disable(EnableCap.Blend);
|
||||||
|
|
||||||
int callCount = gen.DrawCalls.Count;
|
int callCount = gen.DrawCalls.Count;
|
||||||
gen.Clear();
|
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);
|
Console.WriteLine("Vertex Usage: {0} ; Element Usage: {1} Calls: {2}", gen.VertexUsage, gen.ElementUsage, callCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Context.SwapBuffers();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Quik.FreeType\Quik.FreeType.csproj" />
|
||||||
<ProjectReference Include="..\Quik.OpenTK\Quik.OpenTK.csproj" />
|
<ProjectReference Include="..\Quik.OpenTK\Quik.OpenTK.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -22,6 +22,10 @@ namespace QuikTestApplication
|
|||||||
|
|
||||||
public IQuikTexture Texture { get; }
|
public IQuikTexture Texture { get; }
|
||||||
|
|
||||||
|
public override float Ascender => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override float Descender => throw new NotImplementedException();
|
||||||
|
|
||||||
public TestFont(QuikContext context)
|
public TestFont(QuikContext context)
|
||||||
{
|
{
|
||||||
Texture = context.TextureManager.CreateTexture(TextureSize, false, QuikImageFormat.RgbaU8);
|
Texture = context.TextureManager.CreateTexture(TextureSize, false, QuikImageFormat.RgbaU8);
|
||||||
|
Loading…
Reference in New Issue
Block a user