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