From f4f311a49c699a17e45d8b5d65dd43d7fe4ddfe8 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 31 Dec 2025 20:35:56 +1100 Subject: [PATCH] more work --- src/dlib | 2 +- src/editor/ui.d | 439 ++++++++++++++++++++++++++------------------- src/editor/views.d | 15 +- 3 files changed, 263 insertions(+), 193 deletions(-) diff --git a/src/dlib b/src/dlib index 1160d74..10c7fdc 160000 --- a/src/dlib +++ b/src/dlib @@ -1 +1 @@ -Subproject commit 1160d747ccb550b72a60cd68aa22a14f6dcb1ab1 +Subproject commit 10c7fdcaedfdd98af47c640e00f1fad8705f476d diff --git a/src/editor/ui.d b/src/editor/ui.d index 028fc2f..032fafb 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -16,7 +16,7 @@ import std.math.remainder : fmod; import std.algorithm.comparison : clamp; import std.format : sformat; import core.stdc.string : memset; -import core.stdc.math : fabsf; +import core.stdc.math : fabsf, cbrtf; import std.conv; import core.stdc.stdio : sprintf; @@ -29,12 +29,12 @@ import core.stdc.stdio : sprintf; *********************************/ -enum Vec4 BG_COL = Vec4(0.13, 0.13, 0.13, 1.0); -enum Vec4 BG_HL_COL = Vec4(0.24, 0.45, 0.81, 1.0); -enum Vec4 BORDER_COL = Vec4(0.254, 0.254, 0.266, 1.0); -enum Vec4 BORDER_HL_COL = Vec4(0.035, 0.549, 0.824, 1.0); -enum Vec4 TEXT_COL = Vec4(1.0); -enum Vec4 TEXT_HL_COL = Vec4(0.0, 0.0, 0.0, 1.0); +enum Vec4 BG_COL = SRGBVec4(0.13, 0.13, 0.13, 1.0); +enum Vec4 BG_HL_COL = SRGBVec4(0.24, 0.45, 0.81, 1.0); +enum Vec4 BORDER_COL = SRGBVec4(0.254, 0.254, 0.266, 1.0); +enum Vec4 BORDER_HL_COL = SRGBVec4(0.035, 0.549, 0.824, 1.0); +enum Vec4 TEXT_COL = SRGBVec4(1.0); +enum Vec4 TEXT_HL_COL = SRGBVec4(0.0, 0.0, 0.0, 1.0); const u64 VERTEX_MAX_COUNT = 10000; const Vec2 CLICK_BUFFER = Vec2(3.0, 3.0); @@ -100,7 +100,7 @@ enum Axis2D alias A2D = Axis2D; -enum UIFlags +enum UIFlags : u64 { None = 0, DrawBackground = 1<<0, @@ -128,6 +128,11 @@ enum UIFlags OverflowY = 1<<22, Gradient = 1<<23, VerticalAlignText = 1<<24, + SetHot = 1<<25, + SetReady = 1<<26, + AnimateReady = 1<<27, // Fade in/out + AnimateHot = 1<<28, // Hover highlight + AnimateActive = 1<<29, // Animate selected (probably do later) Clamp = UIFlags.ClampX | UIFlags.ClampY, PortalView = UIFlags.PortalViewX | UIFlags.PortalViewY, @@ -236,7 +241,6 @@ struct UICtx LinkedList!(UIInput) events; IVec2 mouse_pos; - IVec2 drag_start; PlatformWindow* window; Renderer rd; @@ -259,9 +263,14 @@ struct UICtx u32 tab_width; Vec4[TS.max][UISH.max] syntax_colors; - UIItem* root; - UIItem* drag_item; - UIItem* focus_item; + UIItem* root_first; + UIItem* root_last; + UIKey drag_key; + UIKey hover_key; + u64 last_hover_frame; + + f32 scroll_rate; + f32 animation_rate; mixin UICtxParameter!(Vec4, "bg_col"); mixin UICtxParameter!(Vec4, "bg_col_end"); @@ -363,6 +372,10 @@ struct UIItem f32 resize_pct; bool rendered; + f32 ready_t; // Item visible + f32 hot_t; // Item to be interacted with (e.g. hover) + f32 active_t; // Item active (retained focus) + mixin UIItemParameters!(); } @@ -503,7 +516,6 @@ InitUICtx(PlatformWindow* window) ctx.free_items = Alloc!(UIItem)(&arena); ctx.arena = arena; ctx.temp_arena = CreateArena(MB(1)); - ctx.drag_item = g_UI_NIL; ctx.transient_items = g_UI_NIL; ctx.font = OpenFont(cast(u8[])FONT_BYTES); ctx.tab_width = 2; @@ -662,20 +674,23 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T) { UICtx* ctx = GetCtx(); UIItem* item = Get(k); + item.flags = flags; + item.signal = UIS.None; Set(item, ctx); - if(item.flags & (UIF.Window | UIF.FloatingWindow)) + if(Nil(ctx.root_first)) + { + ctx.root_first = ctx.root_last = item; + Push!("parent")(item); + } + if(item.flags & (UIF.Window | UIF.FloatingWindow) || Nil(ctx.root_first)) { item.parent = g_UI_NIL; - UIItem* next = ctx.root; - while(!Nil(next.next)) - { - next = next.next; - } - next.next = item; - item.prev = next; + ctx.root_last.next = item; + item.prev = ctx.root_last; + ctx.root_last = item; } else if(!Nil(item.parent)) { @@ -702,12 +717,11 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T) string str = item.display_string.length ? item.display_string : item.key.text; if(item.flags & UIF.TextWrap) { - f32 width = item.size_info[A2D.X].type == ST.TextSize ? item.parent.size.x : item.size.x; u32 ch_per_line = cast(u32)floor(width/abuf.atlas.max_advance); u64 lines = (str.length/ch_per_line) + (str.length%ch_per_line ? 1 : 0); - item.text_lines = ScratchAlloc!(string)(lines); + item.text_lines = ScratchAlloc!(string)(lines); u64 start; for(u64 i = 0; i < lines; i += 1) @@ -744,14 +758,13 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T) } } - f32 scroll_speed = 1 - pow(2, (-60.0f * g_delta)); static foreach(axis; A2D.min .. A2D.max) { if(item.flags & (UIF.ScrollX << 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]); + f32 v = ctx.scroll_rate * (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) @@ -767,65 +780,113 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T) } } + if(item.flags & UIF.AnimateHot) + { + bool is_hot = cast(bool)(item.flags & UIF.SetHot) || Hovered!(true)(item); + item.hot_t += ctx.animation_rate * (cast(f32)(is_hot) - item.hot_t); + item.bg_col = Mix(item.bg_col, BG_HL_COL, item.hot_t); + } + + if(item.flags & UIF.AnimateReady) + { + item.ready_t += ctx.animation_rate * (1.0 - item.ready_t); + item.bg_col.a *= item.ready_t; + item.bg_col_end.a *= item.ready_t; + item.text_col.a *= item.ready_t; + item.border_col.a *= item.ready_t; + } + if(item.flags & UIF.DrawBorder && item.border_thickness < 0.0009) { item.border_thickness = 1.0; } - item.last_frame = ctx.frame; - item.rendered = false; + item.last_frame = ctx.frame; + item.rendered = false; return item; } +bool +Hovered(bool current_frame, T)(T param) +{ + UICtx* ctx = GetCtx(); + bool result; + + static if(is(T == UIKey)) + { + UIKey key = param; + } + else static if(is(T == UIItem*)) + { + UIKey key = param.key; + } + + if(!ZeroKey(ctx.hover_key)) + { + static if(current_frame) + { + result = key.hash == ctx.hover_key.hash && ctx.last_hover_frame == ctx.frame; + } + else + { + result = key.hash == ctx.hover_key_hash; + } + } + + return result; +} + void Signal(UIItem* item) { UICtx* ctx = GetCtx(); - item.signal = UIS.None; - bool mouse_over = InBounds(ctx.mouse_pos, &item.rect); - - if(mouse_over) + if(item.signal != UIS.None) { - item.signal |= UIS.Hovered; - } + bool mouse_over = InBounds(ctx.mouse_pos, &item.rect); - 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)) + if(mouse_over) { - item.signal |= UIS.Clicked; - taken = true; - } - - if(Nil(ctx.drag_item) && item.flags & UIF.Draggable && i.type == UIE.DragStart && InBounds(i.pos, &item.rect)) - { - item.signal |= UIS.Dragged; - ctx.drag_item = item; - taken = true; + item.signal |= UIS.Hovered; } - if(ctx.drag_item == item && i.type == UIE.Drag) - { - item.signal |= UIS.Dragged; - item.dragged += i.rel; - taken = true; - } + item.dragged = 0; - if(ctx.drag_item == item && i.type == UIE.DragRelease) + for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i); i = i.next) { - ctx.drag_item = g_UI_NIL; - taken = true; - } + bool taken; - if(taken) - { - DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); + if(item.flags & UIF.Clickable && i.type == UIE.Click && InBounds(ctx.mouse_pos, &item.rect)) + { + item.signal |= UIS.Clicked; + taken = true; + } + + if(ZeroKey(ctx.drag_key) && item.flags & UIF.Draggable && i.type == UIE.DragStart && InBounds(i.pos, &item.rect)) + { + item.signal |= UIS.Dragged; + ctx.drag_key = item.key; + taken = true; + } + + if(KeyEq(ctx.drag_key, item.key) && i.type == UIE.Drag) + { + item.signal |= UIS.Dragged; + item.dragged += i.rel; + taken = true; + } + + if(KeyEq(ctx.drag_key, item.key) && i.type == UIE.DragRelease) + { + ctx.drag_key = ZeroKey(); + taken = true; + } + + if(taken) + { + DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); + } } } } @@ -868,8 +929,9 @@ BeginUI(Inputs* inputs) // Convert Inputs ctx.events.first = ctx.events.last = null; - static bool dragging; - static bool mouse_down; + bool mouse_moved; + static bool dragging; + static bool mouse_down; for(InputEvent* i = inputs.first; i; i = i.next) { switch(i.key) @@ -889,6 +951,7 @@ BeginUI(Inputs* inputs) } break; case Input.MouseMotion: { + mouse_moved = true; ctx.mouse_pos = IVec2(i.x, i.y); if(!dragging && mouse_down) @@ -920,6 +983,41 @@ BeginUI(Inputs* inputs) } } + u64 next_frame = ctx.frame+1; + + // Check current mouse target + if(mouse_moved && !Nil(ctx.root_last)) + { + UIItem* last = ctx.root_last; + while(!Nil(last.last)) + { + last = last.last; + } + + UIKey hovered = ZeroKey(); + for(UIItem* item = last; !Nil(item); item = Recurse!(false)(item, g_UI_NIL)) + { + Logf("key %s pos %s rect %s %s", item.key.hash_text, ctx.mouse_pos.v, item.rect.p0.v, item.rect.p1.v); + if(InBounds(ctx.mouse_pos, &item.rect) && !ZeroKey(item.key)) + { + hovered = item.key; + break; + } + } + + if(!ZeroKey(hovered)) + { + ctx.last_hover_frame = next_frame; + ctx.hover_key = hovered; + } + else + { + ctx.hover_key = ZeroKey(); + } + } + + ctx.root_first = ctx.root_last = g_UI_NIL; + // Clean up items KVPair!(UIHash, UIItem*)*[] items = GetAllNodes(&ctx.temp_arena, &ctx.items); foreach(i; 0 .. items.length) @@ -944,10 +1042,12 @@ BeginUI(Inputs* inputs) // Ctx state ctx.transient_items = g_UI_NIL; - ctx.frame += 1; + ctx.frame = next_frame; ctx.f_idx = ctx.frame%FRAME_OVERLAP; ctx.inputs = inputs; + assert(!mouse_moved || ZeroKey(ctx.hover_key) || (ctx.frame == ctx.last_hover_frame)); + ResetStacks(ctx); version(ENABLE_RENDERER) @@ -973,14 +1073,12 @@ BeginUI(Inputs* inputs) ctx.buffers[ctx.f_idx].count = 0; ctx.buffers[ctx.f_idx].vtx_offset = 0; - // Root Item - UISize[2] sizes = [UISize(ST.Pixels, ctx.res.x), UISize(ST.Pixels, ctx.res.y)]; - Push!("size_info")(sizes); - - ctx.root = MakeItem("###root"); + ctx.animation_rate = 1.0 - pow(2.0, (-30.0f * g_delta)); + ctx.scroll_rate = 1.0 - pow(2.0, (-60.0f * g_delta)); - Pop!("size_info"); - Push!("parent")(ctx.root); + // Root Item + Push!("size_info")(UIS2(ST.Pixels, ST.Pixels, ctx.res.x, ctx.res.y), true); + MakeItem("###root"); } void @@ -989,7 +1087,7 @@ EndUI() UICtx* ctx = GetCtx(); // Automatic signals - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, g_UI_NIL)) + for(UIItem* item = ctx.root_first; !Nil(item); item = Recurse!(false)(item, g_UI_NIL)) { if(item.flags & AUTO_FLAGS) { @@ -1026,7 +1124,7 @@ EndUI() static foreach(axis; A2D.min .. A2D.max) { // Pixel sizes - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) + for(UIItem* item = ctx.root_first; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) { if(item.size_info[axis].type == ST.Pixels) { @@ -1040,7 +1138,7 @@ EndUI() } // Percentage sizes - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) + for(UIItem* item = ctx.root_first; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) { if(item.size_info[axis].type == ST.Percentage) { @@ -1061,7 +1159,7 @@ EndUI() } // Sum of children sizes - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, g_UI_NIL)) + for(UIItem* item = ctx.root_first; !Nil(item); item = Recurse!(false)(item, g_UI_NIL)) { if(item.size_info[axis].type == ST.ChildrenSum) { @@ -1083,7 +1181,7 @@ EndUI() } // Violations - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) + for(UIItem* item = ctx.root_first; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) { f32 size = InnerSize!(axis)(item); @@ -1155,7 +1253,7 @@ EndUI() // Calculate final sizes { f32 pos = 0.0; - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) + for(UIItem* item = ctx.root_first; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) { item.last_relative_item = g_UI_NIL; @@ -1196,7 +1294,10 @@ EndUI() } // Render Items - RenderItems(ctx.root); + for(UIItem* item = ctx.root_first; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) + { + RenderItem(ctx, item); + } version(ENABLE_RENDERER) { @@ -1206,13 +1307,13 @@ EndUI() SubmitAndPresent(&ctx.rd); } - if(!Nil(ctx.drag_item)) + if(!ZeroKey(ctx.drag_key)) { for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i); i = i.next) { if(i.type == UIE.DragRelease) { - ctx.drag_item = g_UI_NIL; + ctx.drag_key = ZeroKey(); break; } } @@ -1377,16 +1478,6 @@ RenderItem(UICtx* ctx, UIItem* item) } } -void -RenderItems(UIItem* root) -{ - UICtx* ctx = GetCtx(); - for(UIItem* item = root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) - { - RenderItem(ctx, item); - } -} - u32[2] GetExtent() { @@ -1405,104 +1496,6 @@ StackIDs(string stack, string ctx = "ctx") }; } -void -PushSizeInfo(SizeType type0, f32 value0, f32 strictness0, SizeType type1, f32 value1, f32 strictness1, bool auto_pop = false) -{ - UISize[2] size_info = [ - UISize(type0, value0, strictness0), - UISize(type1, value1, strictness1), - ]; - - Push!("size_info")(size_info, auto_pop); -} - -void -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 -PushCornerRadius(f32 v, bool auto_pop) -{ - Vec4 value = v; - Push!("corner_radius")(value, 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 -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); -} - static string PushScope(string stack, string value)() { @@ -1824,6 +1817,12 @@ HexCol(u64 col) ); } +bool +KeyEq(UIKey k0, UIKey k1) +{ + return k0.hash == k1.hash; +} + static UIKey ZeroKey() { @@ -2088,6 +2087,68 @@ Vec2A2(f32 x0, f32 y0, f32 x1, f32 y1) return [Vec2(x0, y0), Vec2(x1, y1)]; } +static f32 +SRGB(f32 v) +{ + return v < 0.0031308 ? v * 12.92 : pow(v, 0.41666) * 1.055 - 0.055; +} + +static Vec4 +LinearToSRGB(Vec4 v) +{ + return Vec4(SRGB(v.v[0]), SRGB(v.v[1]), SRGB(v.v[2]), v.v[3]); +} + +static Vec4 +SRGBVec4(f32 v) +{ + return LinearToSRGB(Vec4(v)); +} + +static Vec4 +SRGBVec4(f32 r, f32 g, f32 b, f32 a) +{ + return LinearToSRGB(Vec4(r, g, b, a)); +} + +static Vec4 +SRGBToOklab(Vec4 v) +{ + f32 l = 0.4122214708f * v.r + 0.5363325363f * v.g + 0.0514459929f * v.b; + f32 m = 0.2119034982f * v.r + 0.6806995451f * v.g + 0.1073969566f * v.b; + f32 s = 0.0883024619f * v.r + 0.2817188376f * v.g + 0.6299787005f * v.b; + + f32 l_ = cbrtf(l); + f32 m_ = cbrtf(m); + f32 s_ = cbrtf(s); + + return Vec4( + 0.2104542553f*l_ + 0.7936177850f*m_ - 0.0040720468f*s_, + 1.9779984951f*l_ - 2.4285922050f*m_ + 0.4505937099f*s_, + 0.0259040371f*l_ + 0.7827717662f*m_ - 0.8086757660f*s_, + v.a + ); +} + +static Vec4 +OklabToSRGB(Vec4 v) +{ + f32 l_ = v.r + 0.3963377774f * v.g + 0.2158037573f * v.b; + f32 m_ = v.r - 0.1055613458f * v.g - 0.0638541728f * v.b; + f32 s_ = v.r - 0.0894841775f * v.g - 1.2914855480f * v.b; + + float l = l_*l_*l_; + float m = m_*m_*m_; + float s = s_*s_*s_; + + return Vec4( + +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s, + -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s, + -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s, + v.a + ); +} + unittest { { // UI Key diff --git a/src/editor/views.d b/src/editor/views.d index 9c99bf4..afac79f 100644 --- a/src/editor/views.d +++ b/src/editor/views.d @@ -259,7 +259,7 @@ CommandPalette(CmdPalette* cmd) ]; mixin(PushOnce!(cmd_params)); - UIItem* cmd_item = MakeItem("###cmd_palette", UIF.Window|UIF.FixedPosition|UIF.DrawBackground|UIF.DrawBorder); + UIItem* cmd_item = MakeItem("###cmd_palette", UIF.Window|UIF.FixedPosition|UIF.DrawBackground|UIF.DrawBorder|UIF.AnimateReady); f32 padding_y = 4.0; @@ -296,11 +296,20 @@ CommandPalette(CmdPalette* cmd) Vec4(0.35, 0.35, 0.35, 1.0), ]; + // Active should be highlights around the item like File Pilot + for(u64 i = 0; i < cmd.opt_strs.length && i < max_opts; i += 1) { PushDisplayString(Str(cmd.opt_strs[i]), true); - PushBgCol(cmd.selected == i ? HL_BG_COL : opt_cols[i%2], true); - MakeItem(zero, UIF.DrawBackground|UIF.DrawText); + PushBgCol(opt_cols[i%2], true); + + UIKey k = MakeKey("###copt_%s", i); + if(Hovered!(true)(k)) + { + cmd.selected = i; + } + + MakeItem(k, UIF.DrawBackground|UIF.DrawText|UIF.AnimateHot|(cmd.selected == i ? UIF.SetHot : UIF.None)); } }