implement scrolling, various fixes

This commit is contained in:
Matthew 2025-12-23 18:43:20 +11:00
parent 39ab34ebeb
commit ace749228d
3 changed files with 326 additions and 110 deletions

@ -1 +1 @@
Subproject commit 7f4c109106eaabda180158a8f7a92c9d2e60d3db
Subproject commit 809814577db807d5911e79216d6e114a2d5a2dfd

View File

@ -153,8 +153,12 @@ struct UIInput
UIEvent type;
UIInput* next, prev;
union
{
struct
{
Input key;
string text;
};
IVec2 rel;
IVec2 pos;
}
@ -177,7 +181,8 @@ UICtxParameter(T, string name)
mixin(CtxParameterGen!(T, name)());
}
template CtxMemberInfo(int i)
template
CtxMemberInfo(int i)
{
import std.string : endsWith, startsWith;
import std.traits : isInstanceOf, isPointer;
@ -274,11 +279,9 @@ struct StackTop(T)
Stack!(T)* free;
}
mixin template UIItemParameters()
static string
UIItemParameterGen()
{
static string
UIItemParameterGen()
{
string fields = "";
static foreach(i, m; UICtx.tupleof)
{
@ -297,8 +300,11 @@ mixin template UIItemParameters()
}
return fields;
}
}
mixin template
UIItemParameters()
{
mixin(UIItemParameterGen());
}
@ -677,17 +683,21 @@ InitSingleLine:
{
if(item.flags & (UIF.ScrollX << axis))
{
item.scroll_offset.v[axis] += scroll_speed * (item.scroll_target.v[axis] - item.scroll_offset.v[axis]);
if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) > 0.0009)
{
f32 v = scroll_speed * (item.scroll_target.v[axis] - item.scroll_offset.v[axis]);
item.scroll_offset.v[axis] += v;
if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) < 2.0)
{
item.scroll_offset.v[axis] = item.scroll_target.v[axis];
}
}
}
if(item.flags & (UIF.ClampX << axis))
{
item.scroll_offset = clamp(item.scroll_offset.v[axis], item.scroll_clamp[axis].x, item.scroll_clamp[axis].y);
item.scroll_offset.v[axis] = clamp(item.scroll_offset.v[axis], item.scroll_clamp[axis].x, item.scroll_clamp[axis].y);
}
}
@ -822,7 +832,7 @@ BeginUI(Inputs* inputs)
} break;
default:
{
PushUIEvent(ctx, UIInput(type: UIE.Press, key: i.key));
PushUIEvent(ctx, UIInput(type: UIE.Press, key: i.key, text: i.pressed ? i.text : []));
} break;
}
}
@ -851,7 +861,7 @@ BeginUI(Inputs* inputs)
// Ctx state
ctx.f_idx = ctx.frame%FRAME_OVERLAP;
ctx.inputs = inputs;
ctx.char_width = GlyphWidth(&ctx.atlas_buf.atlas.glyphs['0']);
ctx.char_width = GlyphWidth(&ctx.atlas_buf.atlas.glyphs[' ']);
ResetStacks(ctx);
@ -880,13 +890,13 @@ BeginUI(Inputs* inputs)
// Root Item
UISize[2] sizes = [UISize(ST.Pixels, ctx.res.x), UISize(ST.Pixels, ctx.res.y)];
Push!("size_info")(ctx, sizes);
Push!("size_info")(sizes);
ctx.root = MakeItem("###root");
ctx.window_root = MakeItem("###window_root");
Pop!("size_info")(ctx);
Push!("parent")(ctx, ctx.root);
Pop!("size_info");
Push!("parent")(ctx.root);
}
void
@ -999,7 +1009,7 @@ EndUI()
{
f32 size = InnerSize!(axis)(item);
if(item.flags & (UIF.PortalX << axis))
if(item.flags & (UIF.PortalViewX << axis))
{
continue;
}
@ -1067,7 +1077,7 @@ EndUI()
}
else if(item == item.parent.first)
{
pos += item.parent.padding.v[axis];
pos += item.parent.padding.v[axis] + item.parent.view_offset.v[axis];
}
f32 inner_pos = pos;
@ -1082,6 +1092,7 @@ EndUI()
assert(!isNaN(item.rect.p0.v[axis]));
assert(!isNaN(item.rect.p1.v[axis]));
/*
debug
{
bool portal = cast(bool)(item.parent.flags & (UIF.PortalViewX << axis));
@ -1103,6 +1114,7 @@ EndUI()
assert(in_bounds_start && in_bounds_end);
}
}
*/
if(!Nil(item.first))
{
@ -1169,8 +1181,8 @@ RenderItem(UICtx* ctx, UIItem* item)
{
DrawUI(ctx);
u32 x = cast(u32)(scissor_x ? floor(item.rect.p0.x) : ctx.res.x);
u32 y = cast(u32)(scissor_y ? floor(item.rect.p0.y) : ctx.res.y);
u32 x = cast(u32)(scissor_x ? floor(item.rect.p0.x) : 0);
u32 y = cast(u32)(scissor_y ? floor(item.rect.p0.y) : 0);
u32 w = cast(u32)(scissor_x ? floor(item.rect.p1.x) - x : ctx.res.x);
u32 h = cast(u32)(scissor_y ? floor(item.rect.p1.y) - y : ctx.res.y);
SetScissor(&ctx.rd, x, y, w, h);
@ -1178,7 +1190,6 @@ RenderItem(UICtx* ctx, UIItem* item)
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;
@ -1284,23 +1295,117 @@ GetExtent(Renderer* rd)
}
template
StackIDs(string stack)
StackIDs(string stack, string ctx = "ctx")
{
import std.string : replace;
struct Identifiers { string stack, stack_top_node; }
enum Identifiers StackIDs = {
stack: replace("ctx.@", "@", stack),
stack_top_node: replace("ctx.@_top", "@", stack),
stack: replace(ctx~".@", "@", stack),
stack_top_node: replace(ctx~".@_top", "@", stack),
};
}
void
Push(string stack_str, bool auto_pop = false, T)(UICtx* ctx, T value)
PushSizeInfoVec(int i)(SizeType type, f32 value, f32 strictness = 1.0, bool auto_pop = false)
{
UISize[2] size_info = g_size_info_default;
size_info[i].type = type;
size_info[i].value = value;
size_info[i].strictness = strictness;
Push!("size_info")(size_info, auto_pop);
}
void
PushSizeInfoX(SizeType type, f32 value, f32 strictness = 1.0, bool auto_pop = false)
{
PushSizeInfoVec!(0)(type, value, strictness, auto_pop);
}
void
PushSizeInfoY(SizeType type, f32 value, f32 strictness = 1.0, bool auto_pop = false)
{
PushSizeInfoVec!(1)(type, value, strictness, auto_pop);
}
void
PushScrollClampVec(int i)(Vec2 clamp, bool auto_pop)
{
Vec2[2] scroll_clamp = g_scroll_clamp_default;
scroll_clamp[i] = clamp;
Push!("scroll_clamp")(scroll_clamp, auto_pop);
}
void
PushScrollClampX(f32 start, f32 end, bool auto_pop = false)
{
PushScrollClampVec!(0)(Vec2(start, end), auto_pop);
}
void
PushScrollClampY(f32 start, f32 end, bool auto_pop = false)
{
PushScrollClampVec!(1)(Vec2(start, end), auto_pop);
}
void
PushBorderCol(Vec4 col, bool auto_pop = false)
{
Vec4[4] arr = col;
PushBorderCol(arr, auto_pop);
}
void
PushBgCol(Vec4 col, bool auto_pop = false)
{
Vec4[4] arr = col;
PushBgCol(arr, auto_pop);
}
void
PushPaddingX(f32 padding, bool auto_pop = false)
{
PushPadding(Vec2(padding, 0.0), auto_pop);
}
void
PushPaddingY(f32 padding, bool auto_pop = false)
{
PushPadding(Vec2(0.0, padding), auto_pop);
}
void
PushViewOffsetX(f32 offset, bool auto_pop = false)
{
PushViewOffset(Vec2(offset, 0.0), auto_pop);
}
void
PushViewOffsetY(f32 offset, bool auto_pop = false)
{
PushViewOffset(Vec2(0.0, offset), auto_pop);
}
void
PushScrollTargetX(f32 target, bool auto_pop = false)
{
PushScrollTarget(Vec2(target, 0.0), auto_pop);
}
void
PushScrollTargetY(f32 target, bool auto_pop = false)
{
PushScrollTarget(Vec2(0.0, target), auto_pop);
}
void
Push(string stack_str, T)(T value, bool auto_pop = false)
{
import std.string : replace;
enum ids = StackIDs!(stack_str);
enum ids = StackIDs!(stack_str, "g_ui_ctx");
auto stack = &mixin(ids.stack);
auto top = mixin(ids.stack_top_node);
@ -1312,7 +1417,7 @@ Push(string stack_str, bool auto_pop = false, T)(UICtx* ctx, T value)
}
else
{
node = Alloc!(Stack!(T))(&ctx.temp_arena);
node = Alloc!(Stack!(T))(&g_ui_ctx.temp_arena);
}
node.next = stack.top;
@ -1322,12 +1427,29 @@ Push(string stack_str, bool auto_pop = false, T)(UICtx* ctx, T value)
stack.top = node;
}
static string
PushScope(string stack_str, alias __value)()
{
enum string id = __traits(identifier, __value);
static if(id == "__value")
{
string value_str = "cast(typeof("~StackIDs!(stack_str, "g_ui_ctx").stack~".top.value))(" ~ __value.stringof ~ ")";
}
else
{
string value_str = __traits(identifier, __value);
}
return "Push!(\"" ~ stack_str ~ "\")(" ~ value_str ~ ");\n scope(exit) Pop!(\"" ~ stack_str ~ "\");";
}
auto
Pop(string stack_str)(UICtx* ctx)
Pop(string stack_str)()
{
import std.string : replace;
enum ids = StackIDs!(stack_str);
enum ids = StackIDs!(stack_str, "g_ui_ctx");
auto stack = &mixin(ids.stack);
auto top = mixin(ids.stack_top_node);
@ -1345,14 +1467,52 @@ Pop(string stack_str)(UICtx* ctx)
}
void
Pop(stack_strs...)(UICtx* ctx)
Pop(stack_strs...)()
{
static foreach(stack; stack_strs)
{
Pop!(stack)(ctx);
Pop!(stack)();
}
}
static string
GenPushFuncs()
{
import std.array : split;
import std.uni : toUpper;
string funcs = "";
static foreach(i, m; UICtx.tupleof)
{
{
enum info = CtxMemberInfo!(i);
static if(info.is_stack)
{
string[] parts = split(info.id, "_");
string fn_name = "Push";
foreach(p; parts)
{
if(p.length == 1)
{
fn_name ~= toUpper(p[0 .. 1]);
}
else
{
fn_name ~= toUpper(p[0 .. 1]) ~ p[1 .. $];
}
}
funcs ~= "pragma(inline) void " ~ fn_name ~ "(T)(T value, bool auto_pop = false){ Push!(\"" ~ info.id ~ "\")(value, auto_pop); }\n";
}
}
}
return funcs;
}
mixin(GenPushFuncs());
void
AutoPopStacks(UICtx* ctx)
{
@ -1368,7 +1528,7 @@ AutoPopStacks(UICtx* ctx)
if(ctx.tupleof[i].top.auto_pop)
{
ctx.tupleof[i].top.auto_pop = false;
Pop!(member_info.id)(ctx);
Pop!(member_info.id);
}
}
}
@ -1595,8 +1755,6 @@ NewItem(UICtx* ctx)
item = Alloc!(UIItem)(&ctx.arena);
}
item.last_frame = ctx.frame-2;
return item;
}
@ -1666,8 +1824,7 @@ CalcTextWidth(string str)
pragma(inline) f32
GlyphWidth(Glyph* g)
{
f32 width = 0.0;
width += g.ch == '\t' ? (g_ui_ctx.atlas_buf.atlas.glyphs[' '].advance*cast(f32)(g_ui_ctx.tab_width)) : g.advance;
f32 width = g.ch == '\t' ? (g_ui_ctx.atlas_buf.atlas.glyphs[' '].advance*cast(f32)(g_ui_ctx.tab_width)) : g.advance;
return width;
}
@ -1962,11 +2119,22 @@ Dragged(UIItem* item, Rect* rect)
return result;
}
pragma(inline) Vec4[4]
Vec4Arr(Vec4 col)
static Vec4[4]
Vec4Arr(Vec4 vec)
{
Vec4[4] arr = col;
return arr;
return [vec, vec, vec, vec];
}
static Vec2[2]
Vec2ArrX(alias Vec2 vec)()
{
return [vec, Vec2(0.0)];
}
static Vec2[2]
Vec2ArrY(alias Vec2 vec)()
{
return [Vec2(0.0), vec];
}
unittest
@ -1995,7 +2163,7 @@ unittest
Vec4 w = Vec4(1.0);
Vec4[4] col = w;
Push!("bg_col")(ctx, col);
Push!("bg_col")(col);
assert(ctx.bg_col.top.value == col);
EndUI();
@ -2011,7 +2179,7 @@ unittest
{
BeginUI(&inputs);
Push!("size_info")(ctx, MakeUISizeX(ST.Percentage, 0.5));
Push!("size_info")(MakeUISizeX(ST.Percentage, 0.5));
UIItem* root = ctx.root;
UIItem* i0 = MakeItem("###i0");

View File

@ -14,6 +14,93 @@ Nil(UIPanel* panel)
return panel == null || panel == g_UI_NIL_PANEL;
}
void
LineCounterView(u64 max_line, u64 lines, u64 line_offset, f32 view_offset)
{
UICtx* ctx = GetCtx();
UIKey zero = ZeroKey();
u64 ch_width = max_line.toChars().length;
f32 lc_width = cast(f32)(ch_width+1)*ctx.char_width; // Should figure out how to accurately measure text width
PushLayoutAxis(A2D.Y, true);
PushViewOffsetY(view_offset, true);
PushPaddingX(4.0, true);
PushSizeInfoX(ST.Pixels, lc_width, 1.0, true);
UIItem* line_count = MakeItem(zero, UIF.DrawBorder|UIF.PortalViewY);
PushTextCol(Vec4(1.0));
PushSizeInfoY(ST.Pixels, TEXT_SIZE);
PushParent(line_count);
u64 end_line = lines+line_offset;
for(u64 i = line_offset; i < end_line; i += 1)
{
char[] buf = ScratchAlloc!(char)(ch_width);
Push!("display_string")(ConvToStr(sformat(buf, "%s", i)), true);
MakeItem(zero, UIF.DrawText);
}
Pop!("text_col", "size_info", "parent");
}
void
EditorTextView(UIItem* editor, Editor* ed, u64 lines, u64 line_offset, f32 view_offset)
{
PushLayoutAxis(A2D.Y, true);
f32 clamp_y = cast(f32)(ed.buf.line_count-lines)*TEXT_SIZE;
f32 scroll_pos = cast(f32)(ed.line_offset)*TEXT_SIZE;
PushLayoutAxis(A2D.Y, true);
PushScrollClampY(0.0, clamp_y, true);
PushSizeInfo(g_size_info_default, true);
PushBorderCol(Vec4(1.0), true);
PushScrollTargetY(scroll_pos, true);
PushViewOffsetY(view_offset, true);
static bool end;
static f32 count = 0.0;
if(!end)
{
ed.cursor_pos.y = 500;
count += g_delta;
if(count > 0.5)
{
end = !end;
}
}
else
{
ed.cursor_pos.y = 0;
count -= g_delta;
if(count <= 0.0)
{
end = !end;
}
}
editor = MakeItem(editor.key, UIF.DrawBorder|UIF.ScrollY|UIF.ClampY);
u64 end_line = line_offset+lines;
UIKey zero = ZeroKey();
PushSizeInfoY(ST.TextSize, 1.0);
PushParent(editor);
scope(exit) Pop!("size_info", "parent");
u64 i = line_offset;
for(LineBuffer* lb = GetLine(&ed.buf, i); !CheckNil(g_NIL_LINE_BUF, lb) && i < line_offset+lines; i += 1, lb = GetLine(&ed.buf, i))
{
PushDisplayString(ConvToStr(lb.text), true);
UIItem* line = MakeItem(zero, UIF.DrawText);
}
}
void
EditorView(Editor* ed)
{
@ -23,52 +110,32 @@ EditorView(Editor* ed)
UIItem* editor = Get(ed_key);
u64 frame_line_offset = ed.line_offset;
f32 frame_view_offset = editor.scroll_offset.y%TEXT_SIZE;
f32 frame_view_offset = -(editor.scroll_offset.y%TEXT_SIZE);
PushBgCol(Vec4(0.2, 0.3, 0.8, 1.0), true);
PushSizeInfoX(ST.Percentage, 1.0, 1.0, true);
Push!("bg_col", true)(ctx, Vec4Arr(Vec4(0.2, 0.3, 0.8, 1.0)));
Push!("size_info", true)(ctx, MakeUISizeX(ST.Percentage, 1.0));
UIItem* container = MakeItem(zero, UIF.DrawBackground);
Push!("parent")(ctx, container);
PushParent(container);
PushBorderCol(Vec4(1.0));
Push!("border_col")(ctx, Vec4Arr(Vec4(1.0)));
// Line Counter
f32 lc_width = ed.buf.line_count.toChars().length*ctx.char_width;
Push!("layout_axis", true)(ctx, A2D.Y);
Push!("view_offset" )(ctx, Vec2(0.0, frame_view_offset));
Push!("padding", true)(ctx, Vec2(4.0, 0.0));
Push!("size_info", true)(ctx, MakeUISizeX(ST.Pixels, lc_width));
UIItem* line_count = MakeItem(zero, UIF.DrawBorder);
// Editor
f32 scroll_pos = cast(f32)(ed.line_offset)*TEXT_SIZE;
Push!("layout_axis", true)(ctx, A2D.Y);
Push!("scroll_clamp", true)(ctx, cast(Vec2[2])[Vec2(0.0), Vec2(0.0, cast(f32)(ed.buf.line_count)*TEXT_SIZE)]);
Push!("size_info", true)(ctx, MakeUISize(UISize(ST.Percentage, 1.0), UISize(ST.Percentage, 1.0)));
Push!("border_col", true)(ctx, Vec4Arr(Vec4(1.0)));
Push!("scroll_target", true)(ctx, Vec2(0.0, scroll_pos));
editor = MakeItem(ed_key, UIF.DrawBorder|UIF.ScrollY|UIF.ClampY);
Pop!("view_offset")(ctx);
scope(exit) Pop!("parent", "border_col");
u64 view_lines;
if(editor.size.y > 0.0)
{
view_lines = cast(u64)ceil(editor.size.y/TEXT_SIZE);
view_lines = cast(u64)ceil(editor.size.y/TEXT_SIZE)+1;
const u64 SCROLL_BUFFER = 2;
const u64 SCROLL_BUFFER = 4;
u64 start = ed.line_offset;
u64 end = start+view_lines;
if(ed.cursor_pos.y < start)
{
ed.line_offset = clamp(ed.cursor_pos.y - SCROLL_BUFFER, 0, ed.buf.line_count);
u64 pos = ed.cursor_pos.y > SCROLL_BUFFER ? ed.cursor_pos.y - SCROLL_BUFFER : ed.cursor_pos.y;
ed.line_offset = clamp(pos, 0, ed.buf.line_count);
}
else if(ed.cursor_pos.y > end)
{
@ -76,30 +143,11 @@ EditorView(Editor* ed)
}
}
Push!("size_info")(ctx, MakeUISizeY(ST.Pixels, TEXT_SIZE));
Push!("parent" )(ctx, line_count);
Push!("text_col" )(ctx, Vec4(1.0));
u64 start = cast(u64)floor(editor.scroll_offset.y/TEXT_SIZE);
u64 end_line = frame_line_offset+view_lines;
LineCounterView(ed.buf.line_count, view_lines, start, frame_view_offset);
for(u64 i = frame_line_offset; i < end_line; i += 1)
{
MakeItem("%s##ed_%s", i, ed.editor_id, UIF.DrawText);
}
Pop!("parent", "size_info")(ctx);
Push!("size_info")(ctx, MakeUISizeY(ST.TextSize, 1.0));
Push!("parent")(ctx, editor);
u64 i = frame_line_offset;
for(LineBuffer* lb = GetLine(&ed.buf, i); !CheckNil(g_NIL_LINE_BUF, lb) && i < end_line; i += 1, lb = GetLine(&ed.buf, i))
{
Push!("display_string", true)(ctx, ConvToStr(lb.text));
UIItem* line = MakeItem(zero, UIF.DrawText);
}
Pop!("parent", "parent", "text_col")(ctx);
EditorTextView(editor, ed, view_lines, start, frame_view_offset);
}
/*