more work

This commit is contained in:
Matthew 2025-12-31 20:35:56 +11:00
parent 80b454914a
commit f4f311a49c
3 changed files with 263 additions and 193 deletions

@ -1 +1 @@
Subproject commit 1160d747ccb550b72a60cd68aa22a14f6dcb1ab1
Subproject commit 10c7fdcaedfdd98af47c640e00f1fad8705f476d

View File

@ -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,7 +717,6 @@ 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);
@ -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,6 +780,22 @@ 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;
@ -778,12 +807,43 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T)
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;
if(item.signal != UIS.None)
{
bool mouse_over = InBounds(ctx.mouse_pos, &item.rect);
if(mouse_over)
@ -803,23 +863,23 @@ Signal(UIItem* item)
taken = true;
}
if(Nil(ctx.drag_item) && item.flags & UIF.Draggable && i.type == UIE.DragStart && InBounds(i.pos, &item.rect))
if(ZeroKey(ctx.drag_key) && item.flags & UIF.Draggable && i.type == UIE.DragStart && InBounds(i.pos, &item.rect))
{
item.signal |= UIS.Dragged;
ctx.drag_item = item;
ctx.drag_key = item.key;
taken = true;
}
if(ctx.drag_item == item && i.type == UIE.Drag)
if(KeyEq(ctx.drag_key, item.key) && i.type == UIE.Drag)
{
item.signal |= UIS.Dragged;
item.dragged += i.rel;
taken = true;
}
if(ctx.drag_item == item && i.type == UIE.DragRelease)
if(KeyEq(ctx.drag_key, item.key) && i.type == UIE.DragRelease)
{
ctx.drag_item = g_UI_NIL;
ctx.drag_key = ZeroKey();
taken = true;
}
@ -829,6 +889,7 @@ Signal(UIItem* item)
}
}
}
}
void
PushUIEvent(UICtx* ctx, UIInput input)
@ -868,6 +929,7 @@ BeginUI(Inputs* inputs)
// Convert Inputs
ctx.events.first = ctx.events.last = null;
bool mouse_moved;
static bool dragging;
static bool mouse_down;
for(InputEvent* i = inputs.first; i; i = i.next)
@ -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;
ctx.animation_rate = 1.0 - pow(2.0, (-30.0f * g_delta));
ctx.scroll_rate = 1.0 - pow(2.0, (-60.0f * g_delta));
// 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");
Pop!("size_info");
Push!("parent")(ctx.root);
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

View File

@ -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));
}
}