ogl_beamforming

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

Commit: 64db6913f4c561e26884a9e1a83c65c02ff9e14a
Parent: d660ce21ceb8d3126bb05180f5ee4f3fb2854135
Author: Randy Palamar
Date:   Sat, 14 Jun 2025 13:18:07 -0600

ui: clean up ruler interaction jank

Diffstat:
Mui.c | 166+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
1 file changed, 99 insertions(+), 67 deletions(-)

diff --git a/ui.c b/ui.c @@ -73,9 +73,9 @@ typedef struct { } InputState; typedef enum { - RS_NONE, - RS_START, - RS_HOLD, + RulerState_None, + RulerState_Start, + RulerState_Hold, } RulerState; typedef struct { @@ -306,6 +306,7 @@ typedef enum { InteractionKind_Button, InteractionKind_Drag, InteractionKind_Menu, + InteractionKind_Ruler, InteractionKind_Scroll, InteractionKind_Set, InteractionKind_Text, @@ -1471,6 +1472,13 @@ interactions_equal(Interaction a, Interaction b) } function b32 +interaction_is_sticky(Interaction a) +{ + b32 result = a.kind == InteractionKind_Text || a.kind == InteractionKind_Ruler; + return result; +} + +function b32 interaction_is_hot(BeamformerUI *ui, Interaction a) { b32 result = interactions_equal(ui->hot_interaction, a); @@ -1797,6 +1805,35 @@ draw_table(BeamformerUI *ui, Arena arena, Table *table, Rect draw_rect, TextSpec } function void +draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts) +{ + v2 vr_max_p = add_v2(view_rect.pos, view_rect.size); + v2 start_p = world_point_to_screen_2d(view->ruler.start, XZ(view->min_coordinate), + XZ(view->max_coordinate), view_rect.pos, vr_max_p); + v2 end_p = world_point_to_screen_2d(view->ruler.end, XZ(view->min_coordinate), + XZ(view->max_coordinate), view_rect.pos, vr_max_p); + + Color rl_colour = colour_from_normalized(ts.colour); + DrawCircleV(start_p.rl, 3, rl_colour); + DrawLineEx(end_p.rl, start_p.rl, 2, rl_colour); + DrawCircleV(end_p.rl, 3, rl_colour); + + Stream buf = arena_stream(a); + stream_append_f64(&buf, 1e3 * magnitude_v2(sub_v2(view->ruler.end, view->ruler.start)), 100); + stream_append_s8(&buf, s8(" mm")); + + v2 txt_p = start_p; + v2 txt_s = measure_text(*ts.font, stream_to_s8(&buf)); + v2 pixel_delta = sub_v2(start_p, end_p); + if (pixel_delta.y < 0) txt_p.y -= txt_s.y; + if (pixel_delta.x < 0) txt_p.x -= txt_s.x; + if (txt_p.x < view_rect.pos.x) txt_p.x = view_rect.pos.x; + if (txt_p.x + txt_s.x > vr_max_p.x) txt_p.x -= (txt_p.x + txt_s.x) - vr_max_p.x; + + draw_text(stream_to_s8(&buf), txt_p, &ts); +} + +function void draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect display_rect, v2 mouse) { assert(var->type == VT_BEAMFORMER_FRAME_VIEW); @@ -1925,39 +1962,7 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa text_spec.limits.size.w += 16; } - if (view->ruler.state != RS_NONE) { - v2 vr_max_p = add_v2(vr.pos, vr.size); - v2 start_p = world_point_to_screen_2d(view->ruler.start, XZ(view->min_coordinate), - XZ(view->max_coordinate), vr.pos, vr_max_p); - v2 end_p = clamp_v2_rect(mouse, vr); - - if (view->ruler.state == RS_HOLD) { - end_p = world_point_to_screen_2d(view->ruler.end, XZ(view->min_coordinate), - XZ(view->max_coordinate), vr.pos, vr_max_p); - } - - v2 start_p_world = view->ruler.start; - v2 end_p_world = screen_point_to_world_2d(end_p, vr.pos, vr_max_p, - XZ(view->min_coordinate), - XZ(view->max_coordinate)); - v2 pixel_delta = sub_v2(start_p, end_p); - v2 m_delta = sub_v2(end_p_world, start_p_world); - - Color rl_colour = colour_from_normalized(text_spec.colour); - DrawCircleV(start_p.rl, 3, rl_colour); - DrawLineEx(end_p.rl, start_p.rl, 2, rl_colour); - DrawCircleV(end_p.rl, 3, rl_colour); - - Stream buf = arena_stream(a); - stream_append_f64(&buf, 1e3 * magnitude_v2(m_delta), 100); - stream_append_s8(&buf, s8(" mm")); - - v2 txt_p = start_p; - v2 txt_s = measure_text(*text_spec.font, stream_to_s8(&buf)); - if (pixel_delta.y < 0) txt_p.y -= txt_s.y; - if (pixel_delta.x < 0) txt_p.x -= txt_s.x; - draw_text(stream_to_s8(&buf), txt_p, &text_spec); - } + if (view->ruler.state != RulerState_None) draw_view_ruler(view, a, vr, text_spec); 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("")); @@ -2725,9 +2730,27 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) if (scroll) hot->kind = InteractionKind_Scroll; else hot->kind = InteractionKind_Nop; }break; - case VT_BEAMFORMER_FRAME_VIEW: - case VT_CYCLER: - { + case VT_BEAMFORMER_FRAME_VIEW:{ + if (scroll) { + hot->kind = InteractionKind_Scroll; + } else { + hot->kind = InteractionKind_Nop; + BeamformerFrameView *bv = hot->var->generic; + switch (++bv->ruler.state) { + case RulerState_Start:{ + hot->kind = InteractionKind_Ruler; + v2 r_max = add_v2(hot->rect.pos, hot->rect.size); + v2 p = screen_point_to_world_2d(input->mouse, hot->rect.pos, r_max, + XZ(bv->min_coordinate), + XZ(bv->max_coordinate)); + bv->ruler.start = p; + }break; + case RulerState_Hold:{}break; + default:{ bv->ruler.state = RulerState_None; }break; + } + } + }break; + case VT_CYCLER:{ if (scroll) hot->kind = InteractionKind_Scroll; else hot->kind = InteractionKind_Set; }break; @@ -2778,23 +2801,6 @@ ui_end_interact(BeamformerUI *ui, v2 mouse) *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(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; - else bv->ruler.end = p; - }break; - default:{ bv->ruler.state = RS_NONE; }break; - } - }break; InvalidDefaultCase; } }break; @@ -2808,6 +2814,10 @@ ui_end_interact(BeamformerUI *ui, v2 mouse) mouse, it->var, 1); } }break; + case InteractionKind_Ruler:{ + assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW); + ((BeamformerFrameView *)it->var->generic)->ruler.state = RulerState_None; + }break; case InteractionKind_Button:{ ui_button_interaction(ui, it->var); }break; case InteractionKind_Scroll:{ scroll_interaction(it->var, GetMouseWheelMoveV().y); }break; case InteractionKind_Text:{ ui_close_widget(ui, ui->text_input_state.container); }break; @@ -2831,35 +2841,57 @@ ui_end_interact(BeamformerUI *ui, v2 mouse) ui->interaction = (Interaction){.kind = InteractionKind_None}; } +function void +ui_sticky_interaction_check_end(BeamformerUI *ui, v2 mouse) +{ + Interaction *it = &ui->interaction; + switch (it->kind) { + case InteractionKind_Ruler:{ + if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) || !point_in_rect(mouse, it->rect)) + ui_end_interact(ui, mouse); + }break; + case InteractionKind_Text:{ + Interaction text_box = auto_interaction({{0}}, ui->text_input_state.container); + if (!interactions_equal(text_box, ui->hot_interaction)) + ui_end_interact(ui, mouse); + }break; + InvalidDefaultCase; + } +} function void ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect) { - InteractionKind current = ui->interaction.kind; - if (current == InteractionKind_None || current == InteractionKind_Text) { + Interaction *it = &ui->interaction; + if (it->kind == InteractionKind_None || interaction_is_sticky(*it)) { 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) { - /* NOTE(rnp): avoid ending when user clicks existing text box */ - if (current == InteractionKind_Text) { - Interaction text_box = auto_interaction({0}, ui->text_input_state.container); - if (!interactions_equal(text_box, ui->hot_interaction)) - ui_end_interact(ui, input->mouse); - } + if (it->kind != InteractionKind_None) + ui_sticky_interaction_check_end(ui, input->mouse); ui_begin_interact(ui, input, wheel_moved); } } - switch (ui->interaction.kind) { - case InteractionKind_Nop:{ ui->interaction.kind = InteractionKind_None; }break; + switch (it->kind) { + case InteractionKind_Nop:{ it->kind = InteractionKind_None; }break; case InteractionKind_None:{}break; case InteractionKind_Text:{ - if (update_text_input(&ui->text_input_state, ui->interaction.var)) + if (update_text_input(&ui->text_input_state, it->var)) ui_end_interact(ui, input->mouse); }break; + case InteractionKind_Ruler:{ + assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW); + BeamformerFrameView *bv = it->var->generic; + v2 r_max = add_v2(it->rect.pos, it->rect.size); + v2 mouse = clamp_v2_rect(input->mouse, it->rect); + bv->ruler.end = screen_point_to_world_2d(mouse, it->rect.pos, r_max, + XZ(bv->min_coordinate), + XZ(bv->max_coordinate)); + }break; case InteractionKind_Drag:{ if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { ui_end_interact(ui, input->mouse);