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