ui.c (131466B)
1 /* See LICENSE for license details. */ 2 /* TODO(rnp): 3 * [ ]: bug: group at end of parameter listing 4 * [ ]: refactor: ui should be in its own thread and that thread should only be concerned with the ui 5 * [ ]: refactor: remove all the excessive measure_texts (cell drawing, hover_interaction in params table) 6 * [ ]: refactor: move remaining fragment shader stuff into ui 7 * [ ]: refactor: scale table to rect 8 * [ ]: scroll bar for views that don't have enough space 9 * [ ]: allow views to collapse to just their title bar 10 * - title bar struct with expanded. Check when pushing onto draw stack; if expanded 11 * do normal behaviour else make size title bar size and ignore the splits fraction. 12 * [ ]: enforce a minimum region size or allow regions themselves to scroll 13 * [ ]: refactor: add_variable_no_link() 14 * [ ]: refactor: draw_text_limited should clamp to rect and measure text itself 15 * [ ]: draw the ui with a post-order traversal instead of pre-order traversal 16 * [ ]: consider V_HOVER_GROUP and use that to implement submenus 17 * [ ]: menu's need to support nested groups 18 * [ ]: don't redraw on every refresh; instead redraw on mouse movement/event or when a new frame 19 * arrives. For animations the ui can have a list of "timers" which while active will 20 * do a redraw on every refresh until completed. 21 * [ ]: show full non-truncated string on hover 22 * [ ]: refactor: hovered element type and show hovered element in full even when truncated 23 * [ ]: visual indicator for broken shader stage gh#27 24 * [ ]: bug: cross-plane view with different dimensions for each plane 25 * [ ]: refactor: make table_skip_rows useful 26 * [ ]: refactor: better method of grouping variables for views such as FrameView/ComputeStatsView 27 */ 28 29 #define BG_COLOUR (v4){.r = 0.15, .g = 0.12, .b = 0.13, .a = 1.0} 30 #define FG_COLOUR (v4){.r = 0.92, .g = 0.88, .b = 0.78, .a = 1.0} 31 #define FOCUSED_COLOUR (v4){.r = 0.86, .g = 0.28, .b = 0.21, .a = 1.0} 32 #define HOVERED_COLOUR (v4){.r = 0.11, .g = 0.50, .b = 0.59, .a = 1.0} 33 #define RULER_COLOUR (v4){.r = 1.00, .g = 0.70, .b = 0.00, .a = 1.0} 34 #define BORDER_COLOUR v4_lerp(FG_COLOUR, BG_COLOUR, 0.85) 35 36 #define MENU_PLUS_COLOUR (v4){.r = 0.33, .g = 0.42, .b = 1.00, .a = 1.0} 37 #define MENU_CLOSE_COLOUR FOCUSED_COLOUR 38 39 read_only global v4 g_colour_palette[] = { 40 {{0.32, 0.20, 0.50, 1.00}}, 41 {{0.14, 0.39, 0.61, 1.00}}, 42 {{0.61, 0.14, 0.25, 1.00}}, 43 {{0.20, 0.60, 0.24, 1.00}}, 44 {{0.80, 0.60, 0.20, 1.00}}, 45 {{0.15, 0.51, 0.74, 1.00}}, 46 }; 47 48 #define HOVER_SPEED 5.0f 49 #define BLINK_SPEED 1.5f 50 51 #define TABLE_CELL_PAD_HEIGHT 2.0f 52 #define TABLE_CELL_PAD_WIDTH 8.0f 53 54 #define RULER_TEXT_PAD 10.0f 55 #define RULER_TICK_LENGTH 20.0f 56 57 #define UI_SPLIT_HANDLE_THICK 8.0f 58 #define UI_REGION_PAD 32.0f 59 60 /* TODO(rnp) smooth scroll */ 61 #define UI_SCROLL_SPEED 12.0f 62 63 #define LISTING_LINE_PAD 6.0f 64 #define TITLE_BAR_PAD 6.0f 65 66 typedef struct v2_sll { 67 struct v2_sll *next; 68 v2 v; 69 } v2_sll; 70 71 typedef struct BeamformerUI BeamformerUI; 72 typedef struct Variable Variable; 73 74 typedef struct { 75 u8 buf[64]; 76 i32 count; 77 i32 cursor; 78 f32 cursor_blink_t; 79 f32 cursor_blink_scale; 80 Font *font, *hot_font; 81 Variable *container; 82 } InputState; 83 84 typedef enum { 85 RulerState_None, 86 RulerState_Start, 87 RulerState_Hold, 88 } RulerState; 89 90 typedef struct { 91 v2 start; 92 v2 end; 93 RulerState state; 94 } Ruler; 95 96 typedef enum { 97 SB_LATERAL, 98 SB_AXIAL, 99 } ScaleBarDirection; 100 101 typedef struct { 102 f32 *min_value, *max_value; 103 v2_sll *savepoint_stack; 104 v2 scroll_scale; 105 f32 zoom_starting_coord; 106 ScaleBarDirection direction; 107 } ScaleBar; 108 109 typedef struct { f32 val, scale; } scaled_f32; 110 111 typedef enum { 112 RSD_VERTICAL, 113 RSD_HORIZONTAL, 114 } RegionSplitDirection; 115 116 typedef struct { 117 Variable *left; 118 Variable *right; 119 f32 fraction; 120 RegionSplitDirection direction; 121 } RegionSplit; 122 123 #define COMPUTE_STATS_VIEW_LIST \ 124 X(Average, "Average") \ 125 X(Bar, "Bar") 126 127 #define X(kind, ...) ComputeStatsViewKind_ ##kind, 128 typedef enum {COMPUTE_STATS_VIEW_LIST ComputeStatsViewKind_Count} ComputeStatsViewKind; 129 #undef X 130 131 typedef struct { 132 ComputeShaderStats *compute_shader_stats; 133 Variable *cycler; 134 ComputeStatsViewKind kind; 135 } ComputeStatsView; 136 137 typedef struct { 138 b32 *processing; 139 f32 *progress; 140 f32 display_t; 141 f32 display_t_velocity; 142 } ComputeProgressBar; 143 144 typedef enum { 145 VT_NULL, 146 VT_B32, 147 VT_F32, 148 VT_I32, 149 VT_U32, 150 VT_GROUP, 151 VT_CYCLER, 152 VT_SCALED_F32, 153 VT_BEAMFORMER_VARIABLE, 154 VT_BEAMFORMER_FRAME_VIEW, 155 VT_COMPUTE_STATS_VIEW, 156 VT_COMPUTE_PROGRESS_BAR, 157 VT_LIVE_CONTROLS_VIEW, 158 VT_SCALE_BAR, 159 VT_UI_BUTTON, 160 VT_UI_MENU, 161 VT_UI_REGION_SPLIT, 162 VT_UI_TEXT_BOX, 163 VT_UI_VIEW, 164 VT_X_PLANE_SHIFT, 165 } VariableType; 166 167 typedef enum { 168 VariableGroupKind_List, 169 /* NOTE(rnp): special group for vectors with components 170 * stored in separate memory locations */ 171 VariableGroupKind_Vector, 172 } VariableGroupKind; 173 174 typedef struct { 175 VariableGroupKind kind; 176 b32 expanded; 177 Variable *first; 178 Variable *last; 179 Variable *container; 180 } VariableGroup; 181 182 typedef enum { 183 UIViewFlag_CustomText = 1 << 0, 184 UIViewFlag_Floating = 1 << 1, 185 } UIViewFlags; 186 187 typedef struct { 188 Variable *child; 189 Variable *close; 190 Variable *menu; 191 Rect rect; 192 UIViewFlags flags; 193 } UIView; 194 195 /* X(id, text) */ 196 #define FRAME_VIEW_BUTTONS \ 197 X(FV_COPY_HORIZONTAL, "Copy Horizontal") \ 198 X(FV_COPY_VERTICAL, "Copy Vertical") 199 200 #define GLOBAL_MENU_BUTTONS \ 201 X(GM_OPEN_VIEW_RIGHT, "Open View Right") \ 202 X(GM_OPEN_VIEW_BELOW, "Open View Below") 203 204 #define X(id, text) UI_BID_ ##id, 205 typedef enum { 206 UI_BID_VIEW_CLOSE, 207 GLOBAL_MENU_BUTTONS 208 FRAME_VIEW_BUTTONS 209 } UIButtonID; 210 #undef X 211 212 typedef struct { 213 s8 *labels; 214 u32 *state; 215 u32 cycle_length; 216 } VariableCycler; 217 218 typedef struct { 219 s8 suffix; 220 f32 display_scale; 221 f32 scroll_scale; 222 v2 limits; 223 f32 *store; 224 } BeamformerVariable; 225 226 typedef struct { 227 v3 start_point; 228 v3 end_point; 229 } XPlaneShift; 230 231 typedef enum { 232 V_INPUT = 1 << 0, 233 V_TEXT = 1 << 1, 234 V_RADIO_BUTTON = 1 << 2, 235 V_EXTRA_ACTION = 1 << 3, 236 V_HIDES_CURSOR = 1 << 4, 237 V_LIVE_CONTROL = 1 << 28, 238 V_CAUSES_COMPUTE = 1 << 29, 239 V_UPDATE_VIEW = 1 << 30, 240 } VariableFlags; 241 242 struct Variable { 243 s8 name; 244 union { 245 void *generic; 246 BeamformerVariable beamformer_variable; 247 ComputeProgressBar compute_progress_bar; 248 ComputeStatsView compute_stats_view; 249 RegionSplit region_split; 250 ScaleBar scale_bar; 251 UIButtonID button; 252 UIView view; 253 VariableCycler cycler; 254 VariableGroup group; 255 XPlaneShift x_plane_shift; 256 scaled_f32 scaled_real32; 257 b32 bool32; 258 i32 signed32; 259 u32 unsigned32; 260 f32 real32; 261 }; 262 Variable *next; 263 Variable *parent; 264 VariableFlags flags; 265 VariableType type; 266 267 f32 hover_t; 268 f32 name_width; 269 }; 270 271 #define BEAMFORMER_FRAME_VIEW_KIND_LIST \ 272 X(Latest, "Latest") \ 273 X(3DXPlane, "3D X-Plane") \ 274 X(Indexed, "Indexed") \ 275 X(Copy, "Copy") 276 277 typedef enum { 278 #define X(kind, ...) BeamformerFrameViewKind_##kind, 279 BEAMFORMER_FRAME_VIEW_KIND_LIST 280 #undef X 281 BeamformerFrameViewKind_Count, 282 } BeamformerFrameViewKind; 283 284 typedef struct BeamformerFrameView BeamformerFrameView; 285 struct BeamformerFrameView { 286 BeamformerFrameViewKind kind; 287 b32 dirty; 288 BeamformerFrame *frame; 289 BeamformerFrameView *prev, *next; 290 291 uv2 texture_dim; 292 u32 textures[2]; 293 u32 texture_mipmaps; 294 295 /* NOTE(rnp): any pointers to variables are added to the menu and will 296 * be put onto the freelist if the view is closed. */ 297 298 Variable *kind_cycler; 299 Variable *log_scale; 300 Variable threshold; 301 Variable dynamic_range; 302 Variable gamma; 303 304 union { 305 /* BeamformerFrameViewKind_Latest/BeamformerFrameViewKind_Indexed */ 306 struct { 307 Variable lateral_scale_bar; 308 Variable axial_scale_bar; 309 Variable *lateral_scale_bar_active; 310 Variable *axial_scale_bar_active; 311 /* NOTE(rnp): if kind is Latest selects which plane to use 312 * if kind is Indexed selects the index */ 313 Variable *cycler; 314 u32 cycler_state; 315 316 Ruler ruler; 317 318 v3 min_coordinate; 319 v3 max_coordinate; 320 }; 321 322 /* BeamformerFrameViewKind_3DXPlane */ 323 struct { 324 Variable x_plane_shifts[2]; 325 Variable *demo; 326 f32 rotation; 327 v3 hit_test_point; 328 }; 329 }; 330 }; 331 332 typedef struct BeamformerLiveControlsView BeamformerLiveControlsView; 333 struct BeamformerLiveControlsView { 334 Variable transmit_power; 335 Variable tgc_control_points[countof(((BeamformerLiveImagingParameters *)0)->tgc_control_points)]; 336 Variable save_button; 337 Variable stop_button; 338 f32 save_button_blink_t; 339 f32 save_button_blink_scale; 340 u32 hot_field_flag; 341 }; 342 343 typedef enum { 344 InteractionKind_None, 345 InteractionKind_Nop, 346 InteractionKind_Auto, 347 InteractionKind_Button, 348 InteractionKind_Drag, 349 InteractionKind_Menu, 350 InteractionKind_Ruler, 351 InteractionKind_Scroll, 352 InteractionKind_Set, 353 InteractionKind_Text, 354 } InteractionKind; 355 356 typedef struct { 357 InteractionKind kind; 358 union { 359 void *generic; 360 Variable *var; 361 }; 362 Rect rect; 363 } Interaction; 364 365 #define auto_interaction(r, v) (Interaction){.kind = InteractionKind_Auto, .var = v, .rect = r} 366 367 struct BeamformerUI { 368 Arena arena; 369 370 Font font; 371 Font small_font; 372 373 Variable *regions; 374 Variable *variable_freelist; 375 376 Variable floating_widget_sentinal; 377 378 BeamformerFrameView *views; 379 BeamformerFrameView *view_freelist; 380 BeamformerFrame *frame_freelist; 381 382 Interaction interaction; 383 Interaction hot_interaction; 384 Interaction next_interaction; 385 386 InputState text_input_state; 387 388 /* TODO(rnp): ideally this isn't copied all over the place */ 389 BeamformerRenderModel unit_cube_model; 390 391 v2_sll *scale_bar_savepoint_freelist; 392 393 BeamformerFrame *latest_plane[BeamformerViewPlaneTag_Count + 1]; 394 395 BeamformerUIParameters params; 396 b32 flush_params; 397 398 FrameViewRenderContext *frame_view_render_context; 399 400 SharedMemoryRegion shared_memory; 401 BeamformerCtx *beamformer_context; 402 }; 403 404 typedef enum { 405 TF_NONE = 0, 406 TF_ROTATED = 1 << 0, 407 TF_LIMITED = 1 << 1, 408 TF_OUTLINED = 1 << 2, 409 } TextFlags; 410 411 typedef enum { 412 TextAlignment_Center, 413 TextAlignment_Left, 414 TextAlignment_Right, 415 } TextAlignment; 416 417 typedef struct { 418 Font *font; 419 Rect limits; 420 v4 colour; 421 v4 outline_colour; 422 f32 outline_thick; 423 f32 rotation; 424 TextAlignment align; 425 TextFlags flags; 426 } TextSpec; 427 428 typedef enum { 429 TRK_CELLS, 430 TRK_TABLE, 431 } TableRowKind; 432 433 typedef enum { 434 TableCellKind_None, 435 TableCellKind_Variable, 436 TableCellKind_VariableGroup, 437 } TableCellKind; 438 439 typedef struct { 440 s8 text; 441 union { 442 i64 integer; 443 Variable *var; 444 void *generic; 445 }; 446 TableCellKind kind; 447 f32 width; 448 } TableCell; 449 450 typedef struct { 451 void *data; 452 TableRowKind kind; 453 } TableRow; 454 455 typedef struct Table { 456 TableRow *data; 457 iz count; 458 iz capacity; 459 460 /* NOTE(rnp): counted by columns */ 461 TextAlignment *alignment; 462 f32 *widths; 463 464 v4 border_colour; 465 f32 column_border_thick; 466 f32 row_border_thick; 467 v2 size; 468 v2 cell_pad; 469 470 /* NOTE(rnp): row count including nested tables */ 471 i32 rows; 472 i32 columns; 473 474 struct Table *parent; 475 } Table; 476 477 typedef struct { 478 Table *table; 479 i32 row_index; 480 } TableStackFrame; 481 482 typedef struct { 483 TableStackFrame *data; 484 iz count; 485 iz capacity; 486 } TableStack; 487 488 typedef enum { 489 TIK_ROWS, 490 TIK_CELLS, 491 } TableIteratorKind; 492 493 typedef struct { 494 TableStack stack; 495 TableStackFrame frame; 496 497 TableRow *row; 498 i16 column; 499 i16 sub_table_depth; 500 501 TableIteratorKind kind; 502 503 f32 start_x; 504 TextAlignment alignment; 505 Rect cell_rect; 506 } TableIterator; 507 508 function v2 509 measure_glyph(Font font, u32 glyph) 510 { 511 ASSERT(glyph >= 0x20); 512 v2 result = {.y = font.baseSize}; 513 /* NOTE: assumes font glyphs are ordered ASCII */ 514 result.x = font.glyphs[glyph - 0x20].advanceX; 515 if (result.x == 0) 516 result.x = (font.recs[glyph - 0x20].width + font.glyphs[glyph - 0x20].offsetX); 517 return result; 518 } 519 520 function v2 521 measure_text(Font font, s8 text) 522 { 523 v2 result = {.y = font.baseSize}; 524 for (iz i = 0; i < text.len; i++) 525 result.x += measure_glyph(font, text.data[i]).x; 526 return result; 527 } 528 529 function s8 530 clamp_text_to_width(Font font, s8 text, f32 limit) 531 { 532 s8 result = text; 533 f32 width = 0; 534 for (iz i = 0; i < text.len; i++) { 535 f32 next = measure_glyph(font, text.data[i]).w; 536 if (width + next > limit) { 537 result.len = i; 538 break; 539 } 540 width += next; 541 } 542 return result; 543 } 544 545 function v2 546 align_text_in_rect(s8 text, Rect r, Font font) 547 { 548 v2 size = measure_text(font, text); 549 v2 pos = v2_add(r.pos, v2_scale(v2_sub(r.size, size), 0.5)); 550 v2 result = clamp_v2_rect(pos, r); 551 return result; 552 } 553 554 function Texture 555 make_raylib_texture(BeamformerFrameView *v) 556 { 557 Texture result; 558 result.id = v->textures[0]; 559 result.width = v->texture_dim.w; 560 result.height = v->texture_dim.h; 561 result.mipmaps = v->texture_mipmaps; 562 result.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 563 return result; 564 } 565 566 function void 567 stream_append_variable(Stream *s, Variable *var) 568 { 569 switch (var->type) { 570 case VT_UI_BUTTON: 571 case VT_GROUP:{ stream_append_s8(s, var->name); }break; 572 case VT_F32:{ stream_append_f64(s, var->real32, 100); }break; 573 case VT_B32:{ stream_append_s8(s, var->bool32 ? s8("True") : s8("False")); }break; 574 case VT_SCALED_F32:{ stream_append_f64(s, var->scaled_real32.val, 100); }break; 575 case VT_BEAMFORMER_VARIABLE:{ 576 BeamformerVariable *bv = &var->beamformer_variable; 577 stream_append_f64(s, *bv->store * bv->display_scale, 100); 578 }break; 579 case VT_CYCLER:{ 580 u32 index = *var->cycler.state; 581 if (var->cycler.labels) stream_append_s8(s, var->cycler.labels[index]); 582 else stream_append_u64(s, index); 583 }break; 584 InvalidDefaultCase; 585 } 586 } 587 588 function void 589 stream_append_variable_group(Stream *s, Variable *var) 590 { 591 switch (var->type) { 592 case VT_GROUP:{ 593 switch (var->group.kind) { 594 case VariableGroupKind_Vector:{ 595 Variable *v = var->group.first; 596 stream_append_s8(s, s8("{")); 597 while (v) { 598 stream_append_variable(s, v); 599 v = v->next; 600 if (v) stream_append_s8(s, s8(", ")); 601 } 602 stream_append_s8(s, s8("}")); 603 }break; 604 InvalidDefaultCase; 605 } 606 }break; 607 InvalidDefaultCase; 608 } 609 } 610 611 function s8 612 push_das_shader_kind(Stream *s, DASShaderKind shader, u32 transmit_count) 613 { 614 #define X(type, id, pretty, fixed_tx) s8_comp(pretty), 615 read_only local_persist s8 pretty_names[DASShaderKind_Count + 1] = {DAS_TYPES s8_comp("Invalid")}; 616 #undef X 617 #define X(type, id, pretty, fixed_tx) fixed_tx, 618 read_only local_persist u8 fixed_transmits[DASShaderKind_Count + 1] = {DAS_TYPES 0}; 619 #undef X 620 621 stream_append_s8(s, pretty_names[MIN(shader, DASShaderKind_Count)]); 622 if (!fixed_transmits[MIN(shader, DASShaderKind_Count)]) { 623 stream_append_byte(s, '-'); 624 stream_append_u64(s, transmit_count); 625 } 626 627 return stream_to_s8(s); 628 } 629 630 function s8 631 push_custom_view_title(Stream *s, Variable *var) 632 { 633 switch (var->type) { 634 case VT_COMPUTE_STATS_VIEW:{ 635 stream_append_s8(s, s8("Compute Stats: ")); 636 stream_append_variable(s, var->compute_stats_view.cycler); 637 }break; 638 case VT_COMPUTE_PROGRESS_BAR:{ 639 stream_append_s8(s, s8("Compute Progress: ")); 640 stream_append_f64(s, 100 * *var->compute_progress_bar.progress, 100); 641 stream_append_byte(s, '%'); 642 } break; 643 case VT_BEAMFORMER_FRAME_VIEW:{ 644 BeamformerFrameView *bv = var->generic; 645 stream_append_s8(s, s8("Frame View")); 646 switch (bv->kind) { 647 case BeamformerFrameViewKind_Copy:{ stream_append_s8(s, s8(": Copy [")); }break; 648 case BeamformerFrameViewKind_Latest:{ 649 #define X(plane, id, pretty) s8_comp(": " pretty " ["), 650 read_only local_persist s8 labels[BeamformerViewPlaneTag_Count + 1] = { 651 BEAMFORMER_VIEW_PLANE_TAG_LIST 652 s8_comp(": Live [") 653 }; 654 #undef X 655 stream_append_s8(s, labels[*bv->cycler->cycler.state % (BeamformerViewPlaneTag_Count + 1)]); 656 }break; 657 case BeamformerFrameViewKind_Indexed:{ 658 stream_append_s8(s, s8(": Index {")); 659 stream_append_u64(s, *bv->cycler->cycler.state % MAX_BEAMFORMED_SAVED_FRAMES); 660 stream_append_s8(s, s8("} [")); 661 }break; 662 case BeamformerFrameViewKind_3DXPlane:{ stream_append_s8(s, s8(": 3D X-Plane")); }break; 663 InvalidDefaultCase; 664 } 665 if (bv->kind != BeamformerFrameViewKind_3DXPlane) { 666 stream_append_hex_u64(s, bv->frame? bv->frame->id : 0); 667 stream_append_byte(s, ']'); 668 } 669 }break; 670 InvalidDefaultCase; 671 } 672 return stream_to_s8(s); 673 } 674 675 #define table_new(a, init, ...) table_new_(a, init, arg_list(TextAlignment, ##__VA_ARGS__)) 676 function Table * 677 table_new_(Arena *a, i32 initial_capacity, TextAlignment *alignment, i32 columns) 678 { 679 Table *result = push_struct(a, Table); 680 da_reserve(a, result, initial_capacity); 681 result->columns = columns; 682 result->alignment = push_array(a, TextAlignment, columns); 683 result->widths = push_array(a, f32, columns); 684 result->cell_pad = (v2){{TABLE_CELL_PAD_WIDTH, TABLE_CELL_PAD_HEIGHT}}; 685 mem_copy(result->alignment, alignment, sizeof(*alignment) * columns); 686 return result; 687 } 688 689 function i32 690 table_skip_rows(Table *t, f32 draw_height, f32 text_height) 691 { 692 i32 max_rows = draw_height / (text_height + t->cell_pad.h); 693 i32 result = t->rows - MIN(t->rows, max_rows); 694 return result; 695 } 696 697 function TableIterator * 698 table_iterator_new(Table *table, TableIteratorKind kind, Arena *a, i32 starting_row, v2 at, Font *font) 699 { 700 TableIterator *result = push_struct(a, TableIterator); 701 result->kind = kind; 702 result->frame.table = table; 703 result->frame.row_index = starting_row; 704 result->start_x = at.x; 705 result->cell_rect.size.h = font->baseSize; 706 result->cell_rect.pos = v2_add(at, v2_scale(table->cell_pad, 0.5)); 707 result->cell_rect.pos.y += (starting_row - 1) * (result->cell_rect.size.h + table->cell_pad.h + table->row_border_thick); 708 da_reserve(a, &result->stack, 4); 709 return result; 710 } 711 712 function void * 713 table_iterator_next(TableIterator *it, Arena *a) 714 { 715 void *result = 0; 716 717 if (!it->row || it->kind == TIK_ROWS) { 718 for (;;) { 719 TableRow *row = it->frame.table->data + it->frame.row_index++; 720 if (it->frame.row_index <= it->frame.table->count) { 721 if (row->kind == TRK_TABLE) { 722 *da_push(a, &it->stack) = it->frame; 723 it->frame = (TableStackFrame){.table = row->data}; 724 it->sub_table_depth++; 725 } else { 726 result = row; 727 break; 728 } 729 } else if (it->stack.count) { 730 it->frame = it->stack.data[--it->stack.count]; 731 it->sub_table_depth--; 732 } else { 733 break; 734 } 735 } 736 Table *t = it->frame.table; 737 it->row = result; 738 it->column = 0; 739 it->cell_rect.pos.x = it->start_x + t->cell_pad.w / 2 + 740 it->cell_rect.size.h * it->sub_table_depth; 741 it->cell_rect.pos.y += it->cell_rect.size.h + t->row_border_thick + t->cell_pad.h; 742 } 743 744 if (it->row && it->kind == TIK_CELLS) { 745 Table *t = it->frame.table; 746 i32 column = it->column++; 747 it->cell_rect.pos.x += column > 0 ? it->cell_rect.size.w + t->cell_pad.w : 0; 748 it->cell_rect.size.w = t->widths[column]; 749 it->alignment = t->alignment[column]; 750 result = (TableCell *)it->row->data + column; 751 752 if (it->column == t->columns) 753 it->row = 0; 754 } 755 756 return result; 757 } 758 759 function f32 760 table_width(Table *t) 761 { 762 f32 result = 0; 763 i32 valid = 0; 764 for (i32 i = 0; i < t->columns; i++) { 765 result += t->widths[i]; 766 if (t->widths[i] > 0) valid++; 767 } 768 result += t->cell_pad.w * valid; 769 result += MAX(0, (valid - 1)) * t->column_border_thick; 770 return result; 771 } 772 773 function v2 774 table_extent(Table *t, Arena arena, Font *font) 775 { 776 TableIterator *it = table_iterator_new(t, TIK_ROWS, &arena, 0, (v2){0}, font); 777 for (TableRow *row = table_iterator_next(it, &arena); 778 row; 779 row = table_iterator_next(it, &arena)) 780 { 781 for (i32 i = 0; i < it->frame.table->columns; i++) { 782 TableCell *cell = (TableCell *)row->data + i; 783 if (!cell->text.len && cell->var && cell->var->flags & V_RADIO_BUTTON) { 784 cell->width = font->baseSize; 785 } else { 786 cell->width = measure_text(*font, cell->text).w; 787 } 788 it->frame.table->widths[i] = MAX(cell->width, it->frame.table->widths[i]); 789 } 790 } 791 792 t->size = (v2){.x = table_width(t), .y = it->cell_rect.pos.y - t->cell_pad.h / 2}; 793 v2 result = t->size; 794 return result; 795 } 796 797 function v2 798 table_cell_align(TableCell *cell, TextAlignment align, Rect r) 799 { 800 v2 result = r.pos; 801 if (r.size.w >= cell->width) { 802 switch (align) { 803 case TextAlignment_Left:{}break; 804 case TextAlignment_Right:{ result.x += (r.size.w - cell->width); }break; 805 case TextAlignment_Center:{ result.x += (r.size.w - cell->width) / 2; }break; 806 } 807 } 808 return result; 809 } 810 811 function TableCell 812 table_variable_cell(Arena *a, Variable *var) 813 { 814 TableCell result = {.var = var, .kind = TableCellKind_Variable}; 815 if ((var->flags & V_RADIO_BUTTON) == 0) { 816 Stream text = arena_stream(*a); 817 stream_append_variable(&text, var); 818 result.text = arena_stream_commit(a, &text); 819 } 820 return result; 821 } 822 823 function TableRow * 824 table_push_row(Table *t, Arena *a, TableRowKind kind) 825 { 826 TableRow *result = da_push(a, t); 827 if (kind == TRK_CELLS) { 828 result->data = push_array(a, TableCell, t->columns); 829 /* NOTE(rnp): do not increase rows for an empty subtable */ 830 t->rows++; 831 } 832 result->kind = kind; 833 return result; 834 } 835 836 function TableRow * 837 table_push_parameter_row(Table *t, Arena *a, s8 label, Variable *var, s8 suffix) 838 { 839 ASSERT(t->columns >= 3); 840 TableRow *result = table_push_row(t, a, TRK_CELLS); 841 TableCell *cells = result->data; 842 843 cells[0].text = label; 844 cells[1] = table_variable_cell(a, var); 845 cells[2].text = suffix; 846 847 return result; 848 } 849 850 #define table_begin_subtable(t, a, ...) table_begin_subtable_(t, a, arg_list(TextAlignment, ##__VA_ARGS__)) 851 function Table * 852 table_begin_subtable_(Table *table, Arena *a, TextAlignment *alignment, i32 columns) 853 { 854 TableRow *row = table_push_row(table, a, TRK_TABLE); 855 Table *result = row->data = table_new_(a, 0, alignment, columns); 856 result->parent = table; 857 return result; 858 } 859 860 function Table * 861 table_end_subtable(Table *table) 862 { 863 Table *result = table->parent ? table->parent : table; 864 return result; 865 } 866 867 function void 868 resize_frame_view(BeamformerFrameView *view, uv2 dim, b32 depth) 869 { 870 glDeleteTextures(countof(view->textures), view->textures); 871 glCreateTextures(GL_TEXTURE_2D, countof(view->textures) - !!depth, view->textures); 872 873 view->texture_dim = dim; 874 view->texture_mipmaps = ctz_u32(MAX(dim.x, dim.y)) + 1; 875 glTextureStorage2D(view->textures[0], view->texture_mipmaps, GL_RGBA8, dim.x, dim.y); 876 if (depth) glTextureStorage2D(view->textures[1], 1, GL_DEPTH_COMPONENT24, dim.x, dim.y); 877 878 glGenerateTextureMipmap(view->textures[0]); 879 880 /* NOTE(rnp): work around raylib's janky texture sampling */ 881 v4 border_colour = {0}; 882 if (view->kind == BeamformerFrameViewKind_Copy) border_colour = (v4){{0, 0, 0, 1}}; 883 glTextureParameteri(view->textures[0], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); 884 glTextureParameteri(view->textures[0], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); 885 glTextureParameterfv(view->textures[0], GL_TEXTURE_BORDER_COLOR, border_colour.E); 886 /* TODO(rnp): better choice when depth component is included */ 887 glTextureParameteri(view->textures[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST); 888 glTextureParameteri(view->textures[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST); 889 890 /* TODO(rnp): add some ID for the specific view here */ 891 LABEL_GL_OBJECT(GL_TEXTURE, view->textures[0], s8("Frame View Texture")); 892 } 893 894 function void 895 ui_beamformer_frame_view_release_subresources(BeamformerUI *ui, BeamformerFrameView *bv, BeamformerFrameViewKind kind) 896 { 897 if (kind == BeamformerFrameViewKind_Copy && bv->frame) { 898 glDeleteTextures(1, &bv->frame->texture); 899 bv->frame->texture = 0; 900 SLLPush(bv->frame, ui->frame_freelist); 901 } 902 903 if (kind != BeamformerFrameViewKind_3DXPlane) { 904 if (bv->axial_scale_bar.scale_bar.savepoint_stack) 905 SLLPush(bv->axial_scale_bar.scale_bar.savepoint_stack, ui->scale_bar_savepoint_freelist); 906 if (bv->lateral_scale_bar.scale_bar.savepoint_stack) 907 SLLPush(bv->lateral_scale_bar.scale_bar.savepoint_stack, ui->scale_bar_savepoint_freelist); 908 } 909 } 910 911 function void 912 ui_variable_free(BeamformerUI *ui, Variable *var) 913 { 914 if (var) { 915 var->parent = 0; 916 while (var) { 917 if (var->type == VT_GROUP) { 918 var = var->group.first; 919 } else { 920 if (var->type == VT_BEAMFORMER_FRAME_VIEW) { 921 /* TODO(rnp): instead there should be a way of linking these up */ 922 BeamformerFrameView *bv = var->generic; 923 ui_beamformer_frame_view_release_subresources(ui, bv, bv->kind); 924 DLLRemove(bv); 925 /* TODO(rnp): hack; use a sentinal */ 926 if (bv == ui->views) 927 ui->views = bv->next; 928 SLLPush(bv, ui->view_freelist); 929 } 930 931 Variable *next = var->next; 932 SLLPush(var, ui->variable_freelist); 933 if (next) { 934 var = next; 935 } else { 936 var = var->parent; 937 /* NOTE(rnp): when we assign parent here we have already 938 * released the children. Assign type so we don't loop */ 939 if (var) var->type = VT_NULL; 940 } 941 } 942 } 943 } 944 } 945 946 function void 947 ui_variable_free_group_items(BeamformerUI *ui, Variable *group) 948 { 949 assert(group->type == VT_GROUP); 950 /* NOTE(rnp): prevent traversal back to us */ 951 group->group.last->parent = 0; 952 ui_variable_free(ui, group->group.first); 953 group->group.first = group->group.last = 0; 954 } 955 956 function void 957 ui_view_free(BeamformerUI *ui, Variable *view) 958 { 959 assert(view->type == VT_UI_VIEW); 960 ui_variable_free(ui, view->view.child); 961 ui_variable_free(ui, view->view.close); 962 ui_variable_free(ui, view->view.menu); 963 ui_variable_free(ui, view); 964 } 965 966 function Variable * 967 fill_variable(Variable *var, Variable *group, s8 name, u32 flags, VariableType type, Font font) 968 { 969 var->flags = flags; 970 var->type = type; 971 var->name = name; 972 var->parent = group; 973 var->name_width = measure_text(font, name).x; 974 975 if (group && group->type == VT_GROUP) { 976 if (group->group.last) group->group.last = group->group.last->next = var; 977 else group->group.last = group->group.first = var; 978 } 979 980 return var; 981 } 982 983 function Variable * 984 add_variable(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, u32 flags, 985 VariableType type, Font font) 986 { 987 Variable *result = SLLPop(ui->variable_freelist); 988 if (result) zero_struct(result); 989 else result = push_struct(arena, Variable); 990 return fill_variable(result, group, name, flags, type, font); 991 } 992 993 function Variable * 994 add_variable_group(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, VariableGroupKind kind, Font font) 995 { 996 Variable *result = add_variable(ui, group, arena, name, V_INPUT, VT_GROUP, font); 997 result->group.kind = kind; 998 return result; 999 } 1000 1001 function Variable * 1002 end_variable_group(Variable *group) 1003 { 1004 ASSERT(group->type == VT_GROUP); 1005 return group->parent; 1006 } 1007 1008 function void 1009 fill_variable_cycler(Variable *cycler, u32 *store, s8 *labels, u32 cycle_count) 1010 { 1011 cycler->cycler.cycle_length = cycle_count; 1012 cycler->cycler.state = store; 1013 cycler->cycler.labels = labels; 1014 } 1015 1016 function Variable * 1017 add_variable_cycler(BeamformerUI *ui, Variable *group, Arena *arena, u32 flags, Font font, s8 name, 1018 u32 *store, s8 *labels, u32 cycle_count) 1019 { 1020 Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_CYCLER, font); 1021 fill_variable_cycler(result, store, labels, cycle_count); 1022 return result; 1023 } 1024 1025 function Variable * 1026 add_button(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, UIButtonID id, 1027 u32 flags, Font font) 1028 { 1029 Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_UI_BUTTON, font); 1030 result->button = id; 1031 return result; 1032 } 1033 1034 function Variable * 1035 add_ui_split(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, f32 fraction, 1036 RegionSplitDirection direction, Font font) 1037 { 1038 Variable *result = add_variable(ui, parent, arena, name, V_HIDES_CURSOR, VT_UI_REGION_SPLIT, font); 1039 result->region_split.direction = direction; 1040 result->region_split.fraction = fraction; 1041 return result; 1042 } 1043 1044 function Variable * 1045 add_global_menu_to_group(BeamformerUI *ui, Arena *arena, Variable *group) 1046 { 1047 #define X(id, text) add_button(ui, group, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); 1048 GLOBAL_MENU_BUTTONS 1049 #undef X 1050 return group; 1051 } 1052 1053 function Variable * 1054 add_global_menu(BeamformerUI *ui, Arena *arena, Variable *parent) 1055 { 1056 Variable *result = add_variable_group(ui, 0, arena, s8(""), VariableGroupKind_List, ui->small_font); 1057 result->parent = parent; 1058 return add_global_menu_to_group(ui, arena, result); 1059 } 1060 1061 function Variable * 1062 add_ui_view(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, u32 view_flags, b32 menu, b32 closable) 1063 { 1064 Variable *result = add_variable(ui, parent, arena, name, 0, VT_UI_VIEW, ui->small_font); 1065 UIView *view = &result->view; 1066 view->flags = view_flags; 1067 if (menu) view->menu = add_global_menu(ui, arena, result); 1068 if (closable) { 1069 view->close = add_button(ui, 0, arena, s8(""), UI_BID_VIEW_CLOSE, 0, ui->small_font); 1070 /* NOTE(rnp): we do this explicitly so that close doesn't end up in the view group */ 1071 view->close->parent = result; 1072 } 1073 return result; 1074 } 1075 1076 function Variable * 1077 add_floating_view(BeamformerUI *ui, Arena *arena, VariableType type, v2 at, Variable *child, b32 closable) 1078 { 1079 Variable *result = add_ui_view(ui, 0, arena, s8(""), UIViewFlag_Floating, 0, closable); 1080 result->type = type; 1081 result->view.rect.pos = at; 1082 result->view.child = child; 1083 1084 result->parent = &ui->floating_widget_sentinal; 1085 result->next = ui->floating_widget_sentinal.next; 1086 result->next->parent = result; 1087 ui->floating_widget_sentinal.next = result; 1088 return result; 1089 } 1090 1091 function void 1092 fill_beamformer_variable(Variable *var, s8 suffix, f32 *store, v2 limits, f32 display_scale, f32 scroll_scale) 1093 { 1094 BeamformerVariable *bv = &var->beamformer_variable; 1095 bv->suffix = suffix; 1096 bv->store = store; 1097 bv->display_scale = display_scale; 1098 bv->scroll_scale = scroll_scale; 1099 bv->limits = limits; 1100 } 1101 1102 function void 1103 add_beamformer_variable(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, s8 suffix, f32 *store, 1104 v2 limits, f32 display_scale, f32 scroll_scale, u32 flags, Font font) 1105 { 1106 Variable *var = add_variable(ui, group, arena, name, flags, VT_BEAMFORMER_VARIABLE, font); 1107 fill_beamformer_variable(var, suffix, store, limits, display_scale, scroll_scale); 1108 } 1109 1110 function Variable * 1111 add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx) 1112 { 1113 BeamformerUI *ui = ctx->ui; 1114 BeamformerUIParameters *bp = &ui->params; 1115 1116 v2 v2_inf = {.x = -F32_INFINITY, .y = F32_INFINITY}; 1117 1118 /* TODO(rnp): this can be closable once we have a way of opening new views */ 1119 Variable *result = add_ui_view(ui, parent, &ui->arena, s8("Parameters"), 0, 1, 0); 1120 Variable *group = result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, 1121 VT_GROUP, ui->font); 1122 1123 add_beamformer_variable(ui, group, &ui->arena, s8("Sampling Frequency:"), s8("[MHz]"), 1124 &bp->sampling_frequency, (v2){0}, 1e-6, 0, 0, ui->font); 1125 1126 add_beamformer_variable(ui, group, &ui->arena, s8("Center Frequency:"), s8("[MHz]"), 1127 &bp->center_frequency, (v2){.y = 100e-6}, 1e-6, 1e5, 1128 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1129 1130 add_beamformer_variable(ui, group, &ui->arena, s8("Speed of Sound:"), s8("[m/s]"), 1131 &bp->speed_of_sound, (v2){.y = 1e6}, 1, 10, 1132 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1133 1134 group = add_variable_group(ui, group, &ui->arena, s8("Lateral Extent:"), 1135 VariableGroupKind_Vector, ui->font); 1136 { 1137 add_beamformer_variable(ui, group, &ui->arena, s8("Min:"), s8("[mm]"), 1138 bp->output_min_coordinate + 0, v2_inf, 1e3, 0.5e-3, 1139 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1140 1141 add_beamformer_variable(ui, group, &ui->arena, s8("Max:"), s8("[mm]"), 1142 bp->output_max_coordinate + 0, v2_inf, 1e3, 0.5e-3, 1143 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1144 } 1145 group = end_variable_group(group); 1146 1147 group = add_variable_group(ui, group, &ui->arena, s8("Axial Extent:"), 1148 VariableGroupKind_Vector, ui->font); 1149 { 1150 add_beamformer_variable(ui, group, &ui->arena, s8("Min:"), s8("[mm]"), 1151 bp->output_min_coordinate + 2, v2_inf, 1e3, 0.5e-3, 1152 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1153 1154 add_beamformer_variable(ui, group, &ui->arena, s8("Max:"), s8("[mm]"), 1155 bp->output_max_coordinate + 2, v2_inf, 1e3, 0.5e-3, 1156 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1157 } 1158 group = end_variable_group(group); 1159 1160 add_beamformer_variable(ui, group, &ui->arena, s8("Off Axis Position:"), s8("[mm]"), 1161 &bp->off_axis_pos, (v2){.x = -1e3, .y = 1e3}, 0.25e3, 0.5e-3, 1162 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1163 1164 read_only local_persist s8 beamform_plane_labels[] = {s8_comp("XZ"), s8_comp("YZ")}; 1165 add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Beamform Plane:"), 1166 (u32 *)&bp->beamform_plane, beamform_plane_labels, countof(beamform_plane_labels)); 1167 1168 add_beamformer_variable(ui, group, &ui->arena, s8("F#:"), s8(""), &bp->f_number, (v2){.y = 1e3}, 1169 1, 0.1, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1170 1171 read_only local_persist s8 true_false_labels[] = {s8_comp("False"), s8_comp("True")}; 1172 add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Interpolate:"), 1173 &bp->interpolate, true_false_labels, countof(true_false_labels)); 1174 1175 add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Coherency Weighting:"), 1176 &bp->coherency_weighting, true_false_labels, countof(true_false_labels)); 1177 1178 return result; 1179 } 1180 1181 function void 1182 ui_beamformer_frame_view_convert(BeamformerUI *ui, Arena *arena, Variable *view, Variable *menu, 1183 BeamformerFrameViewKind kind, BeamformerFrameView *old, b32 log_scale) 1184 { 1185 assert(menu->group.first == menu->group.last && menu->group.first == 0); 1186 assert(view->type == VT_BEAMFORMER_FRAME_VIEW); 1187 1188 BeamformerFrameView *bv = view->generic; 1189 bv->kind = kind; 1190 bv->dirty = 1; 1191 1192 fill_variable(&bv->dynamic_range, view, s8("Dynamic Range:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 1193 VT_F32, ui->small_font); 1194 fill_variable(&bv->threshold, view, s8("Threshold:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 1195 VT_F32, ui->small_font); 1196 fill_variable(&bv->gamma, view, s8("Gamma:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 1197 VT_SCALED_F32, ui->small_font); 1198 1199 bv->dynamic_range.real32 = old? old->dynamic_range.real32 : 50.0f; 1200 bv->threshold.real32 = old? old->threshold.real32 : 55.0f; 1201 bv->gamma.scaled_real32.val = old? old->gamma.scaled_real32.val : 1.0f; 1202 bv->gamma.scaled_real32.scale = old? old->gamma.scaled_real32.scale : 0.05f; 1203 bv->min_coordinate = (old && old->frame)? old->frame->min_coordinate.xyz : (v3){0}; 1204 bv->max_coordinate = (old && old->frame)? old->frame->max_coordinate.xyz : (v3){0}; 1205 1206 #define X(_t, pretty) s8_comp(pretty), 1207 read_only local_persist s8 kind_labels[] = {BEAMFORMER_FRAME_VIEW_KIND_LIST}; 1208 #undef X 1209 bv->kind_cycler = add_variable_cycler(ui, menu, arena, V_EXTRA_ACTION, ui->small_font, 1210 s8("Kind:"), &bv->kind, kind_labels, countof(kind_labels)); 1211 1212 switch (kind) { 1213 case BeamformerFrameViewKind_3DXPlane:{ 1214 view->flags |= V_HIDES_CURSOR; 1215 resize_frame_view(bv, (uv2){{FRAME_VIEW_RENDER_TARGET_SIZE}}, 0); 1216 glTextureParameteri(bv->textures[0], GL_TEXTURE_MAG_FILTER, GL_LINEAR); 1217 glTextureParameteri(bv->textures[0], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 1218 fill_variable(bv->x_plane_shifts + 0, view, s8("XZ Shift"), V_INPUT|V_HIDES_CURSOR, 1219 VT_X_PLANE_SHIFT, ui->small_font); 1220 fill_variable(bv->x_plane_shifts + 1, view, s8("YZ Shift"), V_INPUT|V_HIDES_CURSOR, 1221 VT_X_PLANE_SHIFT, ui->small_font); 1222 bv->demo = add_variable(ui, menu, arena, s8("Demo Mode"), V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); 1223 }break; 1224 default:{ 1225 view->flags &= ~V_HIDES_CURSOR; 1226 fill_variable(&bv->lateral_scale_bar, view, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); 1227 fill_variable(&bv->axial_scale_bar, view, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); 1228 ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; 1229 ScaleBar *axial = &bv->axial_scale_bar.scale_bar; 1230 lateral->direction = SB_LATERAL; 1231 axial->direction = SB_AXIAL; 1232 lateral->scroll_scale = (v2){{-0.5e-3, 0.5e-3}}; 1233 axial->scroll_scale = (v2){{ 0, 1.0e-3}}; 1234 lateral->zoom_starting_coord = F32_INFINITY; 1235 axial->zoom_starting_coord = F32_INFINITY; 1236 1237 b32 copy = kind == BeamformerFrameViewKind_Copy; 1238 lateral->min_value = copy ? &bv->min_coordinate.x : ui->params.output_min_coordinate + 0; 1239 lateral->max_value = copy ? &bv->max_coordinate.x : ui->params.output_max_coordinate + 0; 1240 axial->min_value = copy ? &bv->min_coordinate.z : ui->params.output_min_coordinate + 2; 1241 axial->max_value = copy ? &bv->max_coordinate.z : ui->params.output_max_coordinate + 2; 1242 1243 #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); 1244 FRAME_VIEW_BUTTONS 1245 #undef X 1246 1247 bv->axial_scale_bar_active = add_variable(ui, menu, arena, s8("Axial Scale Bar"), 1248 V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); 1249 bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"), 1250 V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); 1251 1252 if (kind == BeamformerFrameViewKind_Latest) { 1253 bv->axial_scale_bar_active->bool32 = 1; 1254 bv->lateral_scale_bar_active->bool32 = 1; 1255 bv->axial_scale_bar.flags |= V_CAUSES_COMPUTE; 1256 bv->lateral_scale_bar.flags |= V_CAUSES_COMPUTE; 1257 } 1258 }break; 1259 } 1260 1261 bv->log_scale = add_variable(ui, menu, arena, s8("Log Scale"), 1262 V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON, VT_B32, ui->small_font); 1263 bv->log_scale->bool32 = log_scale; 1264 1265 switch (kind) { 1266 case BeamformerFrameViewKind_Latest:{ 1267 #define X(_type, _id, pretty) s8_comp(pretty), 1268 read_only local_persist s8 labels[] = {BEAMFORMER_VIEW_PLANE_TAG_LIST s8_comp("Any")}; 1269 #undef X 1270 bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Live:"), 1271 &bv->cycler_state, labels, countof(labels)); 1272 bv->cycler_state = BeamformerViewPlaneTag_Count; 1273 }break; 1274 case BeamformerFrameViewKind_Indexed:{ 1275 bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Index:"), 1276 &bv->cycler_state, 0, MAX_BEAMFORMED_SAVED_FRAMES); 1277 }break; 1278 default:{}break; 1279 } 1280 1281 add_global_menu_to_group(ui, arena, menu); 1282 } 1283 1284 function BeamformerFrameView * 1285 ui_beamformer_frame_view_new(BeamformerUI *ui, Arena *arena) 1286 { 1287 BeamformerFrameView *result = SLLPop(ui->view_freelist); 1288 if (result) zero_struct(result); 1289 else result = push_struct(arena, typeof(*result)); 1290 DLLPushDown(result, ui->views); 1291 return result; 1292 } 1293 1294 function Variable * 1295 add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, 1296 BeamformerFrameViewKind kind, b32 closable, BeamformerFrameView *old) 1297 { 1298 /* TODO(rnp): this can be always closable once we have a way of opening new views */ 1299 Variable *result = add_ui_view(ui, parent, arena, s8(""), UIViewFlag_CustomText, 1, closable); 1300 Variable *var = result->view.child = add_variable(ui, result, arena, s8(""), 0, 1301 VT_BEAMFORMER_FRAME_VIEW, ui->small_font); 1302 Variable *menu = result->view.menu = add_variable_group(ui, 0, arena, s8(""), 1303 VariableGroupKind_List, ui->small_font); 1304 menu->parent = result; 1305 var->generic = ui_beamformer_frame_view_new(ui, arena); 1306 ui_beamformer_frame_view_convert(ui, arena, var, menu, kind, old, old? old->log_scale->bool32 : 0); 1307 return result; 1308 } 1309 1310 function Variable * 1311 add_compute_progress_bar(Variable *parent, BeamformerCtx *ctx) 1312 { 1313 BeamformerUI *ui = ctx->ui; 1314 /* TODO(rnp): this can be closable once we have a way of opening new views */ 1315 Variable *result = add_ui_view(ui, parent, &ui->arena, s8(""), UIViewFlag_CustomText, 1, 0); 1316 result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, 1317 VT_COMPUTE_PROGRESS_BAR, ui->small_font); 1318 ComputeProgressBar *bar = &result->view.child->compute_progress_bar; 1319 bar->progress = &ctx->csctx.processing_progress; 1320 bar->processing = &ctx->csctx.processing_compute; 1321 1322 return result; 1323 } 1324 1325 function Variable * 1326 add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, BeamformerCtx *ctx) 1327 { 1328 /* TODO(rnp): this can be closable once we have a way of opening new views */ 1329 Variable *result = add_ui_view(ui, parent, arena, s8(""), UIViewFlag_CustomText, 0, 0); 1330 result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, 1331 VT_COMPUTE_STATS_VIEW, ui->small_font); 1332 1333 Variable *menu = result->view.menu = add_variable_group(ui, 0, arena, s8(""), 1334 VariableGroupKind_List, ui->small_font); 1335 menu->parent = result; 1336 1337 #define X(_k, label) s8_comp(label), 1338 read_only local_persist s8 labels[] = {COMPUTE_STATS_VIEW_LIST}; 1339 #undef X 1340 1341 ComputeStatsView *csv = &result->view.child->compute_stats_view; 1342 csv->compute_shader_stats = ctx->compute_shader_stats; 1343 csv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Stats View:"), 1344 &csv->kind, labels, countof(labels)); 1345 add_global_menu_to_group(ui, arena, menu); 1346 return result; 1347 } 1348 1349 function Variable * 1350 add_live_controls_view(BeamformerUI *ui, Variable *parent, Arena *arena) 1351 { 1352 BeamformerSharedMemory *sm = ui->shared_memory.region; 1353 BeamformerLiveImagingParameters *lip = &sm->live_imaging_parameters; 1354 /* TODO(rnp): this can be closable once we have a way of opening new views */ 1355 Variable *result = add_ui_view(ui, parent, &ui->arena, s8("Live Controls"), 0, 1, 0); 1356 result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, 1357 VT_LIVE_CONTROLS_VIEW, ui->small_font); 1358 Variable *view = result->view.child; 1359 BeamformerLiveControlsView *lv = view->generic = push_struct(arena, typeof(*lv)); 1360 1361 fill_variable(&lv->transmit_power, view, s8(""), V_INPUT|V_LIVE_CONTROL, 1362 VT_BEAMFORMER_VARIABLE, ui->small_font); 1363 fill_beamformer_variable(&lv->transmit_power, s8(""), &lip->transmit_power, (v2){{0, 1}}, 100, 0.05); 1364 1365 for (u32 i = 0; i < countof(lv->tgc_control_points); i++) { 1366 Variable *v = lv->tgc_control_points + i; 1367 fill_variable(v, view, s8(""), V_INPUT|V_LIVE_CONTROL, VT_BEAMFORMER_VARIABLE, ui->small_font); 1368 fill_beamformer_variable(v, s8(""), lip->tgc_control_points + i, (v2){{0, 1}}, 0, 0.05); 1369 } 1370 1371 fill_variable(&lv->stop_button, view, s8("Stop Imaging"), V_INPUT|V_LIVE_CONTROL, 1372 VT_B32, ui->small_font); 1373 1374 read_only local_persist s8 save_labels[] = {s8_comp("Save Data"), s8_comp("Saving...")}; 1375 fill_variable(&lv->save_button, view, s8("Save Data"), V_INPUT|V_LIVE_CONTROL, 1376 VT_CYCLER, ui->small_font); 1377 fill_variable_cycler(&lv->save_button, &lip->save_active, save_labels, countof(save_labels)); 1378 1379 return result; 1380 } 1381 1382 function Variable * 1383 ui_split_region(BeamformerUI *ui, Variable *region, Variable *split_side, RegionSplitDirection direction) 1384 { 1385 Variable *result = add_ui_split(ui, region, &ui->arena, s8(""), 0.5, direction, ui->small_font); 1386 if (split_side == region->region_split.left) { 1387 region->region_split.left = result; 1388 } else { 1389 region->region_split.right = result; 1390 } 1391 split_side->parent = result; 1392 result->region_split.left = split_side; 1393 return result; 1394 } 1395 1396 function void 1397 ui_add_live_frame_view(BeamformerUI *ui, Variable *view, RegionSplitDirection direction, 1398 BeamformerFrameViewKind kind) 1399 { 1400 Variable *region = view->parent; 1401 assert(region->type == VT_UI_REGION_SPLIT); 1402 assert(view->type == VT_UI_VIEW); 1403 Variable *new_region = ui_split_region(ui, region, view, direction); 1404 new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, kind, 1, 0); 1405 } 1406 1407 function void 1408 ui_beamformer_frame_view_copy_frame(BeamformerUI *ui, BeamformerFrameView *new, BeamformerFrameView *old) 1409 { 1410 assert(old->frame); 1411 new->frame = SLLPop(ui->frame_freelist); 1412 if (!new->frame) new->frame = push_struct(&ui->arena, typeof(*new->frame)); 1413 1414 mem_copy(new->frame, old->frame, sizeof(*new->frame)); 1415 new->frame->texture = 0; 1416 new->frame->next = 0; 1417 alloc_beamform_frame(0, new->frame, old->frame->dim, s8("Frame Copy: "), ui->arena); 1418 1419 glCopyImageSubData(old->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0, 1420 new->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0, 1421 new->frame->dim.x, new->frame->dim.y, new->frame->dim.z); 1422 glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT); 1423 /* TODO(rnp): x vs y here */ 1424 resize_frame_view(new, (uv2){.x = new->frame->dim.x, .y = new->frame->dim.z}, 1); 1425 } 1426 1427 function void 1428 ui_copy_frame(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) 1429 { 1430 Variable *region = view->parent; 1431 assert(region->type == VT_UI_REGION_SPLIT); 1432 assert(view->type == VT_UI_VIEW); 1433 1434 BeamformerFrameView *old = view->view.child->generic; 1435 /* TODO(rnp): hack; it would be better if this was unreachable with a 0 old->frame */ 1436 if (!old->frame) 1437 return; 1438 1439 Variable *new_region = ui_split_region(ui, region, view, direction); 1440 new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, 1441 BeamformerFrameViewKind_Copy, 1, old); 1442 1443 BeamformerFrameView *bv = new_region->region_split.right->view.child->generic; 1444 ui_beamformer_frame_view_copy_frame(ui, bv, old); 1445 } 1446 1447 function v3 1448 beamformer_frame_view_plane_size(BeamformerUI *ui, BeamformerFrameView *view) 1449 { 1450 v3 result; 1451 if (view->kind == BeamformerFrameViewKind_3DXPlane) { 1452 v3 min = v4_from_f32_array(ui->params.output_min_coordinate).xyz; 1453 v3 max = v4_from_f32_array(ui->params.output_max_coordinate).xyz; 1454 result = v3_sub(max, min); 1455 swap(result.y, result.z); 1456 result.x = MAX(1e-3, result.x); 1457 result.y = MAX(1e-3, result.y); 1458 result.z = MAX(1e-3, result.z); 1459 } else { 1460 v2 size = v2_sub(XZ(view->max_coordinate), XZ(view->min_coordinate)); 1461 result = (v3){.x = size.x, .y = size.y}; 1462 } 1463 return result; 1464 } 1465 1466 function f32 1467 x_plane_rotation_for_view_plane(BeamformerFrameView *view, BeamformerViewPlaneTag tag) 1468 { 1469 f32 result = view->rotation; 1470 if (tag == BeamformerViewPlaneTag_YZ) 1471 result += 0.25f; 1472 return result; 1473 } 1474 1475 function v2 1476 normalized_p_in_rect(Rect r, v2 p, b32 invert_y) 1477 { 1478 v2 result = v2_div(v2_scale(v2_sub(p, r.pos), 2.0f), r.size); 1479 if (invert_y) result = (v2){{result.x - 1.0f, 1.0f - result.y}}; 1480 else result = v2_sub(result, (v2){{1.0f, 1.0f}}); 1481 return result; 1482 } 1483 1484 function v3 1485 x_plane_position(BeamformerUI *ui) 1486 { 1487 f32 y_min = ui->params.output_min_coordinate[2]; 1488 f32 y_max = ui->params.output_max_coordinate[2]; 1489 v3 result = {.y = y_min + (y_max - y_min) / 2}; 1490 return result; 1491 } 1492 1493 function v3 1494 offset_x_plane_position(BeamformerUI *ui, BeamformerFrameView *view, BeamformerViewPlaneTag tag) 1495 { 1496 BeamformerSharedMemory *sm = ui->shared_memory.region; 1497 BeamformerLiveImagingParameters *li = &sm->live_imaging_parameters; 1498 m4 x_rotation = m4_rotation_about_y(x_plane_rotation_for_view_plane(view, tag)); 1499 v3 Z = x_rotation.c[2].xyz; 1500 v3 offset = v3_scale(Z, li->image_plane_offsets[tag]); 1501 v3 result = v3_add(x_plane_position(ui), offset); 1502 return result; 1503 } 1504 1505 function v3 1506 camera_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view) 1507 { 1508 v3 size = beamformer_frame_view_plane_size(ui, view); 1509 v3 target = x_plane_position(ui); 1510 f32 dist = v2_magnitude(XY(size)); 1511 v3 result = v3_add(target, (v3){{dist, -0.5f * size.y * tan_f32(50.0f * PI / 180.0f), dist}}); 1512 return result; 1513 } 1514 1515 function m4 1516 view_matrix_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view, v3 camera) 1517 { 1518 assert(view->kind == BeamformerFrameViewKind_3DXPlane); 1519 m4 result = camera_look_at(camera, x_plane_position(ui)); 1520 return result; 1521 } 1522 1523 function m4 1524 projection_matrix_for_x_plane_view(BeamformerFrameView *view) 1525 { 1526 assert(view->kind == BeamformerFrameViewKind_3DXPlane); 1527 f32 aspect = (f32)view->texture_dim.w / view->texture_dim.h; 1528 m4 result = perspective_projection(10e-3, 500e-3, 45.0f * PI / 180.0f, aspect); 1529 return result; 1530 } 1531 1532 function ray 1533 ray_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view, v2 uv) 1534 { 1535 assert(view->kind == BeamformerFrameViewKind_3DXPlane); 1536 ray result = {.origin = camera_for_x_plane_view(ui, view)}; 1537 v4 ray_clip = {{uv.x, uv.y, -1.0f, 1.0f}}; 1538 1539 /* TODO(rnp): combine these so we only do one matrix inversion */ 1540 m4 proj_m = projection_matrix_for_x_plane_view(view); 1541 m4 view_m = view_matrix_for_x_plane_view(ui, view, result.origin); 1542 m4 proj_inv = m4_inverse(proj_m); 1543 m4 view_inv = m4_inverse(view_m); 1544 1545 v4 ray_eye = {.z = -1}; 1546 ray_eye.x = v4_dot(m4_row(proj_inv, 0), ray_clip); 1547 ray_eye.y = v4_dot(m4_row(proj_inv, 1), ray_clip); 1548 result.direction = v3_normalize(m4_mul_v4(view_inv, ray_eye).xyz); 1549 1550 return result; 1551 } 1552 1553 function BeamformerViewPlaneTag 1554 view_plane_tag_from_x_plane_shift(BeamformerFrameView *view, Variable *x_plane_shift) 1555 { 1556 assert(BETWEEN(x_plane_shift, view->x_plane_shifts + 0, view->x_plane_shifts + 1)); 1557 BeamformerViewPlaneTag result = BeamformerViewPlaneTag_XZ; 1558 if (x_plane_shift == view->x_plane_shifts + 1) 1559 result = BeamformerViewPlaneTag_YZ; 1560 return result; 1561 } 1562 1563 function void 1564 render_single_xplane(BeamformerUI *ui, BeamformerFrameView *view, Variable *x_plane_shift, 1565 u32 program, f32 rotation_turns, v3 translate, BeamformerViewPlaneTag tag) 1566 { 1567 u32 texture = 0; 1568 if (ui->latest_plane[tag]) 1569 texture = ui->latest_plane[tag]->texture; 1570 1571 v3 scale = beamformer_frame_view_plane_size(ui, view); 1572 m4 model_transform = y_aligned_volume_transform(scale, translate, rotation_turns); 1573 1574 v4 colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, x_plane_shift->hover_t); 1575 glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model_transform.E); 1576 glProgramUniform4fv(program, FRAME_VIEW_BB_COLOUR_LOC, 1, colour.E); 1577 glProgramUniform1ui(program, FRAME_VIEW_SOLID_BB_LOC, 0); 1578 glBindTextureUnit(0, texture); 1579 glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT, 1580 (void *)ui->unit_cube_model.elements_offset); 1581 1582 XPlaneShift *xp = &x_plane_shift->x_plane_shift; 1583 v3 xp_delta = v3_sub(xp->end_point, xp->start_point); 1584 if (!f32_cmp(v3_magnitude(xp_delta), 0)) { 1585 m4 x_rotation = m4_rotation_about_y(rotation_turns); 1586 v3 Z = x_rotation.c[2].xyz; 1587 v3 f = v3_scale(Z, v3_dot(Z, v3_sub(xp->end_point, xp->start_point))); 1588 1589 /* TODO(rnp): there is no reason to compute the rotation matrix again */ 1590 model_transform = y_aligned_volume_transform(scale, v3_add(f, translate), rotation_turns); 1591 1592 glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model_transform.E); 1593 glProgramUniform1ui(program, FRAME_VIEW_SOLID_BB_LOC, 1); 1594 glProgramUniform4fv(program, FRAME_VIEW_BB_COLOUR_LOC, 1, HOVERED_COLOUR.E); 1595 glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT, 1596 (void *)ui->unit_cube_model.elements_offset); 1597 } 1598 } 1599 1600 function void 1601 render_3D_xplane(BeamformerUI *ui, BeamformerFrameView *view, u32 program) 1602 { 1603 if (view->demo->bool32) { 1604 view->rotation += dt_for_frame * 0.125f; 1605 if (view->rotation > 1.0f) view->rotation -= 1.0f; 1606 } 1607 1608 v3 camera = camera_for_x_plane_view(ui, view); 1609 m4 view_m = view_matrix_for_x_plane_view(ui, view, camera); 1610 m4 projection = projection_matrix_for_x_plane_view(view); 1611 1612 glProgramUniformMatrix4fv(program, FRAME_VIEW_VIEW_MATRIX_LOC, 1, 0, view_m.E); 1613 glProgramUniformMatrix4fv(program, FRAME_VIEW_PROJ_MATRIX_LOC, 1, 0, projection.E); 1614 glProgramUniform1f(program, FRAME_VIEW_BB_FRACTION_LOC, FRAME_VIEW_BB_FRACTION); 1615 1616 v3 model_translate = offset_x_plane_position(ui, view, BeamformerViewPlaneTag_XZ); 1617 render_single_xplane(ui, view, view->x_plane_shifts + 0, program, 1618 x_plane_rotation_for_view_plane(view, BeamformerViewPlaneTag_XZ), 1619 model_translate, BeamformerViewPlaneTag_XZ); 1620 model_translate = offset_x_plane_position(ui, view, BeamformerViewPlaneTag_YZ); 1621 model_translate.y -= 0.0001; 1622 render_single_xplane(ui, view, view->x_plane_shifts + 1, program, 1623 x_plane_rotation_for_view_plane(view, BeamformerViewPlaneTag_YZ), 1624 model_translate, BeamformerViewPlaneTag_YZ); 1625 } 1626 1627 function void 1628 render_2D_plane(BeamformerUI *ui, BeamformerFrameView *view, u32 program) 1629 { 1630 m4 view_m = m4_identity(); 1631 v3 size = beamformer_frame_view_plane_size(ui, view); 1632 m4 model = m4_scale(size); 1633 m4 projection = orthographic_projection(0, 1, size.y / 2, size.x / 2); 1634 1635 glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model.E); 1636 glProgramUniformMatrix4fv(program, FRAME_VIEW_VIEW_MATRIX_LOC, 1, 0, view_m.E); 1637 glProgramUniformMatrix4fv(program, FRAME_VIEW_PROJ_MATRIX_LOC, 1, 0, projection.E); 1638 1639 glProgramUniform1f(program, FRAME_VIEW_BB_FRACTION_LOC, 0); 1640 glBindTextureUnit(0, view->frame->texture); 1641 glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT, 1642 (void *)ui->unit_cube_model.elements_offset); 1643 } 1644 1645 function b32 1646 frame_view_ready_to_present(BeamformerUI *ui, BeamformerFrameView *view) 1647 { 1648 b32 result = !uv2_equal((uv2){0}, view->texture_dim) && view->frame; 1649 result |= view->kind == BeamformerFrameViewKind_3DXPlane && 1650 ui->latest_plane[BeamformerViewPlaneTag_Count]; 1651 return result; 1652 } 1653 1654 function b32 1655 view_update(BeamformerUI *ui, BeamformerFrameView *view) 1656 { 1657 if (view->kind == BeamformerFrameViewKind_Latest) { 1658 u32 index = *view->cycler->cycler.state; 1659 view->dirty |= view->frame != ui->latest_plane[index]; 1660 view->frame = ui->latest_plane[index]; 1661 if (view->dirty) { 1662 view->min_coordinate = v4_from_f32_array(ui->params.output_min_coordinate).xyz; 1663 view->max_coordinate = v4_from_f32_array(ui->params.output_max_coordinate).xyz; 1664 } 1665 } 1666 1667 /* TODO(rnp): x-z or y-z */ 1668 /* TODO(rnp): add method of setting a target size in frame view */ 1669 uv2 current = view->texture_dim; 1670 uv2 target = {.w = ui->params.output_points[0], .h = ui->params.output_points[2]}; 1671 if (view->kind != BeamformerFrameViewKind_Copy && 1672 view->kind != BeamformerFrameViewKind_3DXPlane && 1673 !uv2_equal(current, target) && !uv2_equal(target, (uv2){0})) 1674 { 1675 resize_frame_view(view, target, 1); 1676 view->dirty = 1; 1677 } 1678 view->dirty |= ui->frame_view_render_context->updated; 1679 view->dirty |= view->kind == BeamformerFrameViewKind_3DXPlane; 1680 1681 b32 result = frame_view_ready_to_present(ui, view) && view->dirty; 1682 return result; 1683 } 1684 1685 function void 1686 update_frame_views(BeamformerUI *ui, Rect window) 1687 { 1688 FrameViewRenderContext *ctx = ui->frame_view_render_context; 1689 b32 fbo_bound = 0; 1690 for (BeamformerFrameView *view = ui->views; view; view = view->next) { 1691 if (view_update(ui, view)) { 1692 if (!fbo_bound) { 1693 fbo_bound = 1; 1694 glBindFramebuffer(GL_FRAMEBUFFER, ctx->framebuffers[0]); 1695 glUseProgram(ctx->shader); 1696 glBindVertexArray(ui->unit_cube_model.vao); 1697 glEnable(GL_DEPTH_TEST); 1698 } 1699 1700 u32 fb = ctx->framebuffers[0]; 1701 u32 program = ctx->shader; 1702 glViewport(0, 0, view->texture_dim.w, view->texture_dim.h); 1703 glProgramUniform1f(program, FRAME_VIEW_THRESHOLD_LOC, view->threshold.real32); 1704 glProgramUniform1f(program, FRAME_VIEW_DYNAMIC_RANGE_LOC, view->dynamic_range.real32); 1705 glProgramUniform1f(program, FRAME_VIEW_GAMMA_LOC, view->gamma.scaled_real32.val); 1706 glProgramUniform1ui(program, FRAME_VIEW_LOG_SCALE_LOC, view->log_scale->bool32); 1707 1708 if (view->kind == BeamformerFrameViewKind_3DXPlane) { 1709 glNamedFramebufferRenderbuffer(fb, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ctx->renderbuffers[0]); 1710 glNamedFramebufferRenderbuffer(fb, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, ctx->renderbuffers[1]); 1711 glClearNamedFramebufferfv(fb, GL_COLOR, 0, (f32 []){0, 0, 0, 0}); 1712 glClearNamedFramebufferfv(fb, GL_DEPTH, 0, (f32 []){1}); 1713 render_3D_xplane(ui, view, program); 1714 /* NOTE(rnp): resolve multisampled scene */ 1715 glNamedFramebufferTexture(ctx->framebuffers[1], GL_COLOR_ATTACHMENT0, view->textures[0], 0); 1716 glBlitNamedFramebuffer(fb, ctx->framebuffers[1], 0, 0, FRAME_VIEW_RENDER_TARGET_SIZE, 1717 0, 0, FRAME_VIEW_RENDER_TARGET_SIZE, GL_COLOR_BUFFER_BIT, GL_NEAREST); 1718 } else { 1719 glNamedFramebufferTexture(fb, GL_COLOR_ATTACHMENT0, view->textures[0], 0); 1720 glNamedFramebufferTexture(fb, GL_DEPTH_ATTACHMENT, view->textures[1], 0); 1721 glClearNamedFramebufferfv(fb, GL_COLOR, 0, (f32 []){0, 0, 0, 0}); 1722 glClearNamedFramebufferfv(fb, GL_DEPTH, 0, (f32 []){1}); 1723 render_2D_plane(ui, view, program); 1724 } 1725 glGenerateTextureMipmap(view->textures[0]); 1726 view->dirty = 0; 1727 } 1728 } 1729 if (fbo_bound) { 1730 glBindFramebuffer(GL_FRAMEBUFFER, 0); 1731 glViewport(window.pos.x, window.pos.y, window.size.w, window.size.h); 1732 /* NOTE(rnp): I don't trust raylib to not mess with us */ 1733 glBindVertexArray(0); 1734 glDisable(GL_DEPTH_TEST); 1735 } 1736 } 1737 1738 function Color 1739 colour_from_normalized(v4 rgba) 1740 { 1741 return (Color){.r = rgba.r * 255.0f, .g = rgba.g * 255.0f, 1742 .b = rgba.b * 255.0f, .a = rgba.a * 255.0f}; 1743 } 1744 1745 function Color 1746 fade(Color a, f32 visibility) 1747 { 1748 a.a = (u8)((f32)a.a * visibility); 1749 return a; 1750 } 1751 1752 function v2 1753 draw_text_base(Font font, s8 text, v2 pos, Color colour) 1754 { 1755 v2 off = v2_floor(pos); 1756 for (iz i = 0; i < text.len; i++) { 1757 /* NOTE: assumes font glyphs are ordered ASCII */ 1758 i32 idx = text.data[i] - 0x20; 1759 Rectangle dst = { 1760 off.x + font.glyphs[idx].offsetX - font.glyphPadding, 1761 off.y + font.glyphs[idx].offsetY - font.glyphPadding, 1762 font.recs[idx].width + 2.0f * font.glyphPadding, 1763 font.recs[idx].height + 2.0f * font.glyphPadding 1764 }; 1765 Rectangle src = { 1766 font.recs[idx].x - font.glyphPadding, 1767 font.recs[idx].y - font.glyphPadding, 1768 font.recs[idx].width + 2.0f * font.glyphPadding, 1769 font.recs[idx].height + 2.0f * font.glyphPadding 1770 }; 1771 DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); 1772 1773 off.x += font.glyphs[idx].advanceX; 1774 if (font.glyphs[idx].advanceX == 0) 1775 off.x += font.recs[idx].width; 1776 } 1777 v2 result = {.x = off.x - pos.x, .y = font.baseSize}; 1778 return result; 1779 } 1780 1781 /* NOTE(rnp): expensive but of the available options in raylib this gives the best results */ 1782 function v2 1783 draw_outlined_text(s8 text, v2 pos, TextSpec *ts) 1784 { 1785 f32 ow = ts->outline_thick; 1786 Color outline = colour_from_normalized(ts->outline_colour); 1787 Color colour = colour_from_normalized(ts->colour); 1788 draw_text_base(*ts->font, text, v2_sub(pos, (v2){{ ow, ow}}), outline); 1789 draw_text_base(*ts->font, text, v2_sub(pos, (v2){{ ow, -ow}}), outline); 1790 draw_text_base(*ts->font, text, v2_sub(pos, (v2){{-ow, ow}}), outline); 1791 draw_text_base(*ts->font, text, v2_sub(pos, (v2){{-ow, -ow}}), outline); 1792 1793 v2 result = draw_text_base(*ts->font, text, pos, colour); 1794 1795 return result; 1796 } 1797 1798 function v2 1799 draw_text(s8 text, v2 pos, TextSpec *ts) 1800 { 1801 if (ts->flags & TF_ROTATED) { 1802 rlPushMatrix(); 1803 rlTranslatef(pos.x, pos.y, 0); 1804 rlRotatef(ts->rotation, 0, 0, 1); 1805 pos = (v2){0}; 1806 } 1807 1808 v2 result = measure_text(*ts->font, text); 1809 /* TODO(rnp): the size of this should be stored for each font */ 1810 s8 ellipsis = s8("..."); 1811 b32 clamped = ts->flags & TF_LIMITED && result.w > ts->limits.size.w; 1812 if (clamped) { 1813 f32 ellipsis_width = measure_text(*ts->font, ellipsis).x; 1814 if (ellipsis_width < ts->limits.size.w) { 1815 text = clamp_text_to_width(*ts->font, text, ts->limits.size.w - ellipsis_width); 1816 } else { 1817 text.len = 0; 1818 ellipsis.len = 0; 1819 } 1820 } 1821 1822 Color colour = colour_from_normalized(ts->colour); 1823 if (ts->flags & TF_OUTLINED) result.x = draw_outlined_text(text, pos, ts).x; 1824 else result.x = draw_text_base(*ts->font, text, pos, colour).x; 1825 1826 if (clamped) { 1827 pos.x += result.x; 1828 if (ts->flags & TF_OUTLINED) result.x += draw_outlined_text(ellipsis, pos, ts).x; 1829 else result.x += draw_text_base(*ts->font, ellipsis, pos, 1830 colour).x; 1831 } 1832 1833 if (ts->flags & TF_ROTATED) rlPopMatrix(); 1834 1835 return result; 1836 } 1837 1838 function Rect 1839 extend_rect_centered(Rect r, v2 delta) 1840 { 1841 r.size.w += delta.x; 1842 r.size.h += delta.y; 1843 r.pos.x -= delta.x / 2; 1844 r.pos.y -= delta.y / 2; 1845 return r; 1846 } 1847 1848 function Rect 1849 shrink_rect_centered(Rect r, v2 delta) 1850 { 1851 delta.x = MIN(delta.x, r.size.w); 1852 delta.y = MIN(delta.y, r.size.h); 1853 r.size.w -= delta.x; 1854 r.size.h -= delta.y; 1855 r.pos.x += delta.x / 2; 1856 r.pos.y += delta.y / 2; 1857 return r; 1858 } 1859 1860 function Rect 1861 scale_rect_centered(Rect r, v2 scale) 1862 { 1863 Rect or = r; 1864 r.size.w *= scale.x; 1865 r.size.h *= scale.y; 1866 r.pos.x += (or.size.w - r.size.w) / 2; 1867 r.pos.y += (or.size.h - r.size.h) / 2; 1868 return r; 1869 } 1870 1871 function b32 1872 interactions_equal(Interaction a, Interaction b) 1873 { 1874 b32 result = (a.kind == b.kind) && (a.generic == b.generic); 1875 return result; 1876 } 1877 1878 function b32 1879 interaction_is_sticky(Interaction a) 1880 { 1881 b32 result = a.kind == InteractionKind_Text || a.kind == InteractionKind_Ruler; 1882 return result; 1883 } 1884 1885 function b32 1886 interaction_is_hot(BeamformerUI *ui, Interaction a) 1887 { 1888 b32 result = interactions_equal(ui->hot_interaction, a); 1889 return result; 1890 } 1891 1892 function b32 1893 point_in_rect(v2 p, Rect r) 1894 { 1895 v2 end = v2_add(r.pos, r.size); 1896 b32 result = BETWEEN(p.x, r.pos.x, end.x) & BETWEEN(p.y, r.pos.y, end.y); 1897 return result; 1898 } 1899 1900 function v2 1901 screen_point_to_world_2d(v2 p, v2 screen_min, v2 screen_max, v2 world_min, v2 world_max) 1902 { 1903 v2 pixels_to_m = v2_div(v2_sub(world_max, world_min), v2_sub(screen_max, screen_min)); 1904 v2 result = v2_add(v2_mul(v2_sub(p, screen_min), pixels_to_m), world_min); 1905 return result; 1906 } 1907 1908 function v2 1909 world_point_to_screen_2d(v2 p, v2 world_min, v2 world_max, v2 screen_min, v2 screen_max) 1910 { 1911 v2 m_to_pixels = v2_div(v2_sub(screen_max, screen_min), v2_sub(world_max, world_min)); 1912 v2 result = v2_add(v2_mul(v2_sub(p, world_min), m_to_pixels), screen_min); 1913 return result; 1914 } 1915 1916 function b32 1917 hover_interaction(BeamformerUI *ui, v2 mouse, Interaction interaction) 1918 { 1919 Variable *var = interaction.var; 1920 b32 result = point_in_rect(mouse, interaction.rect); 1921 if (result) ui->next_interaction = interaction; 1922 if (interaction_is_hot(ui, interaction)) var->hover_t += HOVER_SPEED * dt_for_frame; 1923 else var->hover_t -= HOVER_SPEED * dt_for_frame; 1924 var->hover_t = CLAMP01(var->hover_t); 1925 return result; 1926 } 1927 1928 function void 1929 draw_close_button(BeamformerUI *ui, Variable *close, v2 mouse, Rect r, v2 x_scale) 1930 { 1931 assert(close->type == VT_UI_BUTTON); 1932 hover_interaction(ui, mouse, auto_interaction(r, close)); 1933 1934 Color colour = colour_from_normalized(v4_lerp(MENU_CLOSE_COLOUR, FG_COLOUR, close->hover_t)); 1935 r = scale_rect_centered(r, x_scale); 1936 DrawLineEx(r.pos.rl, v2_add(r.pos, r.size).rl, 4, colour); 1937 DrawLineEx(v2_add(r.pos, (v2){.x = r.size.w}).rl, 1938 v2_add(r.pos, (v2){.y = r.size.h}).rl, 4, colour); 1939 } 1940 1941 function Rect 1942 draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mouse) 1943 { 1944 assert(ui_view->type == VT_UI_VIEW); 1945 UIView *view = &ui_view->view; 1946 1947 s8 title = ui_view->name; 1948 if (view->flags & UIViewFlag_CustomText) { 1949 Stream buf = arena_stream(arena); 1950 push_custom_view_title(&buf, ui_view->view.child); 1951 title = arena_stream_commit(&arena, &buf); 1952 } 1953 1954 Rect result, title_rect; 1955 cut_rect_vertical(r, ui->small_font.baseSize + TITLE_BAR_PAD, &title_rect, &result); 1956 cut_rect_vertical(result, LISTING_LINE_PAD, 0, &result); 1957 1958 DrawRectangleRec(title_rect.rl, BLACK); 1959 1960 title_rect = shrink_rect_centered(title_rect, (v2){.x = 1.5 * TITLE_BAR_PAD}); 1961 DrawRectangleRounded(title_rect.rl, 0.5, 0, fade(colour_from_normalized(BG_COLOUR), 0.55)); 1962 title_rect = shrink_rect_centered(title_rect, (v2){.x = 3 * TITLE_BAR_PAD}); 1963 1964 if (view->close) { 1965 Rect close; 1966 cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &close); 1967 draw_close_button(ui, view->close, mouse, close, (v2){{.4, .4}}); 1968 } 1969 1970 if (view->menu) { 1971 Rect menu; 1972 cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &menu); 1973 Interaction interaction = {.kind = InteractionKind_Menu, .var = view->menu, .rect = menu}; 1974 hover_interaction(ui, mouse, interaction); 1975 1976 Color colour = colour_from_normalized(v4_lerp(MENU_PLUS_COLOUR, FG_COLOUR, view->menu->hover_t)); 1977 menu = shrink_rect_centered(menu, (v2){.x = 14, .y = 14}); 1978 DrawLineEx(v2_add(menu.pos, (v2){.x = menu.size.w / 2}).rl, 1979 v2_add(menu.pos, (v2){.x = menu.size.w / 2, .y = menu.size.h}).rl, 4, colour); 1980 DrawLineEx(v2_add(menu.pos, (v2){.y = menu.size.h / 2}).rl, 1981 v2_add(menu.pos, (v2){.x = menu.size.w, .y = menu.size.h / 2}).rl, 4, colour); 1982 } 1983 1984 v2 title_pos = title_rect.pos; 1985 title_pos.y += 0.5 * TITLE_BAR_PAD; 1986 TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED, .colour = FG_COLOUR, 1987 .limits.size = title_rect.size}; 1988 draw_text(title, title_pos, &text_spec); 1989 1990 return result; 1991 } 1992 1993 /* TODO(rnp): once this has more callers decide if it would be better for this to take 1994 * an orientation rather than force CCW/right-handed */ 1995 function void 1996 draw_ruler(BeamformerUI *ui, Arena arena, v2 start_point, v2 end_point, 1997 f32 start_value, f32 end_value, f32 *markers, u32 marker_count, 1998 u32 segments, s8 suffix, v4 marker_colour, v4 txt_colour) 1999 { 2000 b32 draw_plus = SIGN(start_value) != SIGN(end_value); 2001 2002 end_point = v2_sub(end_point, start_point); 2003 f32 rotation = atan2_f32(end_point.y, end_point.x) * 180 / PI; 2004 2005 rlPushMatrix(); 2006 rlTranslatef(start_point.x, start_point.y, 0); 2007 rlRotatef(rotation, 0, 0, 1); 2008 2009 f32 inc = v2_magnitude(end_point) / segments; 2010 f32 value_inc = (end_value - start_value) / segments; 2011 f32 value = start_value; 2012 2013 Stream buf = arena_stream(arena); 2014 v2 sp = {0}, ep = {.y = RULER_TICK_LENGTH}; 2015 v2 tp = {.x = ui->small_font.baseSize / 2, .y = ep.y + RULER_TEXT_PAD}; 2016 TextSpec text_spec = {.font = &ui->small_font, .rotation = 90, .colour = txt_colour, .flags = TF_ROTATED}; 2017 Color rl_txt_colour = colour_from_normalized(txt_colour); 2018 for (u32 j = 0; j <= segments; j++) { 2019 DrawLineEx(sp.rl, ep.rl, 3, rl_txt_colour); 2020 2021 stream_reset(&buf, 0); 2022 if (draw_plus && value > 0) stream_append_byte(&buf, '+'); 2023 stream_append_f64(&buf, value, 10); 2024 stream_append_s8(&buf, suffix); 2025 draw_text(stream_to_s8(&buf), tp, &text_spec); 2026 2027 value += value_inc; 2028 sp.x += inc; 2029 ep.x += inc; 2030 tp.x += inc; 2031 } 2032 2033 Color rl_marker_colour = colour_from_normalized(marker_colour); 2034 ep.y += RULER_TICK_LENGTH; 2035 for (u32 i = 0; i < marker_count; i++) { 2036 if (markers[i] < F32_INFINITY) { 2037 ep.x = sp.x = markers[i]; 2038 DrawLineEx(sp.rl, ep.rl, 3, rl_marker_colour); 2039 DrawCircleV(ep.rl, 3, rl_marker_colour); 2040 } 2041 } 2042 2043 rlPopMatrix(); 2044 } 2045 2046 function void 2047 do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect draw_rect, 2048 f32 start_value, f32 end_value, s8 suffix) 2049 { 2050 assert(scale_bar->type == VT_SCALE_BAR); 2051 ScaleBar *sb = &scale_bar->scale_bar; 2052 2053 v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); 2054 2055 Rect tick_rect = draw_rect; 2056 v2 start_pos = tick_rect.pos; 2057 v2 end_pos = tick_rect.pos; 2058 v2 relative_mouse = v2_sub(mouse, tick_rect.pos); 2059 2060 f32 markers[2]; 2061 u32 marker_count = 1; 2062 2063 v2 world_zoom_point = {{sb->zoom_starting_coord, sb->zoom_starting_coord}}; 2064 v2 screen_zoom_point = world_point_to_screen_2d(world_zoom_point, 2065 (v2){{*sb->min_value, *sb->min_value}}, 2066 (v2){{*sb->max_value, *sb->max_value}}, 2067 (v2){0}, tick_rect.size); 2068 u32 tick_count; 2069 if (sb->direction == SB_AXIAL) { 2070 tick_rect.size.x = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x; 2071 tick_count = tick_rect.size.y / (1.5 * ui->small_font.baseSize); 2072 start_pos.y += tick_rect.size.y; 2073 markers[0] = tick_rect.size.y - screen_zoom_point.y; 2074 markers[1] = tick_rect.size.y - relative_mouse.y; 2075 } else { 2076 tick_rect.size.y = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x; 2077 tick_count = tick_rect.size.x / (1.5 * ui->small_font.baseSize); 2078 end_pos.x += tick_rect.size.x; 2079 markers[0] = screen_zoom_point.x; 2080 markers[1] = relative_mouse.x; 2081 } 2082 2083 if (hover_interaction(ui, mouse, auto_interaction(tick_rect, scale_bar))) 2084 marker_count = 2; 2085 2086 draw_ruler(ui, arena, start_pos, end_pos, start_value, end_value, markers, marker_count, 2087 tick_count, suffix, RULER_COLOUR, v4_lerp(FG_COLOUR, HOVERED_COLOUR, scale_bar->hover_t)); 2088 } 2089 2090 function v2 2091 draw_radio_button(BeamformerUI *ui, Variable *var, v2 at, v2 mouse, v4 base_colour, f32 size) 2092 { 2093 assert(var->type == VT_B32); 2094 b32 value = var->bool32; 2095 2096 v2 result = (v2){.x = size, .y = size}; 2097 Rect hover_rect = {.pos = at, .size = result}; 2098 hover_rect.pos.y += 1; 2099 hover_interaction(ui, mouse, auto_interaction(hover_rect, var)); 2100 2101 hover_rect = shrink_rect_centered(hover_rect, (v2){.x = 8, .y = 8}); 2102 Rect inner = shrink_rect_centered(hover_rect, (v2){.x = 4, .y = 4}); 2103 v4 fill = v4_lerp(value? base_colour : (v4){0}, HOVERED_COLOUR, var->hover_t); 2104 DrawRectangleRoundedLinesEx(hover_rect.rl, 0.2, 0, 2, colour_from_normalized(base_colour)); 2105 DrawRectangleRec(inner.rl, colour_from_normalized(fill)); 2106 2107 return result; 2108 } 2109 2110 function f32 2111 draw_variable_slider(BeamformerUI *ui, Variable *var, Rect r, f32 fill, v4 fill_colour, v2 mouse) 2112 { 2113 f32 border_thick = 3; 2114 f32 bar_height_frac = 0.8; 2115 v2 bar_size = {{6, bar_height_frac * r.size.y}}; 2116 2117 Rect inner = shrink_rect_centered(r, (v2){{2 * border_thick, // NOTE(rnp): raylib jank 2118 MAX(0, 2 * (r.size.y - bar_size.y))}}); 2119 Rect filled = inner; 2120 filled.size.w *= fill; 2121 2122 Rect bar; 2123 bar.pos = v2_add(r.pos, (v2){{fill * (r.size.w - bar_size.w), (1 - bar_height_frac) * 0.5 * r.size.y}}); 2124 bar.size = bar_size; 2125 v4 bar_colour = v4_lerp(FG_COLOUR, FOCUSED_COLOUR, var->hover_t); 2126 2127 hover_interaction(ui, mouse, auto_interaction(inner, var)); 2128 2129 DrawRectangleRec(filled.rl, colour_from_normalized(fill_colour)); 2130 DrawRectangleRoundedLinesEx(inner.rl, 0.2, 0, border_thick, BLACK); 2131 DrawRectangleRounded(bar.rl, 0.6, 1, colour_from_normalized(bar_colour)); 2132 2133 return r.size.y; 2134 } 2135 2136 function v2 2137 draw_fancy_button(BeamformerUI *ui, Variable *var, s8 label, Rect r, v4 border_colour, v2 mouse, TextSpec ts) 2138 { 2139 assert(ts.font->baseSize <= r.size.h * 0.8); 2140 f32 pad = 0.1 * r.size.h; 2141 2142 v2 shadow_off = {{2.5, 3}}; 2143 f32 border_thick = 3; 2144 v2 border_size = v2_add((v2){{pad + 2 * border_thick, pad}}, shadow_off); 2145 2146 Rect border = shrink_rect_centered(r, border_size); 2147 Rect inner = shrink_rect_centered(border, (v2){{pad, pad}}); 2148 2149 ts.limits.size = inner.size; 2150 hover_interaction(ui, mouse, auto_interaction(inner, var)); 2151 2152 border.pos = v2_add(border.pos, shadow_off); 2153 2154 DrawRectangleRoundedLinesEx(border.rl, 0.6, 0, border_thick, fade(BLACK, 0.8)); 2155 border.pos = v2_sub(border.pos, shadow_off); 2156 DrawRectangleRounded(border.rl, 0.6, 1, colour_from_normalized(BG_COLOUR)); 2157 DrawRectangleRoundedLinesEx(border.rl, 0.6, 0, border_thick, colour_from_normalized(border_colour)); 2158 2159 /* TODO(rnp): teach draw_text() about alignment */ 2160 v2 at = align_text_in_rect(label, inner, *ts.font); 2161 at = v2_add(at, (v2){{3, 3}}); 2162 v4 base_colour = ts.colour; 2163 ts.colour = (v4){{0, 0, 0, 0.8}}; 2164 draw_text(label, at, &ts); 2165 2166 at = v2_sub(at, (v2){{3, 3}}); 2167 ts.colour = v4_lerp(base_colour, HOVERED_COLOUR, var->hover_t); 2168 draw_text(label, at, &ts); 2169 2170 v2 result = v2_add(r.size, border_size); 2171 return result; 2172 } 2173 2174 function v2 2175 draw_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, v4 base_colour, TextSpec text_spec) 2176 { 2177 v2 result; 2178 if (var->flags & V_RADIO_BUTTON) { 2179 result = draw_radio_button(ui, var, at, mouse, base_colour, text_spec.font->baseSize); 2180 } else { 2181 Stream buf = arena_stream(arena); 2182 stream_append_variable(&buf, var); 2183 s8 text = arena_stream_commit(&arena, &buf); 2184 result = measure_text(*text_spec.font, text); 2185 2186 if (var->flags & V_INPUT) { 2187 Rect text_rect = {.pos = at, .size = result}; 2188 text_rect = extend_rect_centered(text_rect, (v2){.x = 8}); 2189 if (hover_interaction(ui, mouse, auto_interaction(text_rect, var)) && (var->flags & V_TEXT)) 2190 ui->text_input_state.hot_font = text_spec.font; 2191 text_spec.colour = v4_lerp(base_colour, HOVERED_COLOUR, var->hover_t); 2192 } 2193 2194 draw_text(text, at, &text_spec); 2195 } 2196 return result; 2197 } 2198 2199 function void 2200 draw_table_cell(BeamformerUI *ui, Arena arena, TableCell *cell, Rect cell_rect, 2201 TextAlignment alignment, TextSpec ts, v2 mouse) 2202 { 2203 f32 x_off = cell_rect.pos.x; 2204 v2 cell_at = table_cell_align(cell, alignment, cell_rect); 2205 ts.limits.size.w -= (cell_at.x - x_off); 2206 cell_rect.size.w = MIN(ts.limits.size.w, cell_rect.size.w); 2207 2208 /* TODO(rnp): push truncated text for hovering */ 2209 switch (cell->kind) { 2210 case TableCellKind_None:{ draw_text(cell->text, cell_at, &ts); }break; 2211 case TableCellKind_Variable:{ 2212 if (cell->var->flags & V_INPUT) { 2213 draw_variable(ui, arena, cell->var, cell_at, mouse, ts.colour, ts); 2214 } else if (cell->text.len) { 2215 draw_text(cell->text, cell_at, &ts); 2216 } 2217 }break; 2218 case TableCellKind_VariableGroup:{ 2219 Variable *v = cell->var->group.first; 2220 f32 dw = draw_text(s8("{"), cell_at, &ts).x; 2221 while (v) { 2222 cell_at.x += dw; 2223 ts.limits.size.w -= dw; 2224 dw = draw_variable(ui, arena, v, cell_at, mouse, ts.colour, ts).x; 2225 2226 v = v->next; 2227 if (v) { 2228 cell_at.x += dw; 2229 ts.limits.size.w -= dw; 2230 dw = draw_text(s8(", "), cell_at, &ts).x; 2231 } 2232 } 2233 cell_at.x += dw; 2234 ts.limits.size.w -= dw; 2235 draw_text(s8("}"), cell_at, &ts); 2236 }break; 2237 } 2238 } 2239 2240 function void 2241 draw_table_borders(Table *t, Rect r, f32 line_height) 2242 { 2243 if (t->column_border_thick > 0) { 2244 v2 start = {.x = r.pos.x, .y = r.pos.y + t->cell_pad.h / 2}; 2245 v2 end = start; 2246 end.y += t->size.y - t->cell_pad.y; 2247 for (i32 i = 0; i < t->columns - 1; i++) { 2248 f32 dx = t->widths[i] + t->cell_pad.w + t->column_border_thick; 2249 start.x += dx; 2250 end.x += dx; 2251 if (t->widths[i + 1] > 0) 2252 DrawLineEx(start.rl, end.rl, t->column_border_thick, fade(BLACK, 0.8)); 2253 } 2254 } 2255 2256 if (t->row_border_thick > 0) { 2257 v2 start = {.x = r.pos.x + t->cell_pad.w / 2, .y = r.pos.y}; 2258 v2 end = start; 2259 end.x += t->size.x - t->cell_pad.x; 2260 for (i32 i = 0; i < t->rows - 1; i++) { 2261 f32 dy = line_height + t->cell_pad.y + t->row_border_thick; 2262 start.y += dy; 2263 end.y += dy; 2264 DrawLineEx(start.rl, end.rl, t->row_border_thick, fade(BLACK, 0.8)); 2265 } 2266 } 2267 } 2268 2269 function v2 2270 draw_table(BeamformerUI *ui, Arena arena, Table *table, Rect draw_rect, TextSpec ts, v2 mouse, b32 skip_rows) 2271 { 2272 ts.flags |= TF_LIMITED; 2273 2274 v2 result = {.x = table_width(table)}; 2275 i32 row_index = skip_rows? table_skip_rows(table, draw_rect.size.h, ts.font->baseSize) : 0; 2276 TableIterator *it = table_iterator_new(table, TIK_CELLS, &arena, row_index, draw_rect.pos, ts.font); 2277 for (TableCell *cell = table_iterator_next(it, &arena); 2278 cell; 2279 cell = table_iterator_next(it, &arena)) 2280 { 2281 ts.limits.size.w = draw_rect.size.w - (it->cell_rect.pos.x - it->start_x); 2282 draw_table_cell(ui, arena, cell, it->cell_rect, it->alignment, ts, mouse); 2283 } 2284 draw_table_borders(table, draw_rect, ts.font->baseSize); 2285 result.y = it->cell_rect.pos.y - draw_rect.pos.y - table->cell_pad.h / 2; 2286 return result; 2287 } 2288 2289 function void 2290 draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts) 2291 { 2292 v2 vr_max_p = v2_add(view_rect.pos, view_rect.size); 2293 v2 start_p = world_point_to_screen_2d(view->ruler.start, XZ(view->min_coordinate), 2294 XZ(view->max_coordinate), view_rect.pos, vr_max_p); 2295 v2 end_p = world_point_to_screen_2d(view->ruler.end, XZ(view->min_coordinate), 2296 XZ(view->max_coordinate), view_rect.pos, vr_max_p); 2297 2298 Color rl_colour = colour_from_normalized(ts.colour); 2299 DrawCircleV(start_p.rl, 3, rl_colour); 2300 DrawLineEx(end_p.rl, start_p.rl, 2, rl_colour); 2301 DrawCircleV(end_p.rl, 3, rl_colour); 2302 2303 Stream buf = arena_stream(a); 2304 stream_append_f64(&buf, 1e3 * v2_magnitude(v2_sub(view->ruler.end, view->ruler.start)), 100); 2305 stream_append_s8(&buf, s8(" mm")); 2306 2307 v2 txt_p = start_p; 2308 v2 txt_s = measure_text(*ts.font, stream_to_s8(&buf)); 2309 v2 pixel_delta = v2_sub(start_p, end_p); 2310 if (pixel_delta.y < 0) txt_p.y -= txt_s.y; 2311 if (pixel_delta.x < 0) txt_p.x -= txt_s.x; 2312 if (txt_p.x < view_rect.pos.x) txt_p.x = view_rect.pos.x; 2313 if (txt_p.x + txt_s.x > vr_max_p.x) txt_p.x -= (txt_p.x + txt_s.x) - vr_max_p.x; 2314 2315 draw_text(stream_to_s8(&buf), txt_p, &ts); 2316 } 2317 2318 function v2 2319 draw_frame_view_controls(BeamformerUI *ui, Arena arena, BeamformerFrameView *view, Rect vr, v2 mouse) 2320 { 2321 TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED|TF_OUTLINED, 2322 .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1, 2323 .limits.size.x = vr.size.w}; 2324 2325 Table *table = table_new(&arena, 3, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); 2326 table_push_parameter_row(table, &arena, view->gamma.name, &view->gamma, s8("")); 2327 table_push_parameter_row(table, &arena, view->threshold.name, &view->threshold, s8("")); 2328 if (view->log_scale->bool32) 2329 table_push_parameter_row(table, &arena, view->dynamic_range.name, &view->dynamic_range, s8("[dB]")); 2330 2331 Rect table_rect = vr; 2332 f32 height = table_extent(table, arena, text_spec.font).y; 2333 height = MIN(height, vr.size.h); 2334 table_rect.pos.w += 8; 2335 table_rect.pos.y += vr.size.h - height - 8; 2336 table_rect.size.h = height; 2337 table_rect.size.w = vr.size.w - 16; 2338 2339 return draw_table(ui, arena, table, table_rect, text_spec, mouse, 0); 2340 } 2341 2342 function void 2343 draw_3D_xplane_frame_view(BeamformerUI *ui, Arena arena, Variable *var, Rect display_rect, v2 mouse) 2344 { 2345 assert(var->type == VT_BEAMFORMER_FRAME_VIEW); 2346 BeamformerFrameView *view = var->generic; 2347 2348 f32 aspect = (f32)view->texture_dim.w / (f32)view->texture_dim.h; 2349 Rect vr = display_rect; 2350 if (aspect > 1.0f) vr.size.w = vr.size.h; 2351 else vr.size.h = vr.size.w; 2352 2353 if (vr.size.w > display_rect.size.w) { 2354 vr.size.w -= (vr.size.w - display_rect.size.w); 2355 vr.size.h = vr.size.w / aspect; 2356 } else if (vr.size.h > display_rect.size.h) { 2357 vr.size.h -= (vr.size.h - display_rect.size.h); 2358 vr.size.w = vr.size.h * aspect; 2359 } 2360 vr.pos = v2_add(vr.pos, v2_scale(v2_sub(display_rect.size, vr.size), 0.5)); 2361 2362 i32 id = -1; 2363 if (hover_interaction(ui, mouse, auto_interaction(vr, var))) { 2364 ray mouse_ray = ray_for_x_plane_view(ui, view, normalized_p_in_rect(vr, mouse, 0)); 2365 v3 x_size = v3_scale(beamformer_frame_view_plane_size(ui, view), 0.5f); 2366 2367 f32 rotation = x_plane_rotation_for_view_plane(view, BeamformerViewPlaneTag_XZ); 2368 m4 x_rotation = m4_rotation_about_y(rotation); 2369 v3 x_position = offset_x_plane_position(ui, view, BeamformerViewPlaneTag_XZ); 2370 2371 f32 test[2] = {0}; 2372 test[0] = obb_raycast(x_rotation, x_size, x_position, mouse_ray); 2373 2374 x_position = offset_x_plane_position(ui, view, BeamformerViewPlaneTag_YZ); 2375 rotation = x_plane_rotation_for_view_plane(view, BeamformerViewPlaneTag_YZ); 2376 x_rotation = m4_rotation_about_y(rotation); 2377 test[1] = obb_raycast(x_rotation, x_size, x_position, mouse_ray); 2378 2379 if (test[0] >= 0 && test[1] >= 0) id = test[1] < test[0]? 1 : 0; 2380 else if (test[0] >= 0) id = 0; 2381 else if (test[1] >= 0) id = 1; 2382 2383 if (id != -1) { 2384 view->hit_test_point = v3_add(mouse_ray.origin, v3_scale(mouse_ray.direction, test[id])); 2385 } 2386 } 2387 2388 for (i32 i = 0; i < countof(view->x_plane_shifts); i++) { 2389 Variable *var = view->x_plane_shifts + i; 2390 Interaction interaction = auto_interaction(vr, var); 2391 if (id == i) ui->next_interaction = interaction; 2392 if (interaction_is_hot(ui, interaction)) var->hover_t += HOVER_SPEED * dt_for_frame; 2393 else var->hover_t -= HOVER_SPEED * dt_for_frame; 2394 var->hover_t = CLAMP01(var->hover_t); 2395 } 2396 2397 Rectangle tex_r = {0, 0, view->texture_dim.w, view->texture_dim.h}; 2398 NPatchInfo tex_np = {tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH}; 2399 DrawTextureNPatch(make_raylib_texture(view), tex_np, vr.rl, (Vector2){0}, 0, WHITE); 2400 2401 draw_frame_view_controls(ui, arena, view, vr, mouse); 2402 } 2403 2404 function void 2405 draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect display_rect, v2 mouse) 2406 { 2407 assert(var->type == VT_BEAMFORMER_FRAME_VIEW); 2408 BeamformerFrameView *view = var->generic; 2409 BeamformerFrame *frame = view->frame; 2410 2411 v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); 2412 f32 scale_bar_size = 1.2 * txt_s.x + RULER_TICK_LENGTH; 2413 2414 v3 min = view->min_coordinate; 2415 v3 max = view->max_coordinate; 2416 v2 requested_dim = v2_sub(XZ(max), XZ(min)); 2417 f32 aspect = requested_dim.w / requested_dim.h; 2418 2419 Rect vr = display_rect; 2420 v2 scale_bar_area = {0}; 2421 if (view->axial_scale_bar_active->bool32) { 2422 vr.pos.y += 0.5 * ui->small_font.baseSize; 2423 scale_bar_area.x += scale_bar_size; 2424 scale_bar_area.y += ui->small_font.baseSize; 2425 } 2426 2427 if (view->lateral_scale_bar_active->bool32) { 2428 vr.pos.x += 0.5 * ui->small_font.baseSize; 2429 scale_bar_area.x += ui->small_font.baseSize; 2430 scale_bar_area.y += scale_bar_size; 2431 } 2432 2433 vr.size = v2_sub(vr.size, scale_bar_area); 2434 if (aspect > 1) vr.size.h = vr.size.w / aspect; 2435 else vr.size.w = vr.size.h * aspect; 2436 2437 v2 occupied = v2_add(vr.size, scale_bar_area); 2438 if (occupied.w > display_rect.size.w) { 2439 vr.size.w -= (occupied.w - display_rect.size.w); 2440 vr.size.h = vr.size.w / aspect; 2441 } else if (occupied.h > display_rect.size.h) { 2442 vr.size.h -= (occupied.h - display_rect.size.h); 2443 vr.size.w = vr.size.h * aspect; 2444 } 2445 occupied = v2_add(vr.size, scale_bar_area); 2446 vr.pos = v2_add(vr.pos, v2_scale(v2_sub(display_rect.size, occupied), 0.5)); 2447 2448 /* TODO(rnp): make this depend on the requested draw orientation (x-z or y-z or x-y) */ 2449 v2 output_dim = v2_sub(XZ(frame->max_coordinate), XZ(frame->min_coordinate)); 2450 v2 pixels_per_meter = { 2451 .w = (f32)view->texture_dim.w / output_dim.w, 2452 .h = (f32)view->texture_dim.h / output_dim.h, 2453 }; 2454 2455 /* NOTE(rnp): math to resize the texture without stretching when the view changes 2456 * but the texture hasn't been (or cannot be) rebeamformed */ 2457 v2 texture_points = v2_mul(pixels_per_meter, requested_dim); 2458 /* TODO(rnp): this also depends on x-y, y-z, x-z */ 2459 v2 texture_start = { 2460 .x = pixels_per_meter.x * 0.5 * (output_dim.x - requested_dim.x), 2461 .y = pixels_per_meter.y * (frame->max_coordinate.z - max.z), 2462 }; 2463 2464 Rectangle tex_r = {texture_start.x, texture_start.y, texture_points.x, texture_points.y}; 2465 NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH }; 2466 DrawTextureNPatch(make_raylib_texture(view), tex_np, vr.rl, (Vector2){0}, 0, WHITE); 2467 2468 v2 start_pos = vr.pos; 2469 start_pos.y += vr.size.y; 2470 2471 if (vr.size.w > 0 && view->lateral_scale_bar_active->bool32) { 2472 do_scale_bar(ui, a, &view->lateral_scale_bar, mouse, 2473 (Rect){.pos = start_pos, .size = vr.size}, 2474 *view->lateral_scale_bar.scale_bar.min_value * 1e3, 2475 *view->lateral_scale_bar.scale_bar.max_value * 1e3, s8(" mm")); 2476 } 2477 2478 start_pos = vr.pos; 2479 start_pos.x += vr.size.x; 2480 2481 if (vr.size.h > 0 && view->axial_scale_bar_active->bool32) { 2482 do_scale_bar(ui, a, &view->axial_scale_bar, mouse, 2483 (Rect){.pos = start_pos, .size = vr.size}, 2484 *view->axial_scale_bar.scale_bar.max_value * 1e3, 2485 *view->axial_scale_bar.scale_bar.min_value * 1e3, s8(" mm")); 2486 } 2487 2488 TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED|TF_OUTLINED, 2489 .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1, 2490 .limits.size.x = vr.size.w}; 2491 2492 f32 draw_table_width = vr.size.w; 2493 /* NOTE: avoid hover_t modification */ 2494 Interaction viewer = auto_interaction(vr, var); 2495 if (point_in_rect(mouse, viewer.rect)) { 2496 ui->next_interaction = viewer; 2497 2498 v2 world = screen_point_to_world_2d(mouse, vr.pos, v2_add(vr.pos, vr.size), 2499 XZ(view->min_coordinate), 2500 XZ(view->max_coordinate)); 2501 Stream buf = arena_stream(a); 2502 stream_append_v2(&buf, v2_scale(world, 1e3)); 2503 2504 text_spec.limits.size.w -= 4; 2505 v2 txt_s = measure_text(*text_spec.font, stream_to_s8(&buf)); 2506 v2 txt_p = { 2507 .x = vr.pos.x + vr.size.w - txt_s.w - 4, 2508 .y = vr.pos.y + vr.size.h - txt_s.h - 4, 2509 }; 2510 txt_p.x = MAX(vr.pos.x, txt_p.x); 2511 draw_table_width -= draw_text(stream_to_s8(&buf), txt_p, &text_spec).w; 2512 text_spec.limits.size.w += 4; 2513 } 2514 2515 { 2516 Stream buf = arena_stream(a); 2517 s8 shader = push_das_shader_kind(&buf, frame->das_shader_kind, frame->compound_count); 2518 text_spec.font = &ui->font; 2519 text_spec.limits.size.w -= 16; 2520 v2 txt_s = measure_text(*text_spec.font, shader); 2521 v2 txt_p = { 2522 .x = vr.pos.x + vr.size.w - txt_s.w - 16, 2523 .y = vr.pos.y + 4, 2524 }; 2525 txt_p.x = MAX(vr.pos.x, txt_p.x); 2526 draw_text(stream_to_s8(&buf), txt_p, &text_spec); 2527 text_spec.font = &ui->small_font; 2528 text_spec.limits.size.w += 16; 2529 } 2530 2531 if (view->ruler.state != RulerState_None) draw_view_ruler(view, a, vr, text_spec); 2532 2533 vr.size.w = draw_table_width; 2534 draw_frame_view_controls(ui, a, view, vr, mouse); 2535 } 2536 2537 function v2 2538 draw_compute_progress_bar(BeamformerUI *ui, Arena arena, ComputeProgressBar *state, Rect r) 2539 { 2540 if (*state->processing) state->display_t_velocity += 65 * dt_for_frame; 2541 else state->display_t_velocity -= 45 * dt_for_frame; 2542 2543 state->display_t_velocity = CLAMP(state->display_t_velocity, -10, 10); 2544 state->display_t += state->display_t_velocity * dt_for_frame; 2545 state->display_t = CLAMP01(state->display_t); 2546 2547 if (state->display_t > (1.0 / 255.0)) { 2548 Rect outline = {.pos = r.pos, .size = {.w = r.size.w, .h = ui->font.baseSize}}; 2549 outline = scale_rect_centered(outline, (v2){.x = 0.96, .y = 0.7}); 2550 Rect filled = outline; 2551 filled.size.w *= *state->progress; 2552 DrawRectangleRounded(filled.rl, 2, 0, fade(colour_from_normalized(HOVERED_COLOUR), 2553 state->display_t)); 2554 DrawRectangleRoundedLinesEx(outline.rl, 2, 0, 3, fade(BLACK, state->display_t)); 2555 } 2556 2557 v2 result = {.x = r.size.w, .y = ui->font.baseSize}; 2558 return result; 2559 } 2560 2561 function s8 2562 push_compute_time(Arena *arena, s8 prefix, f32 time) 2563 { 2564 Stream sb = arena_stream(*arena); 2565 stream_append_s8(&sb, prefix); 2566 stream_append_f64_e(&sb, time); 2567 return arena_stream_commit(arena, &sb); 2568 } 2569 2570 function v2 2571 draw_compute_stats_bar_view(BeamformerUI *ui, Arena arena, ComputeShaderStats *stats, u32 *stages, 2572 u32 stages_count, f32 compute_time_sum, TextSpec ts, Rect r, v2 mouse) 2573 { 2574 read_only local_persist s8 frame_labels[] = {s8_comp("0:"), s8_comp("-1:"), s8_comp("-2:"), s8_comp("-3:")}; 2575 f32 total_times[countof(frame_labels)] = {0}; 2576 Table *table = table_new(&arena, countof(frame_labels), TextAlignment_Right, TextAlignment_Left); 2577 for (u32 i = 0; i < countof(frame_labels); i++) { 2578 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 2579 cells[0].text = frame_labels[i]; 2580 u32 frame_index = (stats->latest_frame_index - i) % countof(stats->table.times); 2581 u32 seen_shaders = 0; 2582 for (u32 j = 0; j < stages_count; j++) { 2583 if ((seen_shaders & (1 << stages[j])) == 0) 2584 total_times[i] += stats->table.times[frame_index][stages[j]]; 2585 seen_shaders |= (1 << stages[j]); 2586 } 2587 } 2588 2589 #define X(e, n, s, h, pn) [BeamformerShaderKind_##e] = s8_comp(pn ": "), 2590 read_only local_persist s8 labels[BeamformerShaderKind_ComputeCount] = {COMPUTE_SHADERS}; 2591 #undef X 2592 2593 v2 result = table_extent(table, arena, ts.font); 2594 2595 f32 remaining_width = r.size.w - result.w - table->cell_pad.w; 2596 f32 average_width = 0.8 * remaining_width; 2597 2598 s8 mouse_text = s8(""); 2599 v2 text_pos; 2600 2601 u32 row_index = 0; 2602 TableIterator *it = table_iterator_new(table, TIK_ROWS, &arena, 0, r.pos, ts.font); 2603 for (TableRow *row = table_iterator_next(it, &arena); 2604 row; 2605 row = table_iterator_next(it, &arena)) 2606 { 2607 Rect cr = it->cell_rect; 2608 cr.size.w = table->widths[0]; 2609 ts.limits.size.w = cr.size.w; 2610 draw_table_cell(ui, arena, (TableCell *)row->data, cr, table->alignment[0], ts, mouse); 2611 2612 u32 frame_index = (stats->latest_frame_index - row_index) % countof(stats->table.times); 2613 f32 total_width = average_width * total_times[row_index] / compute_time_sum; 2614 Rect rect; 2615 rect.pos = v2_add(cr.pos, (v2){.x = cr.size.w + table->cell_pad.w , .y = cr.size.h * 0.15}); 2616 rect.size = (v2){.y = 0.7 * cr.size.h}; 2617 for (u32 i = 0; i < stages_count; i++) { 2618 rect.size.w = total_width * stats->table.times[frame_index][stages[i]] / total_times[row_index]; 2619 Color color = colour_from_normalized(g_colour_palette[stages[i] % countof(g_colour_palette)]); 2620 DrawRectangleRec(rect.rl, color); 2621 if (point_in_rect(mouse, rect)) { 2622 text_pos = v2_add(rect.pos, (v2){.x = table->cell_pad.w}); 2623 mouse_text = push_compute_time(&arena, labels[stages[i]], 2624 stats->table.times[frame_index][stages[i]]); 2625 } 2626 rect.pos.x += rect.size.w; 2627 } 2628 row_index++; 2629 } 2630 2631 v2 start = v2_add(r.pos, (v2){.x = table->widths[0] + average_width + table->cell_pad.w}); 2632 v2 end = v2_add(start, (v2){.y = result.y}); 2633 DrawLineEx(start.rl, end.rl, 4, colour_from_normalized(FG_COLOUR)); 2634 2635 if (mouse_text.len) { 2636 ts.font = &ui->small_font; 2637 ts.flags &= ~TF_LIMITED; 2638 ts.flags |= TF_OUTLINED; 2639 ts.outline_colour = (v4){.a = 1}; 2640 ts.outline_thick = 1; 2641 draw_text(mouse_text, text_pos, &ts); 2642 } 2643 2644 return result; 2645 } 2646 2647 function void 2648 push_table_time_row(Table *table, Arena *arena, s8 label, f32 time) 2649 { 2650 assert(table->columns == 3); 2651 TableCell *cells = table_push_row(table, arena, TRK_CELLS)->data; 2652 cells[0].text = label; 2653 cells[1].text = push_compute_time(arena, s8(""), time); 2654 cells[2].text = s8("[s]"); 2655 } 2656 2657 function v2 2658 draw_compute_stats_view(BeamformerUI *ui, Arena arena, Variable *view, Rect r, v2 mouse) 2659 { 2660 assert(view->type == VT_COMPUTE_STATS_VIEW); 2661 2662 ComputeStatsView *csv = &view->compute_stats_view; 2663 BeamformerSharedMemory *sm = ui->shared_memory.region; 2664 ComputeShaderStats *stats = csv->compute_shader_stats; 2665 f32 compute_time_sum = 0; 2666 u32 stages = sm->compute_stages_count; 2667 TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; 2668 2669 u32 compute_stages[MAX_COMPUTE_SHADER_STAGES]; 2670 mem_copy(compute_stages, sm->compute_stages, stages * sizeof(*compute_stages)); 2671 2672 static_assert(BeamformerShaderKind_ComputeCount <= 32, "shader kind bitfield test"); 2673 u32 seen_shaders = 0; 2674 for (u32 i = 0; i < stages; i++) { 2675 BeamformerShaderKind index = compute_stages[i]; 2676 if ((seen_shaders & (1 << index)) == 0) 2677 compute_time_sum += stats->average_times[index]; 2678 seen_shaders |= (1 << index); 2679 } 2680 2681 v2 result = {0}; 2682 2683 Table *table = table_new(&arena, 2, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); 2684 switch (csv->kind) { 2685 case ComputeStatsViewKind_Average:{ 2686 #define X(e, n, s, h, pn) [BeamformerShaderKind_##e] = s8_comp(pn ":"), 2687 read_only local_persist s8 labels[BeamformerShaderKind_ComputeCount] = {COMPUTE_SHADERS}; 2688 #undef X 2689 da_reserve(&arena, table, stages); 2690 for (u32 i = 0; i < stages; i++) { 2691 push_table_time_row(table, &arena, labels[compute_stages[i]], 2692 stats->average_times[compute_stages[i]]); 2693 } 2694 }break; 2695 case ComputeStatsViewKind_Bar:{ 2696 result = draw_compute_stats_bar_view(ui, arena, stats, compute_stages, stages, compute_time_sum, 2697 text_spec, r, mouse); 2698 r.pos = v2_add(r.pos, (v2){.y = result.y}); 2699 }break; 2700 InvalidDefaultCase; 2701 } 2702 2703 push_table_time_row(table, &arena, s8("Compute Total:"), compute_time_sum); 2704 push_table_time_row(table, &arena, s8("RF Upload Delta:"), stats->rf_time_delta_average); 2705 2706 result = v2_add(result, table_extent(table, arena, text_spec.font)); 2707 draw_table(ui, arena, table, r, text_spec, (v2){0}, 0); 2708 return result; 2709 } 2710 2711 function v2 2712 draw_live_controls_view(BeamformerUI *ui, Arena arena, Variable *var, Rect r, v2 mouse) 2713 { 2714 BeamformerSharedMemory *sm = ui->shared_memory.region; 2715 BeamformerLiveImagingParameters *lip = &sm->live_imaging_parameters; 2716 BeamformerLiveControlsView *lv = var->generic; 2717 2718 TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; 2719 text_spec.limits.size.w = r.size.w; 2720 2721 v2 slider_size = {{MIN(140.0f, r.size.w), ui->font.baseSize}}; 2722 v2 button_size = {{MIN(r.size.w, slider_size.x + ui->font.baseSize), ui->font.baseSize * 1.5}}; 2723 2724 f32 text_off = r.pos.x + 0.5 * MAX(0, (r.size.w - slider_size.w - ui->font.baseSize)); 2725 f32 slider_off = r.pos.x + 0.5 * (r.size.w - slider_size.w); 2726 f32 button_off = r.pos.x + 0.5 * (r.size.w - button_size.w); 2727 2728 v2 at = {{text_off, r.pos.y}}; 2729 2730 v4 hsv_power_slider = {{0.35 * ease_cubic(1.0f - lip->transmit_power), 0.65f, 0.65f, 1}}; 2731 at.y += draw_text(s8("Power:"), at, &text_spec).y; 2732 at.x = slider_off; 2733 at.y += draw_variable_slider(ui, &lv->transmit_power, (Rect){.pos = at, .size = slider_size}, 2734 lip->transmit_power, hsv_to_rgb(hsv_power_slider), mouse); 2735 2736 at.x = text_off; 2737 at.y += draw_text(s8("TGC:"), at, &text_spec).y; 2738 at.x = slider_off; 2739 for (u32 i = 0; i < countof(lip->tgc_control_points); i++) { 2740 Variable *v = lv->tgc_control_points + i; 2741 at.y += draw_variable_slider(ui, v, (Rect){.pos = at, .size = slider_size}, 2742 lip->tgc_control_points[i], g_colour_palette[1], mouse); 2743 2744 if (interaction_is_hot(ui, auto_interaction(r, v))) 2745 lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_TGCControlPoints; 2746 } 2747 2748 at.x = button_off; 2749 at.y += ui->font.baseSize * 0.5; 2750 at.y += draw_fancy_button(ui, &lv->stop_button, lv->stop_button.name, 2751 (Rect){.pos = at, .size = button_size}, 2752 BORDER_COLOUR, mouse, text_spec).y; 2753 2754 if (lip->save_enabled) { 2755 b32 active = lip->save_active; 2756 s8 label = lv->save_button.cycler.labels[active % lv->save_button.cycler.cycle_length]; 2757 2758 lv->save_button_blink_t += lv->save_button_blink_scale * dt_for_frame; 2759 if (lv->save_button_blink_t >= 1.0f) lv->save_button_blink_scale = -BLINK_SPEED; 2760 if (lv->save_button_blink_t <= 0.0f) lv->save_button_blink_scale = BLINK_SPEED; 2761 2762 v4 border_colour = BORDER_COLOUR; 2763 if (active) border_colour = v4_lerp(border_colour, FOCUSED_COLOUR, ease_cubic(lv->save_button_blink_t)); 2764 2765 at.y += ui->font.baseSize * 0.25; 2766 at.y += draw_fancy_button(ui, &lv->save_button, label, (Rect){.pos = at, .size = button_size}, 2767 border_colour, mouse, text_spec).y; 2768 2769 if (interaction_is_hot(ui, auto_interaction(r, &lv->save_button))) 2770 lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_SaveData; 2771 } 2772 2773 if (interaction_is_hot(ui, auto_interaction(r, &lv->transmit_power))) 2774 lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_TransmitPower; 2775 if (interaction_is_hot(ui, auto_interaction(r, &lv->stop_button))) 2776 lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_StopImaging; 2777 2778 v2 result = {{r.size.w, at.y - r.pos.y}}; 2779 return result; 2780 } 2781 2782 struct variable_iterator { Variable *current; }; 2783 function i32 2784 variable_iterator_next(struct variable_iterator *it) 2785 { 2786 i32 result = 0; 2787 2788 if (it->current->type == VT_GROUP && it->current->group.expanded) { 2789 it->current = it->current->group.first; 2790 result++; 2791 } else { 2792 while (it->current) { 2793 if (it->current->next) { 2794 it->current = it->current->next; 2795 break; 2796 } 2797 it->current = it->current->parent; 2798 result--; 2799 } 2800 } 2801 2802 return result; 2803 } 2804 2805 function v2 2806 draw_ui_view_menu(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 mouse, TextSpec text_spec) 2807 { 2808 assert(group->type == VT_GROUP); 2809 Table *table = table_new(&arena, 0, TextAlignment_Left, TextAlignment_Right); 2810 table->row_border_thick = 2.0f; 2811 table->cell_pad = (v2){{16.0f, 8.0f}}; 2812 2813 i32 nesting = 0; 2814 for (struct variable_iterator it = {group->group.first}; 2815 it.current; 2816 nesting = variable_iterator_next(&it)) 2817 { 2818 (void)nesting; 2819 assert(nesting == 0); 2820 Variable *var = it.current; 2821 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 2822 switch (var->type) { 2823 case VT_B32: 2824 case VT_CYCLER: 2825 { 2826 cells[0] = (TableCell){.text = var->name}; 2827 cells[1] = table_variable_cell(&arena, var); 2828 }break; 2829 case VT_UI_BUTTON:{ 2830 cells[0] = (TableCell){.text = var->name, .kind = TableCellKind_Variable, .var = var}; 2831 }break; 2832 InvalidDefaultCase; 2833 } 2834 } 2835 2836 r.size = table_extent(table, arena, text_spec.font); 2837 return draw_table(ui, arena, table, r, text_spec, mouse, 0); 2838 } 2839 2840 function v2 2841 draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 mouse, TextSpec text_spec) 2842 { 2843 assert(group->type == VT_GROUP); 2844 Table *table = table_new(&arena, 0, TextAlignment_Left, TextAlignment_Left, TextAlignment_Right); 2845 2846 i32 nesting = 0; 2847 for (struct variable_iterator it = {group->group.first}; 2848 it.current; 2849 nesting = variable_iterator_next(&it)) 2850 { 2851 while (nesting > 0) { 2852 table = table_begin_subtable(table, &arena, TextAlignment_Left, 2853 TextAlignment_Center, TextAlignment_Right); 2854 nesting--; 2855 } 2856 while (nesting < 0) { table = table_end_subtable(table); nesting++; } 2857 2858 Variable *var = it.current; 2859 switch (var->type) { 2860 case VT_CYCLER: 2861 case VT_BEAMFORMER_VARIABLE: 2862 { 2863 s8 suffix = s8(""); 2864 if (var->type == VT_BEAMFORMER_VARIABLE) 2865 suffix = var->beamformer_variable.suffix; 2866 table_push_parameter_row(table, &arena, var->name, var, suffix); 2867 }break; 2868 case VT_GROUP:{ 2869 VariableGroup *g = &var->group; 2870 2871 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 2872 cells[0] = (TableCell){.text = var->name, .kind = TableCellKind_Variable, .var = var}; 2873 2874 if (!g->expanded) { 2875 Stream sb = arena_stream(arena); 2876 stream_append_variable_group(&sb, var); 2877 cells[1].kind = TableCellKind_VariableGroup; 2878 cells[1].text = arena_stream_commit(&arena, &sb); 2879 cells[1].var = var; 2880 2881 Variable *v = g->first; 2882 assert(!v || v->type == VT_BEAMFORMER_VARIABLE); 2883 /* NOTE(rnp): assume the suffix is the same for all elements */ 2884 if (v) cells[2].text = v->beamformer_variable.suffix; 2885 } 2886 }break; 2887 InvalidDefaultCase; 2888 } 2889 } 2890 2891 v2 result = table_extent(table, arena, text_spec.font); 2892 draw_table(ui, arena, table, r, text_spec, mouse, 0); 2893 return result; 2894 } 2895 2896 function Rect 2897 draw_ui_view_container(BeamformerUI *ui, Variable *var, v2 mouse, Rect bounds) 2898 { 2899 UIView *fw = &var->view; 2900 Rect result = fw->rect; 2901 if (fw->rect.size.x > 0 && fw->rect.size.y > 0) { 2902 f32 line_height = ui->small_font.baseSize; 2903 2904 f32 pad = MAX(line_height + 5, UI_REGION_PAD); 2905 if (fw->rect.pos.y < pad) 2906 fw->rect.pos.y += pad - fw->rect.pos.y; 2907 result = fw->rect; 2908 2909 f32 delta_x = (result.pos.x + result.size.x) - (bounds.size.x + bounds.pos.x); 2910 if (delta_x > 0) { 2911 result.pos.x -= delta_x; 2912 result.pos.x = MAX(0, result.pos.x); 2913 } 2914 2915 Rect container = result; 2916 if (fw->close) { 2917 container.pos.y -= 5 + line_height; 2918 container.size.y += 2 + line_height; 2919 Rect handle = {{container.pos, (v2){.x = container.size.w, .y = 2 + line_height}}}; 2920 Rect close; 2921 hover_interaction(ui, mouse, auto_interaction(container, var)); 2922 cut_rect_horizontal(handle, handle.size.w - handle.size.h - 6, 0, &close); 2923 close.size.w = close.size.h; 2924 DrawRectangleRounded(handle.rl, 0.1, 0, colour_from_normalized(BG_COLOUR)); 2925 DrawRectangleRoundedLinesEx(handle.rl, 0.2, 0, 2, BLACK); 2926 draw_close_button(ui, fw->close, mouse, close, (v2){{0.45, 0.45}}); 2927 } else { 2928 hover_interaction(ui, mouse, auto_interaction(container, var)); 2929 } 2930 f32 roundness = 12.0f / fw->rect.size.y; 2931 DrawRectangleRounded(result.rl, roundness / 2, 0, colour_from_normalized(BG_COLOUR)); 2932 DrawRectangleRoundedLinesEx(result.rl, roundness, 0, 2, BLACK); 2933 } 2934 return result; 2935 } 2936 2937 function void 2938 draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec text_spec) 2939 { 2940 assert(ui_view->type == VT_UI_VIEW || ui_view->type == VT_UI_MENU || ui_view->type == VT_UI_TEXT_BOX); 2941 2942 UIView *view = &ui_view->view; 2943 2944 if (view->flags & UIViewFlag_Floating) { 2945 r = draw_ui_view_container(ui, ui_view, mouse, r); 2946 } else { 2947 if (view->rect.size.h - r.size.h < view->rect.pos.h) 2948 view->rect.pos.h = view->rect.size.h - r.size.h; 2949 2950 if (view->rect.size.h - r.size.h < 0) 2951 view->rect.pos.h = 0; 2952 2953 r.pos.y -= view->rect.pos.h; 2954 } 2955 2956 v2 size = {0}; 2957 2958 Variable *var = view->child; 2959 switch (var->type) { 2960 case VT_GROUP:{ 2961 if (ui_view->type == VT_UI_MENU) 2962 size = draw_ui_view_menu(ui, var, ui->arena, r, mouse, text_spec); 2963 else { 2964 size = draw_ui_view_listing(ui, var, ui->arena, r, mouse, text_spec); 2965 } 2966 }break; 2967 case VT_BEAMFORMER_FRAME_VIEW: { 2968 BeamformerFrameView *bv = var->generic; 2969 if (frame_view_ready_to_present(ui, bv)) { 2970 if (bv->kind == BeamformerFrameViewKind_3DXPlane) 2971 draw_3D_xplane_frame_view(ui, ui->arena, var, r, mouse); 2972 else 2973 draw_beamformer_frame_view(ui, ui->arena, var, r, mouse); 2974 } 2975 } break; 2976 case VT_COMPUTE_PROGRESS_BAR: { 2977 size = draw_compute_progress_bar(ui, ui->arena, &var->compute_progress_bar, r); 2978 } break; 2979 case VT_COMPUTE_STATS_VIEW:{ size = draw_compute_stats_view(ui, ui->arena, var, r, mouse); }break; 2980 case VT_LIVE_CONTROLS_VIEW:{ 2981 if (view->rect.size.h - r.size.h < 0) 2982 r.pos.y += 0.5 * (r.size.h - view->rect.size.h); 2983 BeamformerSharedMemory *sm = ui->shared_memory.region; 2984 if (sm->live_imaging_parameters.active) 2985 size = draw_live_controls_view(ui, ui->arena, var, r, mouse); 2986 }break; 2987 InvalidDefaultCase; 2988 } 2989 2990 view->rect.size = size; 2991 } 2992 2993 function void 2994 draw_layout_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse) 2995 { 2996 if (var->type != VT_UI_REGION_SPLIT) { 2997 v2 shrink = {.x = UI_REGION_PAD, .y = UI_REGION_PAD}; 2998 draw_rect = shrink_rect_centered(draw_rect, shrink); 2999 draw_rect.size = v2_floor(draw_rect.size); 3000 BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); 3001 draw_rect = draw_title_bar(ui, ui->arena, var, draw_rect, mouse); 3002 EndScissorMode(); 3003 } 3004 3005 /* TODO(rnp): post order traversal of the ui tree will remove the need for this */ 3006 if (!CheckCollisionPointRec(mouse.rl, draw_rect.rl)) 3007 mouse = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; 3008 3009 draw_rect.size = v2_floor(draw_rect.size); 3010 BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); 3011 switch (var->type) { 3012 case VT_UI_VIEW: { 3013 hover_interaction(ui, mouse, auto_interaction(draw_rect, var)); 3014 TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; 3015 draw_ui_view(ui, var, draw_rect, mouse, text_spec); 3016 } break; 3017 case VT_UI_REGION_SPLIT: { 3018 RegionSplit *rs = &var->region_split; 3019 3020 Rect split, hover; 3021 switch (rs->direction) { 3022 case RSD_VERTICAL: { 3023 split_rect_vertical(draw_rect, rs->fraction, 0, &split); 3024 split.pos.x += UI_REGION_PAD; 3025 split.pos.y -= UI_SPLIT_HANDLE_THICK / 2; 3026 split.size.h = UI_SPLIT_HANDLE_THICK; 3027 split.size.w -= 2 * UI_REGION_PAD; 3028 hover = extend_rect_centered(split, (v2){.y = 0.75 * UI_REGION_PAD}); 3029 } break; 3030 case RSD_HORIZONTAL: { 3031 split_rect_horizontal(draw_rect, rs->fraction, 0, &split); 3032 split.pos.x -= UI_SPLIT_HANDLE_THICK / 2; 3033 split.pos.y += UI_REGION_PAD; 3034 split.size.w = UI_SPLIT_HANDLE_THICK; 3035 split.size.h -= 2 * UI_REGION_PAD; 3036 hover = extend_rect_centered(split, (v2){.x = 0.75 * UI_REGION_PAD}); 3037 } break; 3038 } 3039 3040 Interaction drag = {.kind = InteractionKind_Drag, .rect = hover, .var = var}; 3041 hover_interaction(ui, mouse, drag); 3042 3043 v4 colour = HOVERED_COLOUR; 3044 colour.a = var->hover_t; 3045 DrawRectangleRounded(split.rl, 0.6, 0, colour_from_normalized(colour)); 3046 } break; 3047 InvalidDefaultCase; 3048 } 3049 EndScissorMode(); 3050 } 3051 3052 function void 3053 draw_ui_regions(BeamformerUI *ui, Rect window, v2 mouse) 3054 { 3055 struct region_frame { 3056 Variable *var; 3057 Rect rect; 3058 } init[16]; 3059 3060 struct { 3061 struct region_frame *data; 3062 iz count; 3063 iz capacity; 3064 } stack = {init, 0, ARRAY_COUNT(init)}; 3065 3066 TempArena arena_savepoint = begin_temp_arena(&ui->arena); 3067 3068 *da_push(&ui->arena, &stack) = (struct region_frame){ui->regions, window}; 3069 while (stack.count) { 3070 struct region_frame *top = stack.data + --stack.count; 3071 Rect rect = top->rect; 3072 draw_layout_variable(ui, top->var, rect, mouse); 3073 3074 if (top->var->type == VT_UI_REGION_SPLIT) { 3075 Rect first, second; 3076 RegionSplit *rs = &top->var->region_split; 3077 switch (rs->direction) { 3078 case RSD_VERTICAL: { 3079 split_rect_vertical(rect, rs->fraction, &first, &second); 3080 } break; 3081 case RSD_HORIZONTAL: { 3082 split_rect_horizontal(rect, rs->fraction, &first, &second); 3083 } break; 3084 } 3085 3086 *da_push(&ui->arena, &stack) = (struct region_frame){rs->right, second}; 3087 *da_push(&ui->arena, &stack) = (struct region_frame){rs->left, first}; 3088 } 3089 } 3090 3091 end_temp_arena(arena_savepoint); 3092 } 3093 3094 function void 3095 draw_floating_widgets(BeamformerUI *ui, Rect window_rect, v2 mouse) 3096 { 3097 TextSpec text_spec = {.font = &ui->small_font, .colour = FG_COLOUR}; 3098 window_rect = shrink_rect_centered(window_rect, (v2){{UI_REGION_PAD, UI_REGION_PAD}}); 3099 for (Variable *var = ui->floating_widget_sentinal.parent; 3100 var != &ui->floating_widget_sentinal; 3101 var = var->parent) 3102 { 3103 if (var->type == VT_UI_TEXT_BOX) { 3104 UIView *fw = &var->view; 3105 InputState *is = &ui->text_input_state; 3106 3107 draw_ui_view_container(ui, var, mouse, fw->rect); 3108 3109 f32 cursor_width = (is->cursor == is->count) ? 0.55 * is->font->baseSize : 4; 3110 s8 text = {.len = is->count, .data = is->buf}; 3111 v2 text_size = measure_text(*is->font, text); 3112 3113 f32 text_pad = 4.0f; 3114 f32 desired_width = text_pad + text_size.w + cursor_width; 3115 fw->rect.size = (v2){{MAX(desired_width, fw->rect.size.w), text_size.h + text_pad}}; 3116 3117 v2 text_position = {{fw->rect.pos.x + text_pad / 2, fw->rect.pos.y + text_pad / 2}}; 3118 f32 cursor_offset = measure_text(*is->font, (s8){is->cursor, text.data}).w; 3119 cursor_offset += text_position.x; 3120 3121 Rect cursor; 3122 cursor.pos = (v2){{cursor_offset, text_position.y}}; 3123 cursor.size = (v2){{cursor_width, text_size.h}}; 3124 3125 v4 cursor_colour = FOCUSED_COLOUR; 3126 cursor_colour.a = CLAMP01(is->cursor_blink_t); 3127 v4 text_colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, fw->child->hover_t); 3128 3129 TextSpec text_spec = {.font = is->font, .colour = text_colour}; 3130 draw_text(text, text_position, &text_spec); 3131 DrawRectanglePro(cursor.rl, (Vector2){0}, 0, colour_from_normalized(cursor_colour)); 3132 } else { 3133 draw_ui_view(ui, var, window_rect, mouse, text_spec); 3134 } 3135 } 3136 } 3137 3138 function void 3139 scroll_interaction(Variable *var, f32 delta) 3140 { 3141 switch (var->type) { 3142 case VT_B32:{ var->bool32 = !var->bool32; }break; 3143 case VT_F32:{ var->real32 += delta; }break; 3144 case VT_I32:{ var->signed32 += delta; }break; 3145 case VT_U32:{ var->unsigned32 += delta; }break; 3146 case VT_SCALED_F32:{ var->scaled_real32.val += delta * var->scaled_real32.scale; }break; 3147 case VT_BEAMFORMER_FRAME_VIEW:{ 3148 BeamformerFrameView *bv = var->generic; 3149 bv->threshold.real32 += delta; 3150 bv->dirty = 1; 3151 } break; 3152 case VT_BEAMFORMER_VARIABLE:{ 3153 BeamformerVariable *bv = &var->beamformer_variable; 3154 f32 value = *bv->store + delta * bv->scroll_scale; 3155 *bv->store = CLAMP(value, bv->limits.x, bv->limits.y); 3156 }break; 3157 case VT_CYCLER:{ 3158 *var->cycler.state += delta > 0? 1 : -1; 3159 *var->cycler.state %= var->cycler.cycle_length; 3160 }break; 3161 case VT_UI_VIEW:{ 3162 var->view.rect.pos.h += UI_SCROLL_SPEED * delta; 3163 var->view.rect.pos.h = MAX(0, var->view.rect.pos.h); 3164 }break; 3165 InvalidDefaultCase; 3166 } 3167 } 3168 3169 function void 3170 begin_text_input(InputState *is, Rect r, Variable *container, v2 mouse) 3171 { 3172 assert(container->type == VT_UI_TEXT_BOX); 3173 Font *font = is->font = is->hot_font; 3174 Stream s = {.cap = countof(is->buf), .data = is->buf}; 3175 stream_append_variable(&s, container->view.child); 3176 is->count = s.widx; 3177 is->container = container; 3178 3179 /* NOTE: extra offset to help with putting a cursor at idx 0 */ 3180 #define TEXT_HALF_CHAR_WIDTH 10 3181 f32 hover_p = CLAMP01((mouse.x - r.pos.x) / r.size.w); 3182 f32 x_off = TEXT_HALF_CHAR_WIDTH, x_bounds = r.size.w * hover_p; 3183 i32 i; 3184 for (i = 0; i < is->count && x_off < x_bounds; i++) { 3185 /* NOTE: assumes font glyphs are ordered ASCII */ 3186 i32 idx = is->buf[i] - 0x20; 3187 x_off += font->glyphs[idx].advanceX; 3188 if (font->glyphs[idx].advanceX == 0) 3189 x_off += font->recs[idx].width; 3190 } 3191 is->cursor = i; 3192 } 3193 3194 function void 3195 end_text_input(InputState *is, Variable *var) 3196 { 3197 f64 value = parse_f64((s8){.len = is->count, .data = is->buf}); 3198 3199 switch (var->type) { 3200 case VT_SCALED_F32:{ var->scaled_real32.val = value; }break; 3201 case VT_F32:{ var->real32 = value; }break; 3202 case VT_BEAMFORMER_VARIABLE:{ 3203 BeamformerVariable *bv = &var->beamformer_variable; 3204 *bv->store = CLAMP(value / bv->display_scale, bv->limits.x, bv->limits.y); 3205 var->hover_t = 0; 3206 }break; 3207 InvalidDefaultCase; 3208 } 3209 } 3210 3211 function b32 3212 update_text_input(InputState *is, Variable *var) 3213 { 3214 assert(is->cursor != -1); 3215 3216 is->cursor_blink_t += is->cursor_blink_scale * dt_for_frame; 3217 if (is->cursor_blink_t >= 1.0f) is->cursor_blink_scale = -BLINK_SPEED; 3218 if (is->cursor_blink_t <= 0.0f) is->cursor_blink_scale = BLINK_SPEED; 3219 3220 var->hover_t -= 2 * HOVER_SPEED * dt_for_frame; 3221 var->hover_t = CLAMP01(var->hover_t); 3222 3223 /* NOTE: handle multiple input keys on a single frame */ 3224 for (i32 key = GetCharPressed(); 3225 is->count < countof(is->buf) && key > 0; 3226 key = GetCharPressed()) 3227 { 3228 b32 allow_key = (BETWEEN(key, '0', '9') || (key == '.') || 3229 (key == '-' && is->cursor == 0)); 3230 if (allow_key) { 3231 mem_move(is->buf + is->cursor + 1, 3232 is->buf + is->cursor, 3233 is->count - is->cursor); 3234 is->buf[is->cursor++] = key; 3235 is->count++; 3236 } 3237 } 3238 3239 is->cursor -= (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) && is->cursor > 0; 3240 is->cursor += (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) && is->cursor < is->count; 3241 3242 if ((IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) && is->cursor > 0) { 3243 is->cursor--; 3244 if (is->cursor < countof(is->buf) - 1) { 3245 mem_move(is->buf + is->cursor, 3246 is->buf + is->cursor + 1, 3247 is->count - is->cursor - 1); 3248 } 3249 is->count--; 3250 } 3251 3252 if ((IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) && is->cursor < is->count) { 3253 mem_move(is->buf + is->cursor, 3254 is->buf + is->cursor + 1, 3255 is->count - is->cursor - 1); 3256 is->count--; 3257 } 3258 3259 b32 result = IsKeyPressed(KEY_ENTER); 3260 return result; 3261 } 3262 3263 function void 3264 scale_bar_interaction(BeamformerUI *ui, ScaleBar *sb, v2 mouse) 3265 { 3266 Interaction *it = &ui->interaction; 3267 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 3268 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 3269 f32 mouse_wheel = GetMouseWheelMoveV().y; 3270 3271 if (mouse_left_pressed) { 3272 v2 world_mouse = screen_point_to_world_2d(mouse, it->rect.pos, 3273 v2_add(it->rect.pos, it->rect.size), 3274 (v2){{*sb->min_value, *sb->min_value}}, 3275 (v2){{*sb->max_value, *sb->max_value}}); 3276 f32 new_coord = F32_INFINITY; 3277 switch (sb->direction) { 3278 case SB_LATERAL: new_coord = world_mouse.x; break; 3279 case SB_AXIAL: new_coord = world_mouse.y; break; 3280 } 3281 if (sb->zoom_starting_coord == F32_INFINITY) { 3282 sb->zoom_starting_coord = new_coord; 3283 } else { 3284 f32 min = sb->zoom_starting_coord; 3285 f32 max = new_coord; 3286 if (min > max) swap(min, max); 3287 3288 v2_sll *savepoint = SLLPop(ui->scale_bar_savepoint_freelist); 3289 if (!savepoint) savepoint = push_struct(&ui->arena, v2_sll); 3290 3291 savepoint->v.x = *sb->min_value; 3292 savepoint->v.y = *sb->max_value; 3293 SLLPush(savepoint, sb->savepoint_stack); 3294 3295 *sb->min_value = min; 3296 *sb->max_value = max; 3297 3298 sb->zoom_starting_coord = F32_INFINITY; 3299 } 3300 } 3301 3302 if (mouse_right_pressed) { 3303 v2_sll *savepoint = sb->savepoint_stack; 3304 if (savepoint) { 3305 *sb->min_value = savepoint->v.x; 3306 *sb->max_value = savepoint->v.y; 3307 sb->savepoint_stack = savepoint->next; 3308 SLLPush(savepoint, ui->scale_bar_savepoint_freelist); 3309 } 3310 sb->zoom_starting_coord = F32_INFINITY; 3311 } 3312 3313 if (mouse_wheel) { 3314 *sb->min_value += mouse_wheel * sb->scroll_scale.x; 3315 *sb->max_value += mouse_wheel * sb->scroll_scale.y; 3316 } 3317 } 3318 3319 function void 3320 ui_widget_bring_to_front(Variable *sentinal, Variable *widget) 3321 { 3322 /* TODO(rnp): clean up the linkage so this can be a macro */ 3323 widget->parent->next = widget->next; 3324 widget->next->parent = widget->parent; 3325 3326 widget->parent = sentinal; 3327 widget->next = sentinal->next; 3328 widget->next->parent = widget; 3329 sentinal->next = widget; 3330 } 3331 3332 function void 3333 ui_view_close(BeamformerUI *ui, Variable *view) 3334 { 3335 switch (view->type) { 3336 case VT_UI_MENU: 3337 case VT_UI_TEXT_BOX: 3338 { 3339 UIView *fw = &view->view; 3340 if (view->type == VT_UI_MENU) { 3341 assert(fw->child->type == VT_GROUP); 3342 fw->child->group.expanded = 0; 3343 fw->child->group.container = 0; 3344 } else { 3345 end_text_input(&ui->text_input_state, fw->child); 3346 } 3347 view->parent->next = view->next; 3348 view->next->parent = view->parent; 3349 if (fw->close) SLLPush(fw->close, ui->variable_freelist); 3350 SLLPush(view, ui->variable_freelist); 3351 }break; 3352 case VT_UI_VIEW:{ 3353 assert(view->parent->type == VT_UI_REGION_SPLIT); 3354 Variable *region = view->parent; 3355 3356 Variable *parent = region->parent; 3357 Variable *remaining = region->region_split.left; 3358 if (remaining == view) remaining = region->region_split.right; 3359 3360 ui_view_free(ui, view); 3361 3362 assert(parent->type == VT_UI_REGION_SPLIT); 3363 if (parent->region_split.left == region) { 3364 parent->region_split.left = remaining; 3365 } else { 3366 parent->region_split.right = remaining; 3367 } 3368 remaining->parent = parent; 3369 3370 SLLPush(region, ui->variable_freelist); 3371 }break; 3372 InvalidDefaultCase; 3373 } 3374 } 3375 3376 function void 3377 ui_button_interaction(BeamformerUI *ui, Variable *button) 3378 { 3379 assert(button->type == VT_UI_BUTTON); 3380 switch (button->button) { 3381 case UI_BID_VIEW_CLOSE:{ ui_view_close(ui, button->parent); }break; 3382 case UI_BID_FV_COPY_HORIZONTAL:{ 3383 ui_copy_frame(ui, button->parent->parent, RSD_HORIZONTAL); 3384 }break; 3385 case UI_BID_FV_COPY_VERTICAL:{ 3386 ui_copy_frame(ui, button->parent->parent, RSD_VERTICAL); 3387 }break; 3388 case UI_BID_GM_OPEN_VIEW_RIGHT:{ 3389 ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL, BeamformerFrameViewKind_Latest); 3390 }break; 3391 case UI_BID_GM_OPEN_VIEW_BELOW:{ 3392 ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL, BeamformerFrameViewKind_Latest); 3393 }break; 3394 } 3395 } 3396 3397 function void 3398 ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) 3399 { 3400 Interaction hot = ui->hot_interaction; 3401 if (hot.kind != InteractionKind_None) { 3402 if (hot.kind == InteractionKind_Auto) { 3403 switch (hot.var->type) { 3404 case VT_NULL:{ hot.kind = InteractionKind_Nop; }break; 3405 case VT_B32:{ hot.kind = InteractionKind_Set; }break; 3406 case VT_SCALE_BAR:{ hot.kind = InteractionKind_Set; }break; 3407 case VT_UI_BUTTON:{ hot.kind = InteractionKind_Button; }break; 3408 case VT_GROUP:{ hot.kind = InteractionKind_Set; }break; 3409 case VT_UI_TEXT_BOX: 3410 case VT_UI_MENU: 3411 { 3412 if (hot.var->type == VT_UI_MENU) { 3413 hot.kind = InteractionKind_Drag; 3414 } else { 3415 hot.kind = InteractionKind_Text; 3416 begin_text_input(&ui->text_input_state, hot.rect, hot.var, input->mouse); 3417 } 3418 ui_widget_bring_to_front(&ui->floating_widget_sentinal, hot.var); 3419 }break; 3420 case VT_UI_VIEW:{ 3421 if (scroll) hot.kind = InteractionKind_Scroll; 3422 else hot.kind = InteractionKind_Nop; 3423 }break; 3424 case VT_X_PLANE_SHIFT:{ 3425 assert(hot.var->parent && hot.var->parent->type == VT_BEAMFORMER_FRAME_VIEW); 3426 BeamformerFrameView *bv = hot.var->parent->generic; 3427 if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { 3428 XPlaneShift *xp = &hot.var->x_plane_shift; 3429 xp->start_point = xp->end_point = bv->hit_test_point; 3430 hot.kind = InteractionKind_Drag; 3431 } else { 3432 if (scroll) { 3433 hot.kind = InteractionKind_Scroll; 3434 hot.var = &bv->threshold; 3435 } else { 3436 hot.kind = InteractionKind_Nop; 3437 } 3438 } 3439 }break; 3440 case VT_BEAMFORMER_FRAME_VIEW:{ 3441 if (scroll) { 3442 hot.kind = InteractionKind_Scroll; 3443 } else { 3444 BeamformerFrameView *bv = hot.var->generic; 3445 switch (bv->kind) { 3446 case BeamformerFrameViewKind_3DXPlane:{ hot.kind = InteractionKind_Drag; }break; 3447 default:{ 3448 hot.kind = InteractionKind_Nop; 3449 switch (++bv->ruler.state) { 3450 case RulerState_Start:{ 3451 hot.kind = InteractionKind_Ruler; 3452 v2 r_max = v2_add(hot.rect.pos, hot.rect.size); 3453 v2 p = screen_point_to_world_2d(input->mouse, hot.rect.pos, r_max, 3454 XZ(bv->min_coordinate), 3455 XZ(bv->max_coordinate)); 3456 bv->ruler.start = p; 3457 }break; 3458 case RulerState_Hold:{}break; 3459 default:{ bv->ruler.state = RulerState_None; }break; 3460 } 3461 }break; 3462 } 3463 } 3464 }break; 3465 case VT_CYCLER:{ 3466 if (scroll) hot.kind = InteractionKind_Scroll; 3467 else hot.kind = InteractionKind_Set; 3468 }break; 3469 case VT_BEAMFORMER_VARIABLE: 3470 case VT_F32: 3471 case VT_SCALED_F32: 3472 { 3473 if (scroll) { 3474 hot.kind = InteractionKind_Scroll; 3475 } else if (hot.var->flags & V_TEXT) { 3476 hot.kind = InteractionKind_Text; 3477 Variable *w = add_floating_view(ui, &ui->arena, VT_UI_TEXT_BOX, 3478 hot.rect.pos, hot.var, 0); 3479 w->view.rect = hot.rect; 3480 begin_text_input(&ui->text_input_state, hot.rect, w, input->mouse); 3481 } else { 3482 hot.kind = InteractionKind_Drag; 3483 } 3484 }break; 3485 InvalidDefaultCase; 3486 } 3487 } 3488 3489 ui->interaction = hot; 3490 3491 if (ui->interaction.var->flags & V_HIDES_CURSOR) { 3492 HideCursor(); 3493 DisableCursor(); 3494 /* wtf raylib */ 3495 SetMousePosition(input->mouse.x, input->mouse.y); 3496 } 3497 } else { 3498 ui->interaction.kind = InteractionKind_Nop; 3499 } 3500 } 3501 3502 function void 3503 ui_extra_actions(BeamformerUI *ui, Variable *var) 3504 { 3505 switch (var->type) { 3506 case VT_CYCLER:{ 3507 assert(var->parent && var->parent->parent && var->parent->parent->type == VT_UI_VIEW); 3508 Variable *view_var = var->parent->parent; 3509 UIView *view = &view_var->view; 3510 switch (view->child->type) { 3511 case VT_BEAMFORMER_FRAME_VIEW:{ 3512 BeamformerFrameView *old = view->child->generic; 3513 BeamformerFrameView *new = view->child->generic = ui_beamformer_frame_view_new(ui, &ui->arena); 3514 BeamformerFrameViewKind last_kind = (old->kind - 1) % BeamformerFrameViewKind_Count; 3515 3516 /* NOTE(rnp): log_scale gets released below before its needed */ 3517 b32 log_scale = old->log_scale->bool32; 3518 ui_variable_free_group_items(ui, view->menu); 3519 3520 ui_beamformer_frame_view_release_subresources(ui, old, last_kind); 3521 ui_beamformer_frame_view_convert(ui, &ui->arena, view->child, view->menu, old->kind, old, log_scale); 3522 if (new->kind == BeamformerFrameViewKind_Copy && old->frame) 3523 ui_beamformer_frame_view_copy_frame(ui, new, old); 3524 3525 DLLRemove(old); 3526 SLLPush(old, ui->view_freelist); 3527 }break; 3528 InvalidDefaultCase; 3529 } 3530 }break; 3531 InvalidDefaultCase; 3532 } 3533 } 3534 3535 function void 3536 ui_live_control_update(BeamformerUI *ui, Variable *controls) 3537 { 3538 assert(controls->type == VT_LIVE_CONTROLS_VIEW); 3539 BeamformerSharedMemory *sm = ui->shared_memory.region; 3540 BeamformerLiveControlsView *lv = controls->generic; 3541 atomic_or_u32(&sm->live_imaging_dirty_flags, lv->hot_field_flag); 3542 } 3543 3544 function void 3545 ui_end_interact(BeamformerUI *ui, v2 mouse) 3546 { 3547 Interaction *it = &ui->interaction; 3548 b32 start_compute = (it->var->flags & V_CAUSES_COMPUTE) != 0; 3549 switch (it->kind) { 3550 case InteractionKind_Nop:{}break; 3551 case InteractionKind_Drag:{ 3552 switch (it->var->type) { 3553 case VT_X_PLANE_SHIFT:{ 3554 assert(it->var->parent && it->var->parent->type == VT_BEAMFORMER_FRAME_VIEW); 3555 XPlaneShift *xp = &it->var->x_plane_shift; 3556 BeamformerFrameView *view = it->var->parent->generic; 3557 BeamformerViewPlaneTag plane = view_plane_tag_from_x_plane_shift(view, it->var); 3558 f32 rotation = x_plane_rotation_for_view_plane(view, plane); 3559 m4 x_rotation = m4_rotation_about_y(rotation); 3560 v3 Z = x_rotation.c[2].xyz; 3561 f32 delta = v3_dot(Z, v3_sub(xp->end_point, xp->start_point)); 3562 xp->start_point = xp->end_point; 3563 3564 BeamformerSharedMemory *sm = ui->shared_memory.region; 3565 BeamformerLiveImagingParameters *li = &sm->live_imaging_parameters; 3566 li->image_plane_offsets[plane] += delta; 3567 atomic_or_u32(&sm->live_imaging_dirty_flags, BeamformerLiveImagingDirtyFlags_ImagePlaneOffsets); 3568 }break; 3569 default:{}break; 3570 } 3571 }break; 3572 case InteractionKind_Set:{ 3573 switch (it->var->type) { 3574 case VT_B32:{ it->var->bool32 = !it->var->bool32; }break; 3575 case VT_GROUP:{ it->var->group.expanded = !it->var->group.expanded; }break; 3576 case VT_SCALE_BAR:{ scale_bar_interaction(ui, &it->var->scale_bar, mouse); }break; 3577 case VT_CYCLER:{ 3578 *it->var->cycler.state += 1; 3579 *it->var->cycler.state %= it->var->cycler.cycle_length; 3580 }break; 3581 InvalidDefaultCase; 3582 } 3583 }break; 3584 case InteractionKind_Menu:{ 3585 assert(it->var->type == VT_GROUP); 3586 VariableGroup *g = &it->var->group; 3587 if (g->container) { 3588 ui_widget_bring_to_front(&ui->floating_widget_sentinal, g->container); 3589 } else { 3590 g->container = add_floating_view(ui, &ui->arena, VT_UI_MENU, mouse, it->var, 1); 3591 } 3592 }break; 3593 case InteractionKind_Ruler:{ 3594 assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW); 3595 ((BeamformerFrameView *)it->var->generic)->ruler.state = RulerState_None; 3596 }break; 3597 case InteractionKind_Button:{ ui_button_interaction(ui, it->var); }break; 3598 case InteractionKind_Scroll:{ scroll_interaction(it->var, GetMouseWheelMoveV().y); }break; 3599 case InteractionKind_Text:{ ui_view_close(ui, ui->text_input_state.container); }break; 3600 InvalidDefaultCase; 3601 } 3602 3603 if (start_compute) ui->flush_params = 1; 3604 if (it->var->flags & V_UPDATE_VIEW) { 3605 Variable *parent = it->var->parent; 3606 BeamformerFrameView *frame = parent->generic; 3607 /* TODO(rnp): more straight forward way of achieving this */ 3608 if (parent->type != VT_BEAMFORMER_FRAME_VIEW) { 3609 assert(parent->parent->type == VT_UI_VIEW); 3610 assert(parent->parent->view.child->type == VT_BEAMFORMER_FRAME_VIEW); 3611 frame = parent->parent->view.child->generic; 3612 } 3613 frame->dirty = 1; 3614 } 3615 3616 if (it->var->flags & V_LIVE_CONTROL) 3617 ui_live_control_update(ui, it->var->parent); 3618 3619 if (it->var->flags & V_HIDES_CURSOR) 3620 EnableCursor(); 3621 3622 if (it->var->flags & V_EXTRA_ACTION) 3623 ui_extra_actions(ui, it->var); 3624 3625 ui->interaction = (Interaction){.kind = InteractionKind_None}; 3626 } 3627 3628 function void 3629 ui_sticky_interaction_check_end(BeamformerUI *ui, v2 mouse) 3630 { 3631 Interaction *it = &ui->interaction; 3632 switch (it->kind) { 3633 case InteractionKind_Ruler:{ 3634 if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) || !point_in_rect(mouse, it->rect)) 3635 ui_end_interact(ui, mouse); 3636 }break; 3637 case InteractionKind_Text:{ 3638 Interaction text_box = auto_interaction({{0}}, ui->text_input_state.container); 3639 if (!interactions_equal(text_box, ui->hot_interaction)) 3640 ui_end_interact(ui, mouse); 3641 }break; 3642 InvalidDefaultCase; 3643 } 3644 } 3645 3646 function void 3647 ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect) 3648 { 3649 Interaction *it = &ui->interaction; 3650 if (it->kind == InteractionKind_None || interaction_is_sticky(*it)) { 3651 ui->hot_interaction = ui->next_interaction; 3652 3653 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 3654 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 3655 b32 wheel_moved = GetMouseWheelMoveV().y != 0; 3656 if (mouse_right_pressed || mouse_left_pressed || wheel_moved) { 3657 if (it->kind != InteractionKind_None) 3658 ui_sticky_interaction_check_end(ui, input->mouse); 3659 ui_begin_interact(ui, input, wheel_moved); 3660 } 3661 } 3662 3663 switch (it->kind) { 3664 case InteractionKind_Nop:{ it->kind = InteractionKind_None; }break; 3665 case InteractionKind_None:{}break; 3666 case InteractionKind_Text:{ 3667 if (update_text_input(&ui->text_input_state, it->var)) 3668 ui_end_interact(ui, input->mouse); 3669 }break; 3670 case InteractionKind_Ruler:{ 3671 assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW); 3672 BeamformerFrameView *bv = it->var->generic; 3673 v2 r_max = v2_add(it->rect.pos, it->rect.size); 3674 v2 mouse = clamp_v2_rect(input->mouse, it->rect); 3675 bv->ruler.end = screen_point_to_world_2d(mouse, it->rect.pos, r_max, 3676 XZ(bv->min_coordinate), 3677 XZ(bv->max_coordinate)); 3678 }break; 3679 case InteractionKind_Drag:{ 3680 if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { 3681 ui_end_interact(ui, input->mouse); 3682 } else { 3683 v2 ws = window_rect.size; 3684 v2 dMouse = v2_sub(input->mouse, input->last_mouse); 3685 3686 switch (it->var->type) { 3687 case VT_BEAMFORMER_VARIABLE:{ 3688 BeamformerVariable *bv = &it->var->beamformer_variable; 3689 /* TODO(rnp): vertical sliders? */ 3690 f32 mouse_frac = CLAMP01((input->mouse.x - it->rect.pos.x) / it->rect.size.w); 3691 *bv->store = bv->limits.x + mouse_frac * (bv->limits.y - bv->limits.x); 3692 }break; 3693 case VT_X_PLANE_SHIFT:{ 3694 assert(it->var->parent && it->var->parent->type == VT_BEAMFORMER_FRAME_VIEW); 3695 v2 mouse = clamp_v2_rect(input->mouse, it->rect); 3696 XPlaneShift *xp = &it->var->x_plane_shift; 3697 ray mouse_ray = ray_for_x_plane_view(ui, it->var->parent->generic, 3698 normalized_p_in_rect(it->rect, mouse, 0)); 3699 /* NOTE(rnp): project start point onto ray */ 3700 v3 s = v3_sub(xp->start_point, mouse_ray.origin); 3701 v3 r = v3_sub(mouse_ray.direction, mouse_ray.origin); 3702 f32 scale = v3_dot(s, r) / v3_magnitude_squared(r); 3703 xp->end_point = v3_add(mouse_ray.origin, v3_scale(r, scale)); 3704 }break; 3705 case VT_BEAMFORMER_FRAME_VIEW:{ 3706 BeamformerFrameView *bv = it->var->generic; 3707 switch (bv->kind) { 3708 case BeamformerFrameViewKind_3DXPlane:{ 3709 bv->rotation += dMouse.x / ws.w; 3710 if (bv->rotation > 1.0f) bv->rotation -= 1.0f; 3711 if (bv->rotation < 0.0f) bv->rotation += 1.0f; 3712 }break; 3713 InvalidDefaultCase; 3714 } 3715 }break; 3716 case VT_UI_MENU:{ 3717 v2 *pos = &ui->interaction.var->view.rect.pos; 3718 *pos = clamp_v2_rect(v2_add(*pos, dMouse), window_rect); 3719 }break; 3720 case VT_UI_REGION_SPLIT:{ 3721 f32 min_fraction = 0; 3722 dMouse = v2_mul(dMouse, (v2){{1.0f / ws.w, 1.0f / ws.h}}); 3723 RegionSplit *rs = &ui->interaction.var->region_split; 3724 switch (rs->direction) { 3725 case RSD_VERTICAL: { 3726 min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5 * UI_REGION_PAD) / ws.h; 3727 rs->fraction += dMouse.y; 3728 } break; 3729 case RSD_HORIZONTAL: { 3730 min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5 * UI_REGION_PAD) / ws.w; 3731 rs->fraction += dMouse.x; 3732 } break; 3733 } 3734 rs->fraction = CLAMP(rs->fraction, min_fraction, 1 - min_fraction); 3735 }break; 3736 default:{}break; 3737 } 3738 if (it->var->flags & V_LIVE_CONTROL) 3739 ui_live_control_update(ui, it->var->parent); 3740 } 3741 } break; 3742 default:{ ui_end_interact(ui, input->mouse); }break; 3743 } 3744 3745 ui->next_interaction = (Interaction){.kind = InteractionKind_None}; 3746 } 3747 3748 function void 3749 ui_init(BeamformerCtx *ctx, Arena store) 3750 { 3751 BeamformerUI *ui = ctx->ui; 3752 if (!ui) { 3753 ui = ctx->ui = push_struct(&store, typeof(*ui)); 3754 ui->arena = store; 3755 ui->frame_view_render_context = &ctx->frame_view_render_context; 3756 ui->unit_cube_model = ctx->csctx.unit_cube_model; 3757 ui->shared_memory = ctx->shared_memory; 3758 ui->beamformer_context = ctx; 3759 3760 /* TODO(rnp): build these into the binary */ 3761 /* TODO(rnp): better font, this one is jank at small sizes */ 3762 ui->font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 28, 0, 0); 3763 ui->small_font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 20, 0, 0); 3764 3765 ui->floating_widget_sentinal.parent = &ui->floating_widget_sentinal; 3766 ui->floating_widget_sentinal.next = &ui->floating_widget_sentinal; 3767 3768 Variable *split = ui->regions = add_ui_split(ui, 0, &ui->arena, s8("UI Root"), 0.36, 3769 RSD_HORIZONTAL, ui->font); 3770 split->region_split.left = add_ui_split(ui, split, &ui->arena, s8(""), 0.475, 3771 RSD_VERTICAL, ui->font); 3772 3773 split = split->region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.70, 3774 RSD_HORIZONTAL, ui->font); 3775 { 3776 split->region_split.left = add_beamformer_frame_view(ui, split, &ui->arena, 3777 BeamformerFrameViewKind_Latest, 0, 0); 3778 split->region_split.right = add_live_controls_view(ui, split, &ui->arena); 3779 } 3780 split = split->parent; 3781 3782 split = split->region_split.left; 3783 split->region_split.left = add_beamformer_parameters_view(split, ctx); 3784 split->region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.22, 3785 RSD_VERTICAL, ui->font); 3786 split = split->region_split.right; 3787 3788 split->region_split.left = add_compute_progress_bar(split, ctx); 3789 split->region_split.right = add_compute_stats_view(ui, split, &ui->arena, ctx); 3790 3791 ctx->ui_read_params = 1; 3792 3793 /* NOTE(rnp): shrink variable size once this fires */ 3794 assert(ui->arena.beg - (u8 *)ui < KB(64)); 3795 } 3796 } 3797 3798 function void 3799 validate_ui_parameters(BeamformerUIParameters *p) 3800 { 3801 if (p->output_min_coordinate[0] > p->output_max_coordinate[0]) 3802 swap(p->output_min_coordinate[0], p->output_max_coordinate[0]); 3803 if (p->output_min_coordinate[2] > p->output_max_coordinate[2]) 3804 swap(p->output_min_coordinate[2], p->output_max_coordinate[2]); 3805 } 3806 3807 function void 3808 draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformerFrame *frame_to_draw, BeamformerViewPlaneTag frame_plane) 3809 { 3810 BeamformerUI *ui = ctx->ui; 3811 BeamformerSharedMemory *sm = ctx->shared_memory.region; 3812 3813 ui->latest_plane[BeamformerViewPlaneTag_Count] = frame_to_draw; 3814 ui->latest_plane[frame_plane] = frame_to_draw; 3815 3816 /* TODO(rnp): there should be a better way of detecting this */ 3817 if (ctx->ui_read_params) { 3818 mem_copy(&ui->params, &sm->parameters.output_min_coordinate, sizeof(ui->params)); 3819 ui->flush_params = 0; 3820 ctx->ui_read_params = 0; 3821 } 3822 3823 /* NOTE: process interactions first because the user interacted with 3824 * the ui that was presented last frame */ 3825 Rect window_rect = {.size = {.w = ctx->window_size.w, .h = ctx->window_size.h}}; 3826 ui_interact(ui, input, window_rect); 3827 3828 if (ui->flush_params) { 3829 i32 lock = BeamformerSharedMemoryLockKind_Parameters; 3830 validate_ui_parameters(&ui->params); 3831 if (ctx->os.shared_memory_region_lock(&ctx->shared_memory, sm->locks, lock, 0)) { 3832 mem_copy(&sm->parameters_ui, &ui->params, sizeof(ui->params)); 3833 ui->flush_params = 0; 3834 atomic_or_u32(&sm->dirty_regions, (1 << (lock - 1))); 3835 b32 dispatch = ctx->os.shared_memory_region_lock(&ctx->shared_memory, sm->locks, 3836 BeamformerSharedMemoryLockKind_DispatchCompute, 3837 0); 3838 sm->start_compute_from_main |= dispatch & ctx->latest_frame->ready_to_present; 3839 ctx->os.shared_memory_region_unlock(&ctx->shared_memory, sm->locks, lock); 3840 } 3841 } 3842 3843 /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */ 3844 update_frame_views(ui, window_rect); 3845 3846 BeginDrawing(); 3847 f32 one = 1; 3848 glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_COLOUR.E); 3849 glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one); 3850 3851 draw_ui_regions(ui, window_rect, input->mouse); 3852 draw_floating_widgets(ui, window_rect, input->mouse); 3853 EndDrawing(); 3854 }