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:
H. Utku Maden 2023-05-13 16:17:57 +03:00
parent 98b1c1a277
commit 9339295378
31 changed files with 2470 additions and 22 deletions

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

@ -0,0 +1,7 @@
namespace Quik.FreeType
{
public enum FTError : int
{
None = 0,
}
}

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

@ -0,0 +1,12 @@
namespace Quik.FreeType
{
public enum FTRenderMode
{
Normal = 0,
Light,
Mono,
Lcd,
LcdVertical,
Sdf
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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))
@ -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,10 +41,11 @@ 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);
} }
"; ";
@ -54,8 +58,40 @@ void main()
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);
@ -80,6 +116,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();
ebo = GL.GenBuffer(); ebo = 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);
@ -217,7 +292,10 @@ void main()
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);