Commit: c78646141a525692dfa3d13a8d99fa36d2db2568
Parent: a85112dca0dd2c5509e71884b574ccfeccd9c743
Author: Randy Palamar
Date: Tue, 8 Jul 2025 06:25:48 -0600
ui: add live controls view
Diffstat:
2 files changed, 311 insertions(+), 67 deletions(-)
diff --git a/beamformer_parameters.h b/beamformer_parameters.h
@@ -5,6 +5,9 @@
* [ ]: Have a method for the library caller to take ownership of a "compute context"
* [ ]: Upload previously exported data for display. maybe this is a UI thing but doing it
* programatically would be nice.
+ * [ ]: Add interface for multi frame upload. RF upload already uses an offset into SM so
+ * that part works fine. We just need a way of specify a multi frame upload. (Data must
+ * be organized for simple offset access per frame).
*/
/* X(enumarant, number, shader file name, needs header, pretty name) */
@@ -163,11 +166,17 @@ _Static_assert((sizeof(BeamformerParameters) & 15) == 0,
#define BEAMFORMER_LIVE_IMAGING_DIRTY_FLAG_LIST \
X(ImagePlaneOffsets, 0) \
- X(TransmitPower, 1)
+ X(TransmitPower, 1) \
+ X(TGCControlPoints, 2) \
+ X(SaveData, 3) \
+ X(StopImaging, 4)
/* NOTE(rnp): if this exceeds 32 you need to fix the flag handling code */
typedef struct {
uint32_t active;
+ uint32_t save_enabled;
+ uint32_t save_active;
float transmit_power;
float image_plane_offsets[BeamformerViewPlaneTag_Count];
+ float tgc_control_points[8];
} BeamformerLiveImagingParameters;
diff --git a/ui.c b/ui.c
@@ -1,12 +1,11 @@
/* See LICENSE for license details. */
/* TODO(rnp):
- * [ ]: live parameters control panel
+ * [ ]: bug: group at end of parameter listing
* [ ]: refactor: ui should be in its own thread and that thread should only be concerned with the ui
* [ ]: 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
* [ ]: 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
* - title bar struct with expanded. Check when pushing onto draw stack; if expanded
* do normal behaviour else make size title bar size and ignore the splits fraction.
@@ -32,6 +31,7 @@
#define FOCUSED_COLOUR (v4){.r = 0.86, .g = 0.28, .b = 0.21, .a = 1.0}
#define HOVERED_COLOUR (v4){.r = 0.11, .g = 0.50, .b = 0.59, .a = 1.0}
#define RULER_COLOUR (v4){.r = 1.00, .g = 0.70, .b = 0.00, .a = 1.0}
+#define BORDER_COLOUR v4_lerp(FG_COLOUR, BG_COLOUR, 0.85)
#define MENU_PLUS_COLOUR (v4){.r = 0.33, .g = 0.42, .b = 1.00, .a = 1.0}
#define MENU_CLOSE_COLOUR FOCUSED_COLOUR
@@ -46,6 +46,7 @@ read_only global v4 g_colour_palette[] = {
};
#define HOVER_SPEED 5.0f
+#define BLINK_SPEED 1.5f
#define TABLE_CELL_PAD_HEIGHT 2.0f
#define TABLE_CELL_PAD_WIDTH 8.0f
@@ -153,6 +154,7 @@ typedef enum {
VT_BEAMFORMER_FRAME_VIEW,
VT_COMPUTE_STATS_VIEW,
VT_COMPUTE_PROGRESS_BAR,
+ VT_LIVE_CONTROLS_VIEW,
VT_SCALE_BAR,
VT_UI_BUTTON,
VT_UI_MENU,
@@ -233,6 +235,7 @@ typedef enum {
V_RADIO_BUTTON = 1 << 2,
V_EXTRA_ACTION = 1 << 3,
V_HIDES_CURSOR = 1 << 4,
+ V_LIVE_CONTROL = 1 << 28,
V_CAUSES_COMPUTE = 1 << 29,
V_UPDATE_VIEW = 1 << 30,
} VariableFlags;
@@ -327,6 +330,17 @@ struct BeamformerFrameView {
};
};
+typedef struct BeamformerLiveControlsView BeamformerLiveControlsView;
+struct BeamformerLiveControlsView {
+ Variable transmit_power;
+ Variable tgc_control_points[countof(((BeamformerLiveImagingParameters *)0)->tgc_control_points)];
+ Variable save_button;
+ Variable stop_button;
+ f32 save_button_blink_t;
+ f32 save_button_blink_scale;
+ u32 hot_field_flag;
+};
+
typedef enum {
InteractionKind_None,
InteractionKind_Nop,
@@ -529,6 +543,15 @@ clamp_text_to_width(Font font, s8 text, f32 limit)
return result;
}
+function v2
+align_text_in_rect(s8 text, Rect r, Font font)
+{
+ v2 size = measure_text(font, text);
+ v2 pos = v2_add(r.pos, v2_scale(v2_sub(r.size, size), 0.5));
+ v2 result = clamp_v2_rect(pos, r);
+ return result;
+}
+
function Texture
make_raylib_texture(BeamformerFrameView *v)
{
@@ -986,14 +1009,20 @@ end_variable_group(Variable *group)
return group->parent;
}
+function void
+fill_variable_cycler(Variable *cycler, u32 *store, s8 *labels, u32 cycle_count)
+{
+ cycler->cycler.cycle_length = cycle_count;
+ cycler->cycler.state = store;
+ cycler->cycler.labels = labels;
+}
+
function Variable *
add_variable_cycler(BeamformerUI *ui, Variable *group, Arena *arena, u32 flags, Font font, s8 name,
u32 *store, s8 *labels, u32 cycle_count)
{
Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_CYCLER, font);
- result->cycler.cycle_length = cycle_count;
- result->cycler.state = store;
- result->cycler.labels = labels;
+ fill_variable_cycler(result, store, labels, cycle_count);
return result;
}
@@ -1064,11 +1093,9 @@ add_floating_view(BeamformerUI *ui, Arena *arena, VariableType type, v2 at, Vari
}
function void
-add_beamformer_variable_f32(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, s8 suffix,
- f32 *store, v2 limits, f32 display_scale, f32 scroll_scale, u32 flags,
- Font font)
+fill_beamformer_variable_f32(Variable *var, s8 suffix, f32 *store, v2 limits, f32 display_scale,
+ f32 scroll_scale)
{
- Variable *var = add_variable(ui, group, arena, name, flags, VT_BEAMFORMER_VARIABLE, font);
BeamformerVariable *bv = &var->beamformer_variable;
bv->suffix = suffix;
bv->store = store;
@@ -1078,6 +1105,15 @@ add_beamformer_variable_f32(BeamformerUI *ui, Variable *group, Arena *arena, s8
bv->limits = limits;
}
+function void
+add_beamformer_variable_f32(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, s8 suffix,
+ f32 *store, v2 limits, f32 display_scale, f32 scroll_scale, u32 flags,
+ Font font)
+{
+ Variable *var = add_variable(ui, group, arena, name, flags, VT_BEAMFORMER_VARIABLE, font);
+ fill_beamformer_variable_f32(var, suffix, store, limits, display_scale, scroll_scale);
+}
+
function Variable *
add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx)
{
@@ -1318,6 +1354,40 @@ add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, Beamfor
}
function Variable *
+add_live_controls_view(BeamformerUI *ui, Variable *parent, Arena *arena)
+{
+ BeamformerSharedMemory *sm = ui->shared_memory.region;
+ BeamformerLiveImagingParameters *lip = &sm->live_imaging_parameters;
+ /* 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("Live Controls"), 0, 1, 0);
+ result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0,
+ VT_LIVE_CONTROLS_VIEW, ui->small_font);
+ Variable *view = result->view.child;
+ BeamformerLiveControlsView *lv = view->generic = push_struct(arena, typeof(*lv));
+
+ fill_variable(&lv->transmit_power, view, s8(""), V_INPUT|V_LIVE_CONTROL,
+ VT_BEAMFORMER_VARIABLE, ui->small_font);
+ fill_beamformer_variable_f32(&lv->transmit_power, s8(""), &lip->transmit_power, (v2){{0, 1}},
+ 100, 0.05);
+
+ for (u32 i = 0; i < countof(lv->tgc_control_points); i++) {
+ Variable *v = lv->tgc_control_points + i;
+ fill_variable(v, view, s8(""), V_INPUT|V_LIVE_CONTROL, VT_BEAMFORMER_VARIABLE, ui->small_font);
+ fill_beamformer_variable_f32(v, s8(""), lip->tgc_control_points + i, (v2){{0, 1}}, 0, 0.05);
+ }
+
+ fill_variable(&lv->stop_button, view, s8("Stop Imaging"), V_INPUT|V_LIVE_CONTROL,
+ VT_B32, ui->small_font);
+
+ read_only local_persist s8 save_labels[] = {s8_comp("Save Data"), s8_comp("Saving...")};
+ fill_variable(&lv->save_button, view, s8("Save Data"), V_INPUT|V_LIVE_CONTROL,
+ VT_CYCLER, ui->small_font);
+ fill_variable_cycler(&lv->save_button, &lip->save_active, save_labels, countof(save_labels));
+
+ return result;
+}
+
+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);
@@ -2051,6 +2121,70 @@ draw_radio_button(BeamformerUI *ui, Variable *var, v2 at, v2 mouse, v4 base_colo
return result;
}
+function f32
+draw_variable_slider(BeamformerUI *ui, Variable *var, Rect r, f32 fill, v4 fill_colour, v2 mouse)
+{
+ f32 border_thick = 3;
+ f32 bar_height_frac = 0.8;
+ v2 bar_size = {{6, bar_height_frac * r.size.y}};
+
+ Rect inner = shrink_rect_centered(r, (v2){{2 * border_thick, // NOTE(rnp): raylib jank
+ MAX(0, 2 * (r.size.y - bar_size.y))}});
+ Rect filled = inner;
+ filled.size.w *= fill;
+
+ Rect bar;
+ bar.pos = v2_add(r.pos, (v2){{fill * (r.size.w - bar_size.w), (1 - bar_height_frac) * 0.5 * r.size.y}});
+ bar.size = bar_size;
+ v4 bar_colour = v4_lerp(FG_COLOUR, FOCUSED_COLOUR, var->hover_t);
+
+ hover_interaction(ui, mouse, auto_interaction(inner, var));
+
+ DrawRectangleRec(filled.rl, colour_from_normalized(fill_colour));
+ DrawRectangleRoundedLinesEx(inner.rl, 0.2, 0, border_thick, BLACK);
+ DrawRectangleRounded(bar.rl, 0.6, 1, colour_from_normalized(bar_colour));
+
+ return r.size.y;
+}
+
+function v2
+draw_fancy_button(BeamformerUI *ui, Variable *var, s8 label, Rect r, v4 border_colour, v2 mouse, TextSpec ts)
+{
+ assert(ts.font->baseSize <= r.size.h * 0.8);
+ f32 pad = 0.1 * r.size.h;
+
+ v2 shadow_off = {{2.5, 3}};
+ f32 border_thick = 3;
+ v2 border_size = v2_add((v2){{pad + 2 * border_thick, pad}}, shadow_off);
+
+ Rect border = shrink_rect_centered(r, border_size);
+ Rect inner = shrink_rect_centered(border, (v2){{pad, pad}});
+
+ ts.limits.size = inner.size;
+ hover_interaction(ui, mouse, auto_interaction(inner, var));
+
+ border.pos = v2_add(border.pos, shadow_off);
+
+ DrawRectangleRoundedLinesEx(border.rl, 0.6, 0, border_thick, fade(BLACK, 0.8));
+ border.pos = v2_sub(border.pos, shadow_off);
+ DrawRectangleRounded(border.rl, 0.6, 1, colour_from_normalized(BG_COLOUR));
+ DrawRectangleRoundedLinesEx(border.rl, 0.6, 0, border_thick, colour_from_normalized(border_colour));
+
+ /* TODO(rnp): teach draw_text() about alignment */
+ v2 at = align_text_in_rect(label, inner, *ts.font);
+ at = v2_add(at, (v2){{3, 3}});
+ v4 base_colour = ts.colour;
+ ts.colour = (v4){{0, 0, 0, 0.8}};
+ draw_text(label, at, &ts);
+
+ at = v2_sub(at, (v2){{3, 3}});
+ ts.colour = v4_lerp(base_colour, HOVERED_COLOUR, var->hover_t);
+ draw_text(label, at, &ts);
+
+ v2 result = v2_add(r.size, border_size);
+ return result;
+}
+
function v2
draw_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, v4 base_colour, TextSpec text_spec)
{
@@ -2588,6 +2722,76 @@ draw_compute_stats_view(BeamformerUI *ui, Arena arena, Variable *view, Rect r, v
return result;
}
+function v2
+draw_live_controls_view(BeamformerUI *ui, Arena arena, Variable *var, Rect r, v2 mouse)
+{
+ BeamformerSharedMemory *sm = ui->shared_memory.region;
+ BeamformerLiveImagingParameters *lip = &sm->live_imaging_parameters;
+ BeamformerLiveControlsView *lv = var->generic;
+
+ TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED};
+ text_spec.limits.size.w = r.size.w;
+
+ v2 slider_size = {{MIN(140.0f, r.size.w), ui->font.baseSize}};
+ v2 button_size = {{MIN(r.size.w, slider_size.x + ui->font.baseSize), ui->font.baseSize * 1.5}};
+
+ f32 text_off = r.pos.x + 0.5 * MAX(0, (r.size.w - slider_size.w - ui->font.baseSize));
+ f32 slider_off = r.pos.x + 0.5 * (r.size.w - slider_size.w);
+ f32 button_off = r.pos.x + 0.5 * (r.size.w - button_size.w);
+
+ v2 at = {{text_off, r.pos.y}};
+
+ at.y += draw_text(s8("Power:"), at, &text_spec).y;
+ at.x = slider_off;
+ at.y += draw_variable_slider(ui, &lv->transmit_power, (Rect){.pos = at, .size = slider_size},
+ lip->transmit_power, g_colour_palette[2], mouse);
+
+ at.x = text_off;
+ at.y += draw_text(s8("TGC:"), at, &text_spec).y;
+ at.x = slider_off;
+ for (u32 i = 0; i < countof(lip->tgc_control_points); i++) {
+ Variable *v = lv->tgc_control_points + i;
+ at.y += draw_variable_slider(ui, v, (Rect){.pos = at, .size = slider_size},
+ lip->tgc_control_points[i], g_colour_palette[1], mouse);
+
+ if (interaction_is_hot(ui, auto_interaction(r, v)))
+ lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_TGCControlPoints;
+ }
+
+ at.x = button_off;
+ at.y += ui->font.baseSize * 0.5;
+ at.y += draw_fancy_button(ui, &lv->stop_button, lv->stop_button.name,
+ (Rect){.pos = at, .size = button_size},
+ BORDER_COLOUR, mouse, text_spec).y;
+
+ if (lip->save_enabled) {
+ b32 active = lip->save_active;
+ s8 label = lv->save_button.cycler.labels[active % lv->save_button.cycler.cycle_length];
+
+ lv->save_button_blink_t += lv->save_button_blink_scale * dt_for_frame;
+ if (lv->save_button_blink_t >= 1.0f) lv->save_button_blink_scale = -BLINK_SPEED;
+ if (lv->save_button_blink_t <= 0.0f) lv->save_button_blink_scale = BLINK_SPEED;
+
+ v4 border_colour = BORDER_COLOUR;
+ if (active) border_colour = v4_lerp(border_colour, FOCUSED_COLOUR, lv->save_button_blink_t);
+
+ at.y += ui->font.baseSize * 0.25;
+ at.y += draw_fancy_button(ui, &lv->save_button, label, (Rect){.pos = at, .size = button_size},
+ border_colour, mouse, text_spec).y;
+
+ if (interaction_is_hot(ui, auto_interaction(r, &lv->save_button)))
+ lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_SaveData;
+ }
+
+ if (interaction_is_hot(ui, auto_interaction(r, &lv->transmit_power)))
+ lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_TransmitPower;
+ if (interaction_is_hot(ui, auto_interaction(r, &lv->stop_button)))
+ lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_StopImaging;
+
+ v2 result = {{r.size.w, at.y - r.pos.y}};
+ return result;
+}
+
struct variable_iterator { Variable *current; };
function i32
variable_iterator_next(struct variable_iterator *it)
@@ -2651,8 +2855,6 @@ draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2
{
assert(group->type == VT_GROUP);
Table *table = table_new(&arena, 0, TextAlignment_Left, TextAlignment_Left, TextAlignment_Right);
- /* NOTE(rnp): minimum width for middle column */
- table->widths[1] = 150;
i32 nesting = 0;
for (struct variable_iterator it = {group->group.first};
@@ -2662,8 +2864,6 @@ draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2
while (nesting > 0) {
table = table_begin_subtable(table, &arena, TextAlignment_Left,
TextAlignment_Center, TextAlignment_Right);
- /* NOTE(rnp): minimum width for middle column */
- table->widths[1] = 100;
nesting--;
}
while (nesting < 0) { table = table_end_subtable(table); nesting++; }
@@ -2790,6 +2990,13 @@ draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec tex
size = draw_compute_progress_bar(ui, ui->arena, &var->compute_progress_bar, r);
} break;
case VT_COMPUTE_STATS_VIEW:{ size = draw_compute_stats_view(ui, ui->arena, var, r, mouse); }break;
+ case VT_LIVE_CONTROLS_VIEW:{
+ if (view->rect.size.h - r.size.h < 0)
+ r.pos.y += 0.5 * (r.size.h - view->rect.size.h);
+ BeamformerSharedMemory *sm = ui->shared_memory.region;
+ if (sm->live_imaging_parameters.active)
+ size = draw_live_controls_view(ui, ui->arena, var, r, mouse);
+ }break;
InvalidDefaultCase;
}
@@ -3031,8 +3238,8 @@ update_text_input(InputState *is, Variable *var)
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 (is->cursor_blink_t >= 1.0f) is->cursor_blink_scale = -BLINK_SPEED;
+ if (is->cursor_blink_t <= 0.0f) is->cursor_blink_scale = BLINK_SPEED;
var->hover_t -= 2 * HOVER_SPEED * dt_for_frame;
var->hover_t = CLAMP01(var->hover_t);
@@ -3214,63 +3421,60 @@ ui_button_interaction(BeamformerUI *ui, Variable *button)
function void
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;
+ 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_TEXT_BOX:
case VT_UI_MENU:
{
- if (hot->var->type == VT_UI_MENU) {
- hot->kind = InteractionKind_Drag;
+ if (hot.var->type == VT_UI_MENU) {
+ hot.kind = InteractionKind_Drag;
} else {
- hot->kind = InteractionKind_Text;
- begin_text_input(&ui->text_input_state, hot->rect, hot->var, input->mouse);
+ hot.kind = InteractionKind_Text;
+ begin_text_input(&ui->text_input_state, hot.rect, hot.var, input->mouse);
}
- ui_widget_bring_to_front(&ui->floating_widget_sentinal, hot->var);
+ 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;
+ if (scroll) hot.kind = InteractionKind_Scroll;
+ else hot.kind = InteractionKind_Nop;
}break;
case VT_X_PLANE_SHIFT:{
- assert(hot->var->parent && hot->var->parent->type == VT_BEAMFORMER_FRAME_VIEW);
- BeamformerFrameView *bv = hot->var->parent->generic;
+ assert(hot.var->parent && hot.var->parent->type == VT_BEAMFORMER_FRAME_VIEW);
+ BeamformerFrameView *bv = hot.var->parent->generic;
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
- XPlaneShift *xp = &hot->var->x_plane_shift;
+ XPlaneShift *xp = &hot.var->x_plane_shift;
xp->start_point = xp->end_point = bv->hit_test_point;
- hot->kind = InteractionKind_Drag;
+ hot.kind = InteractionKind_Drag;
} else {
if (scroll) {
- hot->kind = InteractionKind_Scroll;
- hot->var = &bv->threshold;
+ hot.kind = InteractionKind_Scroll;
+ hot.var = &bv->threshold;
} else {
- hot->kind = InteractionKind_Nop;
+ hot.kind = InteractionKind_Nop;
}
}
}break;
case VT_BEAMFORMER_FRAME_VIEW:{
if (scroll) {
- hot->kind = InteractionKind_Scroll;
+ hot.kind = InteractionKind_Scroll;
} else {
- BeamformerFrameView *bv = hot->var->generic;
+ BeamformerFrameView *bv = hot.var->generic;
switch (bv->kind) {
- case BeamformerFrameViewKind_3DXPlane:{
- if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) hot->kind = InteractionKind_Drag;
- else hot->kind = InteractionKind_Nop;
- }break;
+ case BeamformerFrameViewKind_3DXPlane:{ hot.kind = InteractionKind_Drag; }break;
default:{
- hot->kind = InteractionKind_Nop;
+ hot.kind = InteractionKind_Nop;
switch (++bv->ruler.state) {
case RulerState_Start:{
- hot->kind = InteractionKind_Ruler;
- v2 r_max = v2_add(hot->rect.pos, hot->rect.size);
- v2 p = screen_point_to_world_2d(input->mouse, hot->rect.pos, r_max,
+ hot.kind = InteractionKind_Ruler;
+ v2 r_max = v2_add(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;
@@ -3283,33 +3487,32 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll)
}
}break;
case VT_CYCLER:{
- if (scroll) hot->kind = InteractionKind_Scroll;
- else hot->kind = InteractionKind_Set;
+ 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;
- }
+ assert(hot.var->beamformer_variable.store_type == VT_F32);
} /* 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;
+ hot.kind = InteractionKind_Scroll;
+ } else if (hot.var->flags & V_TEXT) {
+ hot.kind = InteractionKind_Text;
Variable *w = add_floating_view(ui, &ui->arena, VT_UI_TEXT_BOX,
- hot->rect.pos, hot->var, 0);
- w->view.rect = hot->rect;
- begin_text_input(&ui->text_input_state, hot->rect, w, input->mouse);
+ hot.rect.pos, hot.var, 0);
+ w->view.rect = hot.rect;
+ begin_text_input(&ui->text_input_state, hot.rect, w, input->mouse);
+ } else {
+ hot.kind = InteractionKind_Drag;
}
}break;
InvalidDefaultCase;
}
}
- ui->interaction = ui->hot_interaction;
+ ui->interaction = hot;
if (ui->interaction.var->flags & V_HIDES_CURSOR) {
HideCursor();
@@ -3356,6 +3559,15 @@ ui_extra_actions(BeamformerUI *ui, Variable *var)
}
function void
+ui_live_control_update(BeamformerUI *ui, Variable *controls)
+{
+ assert(controls->type == VT_LIVE_CONTROLS_VIEW);
+ BeamformerSharedMemory *sm = ui->shared_memory.region;
+ BeamformerLiveControlsView *lv = controls->generic;
+ atomic_or_u32(&sm->live_imaging_dirty_flags, lv->hot_field_flag);
+}
+
+function void
ui_end_interact(BeamformerUI *ui, v2 mouse)
{
Interaction *it = &ui->interaction;
@@ -3427,6 +3639,9 @@ ui_end_interact(BeamformerUI *ui, v2 mouse)
frame->dirty = 1;
}
+ if (it->var->flags & V_LIVE_CONTROL)
+ ui_live_control_update(ui, it->var->parent);
+
if (it->var->flags & V_HIDES_CURSOR)
EnableCursor();
@@ -3495,6 +3710,17 @@ ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect)
v2 dMouse = v2_sub(input->mouse, input->last_mouse);
switch (it->var->type) {
+ case VT_BEAMFORMER_VARIABLE:{
+ BeamformerVariable *bv = &it->var->beamformer_variable;
+ switch (bv->store_type) {
+ case VT_F32:{
+ /* TODO(rnp): vertical sliders? */
+ f32 mouse_frac = CLAMP01((input->mouse.x - it->rect.pos.x) / it->rect.size.w);
+ *(f32 *)bv->store = bv->limits.x + mouse_frac * (bv->limits.y - bv->limits.x);
+ }break;
+ InvalidDefaultCase;
+ }
+ }break;
case VT_X_PLANE_SHIFT:{
assert(it->var->parent && it->var->parent->type == VT_BEAMFORMER_FRAME_VIEW);
v2 mouse = clamp_v2_rect(input->mouse, it->rect);
@@ -3540,6 +3766,8 @@ ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect)
}break;
default:{}break;
}
+ if (it->var->flags & V_LIVE_CONTROL)
+ ui_live_control_update(ui, it->var->parent);
}
} break;
default:{ ui_end_interact(ui, input->mouse); }break;
@@ -3568,12 +3796,19 @@ ui_init(BeamformerCtx *ctx, Arena store)
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,
+ Variable *split = ui->regions = add_ui_split(ui, 0, &ui->arena, s8("UI Root"), 0.36,
RSD_HORIZONTAL, ui->font);
- 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,
- BeamformerFrameViewKind_Latest, 0, 0);
+ split->region_split.left = add_ui_split(ui, split, &ui->arena, s8(""), 0.475,
+ RSD_VERTICAL, ui->font);
+
+ split = split->region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.70,
+ RSD_HORIZONTAL, ui->font);
+ {
+ split->region_split.left = add_beamformer_frame_view(ui, split, &ui->arena,
+ BeamformerFrameViewKind_Latest, 0, 0);
+ split->region_split.right = add_live_controls_view(ui, split, &ui->arena);
+ }
+ split = split->parent;
split = split->region_split.left;
split->region_split.left = add_beamformer_parameters_view(split, ctx);