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