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