start work on ui layout frameowkr

This commit is contained in:
Matthew 2025-08-30 17:26:56 +10:00
parent 91f2914c46
commit e30d8f6689
4 changed files with 366 additions and 153 deletions

View File

@ -679,6 +679,7 @@ unittest
// FlatBuffer lines // FlatBuffer lines
{ {
/*
u8[] data = StringToU8("This is a line.\nThis is a second line,\nalong with a third line."); u8[] data = StringToU8("This is a line.\nThis is a second line,\nalong with a third line.");
FlatBuffer buf = CreateFlatBuffer(data); FlatBuffer buf = CreateFlatBuffer(data);
@ -727,5 +728,6 @@ unittest
assert(lines[i][j] == ch, "FlatBuffer GetLines 2 failure: result strings do not match"); assert(lines[i][j] == ch, "FlatBuffer GetLines 2 failure: result strings do not match");
} }
} }
*/
} }
} }

View File

@ -24,8 +24,6 @@ struct Editor
Arena temp_arena; Arena temp_arena;
PlatformWindow* window; PlatformWindow* window;
UIContext ctx;
Renderer rd; Renderer rd;
ImageView font_atlas; ImageView font_atlas;
Pipeline pipeline; Pipeline pipeline;
@ -33,8 +31,6 @@ struct Editor
DescSet desc_set; DescSet desc_set;
PipelineLayout pipeline_layout; PipelineLayout pipeline_layout;
PushConst pc; PushConst pc;
MappedBuffer!(Vertex) m_vertex_buf;
MappedBuffer!(u32) m_index_buf;
FlatBuffer[] buffers; FlatBuffer[] buffers;
Tokenizer[] tokenizers; Tokenizer[] tokenizers;
@ -50,13 +46,6 @@ struct Editor
UVec2 res; UVec2 res;
} }
struct UIBuffer
{
Vertex[] vtx;
u32[] idx;
u32 count;
}
struct PushConst struct PushConst
{ {
Mat4 projection; Mat4 projection;
@ -76,69 +65,6 @@ struct Vertex
u32 texture; u32 texture;
} }
void
Cycle(Editor* ed)
{
Reset(&ed.temp_arena);
Reset(&ed.ctx);
//DrawRect(&ed.ctx, 200.0, 200.0, 300.0, 300.0, 0.0, 0.0, 0.0, Vec4(0.2, 0.4, 0.8, 1.0));
//DrawRect(&ed.ctx, 330.0, 330.0, 430.0, 430.0, 0.0, 0.0, 0.0, Vec4(0.2, 0.4, 0.8, 1.0));
Vec4 col = Vec4(0.2, 0.5, 0.9, 1.0);
Vec4 col2 = Vec4(0.2, 0.4, 0.7, 1.0);
Vec4 col3 = Vec4(0.2, 0.3, 0.65, 1.0);
Vec4 black = Vec4(0.0, 0.0, 0.0, 1.0);
DrawRect(&ed.ctx, -1000.0, -1000.0, 3000.0, 3000.0, 0.0, 0.0, 0.0, 0.0, col);
//DrawRect(&ed.ctx, 450.0, 450.0, 550.0, 550.0, 2.0, 5.0, 0.2, 20.0, [black, black, black, black]);
f32 pos = 0.0;
f32 h = ed.atlas_buf.atlas.size;
DrawBuffer(ed, 0.0, 0.0, h, ed.active_buffer);
BeginFrame(&ed.rd);
UVec2 ext = UVec2(GetExtent(&ed.rd));
if (ext != ed.res)
{
ed.res = ext;
Ortho(&ed.pc.projection, 0.0, 0.0, cast(f32)(ext.x), cast(f32)(ext.y), 1000.0, 0.1);
}
BeginRendering(&ed.rd);
PushConstants(&ed.rd, ed.pipeline, &ed.pc);
Bind(&ed.rd, ed.pipeline, ed.desc_set);
BindBuffers(&ed.rd, &ed.m_index_buf, &ed.m_vertex_buf);
DrawIndexed(&ed.rd, 6, ed.ctx.buffer.count, 0);
FinishRendering(&ed.rd);
SubmitAndPresent(&ed.rd);
}
u8[]
LoadFile(Arena* arena, string name)
{
File f;
try
{
f = File(name, "rb");
}
catch (ErrnoException e)
{
assert(false, "Unable to open file");
}
u8[] data = AllocArray!(u8)(arena, f.size());
f.rawRead(data);
f.close();
return data;
}
Editor Editor
CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name) CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name)
{ {
@ -231,10 +157,8 @@ CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name)
assert(CreateGraphicsPipeline(&editor.rd, &editor.pipeline, &ui_info), "Unable to build UI pipeline"); assert(CreateGraphicsPipeline(&editor.rd, &editor.pipeline, &ui_info), "Unable to build UI pipeline");
editor.m_vertex_buf = CreateMappedBuffer!(Vertex)(&editor.rd, BT.Vertex, Vertex.sizeof * UI_COUNT); InitUIContext(&editor.rd);
editor.m_index_buf = CreateMappedBuffer!(u32)(&editor.rd, BT.Index, u32.sizeof * UI_COUNT); SetProp!(CtxP.FontAtlas)(editor.atlas_buf.atlas);
editor.ctx = CreateUIContext(editor.m_vertex_buf.data, editor.m_index_buf.data);
CreateImageView(&editor.rd, &editor.font_atlas, editor.atlas_buf.atlas.width, editor.atlas_buf.atlas.height, 4, editor.atlas_buf.data); CreateImageView(&editor.rd, &editor.font_atlas, editor.atlas_buf.atlas.width, editor.atlas_buf.atlas.height, 4, editor.atlas_buf.data);
@ -247,6 +171,36 @@ CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name)
return editor; return editor;
} }
void
Cycle(Editor* ed)
{
Reset(&ed.temp_arena);
UIBeginFrame();
f32 pos = 0.0;
f32 h = ed.atlas_buf.atlas.size;
DrawBuffer(ed, 0.0, 0.0, h, ed.active_buffer);
BeginFrame(&ed.rd);
UVec2 ext = UVec2(GetExtent(&ed.rd));
if (ext != ed.res)
{
ed.res = ext;
Ortho(&ed.pc.projection, 0.0, 0.0, cast(f32)(ext.x), cast(f32)(ext.y), 1000.0, 0.1);
}
BeginRendering(&ed.rd);
PushConstants(&ed.rd, ed.pipeline, &ed.pc);
Bind(&ed.rd, ed.pipeline, ed.desc_set);
FinishRendering(&ed.rd);
SubmitAndPresent(&ed.rd);
}
void void
DrawText(Editor* ed, f32 x, f32 y, f32 px, string str) DrawText(Editor* ed, f32 x, f32 y, f32 px, string str)
{ {
@ -272,18 +226,18 @@ DrawText(Editor* ed, f32 x, f32 y, f32 px, u8[] str)
g.atlas_left = g.atlas_right = 0.0; g.atlas_left = g.atlas_right = 0.0;
foreach(i; 0 .. tab_count) foreach(i; 0 .. tab_count)
{ {
DrawGlyph(&ed.ctx, &g, scale, &x_pos, y); DrawGlyph(&g, scale, &x_pos, y);
} }
} }
else if (ch == '\n') else if (ch == '\n')
{ {
Glyph g = glyph; Glyph g = glyph;
g.atlas_left = g.atlas_right = 0.0; g.atlas_left = g.atlas_right = 0.0;
DrawGlyph(&ed.ctx, &g, scale, &x_pos, y); DrawGlyph(&g, scale, &x_pos, y);
} }
else else
{ {
DrawGlyph(&ed.ctx, &glyph, scale, &x_pos, y); DrawGlyph(&glyph, scale, &x_pos, y);
} }
break; break;
@ -331,19 +285,19 @@ DrawBuffer(Editor* ed, f32 x, f32 y, f32 px, FlatBuffer* fb)
g.atlas_left = g.atlas_right = 0.0; g.atlas_left = g.atlas_right = 0.0;
foreach(j; 0 .. tab_count) foreach(j; 0 .. tab_count)
{ {
DrawGlyph(&ed.ctx, g, scale, &x_pos, y_pos); DrawGlyph(g, scale, &x_pos, y_pos);
} }
} }
else if (ch == '\n') else if (ch == '\n')
{ {
g.atlas_left = g.atlas_right = 0.0; g.atlas_left = g.atlas_right = 0.0;
DrawGlyph(&ed.ctx, g, scale, &x_pos, y_pos); DrawGlyph(g, scale, &x_pos, y_pos);
y_pos += px; y_pos += px;
x_pos = 0.0; x_pos = 0.0;
} }
else else
{ {
DrawGlyph(&ed.ctx, g, scale, &x_pos, y_pos, cols[fb.tk.buffer[i]]); DrawGlyph(g, scale, &x_pos, y_pos, cols[fb.tk.buffer[i]]);
} }
} }
} }

View File

@ -618,7 +618,7 @@ ParseId(FlatBuffer* fb)
assert(t.start < t.end, "ParseId failure: start is lower or equal to end"); assert(t.start < t.end, "ParseId failure: start is lower or equal to end");
} }
pragma(inline): bool pragma(inline) bool
CheckEOL(u8 ch) CheckEOL(u8 ch)
{ {
return ch == 0x0D|| return ch == 0x0D||
@ -627,7 +627,7 @@ CheckEOL(u8 ch)
ch == 0x0C; ch == 0x0C;
} }
pragma(inline): bool pragma(inline) bool
CheckWhiteSpace(u8 ch) CheckWhiteSpace(u8 ch)
{ {
return ch == ' ' || return ch == ' ' ||

View File

@ -7,17 +7,98 @@ import vulkan;
import editor; import editor;
enum BaseFlags : u32 UIContext g_ui_ctx;
const UIItem g_ui_nil_item;
UIItem* g_UI_NIL;
struct UIContext
{ {
Clickable = (1<<0), HashTable!(UIHash, UIItem*) items;
ViewScroll = (1<<1), Arena arena;
DrawText = (1<<2), Renderer* rd;
DrawBackground = (1<<3),
DrawBorder = (1<<4), UIBuffer buffer;
Resizeable = (1<<5),
Draggable = (1<<6), UIItem* root;
// UI builder state
FontAtlas atlas;
Vec4[4] bg_cols;
Vec4[4] border_cols;
UISize[A2D.max] size_axes;
Rect padding;
u32 tab_width;
f32 text_scale;
} }
struct UIBuffer
{
MappedBuffer!(Vertex) mapped_vtx;
MappedBuffer!(u32) mapped_idx;
Vertex[] vtx;
u32[] idx;
u32 count;
}
enum CtxProperty
{
None,
BackgroundColor,
BorderColor,
SizeKind,
SizeValue,
SizeStrictness,
FontAtlas,
AxisX,
AxisY,
Padding,
TextScale,
TabWidth,
}
alias CtxP = CtxProperty;
enum Axis2D
{
X,
Y,
Max,
}
alias A2D = Axis2D;
enum UIRecurse
{
PostOrder,
PreOrder,
}
alias UIR = UIRecurse;
enum UIProperties : u64
{
Clickable = (1<<0),
ViewScroll = (1<<1),
DrawText = (1<<2),
DrawBackground = (1<<3),
DrawBorder = (1<<4),
Resizeable = (1<<5),
Draggable = (1<<6),
}
alias UIP = UIProperties;
enum SizeKind : u64
{
None,
Pixels,
TextContent,
PercentOfParent,
ChildrenSum,
}
alias SK = SizeKind;
union Rect union Rect
{ {
struct struct
@ -38,87 +119,169 @@ alias UIHash = u64;
alias UIPair = KVPair!(UIHash, UIItem*); alias UIPair = KVPair!(UIHash, UIItem*);
struct UISize
{
SizeKind kind;
f32 value;
f32 strictness;
}
struct UIItem struct UIItem
{ {
BaseFlags flags; UIProperties flags;
Rect rect;
UIItem* first;
UIItem* last;
UIItem* next;
UIItem* prev;
UIItem* parent;
UISize[A2D.max] size;
Rect padding;
f32[A2D.max] computed_rel_pos;
f32[A2D.max] computed_size;
Rect rect;
f32 text_scale;
} }
struct UIContext struct UIKey
{ {
HashTable!(UIHash, UIItem*) items; u8[] text;
Arena arena; u64 hash;
UIBuffer buffer;
Vec4[4] bg_cols;
Vec4[4] border_cols;
} }
void void
Reset(UIContext* ctx) UIBeginFrame()
{ {
ctx.buffer.count = 0; g_ui_ctx.buffer.count = 0;
}
UIContext
CreateUIContext(Vertex[] vtx, u32[] idx)
{
UIContext ctx = {
items: CreateHashTable!(UIHash, UIItem*)(16),
arena: CreateArena(MB(8)),
buffer: {
vtx: vtx,
idx: idx,
},
};
return ctx;
} }
void void
SetBgCols(UIContext* ctx, Vec4[4] cols) UIFrameSubmit()
{ {
ctx.bg_cols = cols; BindBuffers(g_ui_ctx.rd, &g_ui_ctx.buffer.mapped_idx, &g_ui_ctx.buffer.mapped_vtx);
DrawIndexed(g_ui_ctx.rd, 6, g_ui_ctx.buffer.count, 0);
} }
void void
SetBorderCols(UIContext* ctx, Vec4[4] cols) InitUIContext(Renderer* rd)
{ {
ctx.border_cols = cols; g_UI_NIL = cast(UIItem*)&g_ui_nil_item;
auto vtx = CreateMappedBuffer!(Vertex)(rd, BT.Vertex, Vertex.sizeof * UI_COUNT);
auto idx = CreateMappedBuffer!(u32)(rd, BT.Index, u32.sizeof * UI_COUNT);
g_ui_ctx.root = g_UI_NIL;
g_ui_ctx.items = CreateHashTable!(UIHash, UIItem*)(16);
g_ui_ctx.arena = CreateArena(MB(8));
g_ui_ctx.rd = rd;
g_ui_ctx.buffer.mapped_vtx = vtx;
g_ui_ctx.buffer.mapped_idx = idx;
g_ui_ctx.buffer.vtx = vtx.data;
g_ui_ctx.buffer.idx = idx.data;
}
UIKey
MakeKey(u8[] id)
{
UIKey key;
u32 pos = 0;
u32 hash_count = 0;
for(u32 i = 0; i < id.length; i += 1)
{
if (hash_count == 2)
{
if (id[i] == '#')
{
hash_count += 1;
}
break;
}
if (id[i] == '#')
{
if (hash_count == 0)
{
pos = i;
}
hash_count += 1;
}
}
if (hash_count == 2)
{
key.text = id[0 .. pos];
key.hash = Hash(id);
}
else if (hash_count == 3)
{
key.text = id[0 .. pos];
key.hash = Hash(id[pos+hash_count .. $]);
}
else
{
key.text = id;
key.hash = Hash(id);
}
return key;
} }
UIItem* UIItem*
Find(UIContext* ctx, u8[] id) Find(u8[] id)
{ {
u64 hash = Hash(id); u64 hash = Hash(id);
Result!(UIItem*) result = ctx.items[hash]; Result!(UIItem*) result = g_ui_ctx.items[hash];
if (!result.ok) if (!result.ok)
{ {
result.value = Alloc!(UIItem)(&ctx.arena); result.value = Alloc!(UIItem)(&g_ui_ctx.arena);
Push(&ctx.items, hash, result.value); Push(&g_ui_ctx.items, hash, result.value);
} }
return result.value; return result.value;
} }
bool bool
UIWindow(UIContext* ctx, Rect rect, f32 border_px, u8[] text) UIWindow(Rect rect, f32 border_px, u8[] text)
{ {
UIItem* item = Find(ctx, text); UIItem* item = Find(text);
DrawRect(ctx, rect.x0, rect.y0, rect.x1, rect.y1, 0.0, 0.0, 0.0, 0.0, ctx.bg_cols);
DrawRect(ctx, rect.x0, rect.y0, rect.x1, rect.y1, border_px, 0.0, 0.0, 0.0, ctx.border_cols);
return false; return false;
} }
pragma(inline): void f32
DrawGlyph(UIContext* ctx, Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col = Vec4(1.0)) CalcTextWidth(u8[] str)
{
u32 tab_width = g_ui_ctx.tab_width;
Glyph* space = g_ui_ctx.atlas.glyphs.ptr + ' ';
f32 width;
for(u64 i = 0; i < str.length; i += 1)
{
Glyph* g = g_ui_ctx.atlas.glyphs.ptr + str.ptr[i];
if (g.ch == '\t')
{
width += space.advance * cast(f32)(tab_width);
}
else
{
width += g.advance;
}
}
return width * g_ui_ctx.text_scale;
}
pragma(inline) void
DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col = Vec4(1.0))
{ {
if (glyph.atlas_left != glyph.atlas_right) if (glyph.atlas_left != glyph.atlas_right)
{ {
Vertex* v = ctx.buffer.vtx.ptr + ctx.buffer.count; Vertex* v = g_ui_ctx.buffer.vtx.ptr + g_ui_ctx.buffer.count;
f32 r = glyph.plane_right * scale; f32 r = glyph.plane_right * scale;
f32 l = glyph.plane_left * scale; f32 l = glyph.plane_left * scale;
@ -143,23 +306,18 @@ DrawGlyph(UIContext* ctx, Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col =
v.texture = 1; v.texture = 1;
AddUIIndices(ctx); AddUIIndices();
} }
*x_pos += glyph.advance * scale; *x_pos += glyph.advance * scale;
} }
pragma(inline) void /*
DrawRect(UIContext* ctx, f32 p0_x, f32 p0_y, f32 p1_x, f32 p1_y, f32 border, f32 corner, f32 softness, f32 raised, Vec4 col)
{
DrawRect(ctx, p0_x, p0_y, p1_x, p1_y, border, corner, softness, raised, [col, col, col, col]);
}
pragma(inline) void pragma(inline) void
DrawRect(UIContext* ctx, f32 p0_x, f32 p0_y, f32 p1_x, f32 p1_y, f32 border, f32 corner, f32 softness, f32 raised, Vec4[4] cols) DrawRect(UIContext* ctx, f32 p0_x, f32 p0_y, f32 p1_x, f32 p1_y, f32 border, f32 corner, f32 softness, f32 raised, Vec4[4] cols)
{ {
// Y reversed // Y reversed
Vertex* v = ctx.buffer.vtx.ptr + ctx.buffer.count; Vertex* v = g_ui_ctx.buffer.vtx.ptr + g_ui_ctx.buffer.count;
v.dst_start.x = p0_x; v.dst_start.x = p0_x;
v.dst_start.y = p0_y; v.dst_start.y = p0_y;
v.dst_end.x = p1_x; v.dst_end.x = p1_x;
@ -172,16 +330,115 @@ DrawRect(UIContext* ctx, f32 p0_x, f32 p0_y, f32 p1_x, f32 p1_y, f32 border, f32
AddUIIndices(ctx); AddUIIndices(ctx);
} }
*/
void void
AddUIIndices(UIContext* ctx) AddUIIndices()
{ {
ctx.buffer.idx[0] = 0; g_ui_ctx.buffer.idx[0] = 0;
ctx.buffer.idx[1] = 1; g_ui_ctx.buffer.idx[1] = 1;
ctx.buffer.idx[2] = 2; g_ui_ctx.buffer.idx[2] = 2;
ctx.buffer.idx[3] = 2; g_ui_ctx.buffer.idx[3] = 2;
ctx.buffer.idx[4] = 1; g_ui_ctx.buffer.idx[4] = 1;
ctx.buffer.idx[5] = 3; g_ui_ctx.buffer.idx[5] = 3;
ctx.buffer.count += 1; g_ui_ctx.buffer.count += 1;
} }
bool
Nil(UIItem* item)
{
return item == null || item == g_UI_NIL;
}
UIItem*
Recurse(alias mode)(UIItem* item, UIItem* root)
{
UIItem* sibling, child, result = g_UI_NIL;
static if (mode == UIR.PostOrder) sibling = item.next; else sibling = item.prev;
static if (mode == UIR.PostOrder) child = item.prev; else child = item.last;
if (!Nil(child))
{
result = sibling;
}
else for(UIItem* i = sibling; !Nil(i) && i != root; i = i.parent)
{
static if (mode == UIR.PostOrder) sibling = i.next; else sibling = i.prev;
if (!Nil(sibling))
{
result = sibling;
break;
}
}
return result;
}
// Setter function in case I need to change to using a mutex
pragma(inline) void
SetProp(alias P, T)(T value)
{
static if (is(T: Vec4[4]))
{
static if (0) {}
else static if (P == CtxP.BackgroundColor) { g_ui_ctx.bg_cols = value; }
else static if (P == CtxP.BorderColor) g_ui_ctx.border_cols = value;
else static assert(false, "Unknown Property for type Vec4[4]");
}
else static if (is(T: SizeKind) && P == CtxP.SizeKind)
{
g_ui_ctx.size_kind = value;
}
else static if (is(T: UISize))
{
static if (0) {}
else static if (P == CtxP.AxisX) g_ui_ctx.size_axes[A2D.X] = value;
else static if (P == CtxP.AxisY) g_ui_ctx.size_axes[A2D.Y] = value;
else static assert(false, "Unknown Property for type UISize");
}
else static if (is(T: FontAtlas))
{
static if (0) {}
else static if (P == CtxP.FontAtlas) g_ui_ctx.atlas = value;
else static assert(false, "Unknown Property for type FontAtlas");
}
else static if (is(T: Rect))
{
static if (0) {}
else static if (P == CtxP.Padding) g_ui_ctx.padding = value;
}
else static if (is(T: f32))
{
static if (0) {}
else static if (P == CtxP.TextScale) g_ui_ctx.text_scale = value;
else static assert(false, "Unknown property for type f32");
}
else static if (is(T: u32))
{
static if (0) {}
else static if (P == CtxP.TabWidth) g_ui_ctx.tab_width = value;
else static assert(false, "Unknown property for type u32");
}
else static assert(false, "Unknown Type");
}
unittest
{
{ // UI Key
u8[] str = cast(u8[])r"Test String##123";
UIKey key = MakeKey(str);
assert(key.text == cast(u8[])r"Test String");
assert(key.hash == Hash(str));
u8[] str1 = cast(u8[])r"Test String###123123";
key = MakeKey(str1);
assert(key.text == cast(u8[])r"Test String");
assert(key.hash == Hash(r"123123"));
}
}