ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

Commit: c964dae8a3d69ba3ec027706bb92fea46906048b
Parent: f14dab12d92fc9cd8a2089cd2860ab0b135710ba
Author: Randy Palamar
Date:   Wed, 11 Jun 2025 07:37:40 -0600

ui: begin interaction cleanup

introduce FloatingWidget concept and use it to clean up menu handling

Diffstat:
Mui.c | 979+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mutil.h | 2+-
2 files changed, 555 insertions(+), 426 deletions(-)

diff --git a/ui.c b/ui.c @@ -2,19 +2,9 @@ /* TODO(rnp): * [ ]: refactor: ui should be in its own thread and that thread should only be concerned with the ui * [ ]: refactor: ui shouldn't fully destroy itself on hot reload - * [ ]: refactor: remove all the excessive measure_texts (cell drawing, hover_var in params table) + * [ ]: refactor: remove all the excessive measure_texts (cell drawing, hover_interaction in params table) * [ ]: refactor: move remaining fragment shader stuff into ui * [ ]: refactor: scale table to rect - * [ ]: refactor: re-add next_hot variable. this will simplify the code and number of checks - * being performed inline. example: - * if (hovering) - * next_hot = var; - * draw_text(..., var->hover_t); - * // elsewhere in code - * if (!is->active) - * hot = next_hot .... - * - * hot->hover_t += hover_speed * dt_for_frame * [ ]: scroll bar for views that don't have enough space * [ ]: compute times through same path as parameter list ? * [ ]: allow views to collapse to just their title bar @@ -35,9 +25,7 @@ * [ ]: show full non-truncated string on hover * [ ]: refactor: hovered element type and show hovered element in full even when truncated * [ ]: visual indicator for broken shader stage gh#27 - * [ ]: V_UP_HIERARCHY, V_DOWN_HIERARCHY - set active interaction to parent or child ? * [ ]: bug: cross-plane view with different dimensions for each plane - * [ ]: interaction last_rect is weird; need a better way of keeping track of menu position */ #define BG_COLOUR (v4){.r = 0.15, .g = 0.12, .b = 0.13, .a = 1.0} @@ -77,19 +65,10 @@ typedef struct { i32 cursor; f32 cursor_blink_t; f32 cursor_blink_scale; + Font *font, *hot_font; } InputState; typedef enum { - IT_NONE, - IT_NOP, - IT_DRAG, - IT_MENU, - IT_SCROLL, - IT_SET, - IT_TEXT, -} InteractionType; - -typedef enum { RS_NONE, RS_START, RS_HOLD, @@ -160,8 +139,9 @@ typedef enum { VT_COMPUTE_PROGRESS_BAR, VT_SCALE_BAR, VT_UI_BUTTON, - VT_UI_VIEW, + VT_UI_FLOATING_WIDGET, VT_UI_REGION_SPLIT, + VT_UI_VIEW, } VariableType; typedef enum { @@ -177,6 +157,7 @@ typedef struct { Variable *last; b32 expanded; VariableGroupType type; + Variable *container; } VariableGroup; typedef enum { @@ -204,6 +185,7 @@ typedef struct { #define X(id, text) UI_BID_ ##id, typedef enum { UI_BID_CLOSE_VIEW, + UI_BID_CLOSE_WIDGET, GLOBAL_MENU_BUTTONS FRAME_VIEW_BUTTONS } UIButtonID; @@ -225,11 +207,22 @@ typedef struct { } BeamformerVariable; typedef enum { + FloatingWidgetKind_Menu, + FloatingWidgetKind_TextBox, +} FloatingWidgetKind; + +/* TODO(rnp): collapse with UIView? */ +typedef struct { + Variable *reference; + Variable *close; + Rect rect; + FloatingWidgetKind kind; +} FloatingWidget; + +typedef enum { V_INPUT = 1 << 0, V_TEXT = 1 << 1, V_RADIO_BUTTON = 1 << 2, - V_MENU = 1 << 3, - V_CLOSES_MENU = 1 << 4, V_CAUSES_COMPUTE = 1 << 29, V_UPDATE_VIEW = 1 << 30, } VariableFlags; @@ -241,18 +234,19 @@ struct Variable { BeamformerVariable beamformer_variable; ComputeProgressBar compute_progress_bar; ComputeStatsView compute_stats_view; + FloatingWidget floating_widget; RegionSplit region_split; ScaleBar scale_bar; UIButtonID button; UIView view; VariableCycler cycler; VariableGroup group; - scaled_f32 scaled_f32; - b32 b32; - i32 i32; - u32 u32; - f32 f32; - } u; + scaled_f32 scaled_real32; + b32 bool32; + i32 signed32; + u32 unsigned32; + f32 real32; + }; Variable *next; Variable *parent; VariableFlags flags; @@ -303,13 +297,28 @@ typedef struct BeamformerFrameView { b32 needs_update; } BeamformerFrameView; +typedef enum { + InteractionKind_None, + InteractionKind_Nop, + InteractionKind_Auto, + InteractionKind_Button, + InteractionKind_Drag, + InteractionKind_Menu, + InteractionKind_Scroll, + InteractionKind_Set, + InteractionKind_Text, +} InteractionKind; + typedef struct { - Variable *hot; - Variable *active; - InteractionType type; - Rect rect, hot_rect, last_rect; - Font *font, *hot_font; -} InteractionState; + InteractionKind kind; + union { + void *generic; + Variable *var; + }; + Rect rect; +} Interaction; + +#define auto_interaction(r, v) (Interaction){.kind = InteractionKind_Auto, .var = v, .rect = r} struct BeamformerUI { Arena arena; @@ -320,12 +329,17 @@ struct BeamformerUI { Variable *regions; Variable *variable_freelist; + Variable floating_widget_sentinal; + BeamformerFrameView *views; BeamformerFrameView *view_freelist; BeamformFrame *frame_freelist; - InteractionState interaction; - InputState text_input_state; + Interaction interaction; + Interaction hot_interaction; + Interaction next_interaction; + + InputState text_input_state; v2_sll *scale_bar_savepoint_freelist; @@ -497,23 +511,23 @@ stream_append_variable(Stream *s, Variable *var) { switch (var->type) { case VT_UI_BUTTON: - case VT_GROUP: stream_append_s8(s, var->name); break; - case VT_F32: stream_append_f64(s, var->u.f32, 100); break; - case VT_B32: stream_append_s8(s, var->u.b32 ? s8("True") : s8("False")); break; - case VT_SCALED_F32: stream_append_f64(s, var->u.scaled_f32.val, 100); break; - case VT_BEAMFORMER_VARIABLE: { - BeamformerVariable *bv = &var->u.beamformer_variable; + case VT_GROUP:{ stream_append_s8(s, var->name); }break; + case VT_F32:{ stream_append_f64(s, var->real32, 100); }break; + case VT_B32:{ stream_append_s8(s, var->bool32 ? s8("True") : s8("False")); }break; + case VT_SCALED_F32:{ stream_append_f64(s, var->scaled_real32.val, 100); }break; + case VT_BEAMFORMER_VARIABLE:{ + BeamformerVariable *bv = &var->beamformer_variable; switch (bv->store_type) { - case VT_F32: stream_append_f64(s, *(f32 *)bv->store * bv->display_scale, 100); break; - default: INVALID_CODE_PATH; + case VT_F32:{ stream_append_f64(s, *(f32 *)bv->store * bv->display_scale, 100); }break; + InvalidDefaultCase; } - } break; - case VT_CYCLER: { - u32 index = *var->u.cycler.state; - if (var->u.cycler.labels) stream_append_s8(s, var->u.cycler.labels[index]); - else stream_append_u64(s, index); - } break; - default: INVALID_CODE_PATH; + }break; + case VT_CYCLER:{ + u32 index = *var->cycler.state; + if (var->cycler.labels) stream_append_s8(s, var->cycler.labels[index]); + else stream_append_u64(s, index); + }break; + InvalidDefaultCase; } } @@ -733,21 +747,21 @@ ui_variable_free(BeamformerUI *ui, Variable *var) var->parent = 0; while (var) { if (var->type == VT_GROUP) { - var = var->u.group.first; + var = var->group.first; } else { if (var->type == VT_BEAMFORMER_FRAME_VIEW) { /* TODO(rnp): instead there should be a way of linking these up */ - BeamformerFrameView *bv = var->u.generic; + BeamformerFrameView *bv = var->generic; if (bv->type == FVT_COPY) { glDeleteTextures(1, &bv->frame->texture); bv->frame->texture = 0; SLLPush(bv->frame, ui->frame_freelist); } - if (bv->axial_scale_bar.u.scale_bar.savepoint_stack) - SLLPush(bv->axial_scale_bar.u.scale_bar.savepoint_stack, + if (bv->axial_scale_bar.scale_bar.savepoint_stack) + SLLPush(bv->axial_scale_bar.scale_bar.savepoint_stack, ui->scale_bar_savepoint_freelist); - if (bv->lateral_scale_bar.u.scale_bar.savepoint_stack) - SLLPush(bv->lateral_scale_bar.u.scale_bar.savepoint_stack, + if (bv->lateral_scale_bar.scale_bar.savepoint_stack) + SLLPush(bv->lateral_scale_bar.scale_bar.savepoint_stack, ui->scale_bar_savepoint_freelist); DLLRemove(bv); /* TODO(rnp): hack; use a sentinal */ @@ -774,10 +788,10 @@ ui_variable_free(BeamformerUI *ui, Variable *var) function void ui_view_free(BeamformerUI *ui, Variable *view) { - ASSERT(view->type == VT_UI_VIEW); - ui_variable_free(ui, view->u.view.child); - ui_variable_free(ui, view->u.view.close); - ui_variable_free(ui, view->u.view.menu); + assert(view->type == VT_UI_VIEW); + ui_variable_free(ui, view->view.child); + ui_variable_free(ui, view->view.close); + ui_variable_free(ui, view->view.menu); ui_variable_free(ui, view); } @@ -791,8 +805,8 @@ fill_variable(Variable *var, Variable *group, s8 name, u32 flags, VariableType t var->name_width = measure_text(font, name).x; if (group && group->type == VT_GROUP) { - if (group->u.group.last) group->u.group.last = group->u.group.last->next = var; - else group->u.group.last = group->u.group.first = var; + if (group->group.last) group->group.last = group->group.last->next = var; + else group->group.last = group->group.first = var; } return var; @@ -811,8 +825,8 @@ add_variable(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, u32 flags function Variable * add_variable_group(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, VariableGroupType type, Font font) { - Variable *result = add_variable(ui, group, arena, name, V_INPUT, VT_GROUP, font); - result->u.group.type = type; + Variable *result = add_variable(ui, group, arena, name, V_INPUT, VT_GROUP, font); + result->group.type = type; return result; } @@ -828,9 +842,9 @@ add_variable_cycler(BeamformerUI *ui, Variable *group, Arena *arena, u32 flags, u32 *store, s8 *labels, u32 cycle_count) { Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_CYCLER, font); - result->u.cycler.cycle_length = cycle_count; - result->u.cycler.state = store; - result->u.cycler.labels = labels; + result->cycler.cycle_length = cycle_count; + result->cycler.state = store; + result->cycler.labels = labels; return result; } @@ -839,7 +853,23 @@ add_button(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, UIButtonID u32 flags, Font font) { Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_UI_BUTTON, font); - result->u.button = id; + result->button = id; + return result; +} + +function Variable * +add_floating_widget(BeamformerUI *ui, Arena *arena, FloatingWidgetKind kind, v2 at, Variable *reference) +{ + Variable *result = add_variable(ui, 0, arena, s8(""), 0, VT_UI_FLOATING_WIDGET, ui->small_font); + result->floating_widget.rect.pos = at; + result->floating_widget.kind = kind; + result->floating_widget.reference = reference; + result->floating_widget.close = add_button(ui, result, arena, s8(""), UI_BID_CLOSE_WIDGET, + 0, ui->small_font); + result->parent = &ui->floating_widget_sentinal; + result->next = ui->floating_widget_sentinal.next; + result->next->parent = result; + ui->floating_widget_sentinal.next = result; return result; } @@ -848,8 +878,8 @@ add_ui_split(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, f32 frac RegionSplitDirection direction, Font font) { Variable *result = add_variable(ui, parent, arena, name, 0, VT_UI_REGION_SPLIT, font); - result->u.region_split.direction = direction; - result->u.region_split.fraction = fraction; + result->region_split.direction = direction; + result->region_split.fraction = fraction; return result; } @@ -858,9 +888,7 @@ add_global_menu(BeamformerUI *ui, Arena *arena, Variable *parent) { Variable *result = add_variable_group(ui, 0, &ui->arena, s8(""), VG_LIST, ui->small_font); result->parent = parent; - result->flags = V_MENU; - #define X(id, text) add_button(ui, result, &ui->arena, s8(text), UI_BID_ ##id, \ - V_CLOSES_MENU, ui->small_font); + #define X(id, text) add_button(ui, result, &ui->arena, s8(text), UI_BID_ ##id, 0, ui->small_font); GLOBAL_MENU_BUTTONS #undef X return result; @@ -870,7 +898,7 @@ function Variable * add_ui_view(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, u32 view_flags, b32 closable) { Variable *result = add_variable(ui, parent, arena, name, 0, VT_UI_VIEW, ui->small_font); - UIView *view = &result->u.view; + UIView *view = &result->view; view->flags = view_flags; view->menu = add_global_menu(ui, arena, result); if (closable) { @@ -887,7 +915,7 @@ add_beamformer_variable_f32(BeamformerUI *ui, Variable *group, Arena *arena, s8 Font font) { Variable *var = add_variable(ui, group, arena, name, flags, VT_BEAMFORMER_VARIABLE, font); - BeamformerVariable *bv = &var->u.beamformer_variable; + BeamformerVariable *bv = &var->beamformer_variable; bv->suffix = suffix; bv->store = store; bv->store_type = VT_F32; @@ -906,8 +934,8 @@ add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx) /* TODO(rnp): this can be closable once we have a way of opening new views */ Variable *result = add_ui_view(ui, parent, &ui->arena, s8("Parameters"), 0, 0); - Variable *group = result->u.view.child = add_variable(ui, result, &ui->arena, s8(""), 0, - VT_GROUP, ui->font); + Variable *group = result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, + VT_GROUP, ui->font); add_beamformer_variable_f32(ui, group, &ui->arena, s8("Sampling Frequency:"), s8("[MHz]"), &bp->sampling_frequency, (v2){0}, 1e-6, 0, 0, ui->font); @@ -971,16 +999,16 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, { /* TODO(rnp): this can be always closable once we have a way of opening new views */ Variable *result = add_ui_view(ui, parent, arena, s8(""), UI_VIEW_CUSTOM_TEXT, closable); - Variable *var = result->u.view.child = add_variable(ui, result, arena, s8(""), 0, - VT_BEAMFORMER_FRAME_VIEW, ui->small_font); + Variable *var = result->view.child = add_variable(ui, result, arena, s8(""), 0, + VT_BEAMFORMER_FRAME_VIEW, ui->small_font); BeamformerFrameView *bv = SLLPop(ui->view_freelist); if (bv) zero_struct(bv); else bv = push_struct(arena, typeof(*bv)); DLLPushDown(bv, ui->views); - var->u.generic = bv; - bv->type = type; + var->generic = bv; + bv->type = type; fill_variable(&bv->dynamic_range, var, s8("Dynamic Range:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, VT_F32, ui->small_font); @@ -989,15 +1017,15 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, fill_variable(&bv->gamma, var, s8("Gamma:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, VT_SCALED_F32, ui->small_font); - bv->dynamic_range.u.f32 = 50.0f; - bv->threshold.u.f32 = 55.0f; - bv->gamma.u.scaled_f32.val = 1.0f; - bv->gamma.u.scaled_f32.scale = 0.05f; + bv->dynamic_range.real32 = 50.0f; + bv->threshold.real32 = 55.0f; + bv->gamma.scaled_real32.val = 1.0f; + bv->gamma.scaled_real32.scale = 0.05f; fill_variable(&bv->lateral_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); fill_variable(&bv->axial_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); - ScaleBar *lateral = &bv->lateral_scale_bar.u.scale_bar; - ScaleBar *axial = &bv->axial_scale_bar.u.scale_bar; + ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; + ScaleBar *axial = &bv->axial_scale_bar.scale_bar; lateral->direction = SB_LATERAL; axial->direction = SB_AXIAL; lateral->scroll_scale = (v2){.x = -0.5e-3, .y = 0.5e-3}; @@ -1005,13 +1033,13 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, lateral->zoom_starting_coord = F32_INFINITY; axial->zoom_starting_coord = F32_INFINITY; - Variable *menu = result->u.view.menu; + Variable *menu = result->view.menu; /* TODO(rnp): push to head of list? */ - Variable *old_menu_first = menu->u.group.first; - Variable *old_menu_last = menu->u.group.last; - menu->u.group.first = menu->u.group.last = 0; + Variable *old_menu_first = menu->group.first; + Variable *old_menu_last = menu->group.last; + menu->group.first = menu->group.last = 0; - #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, V_CLOSES_MENU, ui->small_font); + #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); FRAME_VIEW_BUTTONS #undef X @@ -1039,8 +1067,8 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"), V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); - menu->u.group.last->next = old_menu_first; - menu->u.group.last = old_menu_last; + menu->group.last->next = old_menu_first; + menu->group.last = old_menu_last; return result; } @@ -1051,9 +1079,9 @@ add_compute_progress_bar(Variable *parent, BeamformerCtx *ctx) BeamformerUI *ui = ctx->ui; /* TODO(rnp): this can be closable once we have a way of opening new views */ Variable *result = add_ui_view(ui, parent, &ui->arena, s8(""), UI_VIEW_CUSTOM_TEXT, 0); - result->u.view.child = add_variable(ui, result, &ui->arena, s8(""), 0, - VT_COMPUTE_PROGRESS_BAR, ui->small_font); - ComputeProgressBar *bar = &result->u.view.child->u.compute_progress_bar; + result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, + VT_COMPUTE_PROGRESS_BAR, ui->small_font); + ComputeProgressBar *bar = &result->view.child->compute_progress_bar; bar->progress = &ctx->csctx.processing_progress; bar->processing = &ctx->csctx.processing_compute; @@ -1064,8 +1092,8 @@ function Variable * add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, VariableType type) { /* TODO(rnp): this can be closable once we have a way of opening new views */ - Variable *result = add_ui_view(ui, parent, arena, s8(""), UI_VIEW_CUSTOM_TEXT, 0); - result->u.view.child = add_variable(ui, result, &ui->arena, s8(""), 0, type, ui->small_font); + Variable *result = add_ui_view(ui, parent, arena, s8(""), UI_VIEW_CUSTOM_TEXT, 0); + result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, type, ui->small_font); return result; } @@ -1073,27 +1101,27 @@ function Variable * ui_split_region(BeamformerUI *ui, Variable *region, Variable *split_side, RegionSplitDirection direction) { Variable *result = add_ui_split(ui, region, &ui->arena, s8(""), 0.5, direction, ui->small_font); - if (split_side == region->u.region_split.left) { - region->u.region_split.left = result; + if (split_side == region->region_split.left) { + region->region_split.left = result; } else { - region->u.region_split.right = result; + region->region_split.right = result; } split_side->parent = result; - result->u.region_split.left = split_side; + result->region_split.left = split_side; return result; } function void ui_fill_live_frame_view(BeamformerUI *ui, BeamformerFrameView *bv) { - ScaleBar *lateral = &bv->lateral_scale_bar.u.scale_bar; - ScaleBar *axial = &bv->axial_scale_bar.u.scale_bar; + ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; + ScaleBar *axial = &bv->axial_scale_bar.scale_bar; lateral->min_value = ui->params.output_min_coordinate + 0; lateral->max_value = ui->params.output_max_coordinate + 0; axial->min_value = ui->params.output_min_coordinate + 2; axial->max_value = ui->params.output_max_coordinate + 2; - bv->axial_scale_bar_active->u.b32 = 1; - bv->lateral_scale_bar_active->u.b32 = 1; + bv->axial_scale_bar_active->bool32 = 1; + bv->lateral_scale_bar_active->bool32 = 1; bv->ctx = ui->frame_view_render_context; bv->axial_scale_bar.flags |= V_CAUSES_COMPUTE; bv->lateral_scale_bar.flags |= V_CAUSES_COMPUTE; @@ -1107,9 +1135,9 @@ ui_add_live_frame_view(BeamformerUI *ui, Variable *view, RegionSplitDirection di ASSERT(view->type == VT_UI_VIEW); Variable *new_region = ui_split_region(ui, region, view, direction); - new_region->u.region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_LATEST, 1); + new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_LATEST, 1); - ui_fill_live_frame_view(ui, new_region->u.region_split.right->u.group.first->u.generic); + ui_fill_live_frame_view(ui, new_region->region_split.right->group.first->generic); } function void @@ -1119,30 +1147,30 @@ ui_copy_frame(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) ASSERT(region->type == VT_UI_REGION_SPLIT); ASSERT(view->type == VT_UI_VIEW); - BeamformerFrameView *old = view->u.group.first->u.generic; + BeamformerFrameView *old = view->group.first->generic; /* TODO(rnp): hack; it would be better if this was unreachable with a 0 old->frame */ if (!old->frame) return; Variable *new_region = ui_split_region(ui, region, view, direction); - new_region->u.region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_COPY, 1); + new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_COPY, 1); - BeamformerFrameView *bv = new_region->u.region_split.right->u.group.first->u.generic; - ScaleBar *lateral = &bv->lateral_scale_bar.u.scale_bar; - ScaleBar *axial = &bv->axial_scale_bar.u.scale_bar; + BeamformerFrameView *bv = new_region->region_split.right->group.first->generic; + ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; + ScaleBar *axial = &bv->axial_scale_bar.scale_bar; lateral->min_value = &bv->min_coordinate.x; lateral->max_value = &bv->max_coordinate.x; axial->min_value = &bv->min_coordinate.z; axial->max_value = &bv->max_coordinate.z; - bv->ctx = old->ctx; - bv->needs_update = 1; - bv->threshold.u.f32 = old->threshold.u.f32; - bv->dynamic_range.u.f32 = old->dynamic_range.u.f32; - bv->gamma.u.f32 = old->gamma.u.f32; - bv->log_scale->u.b32 = old->log_scale->u.b32; - bv->min_coordinate = old->frame->min_coordinate; - bv->max_coordinate = old->frame->max_coordinate; + bv->ctx = old->ctx; + bv->needs_update = 1; + bv->threshold.real32 = old->threshold.real32; + bv->dynamic_range.real32 = old->dynamic_range.real32; + bv->gamma.real32 = old->gamma.real32; + bv->log_scale->bool32 = old->log_scale->bool32; + bv->min_coordinate = old->frame->min_coordinate; + bv->max_coordinate = old->frame->max_coordinate; bv->frame = SLLPop(ui->frame_freelist); if (!bv->frame) bv->frame = push_struct(&ui->arena, typeof(*bv->frame)); @@ -1164,7 +1192,7 @@ function b32 view_update(BeamformerUI *ui, BeamformerFrameView *view) { if (view->type == FVT_LATEST) { - u32 index = *view->cycler->u.cycler.state; + u32 index = *view->cycler->cycler.state; view->needs_update |= view->frame != ui->latest_plane[index]; view->frame = ui->latest_plane[index]; if (view->needs_update) { @@ -1203,10 +1231,10 @@ update_frame_views(BeamformerUI *ui, Rect window) glClearNamedFramebufferfv(view->ctx->framebuffer, GL_COLOR, 0, (f32 []){0.79, 0.46, 0.77, 1}); glBindTextureUnit(0, view->frame->texture); - glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC, view->dynamic_range.u.f32); - glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_THRESHOLD_LOC, view->threshold.u.f32); - glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_GAMMA_LOC, view->gamma.u.scaled_f32.val); - glProgramUniform1ui(view->ctx->shader, FRAME_VIEW_RENDER_LOG_SCALE_LOC, view->log_scale->u.b32); + glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC, view->dynamic_range.real32); + glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_THRESHOLD_LOC, view->threshold.real32); + glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_GAMMA_LOC, view->gamma.scaled_real32.val); + glProgramUniform1ui(view->ctx->shader, FRAME_VIEW_RENDER_LOG_SCALE_LOC, view->log_scale->bool32); glDrawArrays(GL_TRIANGLES, 0, 6); glGenerateTextureMipmap(view->texture); @@ -1283,11 +1311,11 @@ push_custom_view_title(Stream *s, Variable *var) } break; case VT_COMPUTE_PROGRESS_BAR: { stream_append_s8(s, s8("Compute Progress: ")); - stream_append_f64(s, 100 * *var->u.compute_progress_bar.progress, 100); + stream_append_f64(s, 100 * *var->compute_progress_bar.progress, 100); stream_append_byte(s, '%'); } break; case VT_BEAMFORMER_FRAME_VIEW: { - BeamformerFrameView *bv = var->u.generic; + BeamformerFrameView *bv = var->generic; stream_append_s8(s, s8("Frame View")); switch (bv->type) { case FVT_COPY: stream_append_s8(s, s8(": Copy [")); break; @@ -1295,11 +1323,11 @@ push_custom_view_title(Stream *s, Variable *var) #define X(plane, id, pretty) s8_comp(": " pretty " ["), read_only local_persist s8 labels[IPT_LAST + 1] = {IMAGE_PLANE_TAGS s8_comp(": Live [")}; #undef X - stream_append_s8(s, labels[*bv->cycler->u.cycler.state % (IPT_LAST + 1)]); + stream_append_s8(s, labels[*bv->cycler->cycler.state % (IPT_LAST + 1)]); } break; case FVT_INDEXED: { stream_append_s8(s, s8(": Index {")); - stream_append_u64(s, *bv->cycler->u.cycler.state % MAX_BEAMFORMED_SAVED_FRAMES); + stream_append_u64(s, *bv->cycler->cycler.state % MAX_BEAMFORMED_SAVED_FRAMES); stream_append_s8(s, s8("} [")); } break; } @@ -1431,6 +1459,20 @@ scale_rect_centered(Rect r, v2 scale) } function b32 +interactions_equal(Interaction a, Interaction b) +{ + b32 result = (a.kind == b.kind) && (a.generic == b.generic); + return result; +} + +function b32 +interaction_is_hot(BeamformerUI *ui, Interaction a) +{ + b32 result = interactions_equal(ui->hot_interaction, a); + return result; +} + +function b32 point_in_rect(v2 p, Rect r) { v2 end = add_v2(r.pos, r.size); @@ -1455,39 +1497,40 @@ world_point_to_screen_2d(v2 p, v2 world_min, v2 world_max, v2 screen_min, v2 scr } function b32 -hover_rect(v2 mouse, Rect rect, f32 *hover_t) -{ - b32 hovering = point_in_rect(mouse, rect); - if (hovering) *hover_t += HOVER_SPEED * dt_for_frame; - else *hover_t -= HOVER_SPEED * dt_for_frame; - *hover_t = CLAMP01(*hover_t); - return hovering; +hover_interaction(BeamformerUI *ui, v2 mouse, Interaction interaction) +{ + Variable *var = interaction.var; + b32 result = point_in_rect(mouse, interaction.rect); + if (result) ui->next_interaction = interaction; + if (interaction_is_hot(ui, interaction)) var->hover_t += HOVER_SPEED * dt_for_frame; + else var->hover_t -= HOVER_SPEED * dt_for_frame; + var->hover_t = CLAMP01(var->hover_t); + return result; } -function b32 -hover_var(BeamformerUI *ui, v2 mouse, Rect rect, Variable *var) -{ - b32 result = 0; - if (ui->interaction.type != IT_DRAG || ui->interaction.active == var) { - result = hover_rect(mouse, rect, &var->hover_t); - if (result) { - ui->interaction.hot_rect = rect; - ui->interaction.hot = var; - } - } - return result; +function void +draw_close_button(BeamformerUI *ui, Variable *close, v2 mouse, Rect r, v2 x_scale) +{ + assert(close->type == VT_UI_BUTTON); + hover_interaction(ui, mouse, auto_interaction(r, close)); + + Color colour = colour_from_normalized(lerp_v4(MENU_CLOSE_COLOUR, FG_COLOUR, close->hover_t)); + r = scale_rect_centered(r, x_scale); + DrawLineEx(r.pos.rl, add_v2(r.pos, r.size).rl, 4, colour); + DrawLineEx(add_v2(r.pos, (v2){.x = r.size.w}).rl, + add_v2(r.pos, (v2){.y = r.size.h}).rl, 4, colour); } function Rect draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mouse) { - ASSERT(ui_view->type == VT_UI_VIEW); - UIView *view = &ui_view->u.view; + assert(ui_view->type == VT_UI_VIEW); + UIView *view = &ui_view->view; s8 title = ui_view->name; if (view->flags & UI_VIEW_CUSTOM_TEXT) { Stream buf = arena_stream(arena); - push_custom_view_title(&buf, ui_view->u.group.first); + push_custom_view_title(&buf, ui_view->group.first); title = arena_stream_commit(&arena, &buf); } @@ -1504,20 +1547,14 @@ draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mous if (view->close) { Rect close; cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &close); - hover_var(ui, mouse, close, view->close); - - Color colour = colour_from_normalized(lerp_v4(MENU_CLOSE_COLOUR, FG_COLOUR, view->close->hover_t)); - close = shrink_rect_centered(close, (v2){.x = 16, .y = 16}); - DrawLineEx(close.pos.rl, add_v2(close.pos, close.size).rl, 4, colour); - DrawLineEx(add_v2(close.pos, (v2){.x = close.size.w}).rl, - add_v2(close.pos, (v2){.y = close.size.h}).rl, 4, colour); + draw_close_button(ui, view->close, mouse, close, (v2){{.4, .4}}); } if (view->menu) { Rect menu; cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &menu); - if (hover_var(ui, mouse, menu, view->menu)) - ui->interaction.hot_font = &ui->small_font; + Interaction interaction = {.kind = InteractionKind_Menu, .var = view->menu, .rect = menu}; + hover_interaction(ui, mouse, interaction); Color colour = colour_from_normalized(lerp_v4(MENU_PLUS_COLOUR, FG_COLOUR, view->menu->hover_t)); menu = shrink_rect_centered(menu, (v2){.x = 14, .y = 14}); @@ -1593,8 +1630,8 @@ function void do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect draw_rect, f32 start_value, f32 end_value, s8 suffix) { - ASSERT(scale_bar->type == VT_SCALE_BAR); - ScaleBar *sb = &scale_bar->u.scale_bar; + assert(scale_bar->type == VT_SCALE_BAR); + ScaleBar *sb = &scale_bar->scale_bar; v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); @@ -1626,7 +1663,7 @@ do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect markers[1] = relative_mouse.x; } - if (hover_var(ui, mouse, tick_rect, scale_bar)) + if (hover_interaction(ui, mouse, auto_interaction(tick_rect, scale_bar))) marker_count = 2; draw_ruler(ui, arena, start_pos, end_pos, start_value, end_value, markers, marker_count, @@ -1636,19 +1673,19 @@ do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect function v2 draw_radio_button(BeamformerUI *ui, Variable *var, v2 at, v2 mouse, v4 base_colour, f32 size) { - ASSERT(var->type == VT_B32 || var->type == VT_BEAMFORMER_VARIABLE); + assert(var->type == VT_B32 || var->type == VT_BEAMFORMER_VARIABLE); b32 value; if (var->type == VT_B32) { - value = var->u.b32; + value = var->bool32; } else { - ASSERT(var->u.beamformer_variable.store_type == VT_B32); - value = *(b32 *)var->u.beamformer_variable.store; + assert(var->beamformer_variable.store_type == VT_B32); + value = *(b32 *)var->beamformer_variable.store; } v2 result = (v2){.x = size, .y = size}; Rect hover_rect = {.pos = at, .size = result}; hover_rect.pos.y += 1; - hover_var(ui, mouse, hover_rect, var); + hover_interaction(ui, mouse, auto_interaction(hover_rect, var)); hover_rect = shrink_rect_centered(hover_rect, (v2){.x = 8, .y = 8}); Rect inner = shrink_rect_centered(hover_rect, (v2){.x = 4, .y = 4}); @@ -1674,8 +1711,8 @@ draw_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, v4 if (var->flags & V_INPUT) { Rect text_rect = {.pos = at, .size = result}; text_rect = extend_rect_centered(text_rect, (v2){.x = 8}); - if (hover_var(ui, mouse, text_rect, var) && (var->flags & V_TEXT)) - ui->interaction.hot_font = text_spec.font; + if (hover_interaction(ui, mouse, auto_interaction(text_rect, var)) && (var->flags & V_TEXT)) + ui->text_input_state.hot_font = text_spec.font; text_spec.colour = lerp_v4(base_colour, HOVERED_COLOUR, var->hover_t); } @@ -1697,8 +1734,8 @@ draw_table_cell(BeamformerUI *ui, TableCell *cell, Rect cell_rect, TextAlignment v4 base_colour = ts.colour; if (cell->kind == TCK_VARIABLE && cell->var->flags & V_INPUT) { Rect hover = {.pos = cell_at, .size = {.w = cell->width, .h = cell_rect.size.h}}; - if (hover_var(ui, mouse, hover, cell->var) && (cell->var->flags & V_TEXT)) - ui->interaction.hot_font = ts.font; + if (hover_interaction(ui, mouse, auto_interaction(hover, cell->var)) && (cell->var->flags & V_TEXT)) + ui->text_input_state.hot_font = ts.font; ts.colour = lerp_v4(ts.colour, HOVERED_COLOUR, cell->var->hover_t); } @@ -1757,9 +1794,8 @@ draw_table(BeamformerUI *ui, Arena arena, Table *table, Rect draw_rect, TextSpec function void draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect display_rect, v2 mouse) { - ASSERT(var->type == VT_BEAMFORMER_FRAME_VIEW); - InteractionState *is = &ui->interaction; - BeamformerFrameView *view = var->u.generic; + assert(var->type == VT_BEAMFORMER_FRAME_VIEW); + BeamformerFrameView *view = var->generic; BeamformFrame *frame = view->frame; v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); @@ -1772,13 +1808,13 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa Rect vr = display_rect; v2 scale_bar_area = {0}; - if (view->axial_scale_bar_active->u.b32) { + if (view->axial_scale_bar_active->bool32) { vr.pos.y += 0.5 * ui->small_font.baseSize; scale_bar_area.x += scale_bar_size; scale_bar_area.y += ui->small_font.baseSize; } - if (view->lateral_scale_bar_active->u.b32) { + if (view->lateral_scale_bar_active->bool32) { vr.pos.x += 0.5 * ui->small_font.baseSize; scale_bar_area.x += ui->small_font.baseSize; scale_bar_area.y += scale_bar_size; @@ -1824,21 +1860,21 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa v2 start_pos = vr.pos; start_pos.y += vr.size.y; - if (vr.size.w > 0 && view->lateral_scale_bar_active->u.b32) { + if (vr.size.w > 0 && view->lateral_scale_bar_active->bool32) { do_scale_bar(ui, a, &view->lateral_scale_bar, mouse, (Rect){.pos = start_pos, .size = vr.size}, - *view->lateral_scale_bar.u.scale_bar.min_value * 1e3, - *view->lateral_scale_bar.u.scale_bar.max_value * 1e3, s8(" mm")); + *view->lateral_scale_bar.scale_bar.min_value * 1e3, + *view->lateral_scale_bar.scale_bar.max_value * 1e3, s8(" mm")); } start_pos = vr.pos; start_pos.x += vr.size.x; - if (vr.size.h > 0 && view->axial_scale_bar_active->u.b32) { + if (vr.size.h > 0 && view->axial_scale_bar_active->bool32) { do_scale_bar(ui, a, &view->axial_scale_bar, mouse, (Rect){.pos = start_pos, .size = vr.size}, - *view->axial_scale_bar.u.scale_bar.max_value * 1e3, - *view->axial_scale_bar.u.scale_bar.min_value * 1e3, s8(" mm")); + *view->axial_scale_bar.scale_bar.max_value * 1e3, + *view->axial_scale_bar.scale_bar.min_value * 1e3, s8(" mm")); } TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED|TF_OUTLINED, @@ -1846,9 +1882,10 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa .limits.size.x = vr.size.w}; f32 draw_table_width = vr.size.w; - if (point_in_rect(mouse, vr)) { - is->hot = var; - is->hot_rect = vr; + /* NOTE: avoid hover_t modification */ + Interaction viewer = auto_interaction(vr, var); + if (point_in_rect(mouse, viewer.rect)) { + ui->next_interaction = viewer; v2 world = screen_point_to_world_2d(mouse, vr.pos, add_v2(vr.pos, vr.size), XZ(view->min_coordinate), @@ -1920,7 +1957,7 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa Table *table = table_new(&a, 3, 3, (TextAlignment []){TA_LEFT, TA_LEFT, TA_LEFT}); table_push_parameter_row(table, &a, view->gamma.name, &view->gamma, s8("")); table_push_parameter_row(table, &a, view->threshold.name, &view->threshold, s8("")); - if (view->log_scale->u.b32) + if (view->log_scale->bool32) table_push_parameter_row(table, &a, view->dynamic_range.name, &view->dynamic_range, s8("[dB]")); Rect table_rect = vr; @@ -2004,14 +2041,14 @@ draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 /* NOTE(rnp): minimum width for middle column */ table->widths[1] = 150; - Variable *var = group->u.group.first; + Variable *var = group->group.first; while (var) { switch (var->type) { case VT_CYCLER: case VT_BEAMFORMER_VARIABLE: { s8 suffix = s8(""); if (var->type == VT_BEAMFORMER_VARIABLE) - suffix = var->u.beamformer_variable.suffix; + suffix = var->beamformer_variable.suffix; table_push_parameter_row(table, &arena, var->name, var, suffix); while (var) { if (var->next) { @@ -2023,7 +2060,7 @@ draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 } } break; case VT_GROUP: { - VariableGroup *g = &var->u.group; + VariableGroup *g = &var->group; TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; cells[0] = (TableCell){.text = var->name, .kind = TCK_VARIABLE, .var = var}; @@ -2036,9 +2073,9 @@ draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 } else { Variable *v = g->first; - ASSERT(!v || v->type == VT_BEAMFORMER_VARIABLE); + assert(!v || v->type == VT_BEAMFORMER_VARIABLE); /* NOTE(rnp): assume the suffix is the same for all elements */ - if (v) cells[2].text = v->u.beamformer_variable.suffix; + if (v) cells[2].text = v->beamformer_variable.suffix; Stream sb = arena_stream(arena); switch (g->type) { @@ -2078,7 +2115,7 @@ draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 rect.pos = add_v2(it->cell_rect.pos, scale_v2((v2){.x = text_spec.font->baseSize}, it->sub_table_depth)); rect.size = it->cell_rect.size; if (cell->kind == TCK_VARIABLE_GROUP) { - Variable *v = cell->var->u.group.first; + Variable *v = cell->var->group.first; v2 at = table_cell_align(cell, it->alignment, rect); text_spec.limits.size.w = r.size.w - (at.x - it->start_x); f32 dw = draw_text(s8("{"), at, &text_spec).x; @@ -2109,7 +2146,7 @@ function void draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec text_spec) { ASSERT(ui_view->type == VT_UI_VIEW); - UIView *view = &ui_view->u.view; + UIView *view = &ui_view->view; if (view->needed_height - r.size.h < view->offset) view->offset = view->needed_height - r.size.h; @@ -2125,21 +2162,21 @@ draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec tex switch (var->type) { case VT_GROUP: size = draw_ui_view_listing(ui, var, ui->arena, r, mouse, text_spec); break; case VT_BEAMFORMER_FRAME_VIEW: { - BeamformerFrameView *bv = var->u.generic; + BeamformerFrameView *bv = var->generic; if (frame_view_ready_to_present(bv)) draw_beamformer_frame_view(ui, ui->arena, var, r, mouse); } break; case VT_COMPUTE_PROGRESS_BAR: { - size = draw_compute_progress_bar(ui, ui->arena, &var->u.compute_progress_bar, r); + size = draw_compute_progress_bar(ui, ui->arena, &var->compute_progress_bar, r); } break; case VT_COMPUTE_LATEST_STATS_VIEW: case VT_COMPUTE_STATS_VIEW: { - ComputeShaderStats *stats = var->u.compute_stats_view.stats; + ComputeShaderStats *stats = var->compute_stats_view.stats; if (var->type == VT_COMPUTE_LATEST_STATS_VIEW) stats = *(ComputeShaderStats **)stats; - size = draw_compute_stats_view(var->u.compute_stats_view.ctx, ui->arena, stats, r); + size = draw_compute_stats_view(var->compute_stats_view.ctx, ui->arena, stats, r); } break; - default: INVALID_CODE_PATH; + InvalidDefaultCase; } view->needed_height = size.y; @@ -2150,7 +2187,7 @@ draw_active_text_box(BeamformerUI *ui, Variable *var) { InputState *is = &ui->text_input_state; Rect box = ui->interaction.rect; - Font *font = ui->interaction.font; + Font *font = is->font; s8 text = {.len = is->count, .data = is->buf}; v2 text_size = measure_text(*font, text); @@ -2180,16 +2217,15 @@ draw_active_text_box(BeamformerUI *ui, Variable *var) DrawRectanglePro(cursor.rl, (Vector2){0}, 0, colour_from_normalized(cursor_colour)); } -function void -draw_active_menu(BeamformerUI *ui, Arena arena, Variable *menu, v2 mouse, Rect window) +function Rect +draw_menu(BeamformerUI *ui, Arena arena, Variable *menu, Font *font, v2 at, v2 mouse, Rect window) { - ASSERT(menu->type == VT_GROUP); + assert(menu->type == VT_GROUP); - Font *font = ui->interaction.font; f32 font_height = font->baseSize; f32 max_label_width = 0; - Variable *item = menu->u.group.first; + Variable *item = menu->group.first; i32 item_count = 0; b32 radio = 0; while (item) { @@ -2200,7 +2236,6 @@ draw_active_menu(BeamformerUI *ui, Arena arena, Variable *menu, v2 mouse, Rect w } f32 radio_button_width = radio? font_height : 0; - v2 at = ui->interaction.rect.pos; f32 menu_width = max_label_width + radio_button_width + 8; f32 menu_height = item_count * font_height + (item_count - 1) * 2; menu_height = MAX(menu_height, 0); @@ -2211,19 +2246,17 @@ draw_active_menu(BeamformerUI *ui, Arena arena, Variable *menu, v2 mouse, Rect w at.y = window.size.h - menu_height - 12; /* TODO(rnp): scroll menu if it doesn't fit on screen */ - Rect menu_rect = {.pos = at, .size = {.w = menu_width, .h = menu_height}}; - Rect bg_rect = extend_rect_centered(menu_rect, (v2){.x = 12, .y = 8}); - menu_rect = extend_rect_centered(menu_rect, (v2){.x = 6, .y = 4}); - DrawRectangleRounded(bg_rect.rl, 0.1, 0, fade(BLACK, 0.8)); - DrawRectangleRounded(menu_rect.rl, 0.1, 0, colour_from_normalized(BG_COLOUR)); - v2 start = at; + Rect result = {.pos = at, .size = {.w = menu_width, .h = menu_height}}; + result.size = add_v2(result.size, (v2){.x = 12, .y = 8}); + + v2 start = at = add_v2(result.pos, (v2){.x = 6, .y = 4}); for (i32 i = 0; i < item_count - 1; i++) { at.y += 2 + font_height; DrawLineEx((v2){.x = at.x - 3, .y = at.y}.rl, add_v2(at, (v2){.w = menu_width + 3}).rl, 2, fade(BLACK, 0.8)); } - item = menu->u.group.first; + item = menu->group.first; TextSpec text_spec = {.font = font, .colour = FG_COLOUR, .limits.size.w = menu_width}; at = start; while (item) { @@ -2237,6 +2270,7 @@ draw_active_menu(BeamformerUI *ui, Arena arena, Variable *menu, v2 mouse, Rect w at.y += draw_variable(ui, arena, item, at, mouse, FG_COLOUR, text_spec).y + 2; item = item->next; } + return result; } function void @@ -2259,12 +2293,12 @@ draw_layout_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse) BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); switch (var->type) { case VT_UI_VIEW: { - hover_var(ui, mouse, draw_rect, var); + hover_interaction(ui, mouse, auto_interaction(draw_rect, var)); TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; draw_ui_view(ui, var, draw_rect, mouse, text_spec); } break; case VT_UI_REGION_SPLIT: { - RegionSplit *rs = &var->u.region_split; + RegionSplit *rs = &var->region_split; Rect split, hover; switch (rs->direction) { @@ -2286,13 +2320,14 @@ draw_layout_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse) } break; } - hover_var(ui, mouse, hover, var); + Interaction drag = {.kind = InteractionKind_Drag, .rect = hover, .var = var}; + hover_interaction(ui, mouse, drag); v4 colour = HOVERED_COLOUR; colour.a = var->hover_t; DrawRectangleRounded(split.rl, 0.6, 0, colour_from_normalized(colour)); } break; - default: INVALID_CODE_PATH; break; + InvalidDefaultCase; } EndScissorMode(); } @@ -2321,7 +2356,7 @@ draw_ui_regions(BeamformerUI *ui, Rect window, v2 mouse) if (top->var->type == VT_UI_REGION_SPLIT) { Rect first, second; - RegionSplit *rs = &top->var->u.region_split; + RegionSplit *rs = &top->var->region_split; switch (rs->direction) { case RSD_VERTICAL: { split_rect_vertical(rect, rs->fraction, &first, &second); @@ -2340,45 +2375,102 @@ draw_ui_regions(BeamformerUI *ui, Rect window, v2 mouse) } function void +draw_floating_widget_container(BeamformerUI *ui, Variable *var, v2 mouse, Rect bounds) +{ + FloatingWidget *fw = &var->floating_widget; + if (fw->rect.size.x > 0 && fw->rect.size.y > 0) { + f32 line_height = ui->small_font.baseSize; + + if (fw->rect.pos.y - line_height < 0) fw->rect.pos.y += line_height - fw->rect.pos.y; + f32 delta_x = fw->rect.pos.x + fw->rect.size.x - bounds.size.x; + if (delta_x > 0) { + fw->rect.pos.x -= delta_x; + fw->rect.pos.x = MAX(0, fw->rect.pos.x); + } + Rect container = fw->rect; + container.pos.y -= 5 + line_height; + container.size.y += 2 + line_height; + Rect handle = {{container.pos, (v2){.x = container.size.w, .y = 2 + line_height}}}; + Rect close; + hover_interaction(ui, mouse, auto_interaction(container, var)); + cut_rect_horizontal(handle, handle.size.w - handle.size.h - 6, 0, &close); + close.size.w = close.size.h; + + f32 roundness = 12.0f / fw->rect.size.y; + DrawRectangleRounded(handle.rl, 0.1, 0, colour_from_normalized(BG_COLOUR)); + DrawRectangleRoundedLinesEx(handle.rl, 0.2, 0, 2, BLACK); + DrawRectangleRounded(fw->rect.rl, roundness / 2, 0, colour_from_normalized(BG_COLOUR)); + DrawRectangleRoundedLinesEx(fw->rect.rl, roundness, 0, 2, BLACK); + draw_close_button(ui, fw->close, mouse, close, (v2){{0.45, 0.45}}); + } +} + +function void +draw_floating_widgets(BeamformerUI *ui, Rect window_rect, v2 mouse) +{ + for (Variable *var = ui->floating_widget_sentinal.parent; + var != &ui->floating_widget_sentinal; + var = var->parent) + { + assert(var->type == VT_UI_FLOATING_WIDGET); + FloatingWidget *fw = &var->floating_widget; + draw_floating_widget_container(ui, var, mouse, window_rect); + + Rect draw_rect = {0}; + switch (fw->kind) { + case FloatingWidgetKind_Menu:{ + draw_rect = draw_menu(ui, ui->arena, fw->reference, &ui->small_font, + fw->rect.pos, mouse, window_rect); + }break; + case FloatingWidgetKind_TextBox:{ + }break; + InvalidDefaultCase; + } + fw->rect = draw_rect; + } +} + +function void scroll_interaction(Variable *var, f32 delta) { switch (var->type) { - case VT_B32: var->u.b32 = !var->u.b32; break; - case VT_F32: var->u.f32 += delta; break; - case VT_I32: var->u.i32 += delta; break; - case VT_U32: var->u.u32 += delta; break; - case VT_SCALED_F32: var->u.scaled_f32.val += delta * var->u.scaled_f32.scale; break; - case VT_BEAMFORMER_FRAME_VIEW: { - BeamformerFrameView *bv = var->u.generic; - bv->needs_update = 1; - bv->threshold.u.f32 += delta; + case VT_B32:{ var->bool32 = !var->bool32; }break; + case VT_F32:{ var->real32 += delta; }break; + case VT_I32:{ var->signed32 += delta; }break; + case VT_U32:{ var->unsigned32 += delta; }break; + case VT_SCALED_F32:{ var->scaled_real32.val += delta * var->scaled_real32.scale; }break; + case VT_BEAMFORMER_FRAME_VIEW:{ + BeamformerFrameView *bv = var->generic; + bv->needs_update = 1; + bv->threshold.real32 += delta; } break; - case VT_BEAMFORMER_VARIABLE: { - BeamformerVariable *bv = &var->u.beamformer_variable; + case VT_BEAMFORMER_VARIABLE:{ + BeamformerVariable *bv = &var->beamformer_variable; switch (bv->store_type) { - case VT_F32: { + case VT_F32:{ f32 val = *(f32 *)bv->store + delta * bv->scroll_scale; *(f32 *)bv->store = CLAMP(val, bv->limits.x, bv->limits.y); - } break; - INVALID_DEFAULT_CASE; + }break; + InvalidDefaultCase; } - } break; - case VT_CYCLER: { - *var->u.cycler.state += delta > 0? 1 : -1; - *var->u.cycler.state %= var->u.cycler.cycle_length; - } break; - case VT_UI_VIEW: { - var->u.view.offset += UI_SCROLL_SPEED * delta; - var->u.view.offset = MAX(0, var->u.view.offset); - } break; - INVALID_DEFAULT_CASE; + }break; + case VT_CYCLER:{ + *var->cycler.state += delta > 0? 1 : -1; + *var->cycler.state %= var->cycler.cycle_length; + }break; + case VT_UI_VIEW:{ + var->view.offset += UI_SCROLL_SPEED * delta; + var->view.offset = MAX(0, var->view.offset); + }break; + InvalidDefaultCase; } } function void -begin_text_input(InputState *is, Font *font, Rect r, Variable *var, v2 mouse) +begin_text_input(InputState *is, Rect r, Variable *var, v2 mouse) { - Stream s = {.cap = ARRAY_COUNT(is->buf), .data = is->buf}; + Font *font = is->font = is->hot_font; + Stream s = {.cap = countof(is->buf), .data = is->buf}; stream_append_variable(&s, var); is->count = s.widx; @@ -2403,20 +2495,20 @@ end_text_input(InputState *is, Variable *var) f64 value = parse_f64((s8){.len = is->count, .data = is->buf}); switch (var->type) { - case VT_SCALED_F32: var->u.scaled_f32.val = value; break; - case VT_F32: var->u.f32 = value; break; - case VT_BEAMFORMER_VARIABLE: { - BeamformerVariable *bv = &var->u.beamformer_variable; + case VT_SCALED_F32:{ var->scaled_real32.val = value; }break; + case VT_F32:{ var->real32 = value; }break; + case VT_BEAMFORMER_VARIABLE:{ + BeamformerVariable *bv = &var->beamformer_variable; switch (bv->store_type) { - case VT_F32: { + case VT_F32:{ value = CLAMP(value / bv->display_scale, bv->limits.x, bv->limits.y); *(f32 *)bv->store = value; - } break; - INVALID_DEFAULT_CASE; + }break; + InvalidDefaultCase; } var->hover_t = 0; - } break; - INVALID_DEFAULT_CASE; + }break; + InvalidDefaultCase; } } @@ -2472,14 +2564,14 @@ update_text_input(InputState *is, Variable *var) function void scale_bar_interaction(BeamformerUI *ui, ScaleBar *sb, v2 mouse) { - InteractionState *is = &ui->interaction; + Interaction *it = &ui->interaction; b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); f32 mouse_wheel = GetMouseWheelMoveV().y; if (mouse_left_pressed) { - v2 world_mouse = screen_point_to_world_2d(mouse, is->rect.pos, - add_v2(is->rect.pos, is->rect.size), + v2 world_mouse = screen_point_to_world_2d(mouse, it->rect.pos, + add_v2(it->rect.pos, it->rect.size), (v2){{*sb->min_value, *sb->min_value}}, (v2){{*sb->max_value, *sb->max_value}}); f32 new_coord = F32_INFINITY; @@ -2492,7 +2584,7 @@ scale_bar_interaction(BeamformerUI *ui, ScaleBar *sb, v2 mouse) } else { f32 min = sb->zoom_starting_coord; f32 max = new_coord; - if (min > max) SWAP(min, max) + if (min > max) swap(min, max); v2_sll *savepoint = SLLPop(ui->scale_bar_savepoint_freelist); if (!savepoint) savepoint = push_struct(&ui->arena, v2_sll); @@ -2526,126 +2618,156 @@ scale_bar_interaction(BeamformerUI *ui, ScaleBar *sb, v2 mouse) } function void +ui_widget_bring_to_front(Variable *sentinal, Variable *widget) +{ + /* TODO(rnp): clean up the linkage so this can be a macro */ + widget->parent->next = widget->next; + widget->next->parent = widget->parent; + + widget->parent = sentinal; + widget->next = sentinal->next; + widget->next->parent = widget; + sentinal->next = widget; +} + +function void +ui_close_widget(BeamformerUI *ui, Variable *widget) +{ + assert(widget->type == VT_UI_FLOATING_WIDGET); + FloatingWidget *fw = &widget->floating_widget; + switch (fw->kind) { + case FloatingWidgetKind_Menu:{ + assert(fw->reference->type == VT_GROUP); + fw->reference->group.expanded = 0; + fw->reference->group.container = 0; + }break; + InvalidDefaultCase; + } + widget->parent->next = widget->next; + widget->next->parent = widget->parent; + SLLPush(widget->floating_widget.close, ui->variable_freelist); + SLLPush(widget, ui->variable_freelist); +} + +function void ui_button_interaction(BeamformerUI *ui, Variable *button) { - ASSERT(button->type == VT_UI_BUTTON); - switch (button->u.button) { - case UI_BID_FV_COPY_HORIZONTAL: { + assert(button->type == VT_UI_BUTTON); + switch (button->button) { + case UI_BID_FV_COPY_HORIZONTAL:{ ui_copy_frame(ui, button->parent->parent, RSD_HORIZONTAL); - } break; - case UI_BID_FV_COPY_VERTICAL: { + }break; + case UI_BID_FV_COPY_VERTICAL:{ ui_copy_frame(ui, button->parent->parent, RSD_VERTICAL); - } break; - case UI_BID_GM_OPEN_LIVE_VIEW_RIGHT: { + }break; + case UI_BID_GM_OPEN_LIVE_VIEW_RIGHT:{ ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL); - } break; - case UI_BID_GM_OPEN_LIVE_VIEW_BELOW: { + }break; + case UI_BID_GM_OPEN_LIVE_VIEW_BELOW:{ ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL); - } break; - case UI_BID_CLOSE_VIEW: { + }break; + case UI_BID_CLOSE_WIDGET:{ ui_close_widget(ui, button->parent); }break; + case UI_BID_CLOSE_VIEW:{ Variable *view = button->parent; Variable *region = view->parent; - ASSERT(view->type == VT_UI_VIEW && region->type == VT_UI_REGION_SPLIT); + assert(view->type == VT_UI_VIEW && region->type == VT_UI_REGION_SPLIT); Variable *parent = region->parent; - Variable *remaining = region->u.region_split.left; - if (remaining == view) remaining = region->u.region_split.right; + Variable *remaining = region->region_split.left; + if (remaining == view) remaining = region->region_split.right; ui_view_free(ui, view); - ASSERT(parent->type == VT_UI_REGION_SPLIT); - if (parent->u.region_split.left == region) { - parent->u.region_split.left = remaining; + assert(parent->type == VT_UI_REGION_SPLIT); + if (parent->region_split.left == region) { + parent->region_split.left = remaining; } else { - parent->u.region_split.right = remaining; + parent->region_split.right = remaining; } remaining->parent = parent; SLLPush(region, ui->variable_freelist); - } break; + }break; } } function void -ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll, b32 mouse_left_pressed) -{ - InteractionState *is = &ui->interaction; - if (is->hot) { - switch (is->hot->type) { - case VT_NULL: is->type = IT_NOP; break; - case VT_B32: is->type = IT_SET; break; - case VT_UI_REGION_SPLIT: { is->type = IT_DRAG; } break; - case VT_UI_VIEW: { if (scroll) is->type = IT_SCROLL; } break; - case VT_UI_BUTTON: { ui_button_interaction(ui, is->hot); } break; - case VT_SCALE_BAR: { is->type = IT_SET; } break; - case VT_BEAMFORMER_FRAME_VIEW: - case VT_CYCLER: { - if (scroll) is->type = IT_SCROLL; - else is->type = IT_SET; - } break; - case VT_GROUP: { - if (mouse_left_pressed && is->hot->flags & V_MENU) { - is->type = IT_MENU; - } else { - is->type = IT_SET; - } - } break; - case VT_BEAMFORMER_VARIABLE: { - if (is->hot->u.beamformer_variable.store_type == VT_B32) { - is->type = IT_SET; - break; - } - } /* FALLTHROUGH */ - case VT_SCALED_F32: - case VT_F32: { - if (scroll) { - is->type = IT_SCROLL; - } else if (mouse_left_pressed && is->hot->flags & V_TEXT) { - is->type = IT_TEXT; - begin_text_input(&ui->text_input_state, is->hot_font, is->hot_rect, - is->hot, input->mouse); +ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) +{ + Interaction *hot = &ui->hot_interaction; + if (hot->kind != InteractionKind_None) { + if (hot->kind == InteractionKind_Auto) { + switch (hot->var->type) { + case VT_NULL:{ hot->kind = InteractionKind_Nop; }break; + case VT_B32:{ hot->kind = InteractionKind_Set; }break; + case VT_SCALE_BAR:{ hot->kind = InteractionKind_Set; }break; + case VT_UI_BUTTON:{ hot->kind = InteractionKind_Button; }break; + case VT_GROUP:{ hot->kind = InteractionKind_Set; }break; + case VT_UI_FLOATING_WIDGET:{ + hot->kind = InteractionKind_Drag; + ui_widget_bring_to_front(&ui->floating_widget_sentinal, hot->var); + }break; + case VT_UI_VIEW:{ + if (scroll) hot->kind = InteractionKind_Scroll; + else hot->kind = InteractionKind_Nop; + }break; + case VT_BEAMFORMER_FRAME_VIEW: + case VT_CYCLER: + { + if (scroll) hot->kind = InteractionKind_Scroll; + else hot->kind = InteractionKind_Set; + }break; + case VT_BEAMFORMER_VARIABLE:{ + if (hot->var->beamformer_variable.store_type == VT_B32) { + hot->kind = InteractionKind_Set; + break; + } + } /* FALLTHROUGH */ + case VT_F32: + case VT_SCALED_F32: + { + if (scroll) { + hot->kind = InteractionKind_Scroll; + } else if (hot->var->flags & V_TEXT) { + hot->kind = InteractionKind_Text; + begin_text_input(&ui->text_input_state, hot->rect, + hot->var, input->mouse); + } + }break; + InvalidDefaultCase; } - } break; - default: INVALID_CODE_PATH; } - } - if (is->type != IT_NONE) { - is->last_rect = is->rect; - is->active = is->hot; - is->rect = is->hot_rect; - is->font = is->hot_font; + + ui->interaction = ui->hot_interaction; + } else { + ui->interaction.kind = InteractionKind_Nop; } } function void ui_end_interact(BeamformerUI *ui, v2 mouse) { - InteractionState *is = &ui->interaction; - switch (is->type) { - case IT_NOP: break; - case IT_MENU: break; - case IT_DRAG: break; - case IT_SET: { - switch (is->active->type) { - case VT_B32: { is->active->u.b32 = !is->active->u.b32; } break; - case VT_GROUP: { - is->active->u.group.expanded = !is->active->u.group.expanded; - } break; - case VT_CYCLER: { - *is->active->u.cycler.state += 1; - *is->active->u.cycler.state %= is->active->u.cycler.cycle_length; - } break; - case VT_SCALE_BAR: { - scale_bar_interaction(ui, &is->active->u.scale_bar, mouse); - } break; - case VT_BEAMFORMER_FRAME_VIEW: { - BeamformerFrameView *bv = is->hot->u.generic; + Interaction *it = &ui->interaction; + switch (it->kind) { + case InteractionKind_Nop:{}break; + case InteractionKind_Drag:{}break; + case InteractionKind_Set:{ + switch (it->var->type) { + case VT_B32:{ it->var->bool32 = !it->var->bool32; }break; + case VT_GROUP:{ it->var->group.expanded = !it->var->group.expanded; }break; + case VT_SCALE_BAR:{ scale_bar_interaction(ui, &it->var->scale_bar, mouse); }break; + case VT_CYCLER:{ + *it->var->cycler.state += 1; + *it->var->cycler.state %= it->var->cycler.cycle_length; + }break; + case VT_BEAMFORMER_FRAME_VIEW:{ + BeamformerFrameView *bv = it->var->generic; bv->ruler.state++; switch (bv->ruler.state) { case RS_START: case RS_HOLD: { - v2 r_max = add_v2(is->rect.pos, is->rect.size); - v2 p = screen_point_to_world_2d(mouse, is->rect.pos, r_max, + v2 r_max = add_v2(it->rect.pos, it->rect.size); + v2 p = screen_point_to_world_2d(mouse, it->rect.pos, r_max, XZ(bv->min_coordinate), XZ(bv->max_coordinate)); if (bv->ruler.state == RS_START) bv->ruler.start = p; @@ -2654,80 +2776,83 @@ ui_end_interact(BeamformerUI *ui, v2 mouse) default: bv->ruler.state = RS_NONE; break; } } break; - default: INVALID_CODE_PATH; + InvalidDefaultCase; } } break; - case IT_SCROLL: scroll_interaction(is->active, GetMouseWheelMoveV().y); break; - case IT_TEXT: end_text_input(&ui->text_input_state, is->active); break; - default: INVALID_CODE_PATH; + case InteractionKind_Menu:{ + assert(it->var->type == VT_GROUP); + VariableGroup *g = &it->var->group; + if (g->container) { + ui_widget_bring_to_front(&ui->floating_widget_sentinal, g->container); + } else { + g->container = add_floating_widget(ui, &ui->arena, FloatingWidgetKind_Menu, + mouse, it->var); + } + }break; + case InteractionKind_Button:{ ui_button_interaction(ui, it->var); }break; + case InteractionKind_Scroll:{ scroll_interaction(it->var, GetMouseWheelMoveV().y); }break; + case InteractionKind_Text:{ end_text_input(&ui->text_input_state, it->var); }break; + InvalidDefaultCase; } - b32 menu_child = is->active->parent && is->active->parent->flags & V_MENU; - - /* TODO(rnp): better way of clearing the state when the parent is a menu */ - if (menu_child) is->active->hover_t = 0; - - if (is->active->flags & V_CAUSES_COMPUTE) + if (it->var->flags & V_CAUSES_COMPUTE) ui->flush_params = 1; - if (is->active->flags & V_UPDATE_VIEW) { - Variable *parent = is->active->parent; + if (it->var->flags & V_UPDATE_VIEW) { + Variable *parent = it->var->parent; BeamformerFrameView *frame; /* TODO(rnp): more straight forward way of achieving this */ if (parent->type == VT_BEAMFORMER_FRAME_VIEW) { - frame = parent->u.generic; + frame = parent->generic; } else { - ASSERT(parent->flags & V_MENU); - ASSERT(parent->parent->u.group.first->type == VT_BEAMFORMER_FRAME_VIEW); - frame = parent->parent->u.group.first->u.generic; + assert(parent->parent->group.first->type == VT_BEAMFORMER_FRAME_VIEW); + frame = parent->parent->group.first->generic; } frame->needs_update = 1; } - if (menu_child && (is->active->flags & V_CLOSES_MENU) == 0) { - is->type = IT_MENU; - is->rect = is->last_rect; - is->active = is->active->parent; - } else { - is->type = IT_NONE; - is->active = 0; - } + ui->interaction = (Interaction){.kind = InteractionKind_None}; } function void -ui_interact(BeamformerUI *ui, BeamformerInput *input, uv2 window_size) -{ - InteractionState *is = &ui->interaction; - b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); - b32 wheel_moved = GetMouseWheelMoveV().y != 0; - if (mouse_right_pressed || mouse_left_pressed || wheel_moved) { - if (is->type != IT_NONE) - ui_end_interact(ui, input->mouse); - ui_begin_interact(ui, input, wheel_moved, mouse_left_pressed); +ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect) +{ + InteractionKind current = ui->interaction.kind; + if (current == InteractionKind_None || current == InteractionKind_Text) { + ui->hot_interaction = ui->next_interaction; + + b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); + b32 wheel_moved = GetMouseWheelMoveV().y != 0; + if (mouse_right_pressed || mouse_left_pressed || wheel_moved) { + if (ui->interaction.kind != InteractionKind_None) + ui_end_interact(ui, input->mouse); + ui_begin_interact(ui, input, wheel_moved); + } } - if (IsKeyPressed(KEY_ENTER) && is->type == IT_TEXT) - ui_end_interact(ui, input->mouse); - - switch (is->type) { - case IT_NONE: break; - case IT_NOP: break; - case IT_MENU: break; - case IT_SCROLL: ui_end_interact(ui, input->mouse); break; - case IT_SET: ui_end_interact(ui, input->mouse); break; - case IT_TEXT: update_text_input(&ui->text_input_state, is->active); break; - case IT_DRAG: { + switch (ui->interaction.kind) { + case InteractionKind_Nop:{ ui->interaction.kind = InteractionKind_None; }break; + case InteractionKind_None:{}break; + case InteractionKind_Text:{ + update_text_input(&ui->text_input_state, ui->interaction.var); + if (IsKeyPressed(KEY_ENTER)) ui_end_interact(ui, input->mouse); + }break; + case InteractionKind_Drag:{ if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { ui_end_interact(ui, input->mouse); } else { - v2 ws = {.w = window_size.w, .h = window_size.h}; + v2 ws = window_rect.size; v2 dMouse = sub_v2(input->mouse, input->last_mouse); - dMouse = mul_v2(dMouse, (v2){.x = 1.0f / ws.w, .y = 1.0f / ws.h}); - switch (is->active->type) { - case VT_UI_REGION_SPLIT: { + switch (ui->interaction.var->type) { + case VT_UI_FLOATING_WIDGET:{ + v2 *pos = &ui->interaction.var->floating_widget.rect.pos; + *pos = clamp_v2_rect(add_v2(*pos, dMouse), window_rect); + }break; + case VT_UI_REGION_SPLIT:{ f32 min_fraction = 0; - RegionSplit *rs = &is->active->u.region_split; + dMouse = mul_v2(dMouse, (v2){.x = 1.0f / ws.w, .y = 1.0f / ws.h}); + RegionSplit *rs = &ui->interaction.var->region_split; switch (rs->direction) { case RSD_VERTICAL: { min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5 * UI_REGION_PAD) / ws.h; @@ -2739,14 +2864,15 @@ ui_interact(BeamformerUI *ui, BeamformerInput *input, uv2 window_size) } break; } rs->fraction = CLAMP(rs->fraction, min_fraction, 1 - min_fraction); - } break; - default: break; + }break; + default:{}break; } } } break; + default:{ ui_end_interact(ui, input->mouse); }break; } - is->hot = 0; + ui->next_interaction = (Interaction){.kind = InteractionKind_None}; } function void @@ -2782,41 +2908,44 @@ ui_init(BeamformerCtx *ctx, Arena store) ui->font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 28, 0, 0); ui->small_font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 20, 0, 0); + ui->floating_widget_sentinal.parent = &ui->floating_widget_sentinal; + ui->floating_widget_sentinal.next = &ui->floating_widget_sentinal; + Variable *split = ui->regions = add_ui_split(ui, 0, &ui->arena, s8("UI Root"), 0.4, RSD_HORIZONTAL, ui->font); - split->u.region_split.left = add_ui_split(ui, split, &ui->arena, s8(""), 0.475, - RSD_VERTICAL, ui->font); - split->u.region_split.right = add_beamformer_frame_view(ui, split, &ui->arena, FVT_LATEST, 0); + split->region_split.left = add_ui_split(ui, split, &ui->arena, s8(""), 0.475, + RSD_VERTICAL, ui->font); + split->region_split.right = add_beamformer_frame_view(ui, split, &ui->arena, FVT_LATEST, 0); - ui_fill_live_frame_view(ui, split->u.region_split.right->u.view.child->u.generic); + ui_fill_live_frame_view(ui, split->region_split.right->view.child->generic); - split = split->u.region_split.left; - split->u.region_split.left = add_beamformer_parameters_view(split, ctx); - split->u.region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.22, - RSD_VERTICAL, ui->font); - split = split->u.region_split.right; + split = split->region_split.left; + split->region_split.left = add_beamformer_parameters_view(split, ctx); + split->region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.22, + RSD_VERTICAL, ui->font); + split = split->region_split.right; - split->u.region_split.left = add_compute_progress_bar(split, ctx); - split->u.region_split.right = add_compute_stats_view(ui, split, &ui->arena, - VT_COMPUTE_LATEST_STATS_VIEW); + split->region_split.left = add_compute_progress_bar(split, ctx); + split->region_split.right = add_compute_stats_view(ui, split, &ui->arena, + VT_COMPUTE_LATEST_STATS_VIEW); - ComputeStatsView *compute_stats = &split->u.region_split.right->u.group.first->u.compute_stats_view; + ComputeStatsView *compute_stats = &split->region_split.right->group.first->compute_stats_view; compute_stats->ctx = ctx; compute_stats->stats = &ui->latest_compute_stats; ctx->ui_read_params = 1; /* NOTE(rnp): shrink variable size once this fires */ - ASSERT(ui->arena.beg - (u8 *)ui < KB(64)); + assert(ui->arena.beg - (u8 *)ui < KB(64)); } function void validate_ui_parameters(BeamformerUIParameters *p) { if (p->output_min_coordinate[0] > p->output_max_coordinate[0]) - SWAP(p->output_min_coordinate[0], p->output_max_coordinate[0]) + swap(p->output_min_coordinate[0], p->output_max_coordinate[0]); if (p->output_min_coordinate[2] > p->output_max_coordinate[2]) - SWAP(p->output_min_coordinate[2], p->output_max_coordinate[2]) + swap(p->output_min_coordinate[2], p->output_max_coordinate[2]); } function void @@ -2839,7 +2968,8 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw /* NOTE: process interactions first because the user interacted with * the ui that was presented last frame */ - ui_interact(ui, input, ctx->window_size); + Rect window_rect = {.size = {.w = ctx->window_size.w, .h = ctx->window_size.h}}; + ui_interact(ui, input, window_rect); if (ui->flush_params) { i32 lock = BeamformerSharedMemoryLockKind_Parameters; @@ -2858,7 +2988,6 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw } /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */ - Rect window_rect = {.size = {.w = ctx->window_size.w, .h = ctx->window_size.h}}; update_frame_views(ui, window_rect); BeginDrawing(); @@ -2867,9 +2996,9 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one); draw_ui_regions(ui, window_rect, input->mouse); - if (ui->interaction.type == IT_TEXT) - draw_active_text_box(ui, ui->interaction.active); - if (ui->interaction.type == IT_MENU) - draw_active_menu(ui, ui->arena, ui->interaction.active, input->mouse, window_rect); + draw_floating_widgets(ui, window_rect, input->mouse); + + if (ui->interaction.kind == InteractionKind_Text) + draw_active_text_box(ui, ui->interaction.var); EndDrawing(); } diff --git a/util.h b/util.h @@ -68,7 +68,7 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define ORONE(x) ((x)? (x) : 1) #define SIGN(x) ((x) < 0? -1 : 1) -#define SWAP(a, b) {typeof(a) __tmp = (a); (a) = (b); (b) = __tmp;} +#define swap(a, b) do {typeof(a) __tmp = (a); (a) = (b); (b) = __tmp;} while(0) /* NOTE(rnp): no guarantees about actually getting an element */ #define SLLPop(list) list; list = list ? list->next : 0