more work on UI, starting to replace existing UI code

This commit is contained in:
Matthew 2025-12-13 20:56:52 +11:00
parent ad3ababe97
commit 8acbeee072
4 changed files with 250 additions and 90 deletions

View File

@ -16,8 +16,9 @@
"versions": ["VULKAN_DEBUG", "ENABLE_RENDERER"],
"preGenerateCommands-linux": ["./build.sh"],
"preGenerateCommands-windows": [],
"dflags": ["-Xcc=-mno-sse", "-P-I/usr/include/freetype2", "-Jbuild", "-Jassets", "-link-debuglib"],
"dflags-dmd": ["-P=-DSTBI_NO_SIMD"]
"dflags": ["-P-I/usr/include/freetype2", "-Jbuild", "-Jassets"],
"dflags-ldc2": ["-link-debuglib"],
"dflags-dmd": []
},
{
"name": "editor-test",
@ -33,8 +34,9 @@
"versions": ["VULKAN_DEBUG"],
"preGenerateCommands-linux": ["./build.sh"],
"preGenerateCommands-windows": [],
"dflags": ["-Xcc=-mno-sse", "-P-I/usr/include/freetype2", "-Jbuild", "-Jassets", "-link-debuglib", "-unittest"],
"dflags-dmd": ["-P=-DSTBI_NO_SIMD"]
"dflags": ["-P-I/usr/include/freetype2", "-Jbuild", "-Jassets", "-unittest"],
"dflags-ldc2": ["-link-debuglib"],
"dflags-dmd": []
},
]
}

@ -1 +1 @@
Subproject commit e5f91cae6d90c40e458e1208c2ab8f8eb917c99a
Subproject commit 617e03f917911f6f9898086720a6539756f79758

View File

@ -141,6 +141,29 @@ Cycle(EditorCtx* ctx, Inputs* inputs)
BeginUI(inputs);
UICtx* ui_ctx = GetCtx();
Vec4[4] col0 = Vec4(0.2, 0.4, 0.8, 1.0);
Vec4[4] col1 = Vec4(0.8, 0.4, 0.2, 1.0);
Vec4[4] col_sep = Vec4(0.2, 0.8, 0.1, 1.0);
Push!("size_info")(ui_ctx, MakeUISizeX(ST.Percentage, 0.5));
Push!("bg_col", true)(ui_ctx, col0);
UIItem* p0 = MakeItem("###p0", UIF.DrawBackground|UIF.Resizeable);
Logf("%s", *ui_ctx.size_info.top);
Push!("size_info", true)(ui_ctx, MakeUISizeX(ST.Pixels, 2.0));
Logf("%s", *ui_ctx.size_info.top);
Push!("bg_col", true)(ui_ctx, col_sep);
UIItem* sep = MakeItem("###sep", UIF.Draggable|UIF.DrawBackground|UIF.ResizeAdjacent);
Logf("%s", *ui_ctx.size_info.top);
Push!("bg_col", true)(ui_ctx, col1);
UIItem* p1 = MakeItem("###p1", UIF.DrawBackground|UIF.Resizeable);
/*
UIPanel* root = ctx.base_panel;
root.size.x = g_ui_ctx.res.x;
@ -150,7 +173,7 @@ Cycle(EditorCtx* ctx, Inputs* inputs)
static foreach(axis; A2D.min .. A2D.max)
{
for(UIPanel* p = root; !Nil(p); p = Recurse(p, root, g_UI_NIL_PANEL))
for(UIPanel* p = root; !Nil(p); p = Recurse(p, g_UI_NIL_PANEL))
{
f32 pos = p.rect.p0.v[axis];
for(UIPanel* c = p.first; !Nil(c); c = c.next)
@ -167,7 +190,7 @@ Cycle(EditorCtx* ctx, Inputs* inputs)
}
}
for(auto p = ctx.base_panel; !Nil(p); p = Recurse(p, ctx.base_panel, g_UI_NIL_PANEL))
for(auto p = ctx.base_panel; !Nil(p); p = Recurse(p, g_UI_NIL_PANEL))
{
Panel(p);
}
@ -181,6 +204,7 @@ Cycle(EditorCtx* ctx, Inputs* inputs)
CommandPalette(&ctx.cmd);
}
*/
EndUI();
}

View File

@ -14,6 +14,7 @@ import std.math.traits : isNaN;
import std.math.rounding : ceil, floor;
import std.math.exponential : pow;
import std.math.remainder : fmod;
import std.algorithm.comparison : clamp;
import std.format : sformat;
import core.stdc.string : memset;
import core.stdc.math : fabsf;
@ -102,12 +103,17 @@ enum UIFlags
ScrollY = 1<<6,
ClampX = 1<<7,
ClampY = 1<<8,
Resizeable = 1<<9,
ResizeAdjacent = 1<<10,
FloatingWindow = 1<<11,
CenteredWindow = 1<<12,
TextInput = 1<<13,
Clamp = UIFlags.ClampX | UIFlags.ClampY,
}
alias UIF = UIFlags;
const UIFlags AUTO_FLAGS = UIF.ResizeAdjacent;
enum UISignal
{
@ -159,35 +165,27 @@ UICtxParameter(T, string name)
template CtxMemberInfo(int i)
{
import std.string : endsWith;
import std.string : endsWith, startsWith;
import std.traits : isInstanceOf, isPointer;
struct MemberInfo
{
string id;
bool is_stack, is_top, is_stack_top;
bool is_stack, is_top;
}
enum string id = __traits(identifier, UICtx.tupleof[i]);
enum bool is_ptr = isPointer!(typeof(UICtx.tupleof[i]));
enum bool is_top = endsWith(id, "_top");
enum bool is_stack_top = isInstanceOf!(StackTop, typeof(UICtx.tupleof[i]));
enum bool is_stack = startsWith(typeof(UICtx.tupleof[i]).stringof, "StackTop!");
static if(is_ptr)
{
enum bool is_stack = isInstanceOf!(Stack, typeof(*UICtx.tupleof[i]));
}
else
{
enum bool is_stack = false;
}
enum MemberInfo CtxMemberInfo = MemberInfo(id: id, is_stack: is_ptr && is_stack, is_top: is_top, is_stack_top: is_stack_top);
enum MemberInfo CtxMemberInfo = MemberInfo(id: id, is_stack: is_stack, is_top: is_top);
}
struct UICtx
{
HashTable!(UIHash, UIItem*) items;
UIItem* free_items;
Arena arena;
Arena temp_arena;
Inputs* inputs;
@ -219,6 +217,7 @@ struct UICtx
u32 tab_width;
f32 text_size;
UIItem* window_root;
UIItem* root;
UIItem* drag_item;
UIPanel* parent_panel;
@ -245,13 +244,13 @@ struct Stack(T)
{
Stack!(T)* next;
T value;
bool auto_pop;
}
struct StackTop(T)
{
Stack!(T)* top;
Stack!(T)* free;
bool auto_pop;
}
mixin template UIItemParameters()
@ -264,7 +263,7 @@ mixin template UIItemParameters()
{
{
enum info = CtxMemberInfo!(i);
static if(info.is_stack_top)
static if(info.is_stack)
{
fields ~= typeof(UICtx.tupleof[i].top.value).stringof ~ " " ~ info.id ~ ";\n";
}
@ -279,17 +278,20 @@ mixin template UIItemParameters()
struct UIItem
{
UIKey key;
UIFlags flags;
u64 last_frame;
IVec2 dragged;
IVec2 dragged;
UIKey key;
UIItem* next, prev, first, last; // parent in mixin
u64 last_frame;
UISignal signal;
UIFlags flags;
Rect rect;
Vec2 size;
UIItem* next, prev, first, last; // parent in mixin
u8[] display_string;
Rect rect;
Vec2 size;
f32 resize_pct;
u8[] display_string;
mixin UIItemParameters!();
}
@ -417,6 +419,7 @@ InitUICtx(PlatformWindow* window)
UICtx ctx = {
items: CreateHashTable!(UIHash, UIItem*)(12),
free_items: Alloc!(UIItem)(&arena),
arena: arena,
temp_arena: CreateArena(MB(1)),
drag_item: g_UI_NIL,
@ -426,6 +429,9 @@ InitUICtx(PlatformWindow* window)
tab_width: 2,
};
UIItem* fi = ctx.free_items;
fi.first = fi.last = fi.next = fi.prev = fi.parent = g_UI_NIL;
ctx.atlas_buf = CreateAtlas(&arena, ctx.font, 16.0, 256);
version(ENABLE_RENDERER)
@ -435,9 +441,10 @@ InitUICtx(PlatformWindow* window)
for(u64 i = 0; i < FRAME_OVERLAP; i += 1)
{
ctx.buffers[i].m_vtx = CreateMappedBuffer!(Vertex)(&ctx.rd, BT.Vertex, VERTEX_MAX_COUNT);
ctx.buffers[i].m_idx = CreateMappedBuffer!(u32)(&ctx.rd, BT.Index, cast(u64)(ceil(VERTEX_MAX_COUNT*1.5)));
ctx.buffers[i].m_idx = CreateMappedBuffer!(u32)(&ctx.rd, BT.Index, 6);
ctx.buffers[i].vtx = ctx.buffers[i].m_vtx.data;
ctx.buffers[i].idx = ctx.buffers[i].m_idx.data;
ctx.buffers[i].idx[0 .. $] = [0, 1, 2, 2, 1, 3];
}
DescLayoutBinding[2] layout_bindings = [
@ -513,33 +520,54 @@ Set(UIItem* item, UICtx* ctx)
}
UIItem*
MakeItem(T)(T k) if(is(T: UIKey) || StringType!T)
MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T)
{
UICtx* ctx = GetCtx();
UICtx* ctx = GetCtx();
UIItem* item = Get(k);
item.flags = flags;
Set(item, ctx);
if(!Nil(item.parent))
if(item.flags & (UIF.CenteredWindow | UIF.FloatingWindow))
{
item.parent = ctx.window_root;
DLLPush(item.parent, item, g_UI_NIL);
}
else if(!Nil(item.parent))
{
DLLPush(item.parent, item, g_UI_NIL);
}
AutoPopStacks(ctx);
if(item.last_frame != ctx.frame-1 && item.flags & UIF.Resizeable)
{
if(item.size_info[item.parent.layout_axis].type != ST.Percentage)
{
Logf("Warning: Resizing not percentage SizeType item, disabling flag");
item.flags &= ~UIF.Resizeable;
}
else
{
item.resize_pct = item.size_info[item.layout_axis].value;
}
}
item.last_frame = ctx.frame;
return item;
}
UISignal
void
Signal(UIItem* item)
{
UISignal signal;
UICtx* ctx = GetCtx();
UICtx* ctx = GetCtx();
i32 x = ctx.mouse_pos.x;
i32 y = ctx.mouse_pos.y;
i32 x = ctx.mouse_pos.x;
i32 y = ctx.mouse_pos.y;
item.signal = UIS.None;
if(x >= item.rect.p0.x && x <= item.rect.p1.x && y >= item.rect.p0.y && y <= item.rect.p1.y)
{
signal |= UIS.Hovered;
item.signal |= UIS.Hovered;
}
item.dragged = 0;
@ -548,21 +576,27 @@ Signal(UIItem* item)
{
bool taken;
Logf("%s", i.type);
if(item.flags & UIF.Clickable && i.type == UIE.Click && InBounds(ctx.mouse_pos, &item.rect))
{
signal |= UIS.Clicked;
taken = true;
Logf("clicked");
item.signal |= UIS.Clicked;
taken = true;
}
if(Nil(ctx.drag_item) && item.flags & UIF.Draggable && i.type == UIE.DragStart && InBounds(i.pos, &item.rect))
{
signal |= UIS.Dragged;
Logf("dragged");
item.signal |= UIS.Dragged;
ctx.drag_item = item;
taken = true;
}
if(ctx.drag_item == item && i.type == UIE.Drag)
{
Logf("dragged");
item.signal |= UIS.Dragged;
item.dragged += i.rel;
taken = true;
}
@ -572,8 +606,6 @@ Signal(UIItem* item)
DLLRemove(&ctx.events, i, g_UI_NIL_INPUT);
}
}
return signal;
}
void
@ -593,8 +625,8 @@ BeginUI(Inputs* inputs)
Reset(a);
// Convert Inputs
ctx.events.first = ctx.events.last = null;
static bool dragging;
static bool mouse_down;
for(auto i = inputs.first; i; i = i.next)
@ -633,6 +665,21 @@ BeginUI(Inputs* inputs)
}
}
// Clean up items
KVPair!(UIHash, UIItem*)*[] items = GetAllNodes(&ctx.temp_arena, &ctx.items);
foreach(i; 0 .. items.length)
{
UIItem* item = items[i].value;
if(item.last_frame != ctx.frame)
{
Logf("discarding %s", cast(char[])item.key.hash_text);
item.first = item.last = item.parent = item.prev = item.next = g_UI_NIL;
DLLPush(ctx.free_items, item, g_UI_NIL);
}
}
ctx.frame += 1;
// Ctx state
ctx.f_idx = ctx.frame%FRAME_OVERLAP;
ctx.inputs = inputs;
@ -660,13 +707,16 @@ BeginUI(Inputs* inputs)
}
memset(ctx.buffers[ctx.f_idx].vtx.ptr, 0, Vertex.sizeof * ctx.buffers[ctx.f_idx].count);
memset(ctx.buffers[ctx.f_idx].idx.ptr, 0, u32.sizeof * ctx.buffers[ctx.f_idx].count);
ctx.buffers[ctx.f_idx].count = 0;
// Root Item
UISize[2] sizes = [UISize(ST.Pixels, ctx.res.x), UISize(ST.Pixels, ctx.res.y)];
Push!("size_info", true)(ctx, sizes);
ctx.root = MakeItem("###root");
Push!("size_info")(ctx, sizes);
ctx.root = MakeItem("###root");
ctx.window_root = MakeItem("###window_root");
Pop!("size_info")(ctx);
Push!("parent")(ctx, ctx.root);
}
@ -675,9 +725,45 @@ EndUI()
{
UICtx* ctx = GetCtx();
// Automatic signals
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
{
if(item.flags & AUTO_FLAGS)
{
Signal(item);
if(item.signal & UIS.Dragged)
{
Logf("1");
}
if(item.flags & UIF.ResizeAdjacent && item.dragged != IVec2(0))
{
UIItem* prev = !Nil(item.prev) ? item.prev : g_UI_NIL;
UIItem* next = !Nil(item.next) ? item.next : g_UI_NIL;
if(prev.flags & UIF.Resizeable && next.flags & UIF.Resizeable)
{
Axis2D axis = item.parent.layout_axis;
f32 mov_pct = Remap(item.dragged.v[axis], 0.0, item.parent.size.v[axis], 0.0, 1.0);
if(prev.resize_pct > 0.0 && prev.resize_pct < 1.0 && next.resize_pct > 0.0 && next.resize_pct < 1.0)
{
prev.resize_pct -= mov_pct;
next.resize_pct += mov_pct;
prev.resize_pct = clamp(prev.resize_pct, 0.0, 1.0);
next.resize_pct = clamp(next.resize_pct, 0.0, 1.0);
}
}
}
}
}
// Calculate item properties
static foreach(axis; A2D.min .. A2D.max)
{
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL))
// Pixel sizes
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
{
if(item.size_info[axis].type == ST.Pixels)
{
@ -685,10 +771,16 @@ EndUI()
}
}
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL))
// Percentage sizes
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
{
if(item.size_info[axis].type == ST.Percentage)
{
if(item.layout_axis == axis && item.flags & UIF.Resizeable)
{
item.size_info[axis].value = item.resize_pct;
}
for(UIItem* p = item.parent; !Nil(p); p = p.parent)
{
if(p.size_info[axis].type == ST.Pixels)
@ -700,7 +792,8 @@ EndUI()
}
}
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, ctx.root, g_UI_NIL))
// Sum of children sizes
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, g_UI_NIL))
{
if(item.size_info[axis].type == ST.ChildrenSum)
{
@ -712,7 +805,8 @@ EndUI()
}
}
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL))
// Violations
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
{
if(axis == item.layout_axis)
{
@ -724,7 +818,6 @@ EndUI()
if(children_size > item.size[axis])
{
Logf("%s %s %s %s", axis, cast(char[])item.key.hash_text, children_size, item.size[axis]);
u64 child_count;
for(UIItem* c = item.first; !Nil(c); c = c.next)
{
@ -766,6 +859,7 @@ EndUI()
}
}
// Calculate final sizes
{
f32 pos = 0.0;
for(UIItem* item = ctx.root; !Nil(item);)
@ -809,6 +903,11 @@ EndUI()
}
}
// Render Items
RenderItems(ctx.root);
RenderItems(ctx.window_root);
version(ENABLE_RENDERER) with(ctx)
{
BindBuffers(&rd, &buffers[f_idx].m_idx, &buffers[f_idx].m_vtx);
@ -817,8 +916,39 @@ EndUI()
FinishRendering(&rd);
SubmitAndPresent(&rd);
}
}
ctx.frame += 1;
void
RenderItems(UIItem* root)
{
UICtx* ctx = GetCtx();
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
{
if(item.flags & UIF.DrawBackground)
{
// DrawRect
Vertex* v = GetVertex(ctx);
v.dst_start = item.rect.p0 + item.border_thickness;
v.dst_end = item.rect.p1 - item.border_thickness;
v.cols = item.bg_col;
v.corner_radius = item.corner_radius;
AddVertexCount(ctx);
}
if(item.flags & UIF.DrawBorder)
{
Vertex* v = GetVertex(ctx);
v.dst_start = item.rect.p0;
v.dst_end = item.rect.p1;
v.cols = item.border_col;
v.corner_radius = item.corner_radius;
v.border_thickness = item.border_thickness;
v.edge_softness = item.edge_softness;
AddVertexCount(ctx);
}
}
}
u32[2]
@ -862,11 +992,11 @@ Push(string stack_str, bool auto_pop = false, T)(UICtx* ctx, T value)
node = Alloc!(Stack!(T))(&ctx.temp_arena);
}
node.next = stack.top;
node.value = value;
node.next = stack.top;
node.value = value;
node.auto_pop = auto_pop;
stack.top = node;
stack.auto_pop = auto_pop;
}
auto
@ -902,12 +1032,12 @@ AutoPopStacks(UICtx* ctx)
{
{
enum member_info = CtxMemberInfo!(i);
static if(member_info.is_stack && !member_info.is_top)
static if(member_info.is_stack)
{
if(ctx.tupleof[i].auto_pop)
if(ctx.tupleof[i].top.auto_pop)
{
ctx.tupleof[i].top.auto_pop = false;
Pop!(member_info.id)(ctx);
ctx.tupleof[i].auto_pop = false;
}
}
}
@ -924,7 +1054,7 @@ InitStacks(UICtx* ctx)
{
{
enum member_info = CtxMemberInfo!(i);
static if(member_info.is_stack && member_info.is_top)
static if(member_info.is_top)
{
enum global_default = replace("g_@_default", "@", chomp(member_info.id, "_top"));
@ -946,11 +1076,10 @@ ResetStacks(UICtx* ctx)
{
{
enum member_info = CtxMemberInfo!(i);
static if(member_info.is_stack_top)
static if(member_info.is_stack)
{
enum top = replace("ctx.@_top", "@", member_info.id);
ctx.tupleof[i].top = mixin(top);
ctx.tupleof[i].auto_pop = false;
ctx.tupleof[i].free = null;
assert(ctx.tupleof[i].top.next == null, "Top stack node next isn't null");
@ -960,18 +1089,21 @@ ResetStacks(UICtx* ctx)
}
T*
Recurse(bool pre = true, T)(T* panel, T* root, T* nil)
Recurse(bool pre = true, T)(T* node, T* nil)
{
T* child = pre ? node.first : node.last;
T* sibling = pre ? node.next : node.prev;
T* result = nil;
if(!Nil(panel.first))
if(!Nil(child))
{
result = pre ? panel.first : panel.last;
result = child;
}
else for(T* p = panel; !Nil(p) && p != root; p = p.parent)
else for(T* p = node; !Nil(p); p = p.parent)
{
if(!Nil(pre ? p.next : p.prev))
if(!Nil(sibling))
{
result = pre ? p.next : p.prev;
result = sibling;
break;
}
}
@ -1115,16 +1247,26 @@ Get(T)(T k) if(is(T: UIKey) || StringType!T)
UIKey key = MakeKey(k);
}
Result!(UIItem*) result = g_ui_ctx.items[key.hash];
if(!result.ok)
Result!(UIItem*) res = g_ui_ctx.items[key.hash];
if(!res.ok)
{
result.value = Alloc!(UIItem)(&g_ui_ctx.arena);
HTPush(&g_ui_ctx.items, key.hash, result.value);
if(!Nil(g_ui_ctx.free_items.first))
{
res.value = DLLPop(g_ui_ctx.free_items, g_UI_NIL);
}
else
{
res.value = Alloc!(UIItem)(&g_ui_ctx.arena);
HTPush(&g_ui_ctx.items, key.hash, res.value);
}
res.value.last_frame = g_ui_ctx.frame-2;
}
result.value.key = key;
res.value.key = key;
res.value.next = res.value.prev = res.value.first = res.value.last = res.value.parent = g_UI_NIL;
return result.value;
return res.value;
}
f32
@ -1283,7 +1425,7 @@ DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y)
v.src_end.y = gb.atlas_b;
}
AddUIIndices(ctx);
AddVertexCount(ctx);
*x_pos += glyph.advance * scale;
@ -1382,7 +1524,7 @@ DrawRect(f32 x, f32 y, f32 w, f32 h, f32 corner_radius, f32 border, Vec4[4] cols
v.dst_end -= border;
}
AddUIIndices(ctx);
AddVertexCount(ctx);
}
void
@ -1399,21 +1541,13 @@ DrawBorder(Vec2 pos, Vec2 size, f32 border, f32 radius, f32 softness, Vec4[4] co
v.edge_softness = softness;
v.raised = 0.0;
AddUIIndices(ctx);
AddVertexCount(ctx);
}
pragma(inline) void
AddUIIndices(UICtx* ctx)
AddVertexCount(UICtx* ctx)
{
ctx.buffers[ctx.f_idx].idx[0] = 0;
ctx.buffers[ctx.f_idx].idx[1] = 1;
ctx.buffers[ctx.f_idx].idx[2] = 2;
ctx.buffers[ctx.f_idx].idx[3] = 2;
ctx.buffers[ctx.f_idx].idx[4] = 1;
ctx.buffers[ctx.f_idx].idx[5] = 3;
ctx.buffers[ctx.f_idx].count += 1;
assert(ctx.buffers[ctx.f_idx].count < VERTEX_MAX_COUNT);
}