ui code almost ready to replace current code

This commit is contained in:
Matthew 2025-12-12 20:13:12 +11:00
parent 2703c95673
commit ad3ababe97
7 changed files with 537 additions and 251 deletions

View File

@ -13,11 +13,28 @@
"sourcePaths": ["src/editor", "src/dlib", "src/dlib/external/xxhash", "src/VulkanRenderer"],
"libs-linux": ["X11", "vulkan", "stdc++", "xfixes", "freetype"],
"libs-windows": [],
"versions": ["VULKAN_DEBUG"],
"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"]
},
{
"name": "editor-test",
"targetType": "executable",
"targetName": "Editor",
"targetPath": "build",
"sourceFiles-linux": ["build/libvma.a", "build/libstb.a", "build/libm3d.a", "build/libcglm.a"],
"sourceFiles-windows": [],
"importPaths": ["src/editor", "src/dlib", "src/dlib/external/xxhash", "src/VulkanRenderer"],
"sourcePaths": ["src/editor", "src/dlib", "src/dlib/external/xxhash", "src/VulkanRenderer"],
"libs-linux": ["X11", "vulkan", "stdc++", "xfixes", "freetype"],
"libs-windows": [],
"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"]
},
]
}

@ -1 +1 @@
Subproject commit 589b2191babf009bef097d1272fac4a05a4c280d
Subproject commit b3639d9c884c09fb98b750ba613118d633470312

@ -1 +1 @@
Subproject commit 60b4e0cb27ba304c7b9444a2da8a581211c21ca9
Subproject commit e5f91cae6d90c40e458e1208c2ab8f8eb917c99a

View File

@ -139,25 +139,25 @@ Cycle(EditorCtx* ctx, Inputs* inputs)
g_input_mode = ctx.state == ES.InputMode;
BeginUI(ctx, inputs);
BeginUI(inputs);
UIPanel* root = ctx.base_panel;
root.size.x = g_ui_ctx.res.x;
root.size.y = g_ui_ctx.res.y;
root.rect.vec0 = 0.0;
root.rect.vec1 = 0.0;
root.rect.p0 = 0.0;
root.rect.p1 = 0.0;
static foreach(axis; A2D.min .. A2D.max)
{
for(UIPanel* p = root; !Nil(p); p = Recurse(p, root, g_UI_NIL_PANEL))
{
f32 pos = p.rect.vec0.v[axis];
f32 pos = p.rect.p0.v[axis];
for(UIPanel* c = p.first; !Nil(c); c = c.next)
{
c.size.v[axis] = p.axis == axis ? c.pct * p.size.v[axis] : p.size.v[axis];
c.rect.vec0.v[axis] = pos;
c.rect.vec1.v[axis] = pos + c.size.v[axis];
c.rect.p0.v[axis] = pos;
c.rect.p1.v[axis] = pos + c.size.v[axis];
if(axis == p.axis)
{
@ -571,13 +571,13 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs)
UIPanel* panel = GetFocusedPanel();
FlatBuffer* fb = !Nil(panel) ? &panel.ed.buf : null;
for(auto node = inputs.list.first; node != null; node = node.next)
for(auto node = inputs.first; node != null; node = node.next)
{
bool taken = false;
Input key = node.value.key;
Modifier md = node.value.md;
bool pressed = node.value.pressed;
Input key = node.key;
Modifier md = node.md;
bool pressed = node.pressed;
if (pressed)
{
@ -588,11 +588,11 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs)
}
else if(ctx.state == ES.InputMode)
{
taken = HandleInputMode(ctx, node.value);
taken = HandleInputMode(ctx, node);
}
else if(ctx.state == ES.CmdOpen)
{
taken = HandleCmdMode(ctx, node.value);
taken = HandleCmdMode(ctx, node);
}
else if(ctx.state == ES.SetPanelFocus)
{
@ -646,7 +646,7 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs)
} break;
case Semicolon:
{
if(Shift(node.value.md))
if(Shift(node.md))
{
ctx.state = ES.CmdOpen;
taken = true;
@ -701,7 +701,7 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs)
if(taken)
{
DLLRemove(&inputs.list, node, null);
DLLRemove(inputs, node, null);
}
}
@ -714,7 +714,7 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs)
}
void
MoveCursor(InputEvent ev)
MoveCursor(InputEvent* ev)
{
UIPanel* p = GetFocusedPanel();
@ -754,7 +754,7 @@ CharCases()
}
bool
HandleInputMode(EditorCtx* ctx, InputEvent ev)
HandleInputMode(EditorCtx* ctx, InputEvent* ev)
{
bool taken = false;
@ -912,7 +912,7 @@ GetCommands(CmdPalette* cmd)
}
bool
HandleCmdMode(EditorCtx* ctx, InputEvent ev)
HandleCmdMode(EditorCtx* ctx, InputEvent* ev)
{
u8 result = 0;
bool taken = false;

View File

@ -47,7 +47,7 @@ void main(string[] argv)
SetExtent(&g_ui_ctx.rd, window.w, window.h);
}
if(inputs.list.first == null)
if(inputs.first == null)
{
no_ev_count += 1;
Pause();

View File

@ -2,6 +2,7 @@ import dlib;
import dlib.util : HTPush = Push;
import vulkan;
import vulkan : RendererGetExtent = GetExtent;
import buffer;
import parsing;
import editor;
@ -10,7 +11,7 @@ import views;
import std.stdio;
import std.math.traits : isNaN;
import std.math.rounding : ceil;
import std.math.rounding : ceil, floor;
import std.math.exponential : pow;
import std.math.remainder : fmod;
import std.format : sformat;
@ -48,39 +49,38 @@ const u8[] FONT_BYTES = import("pc-9800.ttf");
const u8[] VERTEX_BYTES = import("gui.vert.spv");
const u8[] FRAGMENT_BYTES = import("gui.frag.spv");
f32 g_corner_radius_default = 2.0;
f32 g_edge_softness_default = 0.1;
f32 g_border_thickness_default = 2.0;
Vec4[4] g_bg_col_default = BG_COL;
Vec4[4] g_bg_hl_col_default = BG_HL_COL;
Vec4[4] g_border_col_default = BORDER_COL;
Vec4[4] g_border_hl_col_default = BORDER_HL_COL;
Vec4 g_text_col_default = TEXT_COL;
Vec4 g_text_hl_col_default = TEXT_HL_COL;
UISize[2] g_size_info_default = [UISize(ST.Percentage, 1.0), UISize(ST.Percentage, 1.0)];
f32 g_corner_radius_default = 2.0;
f32 g_edge_softness_default = 0.1;
f32 g_border_thickness_default = 2.0;
Vec4[4] g_bg_col_default = BG_COL;
Vec4[4] g_bg_hl_col_default = BG_HL_COL;
Vec4[4] g_border_col_default = BORDER_COL;
Vec4[4] g_border_hl_col_default = BORDER_HL_COL;
Vec4 g_text_col_default = TEXT_COL;
Vec4 g_text_hl_col_default = TEXT_HL_COL;
UISize[2] g_size_info_default = [UISize(ST.Percentage, 1.0), UISize(ST.Percentage, 1.0)];
Axis2D g_layout_axis_default = A2D.X;
Vec2 g_padding_default = Vec2(0.0);
const UI_COUNT = 5000;
__gshared const UIPanel g_ui_nil_panel;
__gshared UIPanel* g_UI_NIL_PANEL;
__gshared const UIItem g_ui_nil_item;
__gshared UIItem* g_UI_NIL;
__gshared const UIPanel g_ui_nil_panel;
__gshared UIPanel* g_UI_NIL_PANEL;
__gshared const UIItem g_ui_nil_item;
__gshared UIItem* g_UI_NIL;
__gshared const UIInput g_ui_nil_input;
__gshared UIInput* g_UI_NIL_INPUT;
__gshared const Stack!f32 g_nil_f32_stack;
__gshared Stack!f32* g_NIL_F32_STACK;
__gshared const Stack!Vec4[4] g_nil_gradient_col_stack;
__gshared Stack!Vec4[4]* g_NIL_GRAD_COL_STACK;
__gshared const Stack!Vec4 g_nil_vec4_stack;
__gshared Stack!Vec4* g_NIL_VEC4_STACK;
__gshared const Stack!u32 g_nil_u32_stack;
__gshared Stack!u32* g_NIL_U32_STACK;
alias g_parent_default = g_UI_NIL;
__gshared const Stack!f32 g_nil_f32_stack;
__gshared Stack!f32* g_NIL_F32_STACK;
__gshared const Stack!Vec4[4] g_nil_gradient_col_stack;
__gshared Stack!Vec4[4]* g_NIL_GRAD_COL_STACK;
__gshared const Stack!Vec4 g_nil_vec4_stack;
__gshared Stack!Vec4* g_NIL_VEC4_STACK;
__gshared const Stack!u32 g_nil_u32_stack;
__gshared Stack!u32* g_NIL_U32_STACK;
enum Axis2D
{
X,
@ -106,31 +106,47 @@ enum UIFlags
Clamp = UIFlags.ClampX | UIFlags.ClampY,
}
alias UIF = UIFlags;
enum UIEventFlag
{
None, // Events e.g. pressing/releasing/scrolling
}
enum UISignal
{
None = 0x00,
Clicked = 0x01,
Dragged = 0x02, // Handle click/drag specifics in view?
None = 0,
Clicked = 1<<0,
Dragged = 1<<1,
Hovered = 1<<2,
}
alias UIS = UISignal;
enum UIEvent
{
None = 0,
Click = 1<<0,
Drag = 1<<1,
DragStart = 1<<2,
Press = 1<<3,
}
alias UIE = UIEvent;
struct UIInput
{
UIEvent type;
UIInput* next, prev;
union
{
Input key;
IVec2 rel;
IVec2 pos;
}
}
mixin template
UICtxParameter(T, string name)
{
static string
CtxParameterGen(T, string name)()
{
import std.traits;
import std.array;
import std.traits, std.array;
string stack_top = "\tStackTop!("~T.stringof~") "~name~";\n";
string stack = "\tStack!("~T.stringof~")* "~name~"_top;\n";
@ -178,7 +194,11 @@ struct UICtx
u64 frame;
u64 f_idx;
PlatformWindow* window;
LinkedList!(UIInput) events;
IVec2 mouse_pos;
IVec2 drag_start;
PlatformWindow* window;
Renderer rd;
Descriptor font_atlas;
Descriptor sampler;
@ -186,7 +206,7 @@ PlatformWindow* window;
DescSetLayout desc_set_layout;
DescSet desc_set;
PipelineLayout pipeline_layout;
PushConst pc;
Mat4 projection;
Vec2 res;
u8[] font_data;
@ -215,6 +235,8 @@ PlatformWindow* window;
mixin UICtxParameter!(Vec4, "text_col");
mixin UICtxParameter!(Vec4, "text_hl_col");
mixin UICtxParameter!(UISize[2], "size_info");
mixin UICtxParameter!(Axis2D, "layout_axis");
mixin UICtxParameter!(Vec2, "padding");
debug bool dbg;
}
@ -257,14 +279,17 @@ mixin template UIItemParameters()
struct UIItem
{
UIKey key;
u64 last_frame;
Vec2 dragged;
UIKey key;
UIFlags flags;
u64 last_frame;
IVec2 dragged;
UIItem* next, prev, first, last; // parent in mixin
UIItem* next, prev, first, last; // parent in mixin
Rect pref_size;
Rect size;
Rect rect;
Vec2 size;
u8[] display_string;
mixin UIItemParameters!();
}
@ -273,7 +298,7 @@ enum SizeType
{
Pixels,
Percentage,
FitChild,
ChildrenSum,
}
alias ST = SizeType;
@ -294,23 +319,10 @@ struct UIBuffer
u32 count;
}
struct PushConst
{
Mat4 projection;
}
struct GlyphBounds
{
f32 r = 0.0;
f32 l = 0.0;
f32 t = 0.0;
f32 b = 0.0;
f32 w = 0.0;
f32 h = 0.0;
f32 atlas_r = 0.0;
f32 atlas_l = 0.0;
f32 atlas_t = 0.0;
f32 atlas_b = 0.0;
f32 r = 0.0, l = 0.0, t = 0.0, b = 0.0, w = 0.0, h = 0.0;
f32 atlas_r = 0.0, atlas_l = 0.0, atlas_t = 0.0, atlas_b = 0.0;
}
struct Vertex
@ -331,18 +343,7 @@ struct Vertex
union Rect
{
Vec2[2] v;
struct
{
Vec2 vec0;
Vec2 vec1;
};
struct
{
f32 x0;
f32 y0;
f32 x1;
f32 y1;
};
struct { Vec2 p0, p1; };
}
alias UIHash = u64;
@ -351,8 +352,7 @@ alias UIPair = KVPair!(UIHash, UIItem*);
struct UIKey
{
u8[] text;
u8[] hash_text;
u8[] text, hash_text;
u64 hash;
}
@ -361,14 +361,7 @@ struct UIPanel
u8[] id;
Editor* ed;
UIPanel* parent;
UIPanel* next;
UIPanel* prev;
UIPanel* first;
UIPanel* last;
UIPanel* list_next;
UIPanel* parent, next, prev, first, last, list_next;
Axis2D axis;
f32 pct;
Vec4 color;
@ -376,12 +369,8 @@ struct UIPanel
Rect rect;
Vec2 size;
i64 start_ln;
i64 end_ln;
i64 vis_lines;
i64 prev_start_ln;
f32 scroll_offset;
f32 scroll_target;
i64 start_ln, end_ln, vis_lines, prev_start_ln;
f32 scroll_offset, scroll_target;
}
struct TextPart
@ -391,28 +380,30 @@ struct TextPart
u32 count;
}
// Keep defaults, pop anything used once
enum bool StringType(T) = (is(T: string) || is(T: u8[]) || is(T: char[]));
void
InitUICtx(PlatformWindow* window)
{
version(linux)
version(ENABLE_RENDERER)
{
PlatformHandles handles = {
display: window.display,
window: window.window,
};
}
version(linux)
{
PlatformHandles handles = {
display: window.display,
window: window.window,
};
}
version(Windows)
{
version(Windows)
{
}
}
g_UI_NIL = cast(UIItem*)&g_ui_nil_item;
g_UI_NIL_PANEL = cast(UIPanel*)&g_ui_nil_panel;
g_UI_NIL_INPUT = cast(UIInput*)&g_ui_nil_input;
g_NIL_F32_STACK = cast(Stack!f32*)&g_nil_f32_stack;
g_NIL_GRAD_COL_STACK = cast(Stack!Vec4[4]*)&g_nil_gradient_col_stack;
g_NIL_VEC4_STACK = cast(Stack!Vec4*)&g_nil_vec4_stack;
@ -424,83 +415,79 @@ InitUICtx(PlatformWindow* window)
UIBuffer[FRAME_OVERLAP] buffers;
FontFace font = OpenFont(cast(u8[])FONT_BYTES);
FontAtlasBuf atlas_buf = CreateAtlas(&arena, font, 16.0, 256);
Vec4 b = Vec4(Vec3(0.0), 1.0);
UICtx ctx = {
rd: InitRenderer(handles, MB(16), MB(8)),
items: CreateHashTable!(UIHash, UIItem*)(12),
arena: arena,
temp_arena: CreateArena(MB(1)),
drag_item: g_UI_NIL,
atlas_buf: atlas_buf,
font: font,
font: OpenFont(cast(u8[])FONT_BYTES),
font_data: cast(u8[])FONT_BYTES,
text_size: 16.0,
tab_width: 2,
};
for(u64 i = 0; i < FRAME_OVERLAP; i += 1)
ctx.atlas_buf = CreateAtlas(&arena, ctx.font, 16.0, 256);
version(ENABLE_RENDERER)
{
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].vtx = ctx.buffers[i].m_vtx.data;
ctx.buffers[i].idx = ctx.buffers[i].m_idx.data;
ctx.rd = InitRenderer(handles, MB(16), MB(8));
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].vtx = ctx.buffers[i].m_vtx.data;
ctx.buffers[i].idx = ctx.buffers[i].m_idx.data;
}
DescLayoutBinding[2] layout_bindings = [
{ binding: 0, descriptorType: DT.Image, descriptorCount: 1, stageFlags: SS.All },
{ binding: 1, descriptorType: DT.Sampler, descriptorCount: 1, stageFlags: SS.All },
];
ctx.desc_set_layout = CreateDescSetLayout(&ctx.rd, layout_bindings);
ctx.desc_set = AllocDescSet(&ctx.rd, ctx.desc_set_layout);
ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, Mat4.sizeof);
Attribute[14] attributes = [
{ binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 0},
{ binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 1},
{ binding: 0, location: 2, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 2},
{ binding: 0, location: 3, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 3},
{ binding: 0, location: 4, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof },
{ binding: 0, location: 5, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof },
{ binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.src_start.offsetof },
{ binding: 0, location: 7, format: FMT.RG_F32, offset: Vertex.src_end.offsetof },
{ binding: 0, location: 8, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof },
{ binding: 0, location: 9, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof },
{ binding: 0, location: 10, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof },
{ binding: 0, location: 11, format: FMT.R_F32, offset: Vertex.raised.offsetof },
{ binding: 0, location: 12, format: FMT.R_F32, offset: Vertex.z_index.offsetof },
{ binding: 0, location: 13, format: FMT.R_U32, offset: Vertex.texture.offsetof },
];
GfxPipelineInfo ui_info = {
vertex_shader: cast(u8[])VERTEX_BYTES,
frag_shader: cast(u8[])FRAGMENT_BYTES,
input_rate: IR.Instance,
input_rate_stride: Vertex.sizeof,
layout: ctx.pipeline_layout,
vertex_attributes: attributes,
src_color: BF.SrcAlpha,
dst_color: BF.OneMinusSrcAlpha,
color_op: BO.Add,
src_alpha: BF.One,
dst_alpha: BF.Zero,
alpha_op: BO.Add,
};
CreateGraphicsPipeline(&ctx.rd, &ctx.pipeline, &ui_info);
CreateImageView(&ctx.rd, &ctx.font_atlas, ctx.atlas_buf.atlas.width, ctx.atlas_buf.atlas.height, 4, ctx.atlas_buf.data, DT.Image, 0);
ctx.sampler = CreateSampler(&ctx.rd, MipmapMode.Nearest, 1);
Write(&ctx.rd, ctx.desc_set, [ctx.font_atlas, ctx.sampler]);
SetClearColors(&ctx.rd, [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]);
}
DescLayoutBinding[2] layout_bindings = [
{ binding: 0, descriptorType: DT.Image, descriptorCount: 1, stageFlags: SS.All },
{ binding: 1, descriptorType: DT.Sampler, descriptorCount: 1, stageFlags: SS.All },
];
ctx.desc_set_layout = CreateDescSetLayout(&ctx.rd, layout_bindings);
ctx.desc_set = AllocDescSet(&ctx.rd, ctx.desc_set_layout);
ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, PushConst.sizeof);
Attribute[14] attributes = [
{ binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 0},
{ binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 1},
{ binding: 0, location: 2, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 2},
{ binding: 0, location: 3, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 3},
{ binding: 0, location: 4, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof },
{ binding: 0, location: 5, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof },
{ binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.src_start.offsetof },
{ binding: 0, location: 7, format: FMT.RG_F32, offset: Vertex.src_end.offsetof },
{ binding: 0, location: 8, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof },
{ binding: 0, location: 9, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof },
{ binding: 0, location: 10, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof },
{ binding: 0, location: 11, format: FMT.R_F32, offset: Vertex.raised.offsetof },
{ binding: 0, location: 12, format: FMT.R_F32, offset: Vertex.z_index.offsetof },
{ binding: 0, location: 13, format: FMT.R_U32, offset: Vertex.texture.offsetof },
];
GfxPipelineInfo ui_info = {
vertex_shader: cast(u8[])VERTEX_BYTES,
frag_shader: cast(u8[])FRAGMENT_BYTES,
input_rate: IR.Instance,
input_rate_stride: Vertex.sizeof,
layout: ctx.pipeline_layout,
vertex_attributes: attributes,
src_color: BF.SrcAlpha,
dst_color: BF.OneMinusSrcAlpha,
color_op: BO.Add,
src_alpha: BF.One,
dst_alpha: BF.Zero,
alpha_op: BO.Add,
};
CreateGraphicsPipeline(&ctx.rd, &ctx.pipeline, &ui_info);
CreateImageView(&ctx.rd, &ctx.font_atlas, ctx.atlas_buf.atlas.width, ctx.atlas_buf.atlas.height, 4, ctx.atlas_buf.data, DT.Image, 0);
ctx.sampler = CreateSampler(&ctx.rd, MipmapMode.Nearest, 1);
Write(&ctx.rd, ctx.desc_set, [ctx.font_atlas, ctx.sampler]);
SetClearColors(&ctx.rd, [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]);
InitStacks(&ctx);
g_ui_ctx = ctx;
@ -529,29 +516,123 @@ UIItem*
MakeItem(T)(T k) if(is(T: UIKey) || StringType!T)
{
UICtx* ctx = GetCtx();
UIItem* item = Get(k);
Set(item, ctx);
if(!Nil(item.parent))
{
DLLPush(item.parent, item, g_UI_NIL);
}
AutoPopStacks(ctx);
return item;
}
UISignal
Signal(UIItem* item)
{
UISignal signal;
UICtx* ctx = GetCtx();
i32 x = ctx.mouse_pos.x;
i32 y = ctx.mouse_pos.y;
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.dragged = 0;
for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i); i = i.next)
{
bool taken;
if(item.flags & UIF.Clickable && i.type == UIE.Click && InBounds(ctx.mouse_pos, &item.rect))
{
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;
ctx.drag_item = item;
taken = true;
}
if(ctx.drag_item == item && i.type == UIE.Drag)
{
item.dragged += i.rel;
taken = true;
}
if(taken)
{
DLLRemove(&ctx.events, i, g_UI_NIL_INPUT);
}
}
return signal;
}
void
BeginUI(EditorCtx* edctx, Inputs* inputs)
PushUIEvent(UICtx* ctx, UIInput input)
{
UIInput* ev = Alloc!(UIInput)(&ctx.temp_arena);
*ev = input;
DLLPush(&ctx.events, ev, null);
}
void
BeginUI(Inputs* inputs)
{
UICtx* ctx = GetCtx();
Arena* a = &ctx.temp_arena;
Reset(a);
ctx.events.first = ctx.events.last = null;
static bool dragging;
static bool mouse_down;
for(auto i = inputs.first; i; i = i.next)
{
switch(i.key)
{
case Input.LeftClick:
{
mouse_down = i.pressed;
if(!mouse_down && !dragging)
{
PushUIEvent(ctx, UIInput(UIE.Click));
}
dragging = false;
} break;
case Input.MouseMotion:
{
ctx.mouse_pos = IVec2(i.x, i.y);
if(!dragging && mouse_down)
{
PushUIEvent(ctx, UIInput(type: UIE.DragStart, pos: IVec2(i.x, i.y)));
}
dragging = mouse_down;
if(dragging)
{
PushUIEvent(ctx, UIInput(type: UIE.Drag, rel: IVec2(i.rel_x, i.rel_y)));
}
} break;
default:
{
PushUIEvent(ctx, UIInput(type: UIE.Press, key: i.key));
} break;
}
}
// Ctx state
ctx.f_idx = ctx.frame%FRAME_OVERLAP;
ctx.inputs = inputs;
@ -559,26 +640,34 @@ BeginUI(EditorCtx* edctx, Inputs* inputs)
ResetStacks(ctx);
// Rendering
BeginFrame(&ctx.rd);
BeginRendering(&ctx.rd);
version(ENABLE_RENDERER)
{
BeginFrame(&ctx.rd);
BeginRendering(&ctx.rd);
}
Vec2 ext = GetExtent(&ctx.rd);
if(ext != ctx.res)
{
ctx.res = ext;
Ortho(&ctx.pc.projection, 0.0, 0.0, ext.x, ext.y, -10.0, 10.0);
Ortho(&ctx.projection, 0.0, 0.0, ext.x, ext.y, -10.0, 10.0);
}
PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc);
Bind(&ctx.rd, ctx.pipeline, ctx.desc_set);
version(ENABLE_RENDERER)
{
PushConstants(&ctx.rd, ctx.pipeline, &ctx.projection);
Bind(&ctx.rd, ctx.pipeline, ctx.desc_set);
}
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;
ctx.root = Root(ctx);
// 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!("parent")(ctx, ctx.root);
}
void
@ -592,32 +681,161 @@ EndUI()
{
if(item.size_info[axis].type == ST.Pixels)
{
item.size.v[axis] = item.size_info[axis].value;
item.size.v[axis] = item.size_info[axis].value + item.padding.v[axis];
}
}
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL))
{
if(item.size_info[axis].type == ST.Percentage)
{
for(UIItem* p = item.parent; !Nil(p); p = p.parent)
{
if(p.size_info[axis].type == ST.Pixels)
{
item.size.v[axis] = floor(item.size_info[axis].value * item.parent.size[axis]);
break;
}
}
}
}
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, ctx.root, g_UI_NIL))
{
if(item.size_info[axis].type == ST.ChildrenSum)
{
f32 size = 0.0;
for(UIItem* c = item.first; !Nil(c); c = c.next)
{
size += c.size.v[axis];
}
}
}
for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL))
{
if(axis == item.layout_axis)
{
f32 children_size = 0.0;
for(UIItem* c = item.first; !Nil(c); c = c.next)
{
children_size += c.size.v[axis];
}
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)
{
f32 reduced = c.size.v[axis] - c.size.v[axis]*c.size_info[axis].strictness;
children_size -= reduced;
c.size.v[axis] -= reduced;
child_count += 1;
}
if(children_size > 0.0)
{
for(UIItem* c = item.last; !Nil(c); c = c.prev)
{
f32 reduced = Min(children_size, c.size[axis]);
children_size -= reduced;
c.size.v[axis] -= reduced;
if(children_size < 0.0009)
{
break;
}
}
assert(children_size < 0.0009);
}
}
}
else
{
for(UIItem* c = item.first; !Nil(c); c = c.next)
{
if(c.size.v[axis] > item.size.v[axis])
{
c.size.v[axis] = item.size.v[axis];
}
}
}
}
{
f32 pos = 0.0;
for(UIItem* item = ctx.root; !Nil(item);)
{
f32 next_pos = 0.0;
f32 end_pos = pos + item.size.v[axis];
item.rect.p0.v[axis] = pos;
item.rect.p1.v[axis] = end_pos;
assert(!isNaN(item.rect.p0.v[axis]));
assert(!isNaN(item.rect.p1.v[axis]));
next_pos = item.parent.layout_axis == axis ? end_pos : pos;
if(!Nil(item.first))
{
item = item.first;
}
else if(!Nil(item.next))
{
item = item.next;
pos = next_pos;
}
else for(UIItem* p = item.parent;; p = p.parent)
{
if(!Nil(p.next))
{
item = p.next;
pos = item.parent.layout_axis == axis ? item.prev.rect.p1.v[axis] : item.prev.rect.p0.v[axis];
break;
}
if(Nil(p))
{
item = g_UI_NIL;
break;
}
}
}
}
}
with(ctx)
version(ENABLE_RENDERER) with(ctx)
{
BindBuffers(&rd, &buffers[f_idx].m_idx, &buffers[f_idx].m_vtx);
DrawIndexed(&rd, 6, buffers[f_idx].count, 0);
}
FinishRendering(&ctx.rd);
SubmitAndPresent(&ctx.rd);
FinishRendering(&rd);
SubmitAndPresent(&rd);
}
ctx.frame += 1;
}
UIItem*
Root(UICtx* ctx)
u32[2]
GetExtent(Renderer* rd)
{
UISize[2] sizes = [UISize(ST.Pixels, ctx.res.x), UISize(ST.Pixels, ctx.res.y)];
Push!("size_info")(ctx, sizes);
return MakeItem("###root");
version(ENABLE_RENDERER)
{
return RendererGetExtent(rd);
}
else
{
return [1280, 720];
}
}
template StackIDs(string stack)
template
StackIDs(string stack)
{
import std.string : replace;
struct Identifiers { string stack, stack_top_node; }
@ -935,8 +1153,9 @@ GlyphWidth(Glyph* g)
struct TextBuffer
{
u8[] text;
TS[] style;
u8[] text;
TS[] style;
TextBuffer* next;
}
/*
@ -1094,7 +1313,7 @@ DrawPanel(UIPanel* panel, f32 lc_w, bool focus)
{
UICtx* ctx = GetCtx();
Vec2 pos = panel.rect.vec0;
Vec2 pos = panel.rect.p0;
Vec2 size = panel.size;
DrawRect(pos, size, ctx.corner_radius_top.value, ctx.border_thickness_top.value, ctx.bg_col_top.value);
@ -1102,6 +1321,24 @@ DrawPanel(UIPanel* panel, f32 lc_w, bool focus)
DrawBorder(pos, size, ctx.border_thickness_top.value, ctx.corner_radius_top.value, ctx.edge_softness_top.value, focus ? ctx.border_hl_col_top.value : ctx.border_col_top.value);
}
static UISize[2]
MakeUISize(UISize x, UISize y)
{
return [x, y];
}
static UISize[2]
MakeUISizeX(SizeType type, f32 value, f32 strictness = 1.0)
{
return [UISize(type, value, strictness), UISize(ST.Percentage, 1.0)];
}
static UISize[2]
MakeUISizeY(SizeType type, f32 value, f32 strictness = 1.0)
{
return [UISize(ST.Percentage, 1.0), UISize(type, value, strictness)];
}
pragma(inline) Glyph*
GetGlyph(u8 ch)
{
@ -1187,25 +1424,25 @@ Nil(UIItem* item)
}
pragma(inline) bool
InBounds(InputEvent* ev, Vec2 p0, Vec2 p1)
InBounds(T)(T pos, Rect* rect)
{
return ev.x >= p0.x && ev.x <= p1.x && ev.y >= p0.y && ev.y <= p1.y;
return pos.x >= rect.p0.x && pos.x <= rect.p1.x && pos.y >= rect.p0.y && pos.y <= rect.p1.y;
}
bool
Clicked(UIItem* item, Vec2 p0, Vec2 p1)
Clicked(UIItem* item, Rect* rect)
{
bool result;
UICtx* ctx = GetCtx();
for(auto n = ctx.inputs.list.first; !CheckNil(null, n) && Nil(ctx.drag_item); n = n.next)
for(auto n = ctx.inputs.first; !CheckNil(null, n) && Nil(ctx.drag_item); n = n.next)
{
InputEvent* ev = &n.value;
InputEvent* ev = n;
if(ev.key == Input.LeftClick && ev.pressed && InBounds(ev, p0, p1))
if(ev.key == Input.LeftClick && ev.pressed && InBounds(ev, rect))
{
result = true;
DLLRemove(&ctx.inputs.list, n, null);
DLLRemove(ctx.inputs, n, null);
break;
}
}
@ -1214,27 +1451,27 @@ Clicked(UIItem* item, Vec2 p0, Vec2 p1)
}
bool
Dragged(UIItem* item, Vec2 p0, Vec2 p1)
Dragged(UIItem* item, Rect* rect)
{
bool result;
UICtx* ctx = GetCtx();
item.dragged = 0.0;
item.dragged = 0;
if(Nil(ctx.drag_item) && Clicked(item, p0, p1))
if(Nil(ctx.drag_item) && Clicked(item, rect))
{
ctx.drag_item = item;
}
for(auto n = ctx.inputs.list.first; !CheckNil(null, n) && ctx.drag_item == item; n = n.next)
for(auto n = ctx.inputs.first; !CheckNil(null, n) && ctx.drag_item == item; n = n.next)
{
InputEvent* ev = &n.value;
InputEvent* ev = n;
bool taken;
if(ev.key == Input.MouseMotion)
{
item.dragged.x += cast(f32)ev.rel_x;
item.dragged.y += cast(f32)ev.rel_y;
item.dragged.x += ev.rel_x;
item.dragged.y += ev.rel_y;
result = true;
taken = true;
}
@ -1246,7 +1483,7 @@ Dragged(UIItem* item, Vec2 p0, Vec2 p1)
if(taken)
{
DLLRemove(&ctx.inputs.list, n, null);
DLLRemove(ctx.inputs, n, null);
}
}
@ -1270,17 +1507,47 @@ unittest
}
{
UICtx ctx;
InitStacks(&ctx);
ResetStacks(&ctx);
Inputs inputs;
InitUICtx(null);
BeginUI(&inputs);
UICtx* ctx = GetCtx();
Vec4 w = Vec4(1.0);
Vec4[4] col = w;
Push!("bg_col")(&ctx, col);
Push!("bg_col")(ctx, col);
assert(ctx.bg_col.top.value == col);
Pop!("bg_col")(&ctx);
EndUI();
Vec2 extent = GetExtent(null);
assert(extent == ctx.root.size);
BeginUI(&inputs);
assert(ctx.bg_col.top.value == BG_COL);
EndUI();
// Layouts
{
BeginUI(&inputs);
Push!("size_info")(ctx, MakeUISizeX(ST.Percentage, 0.5));
UIItem* root = ctx.root;
UIItem* i0 = MakeItem("###i0");
UIItem* i1 = MakeItem("###i1");
assert(i0.parent == root);
assert(i1.parent == root);
EndUI();
Vec2 expected = Vec2(floor(root.size.x * 0.5), root.size.y);
assert(i0.size == expected);
assert(i1.size == expected);
}
}
}

View File

@ -25,18 +25,20 @@ Panel(UIPanel* panel)
parent.axis == A2D.Y ? 10 : 0
);
Vec2 p0 = panel.rect.vec0+adj;
Vec2 p1 = panel.rect.vec1-adj;
Vec2 p0 = panel.rect.p0+adj;
Vec2 p1 = panel.rect.p1-adj;
Rect r = Rect(p0: p0, p1: p1);
if(!Nil(prev)) with(panel)
{
Vec2 d0 = rect.vec0-adj;
Vec2 d0 = rect.p0-adj;
Vec2 d1 = Vec2(
pax == A2D.X ? rect.vec0.x+adj.x : rect.vec1.x,
pax == A2D.Y ? rect.vec0.y+adj.y : rect.vec1.y
pax == A2D.X ? rect.p0.x+adj.x : rect.p1.x,
pax == A2D.Y ? rect.p0.y+adj.y : rect.p1.y
);
if(Dragged(item, d0, d1) && item.dragged.v[pax] != 0.0)
Rect dr = Rect(p0: d0, p1: d1);
if(Dragged(item, &dr) && item.dragged.v[pax] != 0.0)
{
f32 mov_pct = Remap(item.dragged.v[pax], 0.0, panel.parent.size.v[pax], 0.0, 1.0);
if(CheckPanelBounds(pct + mov_pct) && CheckPanelBounds(panel.prev.pct - mov_pct))
@ -49,7 +51,7 @@ Panel(UIPanel* panel)
if(panel.ed != null)
{
if(Clicked(item, p0, p1))
if(Clicked(item, &r))
{
SetFocusedPanel(panel);
}
@ -76,8 +78,8 @@ Panel(UIPanel* panel)
f32 y_rem = fmod(panel.scroll_offset, TEXT_SIZE);
f32 x = panel.rect.x0;
f32 y = panel.rect.y0 + TEXT_SIZE - fmod(panel.scroll_offset, TEXT_SIZE);
f32 x = panel.rect.p0.x;
f32 y = panel.rect.p1.y + TEXT_SIZE - fmod(panel.scroll_offset, TEXT_SIZE);
u64 i = panel.start_ln;
for(auto buf = GetLine(&ed.buf, i); !CheckNil(g_NIL_LINE_BUF, buf) && i < panel.end_ln; i += 1, buf = GetLine(&ed.buf, i))
@ -97,7 +99,7 @@ Panel(UIPanel* panel)
}
else for(auto n = parts; n != null; n = n.next)
{
auto l = &n.value;
auto l = n;
if(pos.y == i)
{
@ -202,14 +204,14 @@ Nil(UIPanel* panel)
return panel == null || panel == g_UI_NIL_PANEL;
}
Node!(TextBuffer)*
TextBuffer*
MakeMultiline(u8[] text, f32 width, TS[] style = [])
{
f32 scaled_width = width * (g_ui_ctx.atlas_buf.atlas.size/g_ui_ctx.text_size);
f32 text_width = CalcTextWidth(text);
u64 line_count = cast(u64)(ceil(text_width/scaled_width));
Node!(TextBuffer)* node = null;
TextBuffer* node = null;
if(line_count > 0)
{
f32 w = 0.0;
@ -232,15 +234,15 @@ MakeMultiline(u8[] text, f32 width, TS[] style = [])
stl = ScratchAlloc!(TS)(style, start, len);
}
Node!(TextBuffer)* n = node;
TextBuffer* n = node;
for(;;)
{
if(node == null)
{
node = ScratchAlloc!(Node!(TextBuffer))();
node = ScratchAlloc!(TextBuffer)();
node.value.text = str;
node.value.style = stl;
node.text = str;
node.style = stl;
node.next = null;
break;
@ -248,10 +250,10 @@ MakeMultiline(u8[] text, f32 width, TS[] style = [])
if(n.next == null)
{
n.next = ScratchAlloc!(Node!(TextBuffer))();
n.next = ScratchAlloc!(TextBuffer)();
n.next.value.text = str;
n.next.value.style = stl;
n.next.text = str;
n.next.style = stl;
n.next.next = null;
break;