From d4bfdfcd4cf1c45edfd246841e638731f76eec62 Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 30 Mar 2026 21:19:09 -0500 Subject: [PATCH] ui signals wip --- src/ui/ui_core.c | 567 +++++++++++++++++++++++++++++++++++++++++------ src/ui/ui_core.h | 19 +- 2 files changed, 518 insertions(+), 68 deletions(-) diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index 834646e3..807268d5 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -528,6 +528,20 @@ void UI_SetRawTexture(UI_Key key, G_TextureRef tex, Rng2 uv) SllQueuePush(frame->first_cmd_node, frame->last_cmd_node, n); } +UI_ButtonState *UI_SignalButton(UI_Key key, Button button) +{ + UI_Frame *frame = UI_CurrentFrame(); + UI_CmdNode *n = PushStruct(frame->arena, UI_CmdNode); + n->cmd.kind = UI_CmdKind_Signal; + { + n->cmd.signal.key = key; + n->cmd.signal.button = button; + } + ++frame->cmds_count; + SllQueuePush(frame->first_cmd_node, frame->last_cmd_node, n); + return &n->cmd.signal.button_state; +} + //////////////////////////////////////////////////////////// //~ Report @@ -545,9 +559,16 @@ UI_BoxReports UI_ReportsFromKey(UI_Key key) //////////////////////////////////////////////////////////// //~ Begin frame + + + + + + + + UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) { - ////////////////////////////// //- Init persistent state @@ -613,7 +634,7 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) frame->frame_flags = frame_flags; ////////////////////////////// - //- Process controller events + //- Process controller events into signals frame->cursor_pos = prev_frame->cursor_pos; frame->drag_cursor_pos = prev_frame->drag_cursor_pos; @@ -676,15 +697,13 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) } for (u64 button_idx = 0; button_idx < countof(report->buttons); ++button_idx) { - UI_ButtonReport *br = &report->buttons[button_idx]; + UI_ButtonState *br = &report->buttons[button_idx]; b32 old_held = br->held; { ZeroStruct(br); } br->held = old_held; } - box->reports.old_tree_capture = box->reports.tree_capture; - box->reports.tree_capture = UI_NilKey; report->is_hot = 0; } @@ -810,75 +829,111 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) // hot_box->reports.tree_capture = hot_box->key; } - //- Update box reports + + + + + + //- Update reports from signals + for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next) { - f32 lower_target = TweakFloat("UI lower blend target", -0.05, -1, 0); - f32 upper_target = TweakFloat("UI upper blend target", 1.05, 1, 10); - for (u64 pre_index = UI.boxes_count; pre_index-- > 0;) + UI_Cmd cmd = cmd_node->cmd; + if (cmd.kind == UI_CmdKind_Signal) { - UI_Box *box = prev_frame->boxes_pre[pre_index]; - UI_BoxReport *report = &box->reports.draw; - UI_DebugBreak(box, UI_DebugBreakFlag_BuildReport); - - if (box == hot_box) + UI_Box *box = UI_BoxFromKey(cmd.signal.key); + if (box) { - report->is_hot = 1; - box->reports.tree_capture = box->key; - } - - if (!UI_MatchKey(box->reports.tree_capture, box->reports.old_tree_capture)) - { - for (u64 button_idx = 0; button_idx < countof(report->buttons); ++button_idx) + Button button = cmd.signal.button; + UI_ButtonState state = cmd.signal.button_state; + box->reports.draw.buttons[button] = state; + // Propagate button state upwards + for (UI_Box *parent = box->parent; parent; parent = parent->parent) { - UI_ButtonReport *button = &report->buttons[button_idx]; - if (button->held) + if (parent->desc.flags & UI_BoxFlag_CaptureThroughChildren) { - button->held = 0; - button->ups += 1; + parent->reports.draw.buttons[button] = state; } } } - - f32 target_exists = (box->last_build_tick >= (frame->tick - 1)) ? upper_target : lower_target; - f32 target_hovered = report->is_hovered ? Inf : lower_target; - f32 target_hot = report->is_hot ? Inf : lower_target; - f32 target_active = box == active_box ? Inf : lower_target; - f64 target_misc = box->desc.misc; - - // TODO: Configurable per-box blend rates - f32 exists_blend_rate = (30 * frame->dt); - f32 hot_blend_rate = (15 * frame->dt); - f32 active_blend_rate = (15 * frame->dt); - f32 hovered_blend_rate = (15 * frame->dt); - // f64 misc_blend_rate = (30 * frame->dt); - f64 misc_blend_rate = 1; - - report->exists = SaturateF32(LerpF32(report->exists, target_exists, exists_blend_rate)); - report->hot = SaturateF32(LerpF32(report->hot, target_hot, hot_blend_rate)); - report->active = SaturateF32(LerpF32(report->active, target_active, active_blend_rate)); - report->hovered = SaturateF32(LerpF32(report->hovered, target_hovered, hovered_blend_rate)); - report->misc = SaturateF32(LerpF32(report->misc, target_misc, misc_blend_rate)); - - report->screen_rect = box->screen_rect; - report->screen_anchor = box->screen_anchor; - if (mouse_downs > 0) - { - box->reports.drag = *report; - } - - // Propagate upwards captures to tree - if (box->parent && !UI_IsKeyNil(box->reports.tree_capture)) - { - box->parent->reports.tree_capture = box->reports.tree_capture; - UI_Box *captured_child = UI_BoxFromKey(box->reports.tree_capture); - if (box->parent->desc.flags & UI_BoxFlag_CaptureThroughChildren) - { - box->parent->reports.draw = captured_child->reports.draw; - } - } } } + + + + + + + + //- Update box reports from inputs + // { + // f32 lower_target = TweakFloat("UI lower blend target", -0.05, -1, 0); + // f32 upper_target = TweakFloat("UI upper blend target", 1.05, 1, 10); + // for (u64 pre_index = UI.boxes_count; pre_index-- > 0;) + // { + // UI_Box *box = prev_frame->boxes_pre[pre_index]; + // UI_BoxReport *report = &box->reports.draw; + // UI_DebugBreak(box, UI_DebugBreakFlag_BuildReport); + + // if (box == hot_box) + // { + // report->is_hot = 1; + // box->reports.tree_capture = box->key; + // } + + // // if (!UI_MatchKey(box->reports.tree_capture, box->reports.old_tree_capture)) + // // { + // // for (u64 button_idx = 0; button_idx < countof(report->buttons); ++button_idx) + // // { + // // UI_ButtonState *button = &report->buttons[button_idx]; + // // if (button->held) + // // { + // // button->held = 0; + // // button->ups += 1; + // // } + // // } + // // } + + // f32 target_exists = (box->last_build_tick >= (frame->tick - 1)) ? upper_target : lower_target; + // f32 target_hovered = report->is_hovered ? Inf : lower_target; + // f32 target_hot = report->is_hot ? Inf : lower_target; + // f32 target_active = box == active_box ? Inf : lower_target; + // f64 target_misc = box->desc.misc; + + // // TODO: Configurable per-box blend rates + // f32 exists_blend_rate = (30 * frame->dt); + // f32 hot_blend_rate = (15 * frame->dt); + // f32 active_blend_rate = (15 * frame->dt); + // f32 hovered_blend_rate = (15 * frame->dt); + // // f64 misc_blend_rate = (30 * frame->dt); + // f64 misc_blend_rate = 1; + + // report->exists = SaturateF32(LerpF32(report->exists, target_exists, exists_blend_rate)); + // report->hot = SaturateF32(LerpF32(report->hot, target_hot, hot_blend_rate)); + // report->active = SaturateF32(LerpF32(report->active, target_active, active_blend_rate)); + // report->hovered = SaturateF32(LerpF32(report->hovered, target_hovered, hovered_blend_rate)); + // report->misc = SaturateF32(LerpF32(report->misc, target_misc, misc_blend_rate)); + + // report->screen_rect = box->screen_rect; + // report->screen_anchor = box->screen_anchor; + // if (mouse_downs > 0) + // { + // box->reports.drag = *report; + // } + + // // Propagate upwards captures to tree + // if (box->parent && !UI_IsKeyNil(box->reports.tree_capture)) + // { + // box->parent->reports.tree_capture = box->reports.tree_capture; + // UI_Box *captured_child = UI_BoxFromKey(box->reports.tree_capture); + // if (box->parent->desc.flags & UI_BoxFlag_CaptureThroughChildren) + // { + // box->parent->reports.draw.captures = captured_child->reports.draw.captures; + // } + // } + // } + // } + if (mouse_downs > 0) { frame->drag_cursor_pos = frame->cursor_pos; @@ -902,6 +957,392 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) return frame; } + + + + + + + + + + + + + + + + + + + + + + + +// UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) +// { +// ////////////////////////////// +// //- Init persistent state + +// if (!UI.box_arena) +// { +// UI.box_arena = AcquireArena(Gibi(64)); +// UI.gpu_frame_arena = G_AcquireArena(); +// // Init frames +// for (u64 i = 0; i < countof(UI.frames); ++i) +// { +// UI_Frame *frame = &UI.frames[i]; +// frame->arena = AcquireArena(Gibi(64)); +// frame->rects_arena = AcquireArena(Gibi(64)); +// } +// // Init root box +// { +// UI_Box *box = PushStruct(UI.box_arena, UI_Box); +// box->key = UI_RootKey; +// box->gen = 1; +// UI.boxes_count += 1; +// UI_BoxBin *bin = &UI.box_bins[box->key.v % countof(UI.box_bins)]; +// bin->first = box; +// bin->last = box; +// UI.root_box = box; +// } +// } + +// ////////////////////////////// +// //- Begin frame + +// UI.cur_frame_tick += 1; +// UI_Frame *prev_frame = UI_PrevFrame(); +// UI_Frame *frame = UI_CurrentFrame(); + +// { +// Arena *old_arena = frame->arena; +// Arena *old_rects_arena = frame->rects_arena; +// ZeroStruct(frame); +// frame->arena = old_arena; +// frame->rects_arena = old_rects_arena; +// } +// frame->window_frame = WND_BeginFrame(G_Format_R16G16B16A16_Float, WND_BackbufferSizeMode_MatchMonitor); +// UI.cl = G_PrepareCommandList(G_QueueKind_Direct); +// ResetArena(frame->arena); +// ResetArena(frame->rects_arena); +// G_ResetArena(UI.cl, UI.gpu_frame_arena); + +// { +// i64 now_ns = TimeNs(); +// i64 dt_ns = now_ns - prev_frame->time_ns; +// frame->time_ns = now_ns; +// frame->dt_ns = dt_ns; +// frame->dt = SecondsFromNs(frame->dt_ns); +// frame->tick = UI.cur_frame_tick; +// } + +// // Init style stack +// { +// frame->top_stack = PushStruct(frame->arena, UI_Stack); +// UI_PushDefaults(); +// } + +// frame->frame_flags = frame_flags; + +// ////////////////////////////// +// //- Process controller events + +// frame->cursor_pos = prev_frame->cursor_pos; +// frame->drag_cursor_pos = prev_frame->drag_cursor_pos; + +// if (prev_frame->boxes_pre != 0) +// { +// ControllerEventsArray controller_events = frame->window_frame.controller_events; + +// //- Locate boxes +// UI_Box *top_hovered_box = 0; +// UI_Box *active_box = UI_BoxFromKey(prev_frame->active_box); + +// //- Update cursor pos +// for (u64 cev_index = 0; cev_index < controller_events.count; ++cev_index) +// { +// ControllerEvent *cev = &controller_events.events[cev_index]; +// if (cev->kind == ControllerEventKind_CursorMove) +// { +// frame->cursor_pos = Vec2FromVec(cev->cursor_pos); +// } +// } + +// //- Init box reports +// // TODO: Iterate in post order and early out +// for (u64 pre_index = UI.boxes_count; pre_index-- > 0;) +// { +// UI_Box *box = prev_frame->boxes_pre[pre_index]; +// UI_BoxReport *report = &box->reports.draw; +// b32 is_cursor_in_box = 0; +// { +// // TODO: More efficient test. This logic is just copied from the renderer's SDF function for now. +// Rng2 interactable_region = IntersectRng2(box->solved_scissor, box->screen_rect); +// Vec2 p0 = interactable_region.p0; +// Vec2 p1 = interactable_region.p1; +// Vec2 point = frame->cursor_pos; +// b32 is_corner = 0; +// f32 non_corner_edge_dist = MinF32(MinF32(point.x - p0.x, p1.x - point.x), MinF32(point.y - p0.y, p1.y - point.y)); +// f32 corner_edge_dist = non_corner_edge_dist; +// if (non_corner_edge_dist >= 0) +// { +// f32 tl_radius = box->rounding_tl; +// f32 tr_radius = box->rounding_tr; +// f32 br_radius = box->rounding_br; +// f32 bl_radius = box->rounding_bl; +// Vec2 tl = VEC2(p0.x + tl_radius, p0.y + tl_radius); +// Vec2 tr = VEC2(p1.x - tr_radius, p0.y + tr_radius); +// Vec2 br = VEC2(p1.x - br_radius, p1.y - br_radius); +// Vec2 bl = VEC2(p0.x + bl_radius, p1.y - bl_radius); +// if (point.x < tl.x && point.y < tl.y) corner_edge_dist = MinF32(corner_edge_dist, tl_radius - Vec2Len(SubVec2(tl, point))); +// if (point.x > tr.x && point.y < tr.y) corner_edge_dist = MinF32(corner_edge_dist, tr_radius - Vec2Len(SubVec2(tr, point))); +// if (point.x > br.x && point.y > br.y) corner_edge_dist = MinF32(corner_edge_dist, br_radius - Vec2Len(SubVec2(br, point))); +// if (point.x < bl.x && point.y > bl.y) corner_edge_dist = MinF32(corner_edge_dist, bl_radius - Vec2Len(SubVec2(bl, point))); +// } +// is_cursor_in_box = non_corner_edge_dist >= 0 && corner_edge_dist >= 0; +// } +// report->is_hovered = is_cursor_in_box; +// if (top_hovered_box == 0 && (box->desc.flags & UI_BoxFlag_CaptureMouse) && is_cursor_in_box) +// { +// top_hovered_box = box; +// } +// for (u64 button_idx = 0; button_idx < countof(report->buttons); ++button_idx) +// { +// UI_ButtonState *br = &report->buttons[button_idx]; +// b32 old_held = br->held; +// { +// ZeroStruct(br); +// } +// br->held = old_held; +// } +// box->reports.old_tree_capture = box->reports.tree_capture; +// box->reports.tree_capture = UI_NilKey; +// report->is_hot = 0; +// } + +// //- Update state from controller events +// i32 mouse_downs = 0; +// for (u64 cev_index = 0; cev_index < controller_events.count; ++cev_index) +// { +// ControllerEvent *cev = &controller_events.events[cev_index]; +// switch (cev->kind) +// { +// default: break; + +// case ControllerEventKind_ButtonDown: +// { +// if (IsClickButton(cev->button) || IsWheelButton(cev->button)) +// { +// mouse_downs += 1; +// if (top_hovered_box && active_box == 0) +// { +// if (cev->button == Button_M1) +// { +// ++top_hovered_box->reports.draw.buttons[Button_M1].downs; +// top_hovered_box->reports.draw.buttons[Button_M1].held = 1; +// active_box = top_hovered_box; +// } +// else if (cev->button == Button_M2) +// { +// ++top_hovered_box->reports.draw.buttons[Button_M2].downs; +// top_hovered_box->reports.draw.buttons[Button_M2].held = 1; +// active_box = top_hovered_box; +// } +// else if (cev->button == Button_M3) +// { +// ++top_hovered_box->reports.draw.buttons[Button_M3].downs; +// top_hovered_box->reports.draw.buttons[Button_M3].held = 1; +// active_box = top_hovered_box; +// } +// else if (cev->button == Button_WheelUp) +// { +// ++top_hovered_box->reports.draw.buttons[Button_WheelUp].downs; +// ++top_hovered_box->reports.draw.buttons[Button_WheelUp].ups; +// ++top_hovered_box->reports.draw.buttons[Button_WheelUp].presses; +// } +// else if (cev->button == Button_WheelDown) +// { +// ++top_hovered_box->reports.draw.buttons[Button_WheelDown].downs; +// ++top_hovered_box->reports.draw.buttons[Button_WheelDown].ups; +// ++top_hovered_box->reports.draw.buttons[Button_WheelDown].presses; +// } +// cev->captures += 1; +// } +// } +// } break; + +// case ControllerEventKind_ButtonUp: +// { +// if (IsClickButton(cev->button)) +// { +// if (active_box) +// { +// if (cev->button == Button_M1) +// { +// if (active_box == top_hovered_box) +// { +// ++active_box->reports.draw.buttons[Button_M1].presses; +// } +// ++active_box->reports.draw.buttons[Button_M1].ups; +// if (active_box->reports.draw.buttons[Button_M1].held) +// { +// active_box->reports.draw.buttons[Button_M1].held = 0; +// active_box = 0; +// } +// } +// else if (cev->button == Button_M2) +// { +// if (active_box == top_hovered_box) +// { +// ++active_box->reports.draw.buttons[Button_M2].presses; +// } +// ++active_box->reports.draw.buttons[Button_M2].ups; +// if (active_box->reports.draw.buttons[Button_M2].held) +// { +// active_box->reports.draw.buttons[Button_M2].held = 0; +// active_box = 0; +// } +// } +// else if (cev->button == Button_M3) +// { +// if (active_box == top_hovered_box) +// { +// ++active_box->reports.draw.buttons[Button_M3].presses; +// } +// ++active_box->reports.draw.buttons[Button_M3].ups; +// if (active_box->reports.draw.buttons[Button_M3].held) +// { +// active_box->reports.draw.buttons[Button_M3].held = 0; +// active_box = 0; +// } +// } +// } +// } +// else if (IsWheelButton(cev->button) && top_hovered_box) +// { +// cev->captures += 1; +// } +// } break; + +// case ControllerEventKind_Quit: +// { +// SignalExit(0); +// } break; +// } +// } + +// UI_Box *hot_box = active_box; +// if (top_hovered_box && !active_box) +// { +// hot_box = top_hovered_box; +// } + +// if (hot_box) +// { +// // hot_box->reports.tree_capture = hot_box->key; +// } + +// //- Update box reports from captures +// { +// f32 lower_target = TweakFloat("UI lower blend target", -0.05, -1, 0); +// f32 upper_target = TweakFloat("UI upper blend target", 1.05, 1, 10); +// for (u64 pre_index = UI.boxes_count; pre_index-- > 0;) +// { +// UI_Box *box = prev_frame->boxes_pre[pre_index]; +// UI_BoxReport *report = &box->reports.draw; +// UI_DebugBreak(box, UI_DebugBreakFlag_BuildReport); + +// if (box == hot_box) +// { +// report->is_hot = 1; +// box->reports.tree_capture = box->key; +// } + +// // if (!UI_MatchKey(box->reports.tree_capture, box->reports.old_tree_capture)) +// // { +// // for (u64 button_idx = 0; button_idx < countof(report->buttons); ++button_idx) +// // { +// // UI_ButtonState *button = &report->buttons[button_idx]; +// // if (button->held) +// // { +// // button->held = 0; +// // button->ups += 1; +// // } +// // } +// // } + +// f32 target_exists = (box->last_build_tick >= (frame->tick - 1)) ? upper_target : lower_target; +// f32 target_hovered = report->is_hovered ? Inf : lower_target; +// f32 target_hot = report->is_hot ? Inf : lower_target; +// f32 target_active = box == active_box ? Inf : lower_target; +// f64 target_misc = box->desc.misc; + +// // TODO: Configurable per-box blend rates +// f32 exists_blend_rate = (30 * frame->dt); +// f32 hot_blend_rate = (15 * frame->dt); +// f32 active_blend_rate = (15 * frame->dt); +// f32 hovered_blend_rate = (15 * frame->dt); +// // f64 misc_blend_rate = (30 * frame->dt); +// f64 misc_blend_rate = 1; + +// report->exists = SaturateF32(LerpF32(report->exists, target_exists, exists_blend_rate)); +// report->hot = SaturateF32(LerpF32(report->hot, target_hot, hot_blend_rate)); +// report->active = SaturateF32(LerpF32(report->active, target_active, active_blend_rate)); +// report->hovered = SaturateF32(LerpF32(report->hovered, target_hovered, hovered_blend_rate)); +// report->misc = SaturateF32(LerpF32(report->misc, target_misc, misc_blend_rate)); + +// report->screen_rect = box->screen_rect; +// report->screen_anchor = box->screen_anchor; +// if (mouse_downs > 0) +// { +// box->reports.drag = *report; +// } + +// // Propagate upwards captures to tree +// if (box->parent && !UI_IsKeyNil(box->reports.tree_capture)) +// { +// box->parent->reports.tree_capture = box->reports.tree_capture; +// UI_Box *captured_child = UI_BoxFromKey(box->reports.tree_capture); +// if (box->parent->desc.flags & UI_BoxFlag_CaptureThroughChildren) +// { +// box->parent->reports.draw.captures = captured_child->reports.draw.captures; +// } +// } +// } +// } + +// if (mouse_downs > 0) +// { +// frame->drag_cursor_pos = frame->cursor_pos; +// } + +// frame->top_hovered_box = top_hovered_box ? top_hovered_box->key : UI_NilKey; +// frame->hot_box = hot_box ? hot_box->key : UI_NilKey; +// frame->active_box = active_box ? active_box->key : UI_NilKey; +// } + +// ////////////////////////////// +// //- Build root box + +// { +// UI_SetNext(Width, UI_PIX(frame->window_frame.draw_size.x, 1)); +// UI_SetNext(Height, UI_PIX(frame->window_frame.draw_size.y, 1)); +// UI_SetNext(Parent, UI_NilKey); +// UI_BuildBoxEx(UI_RootKey); +// } + +// return frame; +// } + + + + + + + + //////////////////////////////////////////////////////////// //~ Frame helpers diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index bf17718f..772aae0e 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -209,9 +209,9 @@ Struct(UI_Stack) }; //////////////////////////////////////////////////////////// -//~ Report types +//~ Input types -Struct(UI_ButtonReport) +Struct(UI_ButtonState) { b32 held; // Persistent i32 downs; // Mouse button down events over box causing activation @@ -219,11 +219,14 @@ Struct(UI_ButtonReport) i32 presses; // Mouse button events while box is active and hovered }; +//////////////////////////////////////////////////////////// +//~ Report types + Struct(UI_CapturesReport) { b32 is_hovered; b32 is_hot; - UI_ButtonReport buttons[Button_COUNT]; + UI_ButtonState buttons[Button_COUNT]; }; Struct(UI_BoxReport) @@ -242,8 +245,6 @@ Struct(UI_BoxReports) { UI_BoxReport draw; // Box data used for last render UI_BoxReport drag; // Box data during last mouse button down event - UI_Key tree_capture; // Key of captured child (if any) - UI_Key old_tree_capture; }; //////////////////////////////////////////////////////////// @@ -253,6 +254,7 @@ Enum(UI_CmdKind) { UI_CmdKind_None, UI_CmdKind_BuildBox, + UI_CmdKind_Signal, UI_CmdKind_SetRawTexture, }; @@ -297,6 +299,12 @@ Struct(UI_Cmd) { UI_BoxDesc box; struct + { + UI_Key key; + Button button; + UI_ButtonState button_state; + } signal; + struct { UI_Key key; G_TextureRef tex; @@ -544,6 +552,7 @@ UI_Key UI_BuildBoxEx(UI_Key semantic_key); #define UI_BuildBox() UI_BuildBoxEx(UI_NilKey) void UI_SetRawTexture(UI_Key key, G_TextureRef tex, Rng2 uv); +UI_ButtonState *UI_SignalButton(UI_Key key, Button button); #if IsRtcEnabled #define UI_DebugBreak(box, target_flags) do { if (box->desc.debug_break_flags & target_flags) { DEBUGBREAK; } } while (0)