ogl_beamforming

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

Commit: 226250857983e111c7c95a6f1ae1b4e7f8d14b16
Parent: 62bbff4a2ff30b91b07091e89d8d893800180cbb
Author: Randy Palamar
Date:   Thu, 13 Mar 2025 22:00:35 -0600

ui: simplify input text box drawing

By overlaying an active input on top of the underlying display the
rest of the code can simplified.

There is probably a further compression to be made in
draw_variable_list but I will leave it for now.

Diffstat:
Mui.c | 170+++++++++++++++++++++++++++++++++++++------------------------------------------
1 file changed, 79 insertions(+), 91 deletions(-)

diff --git a/ui.c b/ui.c @@ -7,9 +7,6 @@ #define NORMALIZED_FG_COLOUR colour_from_normalized(FG_COLOUR) -/* NOTE: extra space used for allowing mouse clicks after end of text */ -#define TEXT_BOX_EXTRA_X 10.0f - #define TEXT_HOVER_SPEED 5.0f #define RULER_TEXT_PAD 10.0f @@ -150,7 +147,6 @@ typedef struct { typedef struct { Variable *hot; - Variable *next_hot; Variable *active; InteractionType hot_type; InteractionType type; @@ -275,7 +271,7 @@ add_variable(Variable *group, Arena *arena, s8 name, u32 flags, VariableType typ static Variable * add_variable_group(Variable *group, Arena *arena, s8 name, VariableGroupType type, Font font) { - Variable *result = add_variable(group, arena, name, 0, VT_GROUP, font); + Variable *result = add_variable(group, arena, name, V_INPUT, VT_GROUP, font); result->u.group.type = type; return result; } @@ -489,6 +485,16 @@ draw_text_r(Font font, s8 text, v2 pos, f32 rotation, Color colour) } static Rect +extend_rect_centered(Rect r, v2 delta) +{ + r.size.w += delta.x; + r.size.h += delta.y; + r.pos.x -= delta.x / 2; + r.pos.y -= delta.y / 2; + return r; +} + +static Rect scale_rect_centered(Rect r, v2 scale) { Rect or = r; @@ -511,11 +517,11 @@ center_align_text_in_rect(Rect r, s8 text, Font font) } static b32 -hover_text(v2 mouse, Rect text_rect, f32 *hover_t, b32 can_advance) +hover_text(v2 mouse, Rect text_rect, f32 *hover_t) { b32 hovering = CheckCollisionPointRec(mouse.rl, text_rect.rl); - if (hovering && can_advance) *hover_t += TEXT_HOVER_SPEED * dt_for_frame; - else *hover_t -= TEXT_HOVER_SPEED * dt_for_frame; + if (hovering) *hover_t += TEXT_HOVER_SPEED * dt_for_frame; + else *hover_t -= TEXT_HOVER_SPEED * dt_for_frame; *hover_t = CLAMP01(*hover_t); return hovering; } @@ -605,7 +611,7 @@ do_scale_bar(BeamformerUI *ui, Stream *buf, ScaleBar *sb, ScaleBarDirection dire sb->screen_space_to_value = (v2){.x = (*sb->max_value - *sb->min_value) / tick_rect.size.x}; } - if (hover_text(mouse, tick_rect, &sb->hover_t, 1)) { + if (hover_text(mouse, tick_rect, &sb->hover_t)) { Variable *var = zero_struct(&ui->scratch_variable->base); var->u.generic = sb; var->type = VT_SCALE_BAR; @@ -770,49 +776,29 @@ draw_beamform_view(BeamformerCtx *ctx, Arena a, v2 mouse, BeamformerFrameView *v } static v2 -draw_text_beamformer_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, f32 *hover_t) -{ - InputState *is = &ui->text_input_state; - /* TODO(rnp): hack: but this will be removed soon */ - b32 var_text_active = (ui->interaction.type == IT_TEXT) && - ((var->type == ui->interaction.active->type) && - ((iptr)var->u.generic == (iptr)ui->interaction.active->u.generic)); - - Stream buf = arena_stream(&arena); - if (var_text_active) stream_append_s8(&buf, (s8){.len = is->buf_len, .data = is->buf}); - else stream_append_variable(&buf, var); - - v2 text_size = measure_text(ui->font, stream_to_s8(&buf)); - Rect text_rect = {.pos = at, .size = text_size}; - - if (hover_text(mouse, text_rect, hover_t, !var_text_active)) { - ui->interaction.hot = var; - is->hot_rect = text_rect; - is->hot_font = &ui->font; - } - - Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t)); - draw_text(ui->font, stream_to_s8(&buf), text_rect.pos, colour); - - return text_size; -} - -static v2 draw_beamformer_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, f32 *hover_t) { Stream buf = arena_stream(&arena); stream_append_variable(&buf, var); - v2 text_size = measure_text(ui->font, stream_to_s8(&buf)); - Rect text_rect = {.pos = at, .size = text_size}; - - if (hover_text(mouse, text_rect, hover_t, 1)) - ui->interaction.hot = var; - - Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t)); - draw_text(ui->font, stream_to_s8(&buf), text_rect.pos, colour); + Color colour = NORMALIZED_FG_COLOUR; + if (var->flags & V_INPUT) { + v2 text_size = measure_text(ui->font, stream_to_s8(&buf)); + Rect text_rect = {.pos = at, .size = text_size}; + text_rect = extend_rect_centered(text_rect, (v2){.x = 8}); + + if (hover_text(mouse, text_rect, hover_t)) { + if (var->flags & V_TEXT) { + InputState *is = &ui->text_input_state; + is->hot_rect = text_rect; + is->hot_font = &ui->font; + } + ui->interaction.hot = var; + } - return text_size; + colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t)); + } + return draw_text(ui->font, stream_to_s8(&buf), at, colour); } #define LISTING_CENTER_PAD 12.0f @@ -837,19 +823,10 @@ draw_variable_list(BeamformerUI *ui, Variable *group, Rect r, v2 mouse) BeamformerVariable *bv = (BeamformerVariable *)var; suffix = bv->suffix; - advance = draw_text(ui->font, bv->base.name, at, NORMALIZED_FG_COLOUR).y; + advance = draw_text(ui->font, var->name, at, NORMALIZED_FG_COLOUR).y; at.x += x_off + LISTING_CENTER_PAD; - if (var->flags & V_INPUT && var->flags & V_TEXT) { - draw_text_beamformer_variable(ui, ui->arena, var, at, mouse, &bv->hover_t); - } else if (var->flags & V_INPUT) { - draw_beamformer_variable(ui, ui->arena, var, at, mouse, &bv->hover_t); - } else { - Arena arena = ui->arena; - Stream buf = arena_stream(&arena); - stream_append_variable(&buf, var); - draw_text(ui->font, stream_to_s8(&buf), at, NORMALIZED_FG_COLOUR); - } + draw_beamformer_variable(ui, ui->arena, var, at, mouse, &bv->hover_t); while (var) { if (var->next) { @@ -885,13 +862,9 @@ draw_variable_list(BeamformerUI *ui, Variable *group, Rect r, v2 mouse) case VG_V4: { at.x += draw_text(ui->font, s8("{"), at, NORMALIZED_FG_COLOUR).x; while (v) { - if (v->base.flags & V_TEXT) { - at.x += draw_text_beamformer_variable(ui, ui->arena, - (Variable *)v, at, mouse, &v->hover_t).x; - } else { - at.x += draw_beamformer_variable(ui, ui->arena, - (Variable *)v, at, mouse, &v->hover_t).x; - } + at.x += draw_beamformer_variable(ui, ui->arena, + (Variable *)v, at, mouse, &v->hover_t).x; + v = (BeamformerVariable *)v->base.next; if (v) at.x += draw_text(ui->font, s8(", "), at, NORMALIZED_FG_COLOUR).x; @@ -989,31 +962,40 @@ draw_debug_overlay(BeamformerCtx *ctx, BeamformFrame *frame, Arena arena, Rect r } static void -draw_input_cursor(BeamformerUI *ui, v2 mouse) +draw_active_text_box(BeamformerUI *ui, Variable *var) { InputState *is = &ui->text_input_state; Rect box = is->rect; - s8 buf = {.len = is->buf_len, .data = is->buf}; - v2 ts = measure_text(ui->font, buf); - v2 pos = {.x = box.pos.x, .y = box.pos.y + (box.size.y - ts.y) / 2}; - - v4 bg = FOCUSED_COLOUR; - bg.a = 0; - Color cursor_colour = colour_from_normalized(lerp_v4(bg, FOCUSED_COLOUR, - CLAMP01(is->cursor_blink_t))); - buf.len = is->cursor; - v2 sts = measure_text(ui->font, buf); - f32 cursor_x = pos.x + sts.x; - f32 cursor_width; - if (is->cursor == is->buf_len) cursor_width = 20; - else cursor_width = 4; - Rect cursor_r = { - .pos = {.x = cursor_x, .y = pos.y}, - .size = {.w = cursor_width, .h = ts.h}, + s8 text = {.len = is->buf_len, .data = is->buf}; + v2 text_size = measure_text(ui->font, text); + v2 text_position = {.x = box.pos.x, .y = box.pos.y + (box.size.h - text_size.h) / 2}; + + f32 cursor_width = (is->cursor == is->buf_len) ? 16 : 4; + f32 cursor_offset = measure_text(ui->font, (s8){.data = text.data, .len = is->cursor}).w; + cursor_offset += text_position.x; + + box.size.w = MAX(box.size.w, text_size.w + cursor_width); + Rect background = extend_rect_centered(box, (v2){.x = 12, .y = 8}); + box = extend_rect_centered(box, (v2){.x = 8, .y = 4}); + + Rect cursor = { + .pos = {.x = cursor_offset, .y = text_position.y}, + .size = {.w = cursor_width, .h = text_size.h}, }; - DrawRectanglePro(cursor_r.rl, (Vector2){0}, 0, cursor_colour); + v4 cursor_colour = FOCUSED_COLOUR; + cursor_colour.a = CLAMP01(is->cursor_blink_t); + + f32 hover_t = 1; + if (var->type == VT_BEAMFORMER_VARIABLE) + hover_t = ((BeamformerVariable *)var)->hover_t; + + DrawRectangleRounded(background.rl, 0.2, 0, fade(BLACK, 0.8)); + DrawRectangleRounded(box.rl, 0.2, 0, colour_from_normalized(BG_COLOUR)); + draw_text(*is->font, text, text_position, + colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, hover_t))); + DrawRectanglePro(cursor.rl, (Vector2){0}, 0, colour_from_normalized(cursor_colour)); } static void @@ -1104,21 +1086,27 @@ end_text_input(InputState *is, Variable *var) BeamformerVariable *bv = (BeamformerVariable *)var; ASSERT(bv->subtype == VT_F32); scale = bv->params.display_scale; + bv->hover_t = 0; } f32 value = parse_f64((s8){.len = is->buf_len, .data = is->buf}) / scale; ui_store_variable(var, &value); } static void -update_text_input(InputState *is) +update_text_input(InputState *is, Variable *var) { - if (is->cursor == -1) - return; + ASSERT(is->cursor != -1); is->cursor_blink_t += is->cursor_blink_scale * dt_for_frame; if (is->cursor_blink_t >= 1) is->cursor_blink_scale = -1.5f; if (is->cursor_blink_t <= 0) is->cursor_blink_scale = 1.5f; + if (var->type == VT_BEAMFORMER_VARIABLE) { + BeamformerVariable *bv = (BeamformerVariable *)var; + bv->hover_t -= 2 * TEXT_HOVER_SPEED * dt_for_frame; + bv->hover_t = CLAMP01(bv->hover_t); + } + /* NOTE: handle multiple input keys on a single frame */ i32 key = GetCharPressed(); while (key > 0) { @@ -1354,10 +1342,10 @@ ui_interact(BeamformerCtx *ctx, BeamformerInput *input) switch (is->type) { case IT_NONE: break; case IT_NOP: break; - case IT_DISPLAY: display_interaction(ui, input->mouse); break; - case IT_SCROLL: ui_end_interact(ctx, input->mouse); break; - case IT_SET: ui_end_interact(ctx, input->mouse); break; - case IT_TEXT: update_text_input(&ui->text_input_state); break; + case IT_DISPLAY: display_interaction(ui, input->mouse); break; + case IT_SCROLL: ui_end_interact(ctx, input->mouse); break; + case IT_SET: ui_end_interact(ctx, input->mouse); break; + case IT_TEXT: update_text_input(&ui->text_input_state, is->active); break; case IT_DRAG: { if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { ui_end_interact(ctx, input->mouse); @@ -1491,6 +1479,6 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw draw_debug_overlay(ctx, frame_to_draw, ui->arena, lr); if (ui->interaction.type == IT_TEXT) - draw_input_cursor(ui, mouse); + draw_active_text_box(ui, ui->interaction.active); EndDrawing(); }