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