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