ui.c (98889B)
1 /* See LICENSE for license details. */ 2 /* TODO(rnp): 3 * [ ]: refactor: ui should be in its own thread and that thread should only be concerned with the ui 4 * [ ]: refactor: ui shouldn't fully destroy itself on hot reload 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 * [ ]: compute times through same path as parameter list ? 10 * [ ]: allow views to collapse to just their title bar 11 * - title bar struct with expanded. Check when pushing onto draw stack; if expanded 12 * do normal behaviour else make size title bar size and ignore the splits fraction. 13 * [ ]: enforce a minimum region size or allow regions themselves to scroll 14 * [ ]: refactor: add_variable_no_link() 15 * [ ]: refactor: draw_text_limited should clamp to rect and measure text itself 16 * [ ]: ui leaks split beamform views on hot-reload 17 * [ ]: add tag based selection to frame views 18 * [ ]: draw the ui with a post-order traversal instead of pre-order traversal 19 * [ ]: consider V_HOVER_GROUP and use that to implement submenus 20 * [ ]: menu's need to support nested groups 21 * [ ]: don't redraw on every refresh; instead redraw on mouse movement/event or when a new frame 22 * arrives. For animations the ui can have a list of "timers" which while active will 23 * do a redraw on every refresh until completed. 24 * [ ]: show full non-truncated string on hover 25 * [ ]: refactor: hovered element type and show hovered element in full even when truncated 26 * [ ]: visual indicator for broken shader stage gh#27 27 * [ ]: bug: cross-plane view with different dimensions for each plane 28 * [ ]: refactor: make table_skip_rows useful 29 */ 30 31 #define BG_COLOUR (v4){.r = 0.15, .g = 0.12, .b = 0.13, .a = 1.0} 32 #define FG_COLOUR (v4){.r = 0.92, .g = 0.88, .b = 0.78, .a = 1.0} 33 #define FOCUSED_COLOUR (v4){.r = 0.86, .g = 0.28, .b = 0.21, .a = 1.0} 34 #define HOVERED_COLOUR (v4){.r = 0.11, .g = 0.50, .b = 0.59, .a = 1.0} 35 #define RULER_COLOUR (v4){.r = 1.00, .g = 0.70, .b = 0.00, .a = 1.0} 36 37 #define MENU_PLUS_COLOUR (v4){.r = 0.33, .g = 0.42, .b = 1.00, .a = 1.0} 38 #define MENU_CLOSE_COLOUR FOCUSED_COLOUR 39 40 #define HOVER_SPEED 5.0f 41 42 #define TABLE_CELL_PAD_HEIGHT 2.0f 43 #define TABLE_CELL_PAD_WIDTH 8.0f 44 45 #define RULER_TEXT_PAD 10.0f 46 #define RULER_TICK_LENGTH 20.0f 47 48 #define UI_SPLIT_HANDLE_THICK 8.0f 49 #define UI_REGION_PAD 32.0f 50 51 /* TODO(rnp) smooth scroll */ 52 #define UI_SCROLL_SPEED 12.0f 53 54 #define LISTING_LINE_PAD 6.0f 55 #define TITLE_BAR_PAD 6.0f 56 57 typedef struct v2_sll { 58 struct v2_sll *next; 59 v2 v; 60 } v2_sll; 61 62 typedef struct BeamformerUI BeamformerUI; 63 typedef struct Variable Variable; 64 65 typedef struct { 66 u8 buf[64]; 67 i32 count; 68 i32 cursor; 69 f32 cursor_blink_t; 70 f32 cursor_blink_scale; 71 Font *font, *hot_font; 72 Variable *container; 73 } InputState; 74 75 typedef enum { 76 RulerState_None, 77 RulerState_Start, 78 RulerState_Hold, 79 } RulerState; 80 81 typedef struct { 82 v2 start; 83 v2 end; 84 RulerState state; 85 } Ruler; 86 87 typedef enum { 88 SB_LATERAL, 89 SB_AXIAL, 90 } ScaleBarDirection; 91 92 typedef struct { 93 f32 *min_value, *max_value; 94 v2_sll *savepoint_stack; 95 v2 scroll_scale; 96 f32 zoom_starting_coord; 97 ScaleBarDirection direction; 98 } ScaleBar; 99 100 typedef struct { f32 val, scale; } scaled_f32; 101 102 typedef enum { 103 RSD_VERTICAL, 104 RSD_HORIZONTAL, 105 } RegionSplitDirection; 106 107 typedef struct { 108 Variable *left; 109 Variable *right; 110 f32 fraction; 111 RegionSplitDirection direction; 112 } RegionSplit; 113 114 /* TODO(rnp): this should be refactored to not need a BeamformerCtx */ 115 typedef struct { 116 BeamformerCtx *ctx; 117 void *stats; 118 } ComputeStatsView; 119 120 typedef struct { 121 b32 *processing; 122 f32 *progress; 123 f32 display_t; 124 f32 display_t_velocity; 125 } ComputeProgressBar; 126 127 typedef enum { 128 VT_NULL, 129 VT_B32, 130 VT_F32, 131 VT_I32, 132 VT_U32, 133 VT_GROUP, 134 VT_CYCLER, 135 VT_SCALED_F32, 136 VT_BEAMFORMER_VARIABLE, 137 VT_BEAMFORMER_FRAME_VIEW, 138 VT_COMPUTE_STATS_VIEW, 139 VT_COMPUTE_PROGRESS_BAR, 140 VT_SCALE_BAR, 141 VT_UI_BUTTON, 142 VT_UI_MENU, 143 VT_UI_REGION_SPLIT, 144 VT_UI_TEXT_BOX, 145 VT_UI_VIEW, 146 } VariableType; 147 148 typedef enum { 149 VariableGroupKind_List, 150 /* NOTE(rnp): special group for vectors with components 151 * stored in separate memory locations */ 152 VariableGroupKind_Vector, 153 } VariableGroupKind; 154 155 typedef struct { 156 Variable *first; 157 Variable *last; 158 Variable *container; 159 /* TODO(rnp): explore why this can't be at the start of the struct */ 160 VariableGroupKind kind; 161 b32 expanded; 162 } VariableGroup; 163 164 typedef enum { 165 UIViewFlag_CustomText = 1 << 0, 166 UIViewFlag_Floating = 1 << 1, 167 } UIViewFlags; 168 169 typedef struct { 170 Variable *child; 171 Variable *close; 172 Variable *menu; 173 Rect rect; 174 UIViewFlags flags; 175 } UIView; 176 177 /* X(id, text) */ 178 #define FRAME_VIEW_BUTTONS \ 179 X(FV_COPY_HORIZONTAL, "Copy Horizontal") \ 180 X(FV_COPY_VERTICAL, "Copy Vertical") 181 182 #define GLOBAL_MENU_BUTTONS \ 183 X(GM_OPEN_LIVE_VIEW_RIGHT, "Open Live View Right") \ 184 X(GM_OPEN_LIVE_VIEW_BELOW, "Open Live View Below") 185 186 #define X(id, text) UI_BID_ ##id, 187 typedef enum { 188 UI_BID_VIEW_CLOSE, 189 GLOBAL_MENU_BUTTONS 190 FRAME_VIEW_BUTTONS 191 } UIButtonID; 192 #undef X 193 194 typedef struct { 195 s8 *labels; 196 u32 *state; 197 u32 cycle_length; 198 } VariableCycler; 199 200 typedef struct { 201 s8 suffix; 202 f32 display_scale; 203 f32 scroll_scale; 204 v2 limits; 205 void *store; 206 VariableType store_type; 207 } BeamformerVariable; 208 209 typedef enum { 210 V_INPUT = 1 << 0, 211 V_TEXT = 1 << 1, 212 V_RADIO_BUTTON = 1 << 2, 213 V_CAUSES_COMPUTE = 1 << 29, 214 V_UPDATE_VIEW = 1 << 30, 215 } VariableFlags; 216 217 struct Variable { 218 s8 name; 219 union { 220 void *generic; 221 BeamformerVariable beamformer_variable; 222 ComputeProgressBar compute_progress_bar; 223 RegionSplit region_split; 224 ScaleBar scale_bar; 225 UIButtonID button; 226 UIView view; 227 VariableCycler cycler; 228 VariableGroup group; 229 scaled_f32 scaled_real32; 230 b32 bool32; 231 i32 signed32; 232 u32 unsigned32; 233 f32 real32; 234 }; 235 Variable *next; 236 Variable *parent; 237 VariableFlags flags; 238 VariableType type; 239 240 f32 hover_t; 241 f32 name_width; 242 }; 243 244 typedef enum { 245 FVT_LATEST, 246 FVT_INDEXED, 247 FVT_COPY, 248 } BeamformerFrameViewType; 249 250 typedef struct BeamformerFrameView { 251 Variable lateral_scale_bar; 252 Variable axial_scale_bar; 253 254 /* NOTE(rnp): these are pointers because they are added to the menu and will 255 * be put onto the freelist if the view is closed */ 256 Variable *lateral_scale_bar_active; 257 Variable *axial_scale_bar_active; 258 Variable *log_scale; 259 /* NOTE(rnp): if type is LATEST selects which type of latest to use 260 * if type is INDEXED selects the index */ 261 Variable *cycler; 262 u32 cycler_state; 263 264 v4 min_coordinate; 265 v4 max_coordinate; 266 267 Ruler ruler; 268 269 Variable threshold; 270 Variable dynamic_range; 271 Variable gamma; 272 273 FrameViewRenderContext *ctx; 274 BeamformFrame *frame; 275 struct BeamformerFrameView *prev, *next; 276 277 uv2 texture_dim; 278 u32 texture_mipmaps; 279 u32 texture; 280 281 BeamformerFrameViewType type; 282 b32 needs_update; 283 } BeamformerFrameView; 284 285 typedef enum { 286 InteractionKind_None, 287 InteractionKind_Nop, 288 InteractionKind_Auto, 289 InteractionKind_Button, 290 InteractionKind_Drag, 291 InteractionKind_Menu, 292 InteractionKind_Ruler, 293 InteractionKind_Scroll, 294 InteractionKind_Set, 295 InteractionKind_Text, 296 } InteractionKind; 297 298 typedef struct { 299 InteractionKind kind; 300 union { 301 void *generic; 302 Variable *var; 303 }; 304 Rect rect; 305 } Interaction; 306 307 #define auto_interaction(r, v) (Interaction){.kind = InteractionKind_Auto, .var = v, .rect = r} 308 309 struct BeamformerUI { 310 Arena arena; 311 312 Font font; 313 Font small_font; 314 315 Variable *regions; 316 Variable *variable_freelist; 317 318 Variable floating_widget_sentinal; 319 320 BeamformerFrameView *views; 321 BeamformerFrameView *view_freelist; 322 BeamformFrame *frame_freelist; 323 324 Interaction interaction; 325 Interaction hot_interaction; 326 Interaction next_interaction; 327 328 InputState text_input_state; 329 330 v2_sll *scale_bar_savepoint_freelist; 331 332 BeamformFrame *latest_plane[IPT_LAST + 1]; 333 334 BeamformerUIParameters params; 335 b32 flush_params; 336 337 FrameViewRenderContext *frame_view_render_context; 338 OS *os; 339 }; 340 341 typedef enum { 342 TF_NONE = 0, 343 TF_ROTATED = 1 << 0, 344 TF_LIMITED = 1 << 1, 345 TF_OUTLINED = 1 << 2, 346 } TextFlags; 347 348 typedef enum { 349 TextAlignment_Center, 350 TextAlignment_Left, 351 TextAlignment_Right, 352 } TextAlignment; 353 354 typedef struct { 355 Font *font; 356 Rect limits; 357 v4 colour; 358 v4 outline_colour; 359 f32 outline_thick; 360 f32 rotation; 361 TextAlignment align; 362 TextFlags flags; 363 } TextSpec; 364 365 typedef enum { 366 TRK_CELLS, 367 TRK_TABLE, 368 } TableRowKind; 369 370 typedef enum { 371 TableCellKind_None, 372 TableCellKind_Variable, 373 TableCellKind_VariableGroup, 374 } TableCellKind; 375 376 typedef struct { 377 s8 text; 378 union { 379 i64 integer; 380 Variable *var; 381 void *generic; 382 }; 383 TableCellKind kind; 384 f32 width; 385 } TableCell; 386 387 typedef struct { 388 void *data; 389 TableRowKind kind; 390 } TableRow; 391 392 typedef struct Table { 393 TableRow *data; 394 iz count; 395 iz capacity; 396 397 /* NOTE(rnp): counted by columns */ 398 TextAlignment *alignment; 399 f32 *widths; 400 401 v4 border_colour; 402 f32 column_border_thick; 403 f32 row_border_thick; 404 v2 size; 405 v2 cell_pad; 406 407 /* NOTE(rnp): row count including nested tables */ 408 i32 rows; 409 i32 columns; 410 411 struct Table *parent; 412 } Table; 413 414 typedef struct { 415 Table *table; 416 i32 row_index; 417 } TableStackFrame; 418 419 typedef struct { 420 TableStackFrame *data; 421 iz count; 422 iz capacity; 423 } TableStack; 424 425 typedef enum { 426 TIK_ROWS, 427 TIK_CELLS, 428 } TableIteratorKind; 429 430 typedef struct { 431 TableStack stack; 432 TableStackFrame frame; 433 434 TableRow *row; 435 i16 column; 436 i16 sub_table_depth; 437 438 TableIteratorKind kind; 439 440 f32 start_x; 441 TextAlignment alignment; 442 Rect cell_rect; 443 } TableIterator; 444 445 function v2 446 measure_glyph(Font font, u32 glyph) 447 { 448 ASSERT(glyph >= 0x20); 449 v2 result = {.y = font.baseSize}; 450 /* NOTE: assumes font glyphs are ordered ASCII */ 451 result.x = font.glyphs[glyph - 0x20].advanceX; 452 if (result.x == 0) 453 result.x = (font.recs[glyph - 0x20].width + font.glyphs[glyph - 0x20].offsetX); 454 return result; 455 } 456 457 function v2 458 measure_text(Font font, s8 text) 459 { 460 v2 result = {.y = font.baseSize}; 461 for (iz i = 0; i < text.len; i++) 462 result.x += measure_glyph(font, text.data[i]).x; 463 return result; 464 } 465 466 function s8 467 clamp_text_to_width(Font font, s8 text, f32 limit) 468 { 469 s8 result = text; 470 f32 width = 0; 471 for (iz i = 0; i < text.len; i++) { 472 f32 next = measure_glyph(font, text.data[i]).w; 473 if (width + next > limit) { 474 result.len = i; 475 break; 476 } 477 width += next; 478 } 479 return result; 480 } 481 482 function Texture 483 make_raylib_texture(BeamformerFrameView *v) 484 { 485 Texture result; 486 result.id = v->texture; 487 result.width = v->texture_dim.w; 488 result.height = v->texture_dim.h; 489 result.mipmaps = v->texture_mipmaps; 490 result.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 491 return result; 492 } 493 494 function void 495 stream_append_variable(Stream *s, Variable *var) 496 { 497 switch (var->type) { 498 case VT_UI_BUTTON: 499 case VT_GROUP:{ stream_append_s8(s, var->name); }break; 500 case VT_F32:{ stream_append_f64(s, var->real32, 100); }break; 501 case VT_B32:{ stream_append_s8(s, var->bool32 ? s8("True") : s8("False")); }break; 502 case VT_SCALED_F32:{ stream_append_f64(s, var->scaled_real32.val, 100); }break; 503 case VT_BEAMFORMER_VARIABLE:{ 504 BeamformerVariable *bv = &var->beamformer_variable; 505 switch (bv->store_type) { 506 case VT_F32:{ stream_append_f64(s, *(f32 *)bv->store * bv->display_scale, 100); }break; 507 InvalidDefaultCase; 508 } 509 }break; 510 case VT_CYCLER:{ 511 u32 index = *var->cycler.state; 512 if (var->cycler.labels) stream_append_s8(s, var->cycler.labels[index]); 513 else stream_append_u64(s, index); 514 }break; 515 InvalidDefaultCase; 516 } 517 } 518 519 function void 520 stream_append_variable_group(Stream *s, Variable *var) 521 { 522 switch (var->type) { 523 case VT_GROUP:{ 524 switch (var->group.kind) { 525 case VariableGroupKind_Vector:{ 526 Variable *v = var->group.first; 527 stream_append_s8(s, s8("{")); 528 while (v) { 529 stream_append_variable(s, v); 530 v = v->next; 531 if (v) stream_append_s8(s, s8(", ")); 532 } 533 stream_append_s8(s, s8("}")); 534 }break; 535 InvalidDefaultCase; 536 } 537 }break; 538 InvalidDefaultCase; 539 } 540 } 541 542 #define table_new(a, init, ...) table_new_(a, init, arg_list(TextAlignment, ##__VA_ARGS__)) 543 function Table * 544 table_new_(Arena *a, i32 initial_capacity, TextAlignment *alignment, i32 columns) 545 { 546 Table *result = push_struct(a, Table); 547 da_reserve(a, result, initial_capacity); 548 result->columns = columns; 549 result->alignment = push_array(a, TextAlignment, columns); 550 result->widths = push_array(a, f32, columns); 551 result->cell_pad = (v2){{TABLE_CELL_PAD_WIDTH, TABLE_CELL_PAD_HEIGHT}}; 552 mem_copy(result->alignment, alignment, sizeof(*alignment) * columns); 553 return result; 554 } 555 556 function i32 557 table_skip_rows(Table *t, f32 draw_height, f32 text_height) 558 { 559 i32 max_rows = draw_height / (text_height + t->cell_pad.h); 560 i32 result = t->rows - MIN(t->rows, max_rows); 561 return result; 562 } 563 564 function TableIterator * 565 table_iterator_new(Table *table, TableIteratorKind kind, Arena *a, i32 starting_row, v2 at, Font *font) 566 { 567 TableIterator *result = push_struct(a, TableIterator); 568 result->kind = kind; 569 result->frame.table = table; 570 result->frame.row_index = starting_row; 571 result->start_x = at.x; 572 result->cell_rect.size.h = font->baseSize; 573 result->cell_rect.pos = add_v2(at, scale_v2(table->cell_pad, 0.5)); 574 result->cell_rect.pos.y += (starting_row - 1) * (result->cell_rect.size.h + table->cell_pad.h + table->row_border_thick); 575 da_reserve(a, &result->stack, 4); 576 return result; 577 } 578 579 function void * 580 table_iterator_next(TableIterator *it, Arena *a) 581 { 582 void *result = 0; 583 584 if (!it->row || it->kind == TIK_ROWS) { 585 for (;;) { 586 TableRow *row = it->frame.table->data + it->frame.row_index++; 587 if (it->frame.row_index <= it->frame.table->count) { 588 if (row->kind == TRK_TABLE) { 589 *da_push(a, &it->stack) = it->frame; 590 it->frame = (TableStackFrame){.table = row->data}; 591 it->sub_table_depth++; 592 } else { 593 result = row; 594 break; 595 } 596 } else if (it->stack.count) { 597 it->frame = it->stack.data[--it->stack.count]; 598 it->sub_table_depth--; 599 } else { 600 break; 601 } 602 } 603 Table *t = it->frame.table; 604 it->row = result; 605 it->column = 0; 606 it->cell_rect.pos.x = it->start_x + t->cell_pad.w / 2 + 607 it->cell_rect.size.h * it->sub_table_depth; 608 it->cell_rect.pos.y += it->cell_rect.size.h + t->row_border_thick + t->cell_pad.h; 609 } 610 611 if (it->row && it->kind == TIK_CELLS) { 612 Table *t = it->frame.table; 613 i32 column = it->column++; 614 it->cell_rect.pos.x += column > 0 ? it->cell_rect.size.w + t->cell_pad.w : 0; 615 it->cell_rect.size.w = t->widths[column]; 616 it->alignment = t->alignment[column]; 617 result = (TableCell *)it->row->data + column; 618 619 if (it->column == t->columns) 620 it->row = 0; 621 } 622 623 return result; 624 } 625 626 function f32 627 table_width(Table *t) 628 { 629 f32 result = 0; 630 i32 valid = 0; 631 for (i32 i = 0; i < t->columns; i++) { 632 result += t->widths[i]; 633 if (t->widths[i] > 0) valid++; 634 } 635 result += t->cell_pad.w * valid; 636 result += MAX(0, (valid - 1)) * t->column_border_thick; 637 return result; 638 } 639 640 function v2 641 table_extent(Table *t, Arena arena, Font *font) 642 { 643 TableIterator *it = table_iterator_new(t, TIK_ROWS, &arena, 0, (v2){0}, font); 644 for (TableRow *row = table_iterator_next(it, &arena); 645 row; 646 row = table_iterator_next(it, &arena)) 647 { 648 for (i32 i = 0; i < it->frame.table->columns; i++) { 649 TableCell *cell = (TableCell *)row->data + i; 650 if (!cell->text.len && cell->var && cell->var->flags & V_RADIO_BUTTON) { 651 cell->width = font->baseSize; 652 } else { 653 cell->width = measure_text(*font, cell->text).w; 654 } 655 it->frame.table->widths[i] = MAX(cell->width, it->frame.table->widths[i]); 656 } 657 } 658 659 t->size = (v2){.x = table_width(t), .y = it->cell_rect.pos.y - t->cell_pad.h / 2}; 660 v2 result = t->size; 661 return result; 662 } 663 664 function v2 665 table_cell_align(TableCell *cell, TextAlignment align, Rect r) 666 { 667 v2 result = r.pos; 668 if (r.size.w >= cell->width) { 669 switch (align) { 670 case TextAlignment_Left:{}break; 671 case TextAlignment_Right:{ result.x += (r.size.w - cell->width); }break; 672 case TextAlignment_Center:{ result.x += (r.size.w - cell->width) / 2; }break; 673 } 674 } 675 return result; 676 } 677 678 function TableCell 679 table_variable_cell(Arena *a, Variable *var) 680 { 681 TableCell result = {.var = var, .kind = TableCellKind_Variable}; 682 if ((var->flags & V_RADIO_BUTTON) == 0) { 683 Stream text = arena_stream(*a); 684 stream_append_variable(&text, var); 685 result.text = arena_stream_commit(a, &text); 686 } 687 return result; 688 } 689 690 function TableRow * 691 table_push_row(Table *t, Arena *a, TableRowKind kind) 692 { 693 TableRow *result = da_push(a, t); 694 if (kind == TRK_CELLS) { 695 result->data = push_array(a, TableCell, t->columns); 696 /* NOTE(rnp): do not increase rows for an empty subtable */ 697 t->rows++; 698 } 699 result->kind = kind; 700 return result; 701 } 702 703 function TableRow * 704 table_push_parameter_row(Table *t, Arena *a, s8 label, Variable *var, s8 suffix) 705 { 706 ASSERT(t->columns >= 3); 707 TableRow *result = table_push_row(t, a, TRK_CELLS); 708 TableCell *cells = result->data; 709 710 cells[0].text = label; 711 cells[1] = table_variable_cell(a, var); 712 cells[2].text = suffix; 713 714 return result; 715 } 716 717 #define table_begin_subtable(t, a, ...) table_begin_subtable_(t, a, arg_list(TextAlignment, ##__VA_ARGS__)) 718 function Table * 719 table_begin_subtable_(Table *table, Arena *a, TextAlignment *alignment, i32 columns) 720 { 721 TableRow *row = table_push_row(table, a, TRK_TABLE); 722 Table *result = row->data = table_new_(a, 0, alignment, columns); 723 result->parent = table; 724 return result; 725 } 726 727 function Table * 728 table_end_subtable(Table *table) 729 { 730 Table *result = table->parent ? table->parent : table; 731 return result; 732 } 733 734 function void 735 resize_frame_view(BeamformerFrameView *view, uv2 dim) 736 { 737 glDeleteTextures(1, &view->texture); 738 glCreateTextures(GL_TEXTURE_2D, 1, &view->texture); 739 740 view->texture_dim = dim; 741 view->texture_mipmaps = ctz_u32(MAX(dim.x, dim.y)) + 1; 742 /* TODO(rnp): HDR? */ 743 glTextureStorage2D(view->texture, view->texture_mipmaps, GL_RGBA8, dim.x, dim.y); 744 glGenerateTextureMipmap(view->texture); 745 746 /* NOTE(rnp): work around raylib's janky texture sampling */ 747 glTextureParameteri(view->texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); 748 glTextureParameteri(view->texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); 749 glTextureParameterfv(view->texture, GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 1}); 750 glTextureParameteri(view->texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 751 glTextureParameteri(view->texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 752 753 /* TODO(rnp): add some ID for the specific view here */ 754 LABEL_GL_OBJECT(GL_TEXTURE, view->texture, s8("Frame View Texture")); 755 } 756 757 function void 758 ui_variable_free(BeamformerUI *ui, Variable *var) 759 { 760 if (var) { 761 var->parent = 0; 762 while (var) { 763 if (var->type == VT_GROUP) { 764 var = var->group.first; 765 } else { 766 if (var->type == VT_BEAMFORMER_FRAME_VIEW) { 767 /* TODO(rnp): instead there should be a way of linking these up */ 768 BeamformerFrameView *bv = var->generic; 769 if (bv->type == FVT_COPY) { 770 glDeleteTextures(1, &bv->frame->texture); 771 bv->frame->texture = 0; 772 SLLPush(bv->frame, ui->frame_freelist); 773 } 774 if (bv->axial_scale_bar.scale_bar.savepoint_stack) 775 SLLPush(bv->axial_scale_bar.scale_bar.savepoint_stack, 776 ui->scale_bar_savepoint_freelist); 777 if (bv->lateral_scale_bar.scale_bar.savepoint_stack) 778 SLLPush(bv->lateral_scale_bar.scale_bar.savepoint_stack, 779 ui->scale_bar_savepoint_freelist); 780 DLLRemove(bv); 781 /* TODO(rnp): hack; use a sentinal */ 782 if (bv == ui->views) 783 ui->views = bv->next; 784 SLLPush(bv, ui->view_freelist); 785 } 786 787 Variable *next = var->next; 788 SLLPush(var, ui->variable_freelist); 789 if (next) { 790 var = next; 791 } else { 792 var = var->parent; 793 /* NOTE(rnp): when we assign parent here we have already 794 * released the children. Assign type so we don't loop */ 795 if (var) var->type = VT_NULL; 796 } 797 } 798 } 799 } 800 } 801 802 function void 803 ui_view_free(BeamformerUI *ui, Variable *view) 804 { 805 assert(view->type == VT_UI_VIEW); 806 ui_variable_free(ui, view->view.child); 807 ui_variable_free(ui, view->view.close); 808 ui_variable_free(ui, view->view.menu); 809 ui_variable_free(ui, view); 810 } 811 812 function Variable * 813 fill_variable(Variable *var, Variable *group, s8 name, u32 flags, VariableType type, Font font) 814 { 815 var->flags = flags; 816 var->type = type; 817 var->name = name; 818 var->parent = group; 819 var->name_width = measure_text(font, name).x; 820 821 if (group && group->type == VT_GROUP) { 822 if (group->group.last) group->group.last = group->group.last->next = var; 823 else group->group.last = group->group.first = var; 824 } 825 826 return var; 827 } 828 829 function Variable * 830 add_variable(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, u32 flags, 831 VariableType type, Font font) 832 { 833 Variable *result = SLLPop(ui->variable_freelist); 834 if (result) zero_struct(result); 835 else result = push_struct(arena, Variable); 836 return fill_variable(result, group, name, flags, type, font); 837 } 838 839 function Variable * 840 add_variable_group(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, VariableGroupKind kind, Font font) 841 { 842 Variable *result = add_variable(ui, group, arena, name, V_INPUT, VT_GROUP, font); 843 result->group.kind = kind; 844 return result; 845 } 846 847 function Variable * 848 end_variable_group(Variable *group) 849 { 850 ASSERT(group->type == VT_GROUP); 851 return group->parent; 852 } 853 854 function Variable * 855 add_variable_cycler(BeamformerUI *ui, Variable *group, Arena *arena, u32 flags, Font font, s8 name, 856 u32 *store, s8 *labels, u32 cycle_count) 857 { 858 Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_CYCLER, font); 859 result->cycler.cycle_length = cycle_count; 860 result->cycler.state = store; 861 result->cycler.labels = labels; 862 return result; 863 } 864 865 function Variable * 866 add_button(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, UIButtonID id, 867 u32 flags, Font font) 868 { 869 Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_UI_BUTTON, font); 870 result->button = id; 871 return result; 872 } 873 874 function Variable * 875 add_ui_split(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, f32 fraction, 876 RegionSplitDirection direction, Font font) 877 { 878 Variable *result = add_variable(ui, parent, arena, name, 0, VT_UI_REGION_SPLIT, font); 879 result->region_split.direction = direction; 880 result->region_split.fraction = fraction; 881 return result; 882 } 883 884 function Variable * 885 add_global_menu(BeamformerUI *ui, Arena *arena, Variable *parent) 886 { 887 Variable *result = add_variable_group(ui, 0, &ui->arena, s8(""), VariableGroupKind_List, ui->small_font); 888 result->parent = parent; 889 #define X(id, text) add_button(ui, result, &ui->arena, s8(text), UI_BID_ ##id, 0, ui->small_font); 890 GLOBAL_MENU_BUTTONS 891 #undef X 892 return result; 893 } 894 895 function Variable * 896 add_ui_view(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, u32 view_flags, b32 menu, b32 closable) 897 { 898 Variable *result = add_variable(ui, parent, arena, name, 0, VT_UI_VIEW, ui->small_font); 899 UIView *view = &result->view; 900 view->flags = view_flags; 901 if (menu) view->menu = add_global_menu(ui, arena, result); 902 if (closable) { 903 view->close = add_button(ui, 0, arena, s8(""), UI_BID_VIEW_CLOSE, 0, ui->small_font); 904 /* NOTE(rnp): we do this explicitly so that close doesn't end up in the view group */ 905 view->close->parent = result; 906 } 907 return result; 908 } 909 910 function Variable * 911 add_floating_view(BeamformerUI *ui, Arena *arena, VariableType type, v2 at, Variable *child, b32 closable) 912 { 913 Variable *result = add_ui_view(ui, 0, arena, s8(""), UIViewFlag_Floating, 0, closable); 914 result->type = type; 915 result->view.rect.pos = at; 916 result->view.child = child; 917 918 result->parent = &ui->floating_widget_sentinal; 919 result->next = ui->floating_widget_sentinal.next; 920 result->next->parent = result; 921 ui->floating_widget_sentinal.next = result; 922 return result; 923 } 924 925 function void 926 add_beamformer_variable_f32(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, s8 suffix, 927 f32 *store, v2 limits, f32 display_scale, f32 scroll_scale, u32 flags, 928 Font font) 929 { 930 Variable *var = add_variable(ui, group, arena, name, flags, VT_BEAMFORMER_VARIABLE, font); 931 BeamformerVariable *bv = &var->beamformer_variable; 932 bv->suffix = suffix; 933 bv->store = store; 934 bv->store_type = VT_F32; 935 bv->display_scale = display_scale; 936 bv->scroll_scale = scroll_scale; 937 bv->limits = limits; 938 } 939 940 function Variable * 941 add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx) 942 { 943 BeamformerUI *ui = ctx->ui; 944 BeamformerUIParameters *bp = &ui->params; 945 946 v2 v2_inf = {.x = -F32_INFINITY, .y = F32_INFINITY}; 947 948 /* TODO(rnp): this can be closable once we have a way of opening new views */ 949 Variable *result = add_ui_view(ui, parent, &ui->arena, s8("Parameters"), 0, 1, 0); 950 Variable *group = result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, 951 VT_GROUP, ui->font); 952 953 add_beamformer_variable_f32(ui, group, &ui->arena, s8("Sampling Frequency:"), s8("[MHz]"), 954 &bp->sampling_frequency, (v2){0}, 1e-6, 0, 0, ui->font); 955 956 add_beamformer_variable_f32(ui, group, &ui->arena, s8("Center Frequency:"), s8("[MHz]"), 957 &bp->center_frequency, (v2){.y = 100e-6}, 1e-6, 1e5, 958 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 959 960 add_beamformer_variable_f32(ui, group, &ui->arena, s8("Speed of Sound:"), s8("[m/s]"), 961 &bp->speed_of_sound, (v2){.y = 1e6}, 1, 10, 962 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 963 964 group = add_variable_group(ui, group, &ui->arena, s8("Lateral Extent:"), 965 VariableGroupKind_Vector, ui->font); 966 { 967 add_beamformer_variable_f32(ui, group, &ui->arena, s8("Min:"), s8("[mm]"), 968 bp->output_min_coordinate + 0, v2_inf, 1e3, 0.5e-3, 969 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 970 971 add_beamformer_variable_f32(ui, group, &ui->arena, s8("Max:"), s8("[mm]"), 972 bp->output_max_coordinate + 0, v2_inf, 1e3, 0.5e-3, 973 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 974 } 975 group = end_variable_group(group); 976 977 group = add_variable_group(ui, group, &ui->arena, s8("Axial Extent:"), 978 VariableGroupKind_Vector, ui->font); 979 { 980 add_beamformer_variable_f32(ui, group, &ui->arena, s8("Min:"), s8("[mm]"), 981 bp->output_min_coordinate + 2, v2_inf, 1e3, 0.5e-3, 982 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 983 984 add_beamformer_variable_f32(ui, group, &ui->arena, s8("Max:"), s8("[mm]"), 985 bp->output_max_coordinate + 2, v2_inf, 1e3, 0.5e-3, 986 V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 987 } 988 group = end_variable_group(group); 989 990 add_beamformer_variable_f32(ui, group, &ui->arena, s8("Off Axis Position:"), s8("[mm]"), 991 &bp->off_axis_pos, (v2){.x = -1e3, .y = 1e3}, 0.25e3, 992 0.5e-3, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 993 994 read_only local_persist s8 beamform_plane_labels[] = {s8_comp("XZ"), s8_comp("YZ")}; 995 add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Beamform Plane:"), 996 (u32 *)&bp->beamform_plane, beamform_plane_labels, countof(beamform_plane_labels)); 997 998 add_beamformer_variable_f32(ui, group, &ui->arena, s8("F#:"), s8(""), &bp->f_number, 999 (v2){.y = 1e3}, 1, 0.1, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); 1000 1001 read_only local_persist s8 true_false_labels[] = {s8_comp("False"), s8_comp("True")}; 1002 add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Interpolate:"), 1003 &bp->interpolate, true_false_labels, countof(true_false_labels)); 1004 1005 add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Coherency Weighting:"), 1006 &bp->coherency_weighting, true_false_labels, countof(true_false_labels)); 1007 1008 return result; 1009 } 1010 1011 function Variable * 1012 add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, 1013 BeamformerFrameViewType type, b32 closable) 1014 { 1015 /* TODO(rnp): this can be always closable once we have a way of opening new views */ 1016 Variable *result = add_ui_view(ui, parent, arena, s8(""), UIViewFlag_CustomText, 1, closable); 1017 Variable *var = result->view.child = add_variable(ui, result, arena, s8(""), 0, 1018 VT_BEAMFORMER_FRAME_VIEW, ui->small_font); 1019 1020 BeamformerFrameView *bv = SLLPop(ui->view_freelist); 1021 if (bv) zero_struct(bv); 1022 else bv = push_struct(arena, typeof(*bv)); 1023 DLLPushDown(bv, ui->views); 1024 1025 var->generic = bv; 1026 bv->type = type; 1027 1028 fill_variable(&bv->dynamic_range, var, s8("Dynamic Range:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 1029 VT_F32, ui->small_font); 1030 fill_variable(&bv->threshold, var, s8("Threshold:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 1031 VT_F32, ui->small_font); 1032 fill_variable(&bv->gamma, var, s8("Gamma:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 1033 VT_SCALED_F32, ui->small_font); 1034 1035 bv->dynamic_range.real32 = 50.0f; 1036 bv->threshold.real32 = 55.0f; 1037 bv->gamma.scaled_real32.val = 1.0f; 1038 bv->gamma.scaled_real32.scale = 0.05f; 1039 1040 fill_variable(&bv->lateral_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); 1041 fill_variable(&bv->axial_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); 1042 ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; 1043 ScaleBar *axial = &bv->axial_scale_bar.scale_bar; 1044 lateral->direction = SB_LATERAL; 1045 axial->direction = SB_AXIAL; 1046 lateral->scroll_scale = (v2){.x = -0.5e-3, .y = 0.5e-3}; 1047 axial->scroll_scale = (v2){.x = 0, .y = 1e-3}; 1048 lateral->zoom_starting_coord = F32_INFINITY; 1049 axial->zoom_starting_coord = F32_INFINITY; 1050 1051 Variable *menu = result->view.menu; 1052 /* TODO(rnp): push to head of list? */ 1053 Variable *old_menu_first = menu->group.first; 1054 Variable *old_menu_last = menu->group.last; 1055 menu->group.first = menu->group.last = 0; 1056 1057 #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); 1058 FRAME_VIEW_BUTTONS 1059 #undef X 1060 1061 switch (type) { 1062 case FVT_LATEST: { 1063 #define X(_type, _id, pretty) s8_comp(pretty), 1064 read_only local_persist s8 labels[] = {IMAGE_PLANE_TAGS s8_comp("Any")}; 1065 #undef X 1066 bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Live: "), 1067 &bv->cycler_state, labels, countof(labels)); 1068 bv->cycler_state = IPT_LAST; 1069 } break; 1070 case FVT_INDEXED: { 1071 bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Index: "), 1072 &bv->cycler_state, 0, MAX_BEAMFORMED_SAVED_FRAMES); 1073 } break; 1074 default: break; 1075 } 1076 1077 bv->log_scale = add_variable(ui, menu, arena, s8("Log Scale"), 1078 V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON, VT_B32, 1079 ui->small_font); 1080 bv->axial_scale_bar_active = add_variable(ui, menu, arena, s8("Axial Scale Bar"), 1081 V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); 1082 bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"), 1083 V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); 1084 1085 menu->group.last->next = old_menu_first; 1086 menu->group.last = old_menu_last; 1087 1088 return result; 1089 } 1090 1091 function Variable * 1092 add_compute_progress_bar(Variable *parent, BeamformerCtx *ctx) 1093 { 1094 BeamformerUI *ui = ctx->ui; 1095 /* TODO(rnp): this can be closable once we have a way of opening new views */ 1096 Variable *result = add_ui_view(ui, parent, &ui->arena, s8(""), UIViewFlag_CustomText, 1, 0); 1097 result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, 1098 VT_COMPUTE_PROGRESS_BAR, ui->small_font); 1099 ComputeProgressBar *bar = &result->view.child->compute_progress_bar; 1100 bar->progress = &ctx->csctx.processing_progress; 1101 bar->processing = &ctx->csctx.processing_compute; 1102 1103 return result; 1104 } 1105 1106 function Variable * 1107 add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, VariableType type) 1108 { 1109 /* TODO(rnp): this can be closable once we have a way of opening new views */ 1110 Variable *result = add_ui_view(ui, parent, arena, s8(""), UIViewFlag_CustomText, 1, 0); 1111 result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, type, ui->small_font); 1112 return result; 1113 } 1114 1115 function Variable * 1116 ui_split_region(BeamformerUI *ui, Variable *region, Variable *split_side, RegionSplitDirection direction) 1117 { 1118 Variable *result = add_ui_split(ui, region, &ui->arena, s8(""), 0.5, direction, ui->small_font); 1119 if (split_side == region->region_split.left) { 1120 region->region_split.left = result; 1121 } else { 1122 region->region_split.right = result; 1123 } 1124 split_side->parent = result; 1125 result->region_split.left = split_side; 1126 return result; 1127 } 1128 1129 function void 1130 ui_fill_live_frame_view(BeamformerUI *ui, BeamformerFrameView *bv) 1131 { 1132 ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; 1133 ScaleBar *axial = &bv->axial_scale_bar.scale_bar; 1134 lateral->min_value = ui->params.output_min_coordinate + 0; 1135 lateral->max_value = ui->params.output_max_coordinate + 0; 1136 axial->min_value = ui->params.output_min_coordinate + 2; 1137 axial->max_value = ui->params.output_max_coordinate + 2; 1138 bv->axial_scale_bar_active->bool32 = 1; 1139 bv->lateral_scale_bar_active->bool32 = 1; 1140 bv->ctx = ui->frame_view_render_context; 1141 bv->axial_scale_bar.flags |= V_CAUSES_COMPUTE; 1142 bv->lateral_scale_bar.flags |= V_CAUSES_COMPUTE; 1143 } 1144 1145 function void 1146 ui_add_live_frame_view(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) 1147 { 1148 Variable *region = view->parent; 1149 ASSERT(region->type == VT_UI_REGION_SPLIT); 1150 ASSERT(view->type == VT_UI_VIEW); 1151 1152 Variable *new_region = ui_split_region(ui, region, view, direction); 1153 new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_LATEST, 1); 1154 1155 ui_fill_live_frame_view(ui, new_region->region_split.right->group.first->generic); 1156 } 1157 1158 function void 1159 ui_copy_frame(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) 1160 { 1161 Variable *region = view->parent; 1162 ASSERT(region->type == VT_UI_REGION_SPLIT); 1163 ASSERT(view->type == VT_UI_VIEW); 1164 1165 BeamformerFrameView *old = view->group.first->generic; 1166 /* TODO(rnp): hack; it would be better if this was unreachable with a 0 old->frame */ 1167 if (!old->frame) 1168 return; 1169 1170 Variable *new_region = ui_split_region(ui, region, view, direction); 1171 new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_COPY, 1); 1172 1173 BeamformerFrameView *bv = new_region->region_split.right->group.first->generic; 1174 ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; 1175 ScaleBar *axial = &bv->axial_scale_bar.scale_bar; 1176 lateral->min_value = &bv->min_coordinate.x; 1177 lateral->max_value = &bv->max_coordinate.x; 1178 axial->min_value = &bv->min_coordinate.z; 1179 axial->max_value = &bv->max_coordinate.z; 1180 1181 bv->ctx = old->ctx; 1182 bv->needs_update = 1; 1183 bv->threshold.real32 = old->threshold.real32; 1184 bv->dynamic_range.real32 = old->dynamic_range.real32; 1185 bv->gamma.real32 = old->gamma.real32; 1186 bv->log_scale->bool32 = old->log_scale->bool32; 1187 bv->min_coordinate = old->frame->min_coordinate; 1188 bv->max_coordinate = old->frame->max_coordinate; 1189 1190 bv->frame = SLLPop(ui->frame_freelist); 1191 if (!bv->frame) bv->frame = push_struct(&ui->arena, typeof(*bv->frame)); 1192 1193 mem_copy(bv->frame, old->frame, sizeof(*bv->frame)); 1194 bv->frame->texture = 0; 1195 bv->frame->next = 0; 1196 alloc_beamform_frame(0, bv->frame, old->frame->dim, s8("Frame Copy: "), ui->arena); 1197 1198 glCopyImageSubData(old->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0, 1199 bv->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0, 1200 bv->frame->dim.x, bv->frame->dim.y, bv->frame->dim.z); 1201 glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT); 1202 /* TODO(rnp): x vs y here */ 1203 resize_frame_view(bv, (uv2){.x = bv->frame->dim.x, .y = bv->frame->dim.z}); 1204 } 1205 1206 function b32 1207 view_update(BeamformerUI *ui, BeamformerFrameView *view) 1208 { 1209 if (view->type == FVT_LATEST) { 1210 u32 index = *view->cycler->cycler.state; 1211 view->needs_update |= view->frame != ui->latest_plane[index]; 1212 view->frame = ui->latest_plane[index]; 1213 if (view->needs_update) { 1214 view->min_coordinate = v4_from_f32_array(ui->params.output_min_coordinate); 1215 view->max_coordinate = v4_from_f32_array(ui->params.output_max_coordinate); 1216 } 1217 } 1218 1219 /* TODO(rnp): x-z or y-z */ 1220 /* TODO(rnp): add method of setting a target size in frame view */ 1221 uv2 current = view->texture_dim; 1222 uv2 target = {.w = ui->params.output_points[0], .h = ui->params.output_points[2]}; 1223 if (view->type != FVT_COPY && !uv2_equal(current, target) && !uv2_equal(target, (uv2){0})) { 1224 resize_frame_view(view, target); 1225 view->needs_update = 1; 1226 } 1227 1228 return (view->ctx->updated || view->needs_update) && view->frame; 1229 } 1230 1231 function void 1232 update_frame_views(BeamformerUI *ui, Rect window) 1233 { 1234 b32 fbo_bound = 0; 1235 for (BeamformerFrameView *view = ui->views; view; view = view->next) { 1236 if (view_update(ui, view)) { 1237 if (!fbo_bound) { 1238 fbo_bound = 1; 1239 glBindFramebuffer(GL_FRAMEBUFFER, view->ctx->framebuffer); 1240 glUseProgram(view->ctx->shader); 1241 glBindVertexArray(view->ctx->vao); 1242 } 1243 glViewport(0, 0, view->texture_dim.w, view->texture_dim.h); 1244 glNamedFramebufferTexture(view->ctx->framebuffer, GL_COLOR_ATTACHMENT0, 1245 view->texture, 0); 1246 glClearNamedFramebufferfv(view->ctx->framebuffer, GL_COLOR, 0, 1247 (f32 []){0.79, 0.46, 0.77, 1}); 1248 glBindTextureUnit(0, view->frame->texture); 1249 glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC, view->dynamic_range.real32); 1250 glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_THRESHOLD_LOC, view->threshold.real32); 1251 glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_GAMMA_LOC, view->gamma.scaled_real32.val); 1252 glProgramUniform1ui(view->ctx->shader, FRAME_VIEW_RENDER_LOG_SCALE_LOC, view->log_scale->bool32); 1253 1254 glDrawArrays(GL_TRIANGLES, 0, 6); 1255 glGenerateTextureMipmap(view->texture); 1256 view->needs_update = 0; 1257 } 1258 } 1259 if (fbo_bound) { 1260 glBindFramebuffer(GL_FRAMEBUFFER, 0); 1261 glViewport(window.pos.x, window.pos.y, window.size.w, window.size.h); 1262 /* NOTE(rnp): I don't trust raylib to not mess with us */ 1263 glBindVertexArray(0); 1264 } 1265 } 1266 1267 function b32 1268 frame_view_ready_to_present(BeamformerFrameView *view) 1269 { 1270 return !uv2_equal((uv2){0}, view->texture_dim) && view->frame; 1271 } 1272 1273 function Color 1274 colour_from_normalized(v4 rgba) 1275 { 1276 return (Color){.r = rgba.r * 255.0f, .g = rgba.g * 255.0f, 1277 .b = rgba.b * 255.0f, .a = rgba.a * 255.0f}; 1278 } 1279 1280 function Color 1281 fade(Color a, f32 visibility) 1282 { 1283 a.a = (u8)((f32)a.a * visibility); 1284 return a; 1285 } 1286 1287 function v4 1288 lerp_v4(v4 a, v4 b, f32 t) 1289 { 1290 return (v4){ 1291 .x = a.x + t * (b.x - a.x), 1292 .y = a.y + t * (b.y - a.y), 1293 .z = a.z + t * (b.z - a.z), 1294 .w = a.w + t * (b.w - a.w), 1295 }; 1296 } 1297 1298 function s8 1299 push_das_shader_kind(Stream *s, DASShaderKind shader, u32 transmit_count) 1300 { 1301 #define X(type, id, pretty, fixed_tx) s8_comp(pretty), 1302 read_only local_persist s8 pretty_names[DASShaderKind_Count + 1] = {DAS_TYPES s8_comp("Invalid")}; 1303 #undef X 1304 #define X(type, id, pretty, fixed_tx) fixed_tx, 1305 read_only local_persist u8 fixed_transmits[DASShaderKind_Count + 1] = {DAS_TYPES 0}; 1306 #undef X 1307 1308 stream_append_s8(s, pretty_names[MIN(shader, DASShaderKind_Count)]); 1309 if (!fixed_transmits[MIN(shader, DASShaderKind_Count)]) { 1310 stream_append_byte(s, '-'); 1311 stream_append_u64(s, transmit_count); 1312 } 1313 1314 return stream_to_s8(s); 1315 } 1316 1317 function s8 1318 push_custom_view_title(Stream *s, Variable *var) 1319 { 1320 switch (var->type) { 1321 case VT_COMPUTE_STATS_VIEW:{ stream_append_s8(s, s8("Compute Stats (Average)")); } break; 1322 case VT_COMPUTE_PROGRESS_BAR:{ 1323 stream_append_s8(s, s8("Compute Progress: ")); 1324 stream_append_f64(s, 100 * *var->compute_progress_bar.progress, 100); 1325 stream_append_byte(s, '%'); 1326 } break; 1327 case VT_BEAMFORMER_FRAME_VIEW: { 1328 BeamformerFrameView *bv = var->generic; 1329 stream_append_s8(s, s8("Frame View")); 1330 switch (bv->type) { 1331 case FVT_COPY: stream_append_s8(s, s8(": Copy [")); break; 1332 case FVT_LATEST: { 1333 #define X(plane, id, pretty) s8_comp(": " pretty " ["), 1334 read_only local_persist s8 labels[IPT_LAST + 1] = {IMAGE_PLANE_TAGS s8_comp(": Live [")}; 1335 #undef X 1336 stream_append_s8(s, labels[*bv->cycler->cycler.state % (IPT_LAST + 1)]); 1337 } break; 1338 case FVT_INDEXED: { 1339 stream_append_s8(s, s8(": Index {")); 1340 stream_append_u64(s, *bv->cycler->cycler.state % MAX_BEAMFORMED_SAVED_FRAMES); 1341 stream_append_s8(s, s8("} [")); 1342 } break; 1343 } 1344 stream_append_hex_u64(s, bv->frame? bv->frame->id : 0); 1345 stream_append_byte(s, ']'); 1346 } break; 1347 default: INVALID_CODE_PATH; 1348 } 1349 return stream_to_s8(s); 1350 } 1351 1352 function v2 1353 draw_text_base(Font font, s8 text, v2 pos, Color colour) 1354 { 1355 v2 off = floor_v2(pos); 1356 for (iz i = 0; i < text.len; i++) { 1357 /* NOTE: assumes font glyphs are ordered ASCII */ 1358 i32 idx = text.data[i] - 0x20; 1359 Rectangle dst = { 1360 off.x + font.glyphs[idx].offsetX - font.glyphPadding, 1361 off.y + font.glyphs[idx].offsetY - font.glyphPadding, 1362 font.recs[idx].width + 2.0f * font.glyphPadding, 1363 font.recs[idx].height + 2.0f * font.glyphPadding 1364 }; 1365 Rectangle src = { 1366 font.recs[idx].x - font.glyphPadding, 1367 font.recs[idx].y - font.glyphPadding, 1368 font.recs[idx].width + 2.0f * font.glyphPadding, 1369 font.recs[idx].height + 2.0f * font.glyphPadding 1370 }; 1371 DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); 1372 1373 off.x += font.glyphs[idx].advanceX; 1374 if (font.glyphs[idx].advanceX == 0) 1375 off.x += font.recs[idx].width; 1376 } 1377 v2 result = {.x = off.x - pos.x, .y = font.baseSize}; 1378 return result; 1379 } 1380 1381 /* NOTE(rnp): expensive but of the available options in raylib this gives the best results */ 1382 function v2 1383 draw_outlined_text(s8 text, v2 pos, TextSpec *ts) 1384 { 1385 f32 ow = ts->outline_thick; 1386 Color outline = colour_from_normalized(ts->outline_colour); 1387 Color colour = colour_from_normalized(ts->colour); 1388 draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = ow, .y = ow}), outline); 1389 draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = ow, .y = -ow}), outline); 1390 draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = -ow, .y = ow}), outline); 1391 draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = -ow, .y = -ow}), outline); 1392 1393 v2 result = draw_text_base(*ts->font, text, pos, colour); 1394 1395 return result; 1396 } 1397 1398 function v2 1399 draw_text(s8 text, v2 pos, TextSpec *ts) 1400 { 1401 if (ts->flags & TF_ROTATED) { 1402 rlPushMatrix(); 1403 rlTranslatef(pos.x, pos.y, 0); 1404 rlRotatef(ts->rotation, 0, 0, 1); 1405 pos = (v2){0}; 1406 } 1407 1408 v2 result = measure_text(*ts->font, text); 1409 /* TODO(rnp): the size of this should be stored for each font */ 1410 s8 ellipsis = s8("..."); 1411 b32 clamped = ts->flags & TF_LIMITED && result.w > ts->limits.size.w; 1412 if (clamped) { 1413 f32 ellipsis_width = measure_text(*ts->font, ellipsis).x; 1414 if (ellipsis_width < ts->limits.size.w) { 1415 text = clamp_text_to_width(*ts->font, text, ts->limits.size.w - ellipsis_width); 1416 } else { 1417 text.len = 0; 1418 ellipsis.len = 0; 1419 } 1420 } 1421 1422 Color colour = colour_from_normalized(ts->colour); 1423 if (ts->flags & TF_OUTLINED) result.x = draw_outlined_text(text, pos, ts).x; 1424 else result.x = draw_text_base(*ts->font, text, pos, colour).x; 1425 1426 if (clamped) { 1427 pos.x += result.x; 1428 if (ts->flags & TF_OUTLINED) result.x += draw_outlined_text(ellipsis, pos, ts).x; 1429 else result.x += draw_text_base(*ts->font, ellipsis, pos, 1430 colour).x; 1431 } 1432 1433 if (ts->flags & TF_ROTATED) rlPopMatrix(); 1434 1435 return result; 1436 } 1437 1438 function Rect 1439 extend_rect_centered(Rect r, v2 delta) 1440 { 1441 r.size.w += delta.x; 1442 r.size.h += delta.y; 1443 r.pos.x -= delta.x / 2; 1444 r.pos.y -= delta.y / 2; 1445 return r; 1446 } 1447 1448 function Rect 1449 shrink_rect_centered(Rect r, v2 delta) 1450 { 1451 delta.x = MIN(delta.x, r.size.w); 1452 delta.y = MIN(delta.y, r.size.h); 1453 r.size.w -= delta.x; 1454 r.size.h -= delta.y; 1455 r.pos.x += delta.x / 2; 1456 r.pos.y += delta.y / 2; 1457 return r; 1458 } 1459 1460 function Rect 1461 scale_rect_centered(Rect r, v2 scale) 1462 { 1463 Rect or = r; 1464 r.size.w *= scale.x; 1465 r.size.h *= scale.y; 1466 r.pos.x += (or.size.w - r.size.w) / 2; 1467 r.pos.y += (or.size.h - r.size.h) / 2; 1468 return r; 1469 } 1470 1471 function b32 1472 interactions_equal(Interaction a, Interaction b) 1473 { 1474 b32 result = (a.kind == b.kind) && (a.generic == b.generic); 1475 return result; 1476 } 1477 1478 function b32 1479 interaction_is_sticky(Interaction a) 1480 { 1481 b32 result = a.kind == InteractionKind_Text || a.kind == InteractionKind_Ruler; 1482 return result; 1483 } 1484 1485 function b32 1486 interaction_is_hot(BeamformerUI *ui, Interaction a) 1487 { 1488 b32 result = interactions_equal(ui->hot_interaction, a); 1489 return result; 1490 } 1491 1492 function b32 1493 point_in_rect(v2 p, Rect r) 1494 { 1495 v2 end = add_v2(r.pos, r.size); 1496 b32 result = BETWEEN(p.x, r.pos.x, end.x) & BETWEEN(p.y, r.pos.y, end.y); 1497 return result; 1498 } 1499 1500 function v2 1501 screen_point_to_world_2d(v2 p, v2 screen_min, v2 screen_max, v2 world_min, v2 world_max) 1502 { 1503 v2 pixels_to_m = div_v2(sub_v2(world_max, world_min), sub_v2(screen_max, screen_min)); 1504 v2 result = add_v2(mul_v2(sub_v2(p, screen_min), pixels_to_m), world_min); 1505 return result; 1506 } 1507 1508 function v2 1509 world_point_to_screen_2d(v2 p, v2 world_min, v2 world_max, v2 screen_min, v2 screen_max) 1510 { 1511 v2 m_to_pixels = div_v2(sub_v2(screen_max, screen_min), sub_v2(world_max, world_min)); 1512 v2 result = add_v2(mul_v2(sub_v2(p, world_min), m_to_pixels), screen_min); 1513 return result; 1514 } 1515 1516 function b32 1517 hover_interaction(BeamformerUI *ui, v2 mouse, Interaction interaction) 1518 { 1519 Variable *var = interaction.var; 1520 b32 result = point_in_rect(mouse, interaction.rect); 1521 if (result) ui->next_interaction = interaction; 1522 if (interaction_is_hot(ui, interaction)) var->hover_t += HOVER_SPEED * dt_for_frame; 1523 else var->hover_t -= HOVER_SPEED * dt_for_frame; 1524 var->hover_t = CLAMP01(var->hover_t); 1525 return result; 1526 } 1527 1528 function void 1529 draw_close_button(BeamformerUI *ui, Variable *close, v2 mouse, Rect r, v2 x_scale) 1530 { 1531 assert(close->type == VT_UI_BUTTON); 1532 hover_interaction(ui, mouse, auto_interaction(r, close)); 1533 1534 Color colour = colour_from_normalized(lerp_v4(MENU_CLOSE_COLOUR, FG_COLOUR, close->hover_t)); 1535 r = scale_rect_centered(r, x_scale); 1536 DrawLineEx(r.pos.rl, add_v2(r.pos, r.size).rl, 4, colour); 1537 DrawLineEx(add_v2(r.pos, (v2){.x = r.size.w}).rl, 1538 add_v2(r.pos, (v2){.y = r.size.h}).rl, 4, colour); 1539 } 1540 1541 function Rect 1542 draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mouse) 1543 { 1544 assert(ui_view->type == VT_UI_VIEW); 1545 UIView *view = &ui_view->view; 1546 1547 s8 title = ui_view->name; 1548 if (view->flags & UIViewFlag_CustomText) { 1549 Stream buf = arena_stream(arena); 1550 push_custom_view_title(&buf, ui_view->group.first); 1551 title = arena_stream_commit(&arena, &buf); 1552 } 1553 1554 Rect result, title_rect; 1555 cut_rect_vertical(r, ui->small_font.baseSize + TITLE_BAR_PAD, &title_rect, &result); 1556 cut_rect_vertical(result, LISTING_LINE_PAD, 0, &result); 1557 1558 DrawRectangleRec(title_rect.rl, BLACK); 1559 1560 title_rect = shrink_rect_centered(title_rect, (v2){.x = 1.5 * TITLE_BAR_PAD}); 1561 DrawRectangleRounded(title_rect.rl, 0.5, 0, fade(colour_from_normalized(BG_COLOUR), 0.55)); 1562 title_rect = shrink_rect_centered(title_rect, (v2){.x = 3 * TITLE_BAR_PAD}); 1563 1564 if (view->close) { 1565 Rect close; 1566 cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &close); 1567 draw_close_button(ui, view->close, mouse, close, (v2){{.4, .4}}); 1568 } 1569 1570 if (view->menu) { 1571 Rect menu; 1572 cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &menu); 1573 Interaction interaction = {.kind = InteractionKind_Menu, .var = view->menu, .rect = menu}; 1574 hover_interaction(ui, mouse, interaction); 1575 1576 Color colour = colour_from_normalized(lerp_v4(MENU_PLUS_COLOUR, FG_COLOUR, view->menu->hover_t)); 1577 menu = shrink_rect_centered(menu, (v2){.x = 14, .y = 14}); 1578 DrawLineEx(add_v2(menu.pos, (v2){.x = menu.size.w / 2}).rl, 1579 add_v2(menu.pos, (v2){.x = menu.size.w / 2, .y = menu.size.h}).rl, 4, colour); 1580 DrawLineEx(add_v2(menu.pos, (v2){.y = menu.size.h / 2}).rl, 1581 add_v2(menu.pos, (v2){.x = menu.size.w, .y = menu.size.h / 2}).rl, 4, colour); 1582 } 1583 1584 v2 title_pos = title_rect.pos; 1585 title_pos.y += 0.5 * TITLE_BAR_PAD; 1586 TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED, .colour = FG_COLOUR, 1587 .limits.size = title_rect.size}; 1588 draw_text(title, title_pos, &text_spec); 1589 1590 return result; 1591 } 1592 1593 /* TODO(rnp): once this has more callers decide if it would be better for this to take 1594 * an orientation rather than force CCW/right-handed */ 1595 function void 1596 draw_ruler(BeamformerUI *ui, Arena arena, v2 start_point, v2 end_point, 1597 f32 start_value, f32 end_value, f32 *markers, u32 marker_count, 1598 u32 segments, s8 suffix, v4 marker_colour, v4 txt_colour) 1599 { 1600 b32 draw_plus = SIGN(start_value) != SIGN(end_value); 1601 1602 end_point = sub_v2(end_point, start_point); 1603 f32 rotation = atan2_f32(end_point.y, end_point.x) * 180 / PI; 1604 1605 rlPushMatrix(); 1606 rlTranslatef(start_point.x, start_point.y, 0); 1607 rlRotatef(rotation, 0, 0, 1); 1608 1609 f32 inc = magnitude_v2(end_point) / segments; 1610 f32 value_inc = (end_value - start_value) / segments; 1611 f32 value = start_value; 1612 1613 Stream buf = arena_stream(arena); 1614 v2 sp = {0}, ep = {.y = RULER_TICK_LENGTH}; 1615 v2 tp = {.x = ui->small_font.baseSize / 2, .y = ep.y + RULER_TEXT_PAD}; 1616 TextSpec text_spec = {.font = &ui->small_font, .rotation = 90, .colour = txt_colour, .flags = TF_ROTATED}; 1617 Color rl_txt_colour = colour_from_normalized(txt_colour); 1618 for (u32 j = 0; j <= segments; j++) { 1619 DrawLineEx(sp.rl, ep.rl, 3, rl_txt_colour); 1620 1621 stream_reset(&buf, 0); 1622 if (draw_plus && value > 0) stream_append_byte(&buf, '+'); 1623 stream_append_f64(&buf, value, 10); 1624 stream_append_s8(&buf, suffix); 1625 draw_text(stream_to_s8(&buf), tp, &text_spec); 1626 1627 value += value_inc; 1628 sp.x += inc; 1629 ep.x += inc; 1630 tp.x += inc; 1631 } 1632 1633 Color rl_marker_colour = colour_from_normalized(marker_colour); 1634 ep.y += RULER_TICK_LENGTH; 1635 for (u32 i = 0; i < marker_count; i++) { 1636 if (markers[i] < F32_INFINITY) { 1637 ep.x = sp.x = markers[i]; 1638 DrawLineEx(sp.rl, ep.rl, 3, rl_marker_colour); 1639 DrawCircleV(ep.rl, 3, rl_marker_colour); 1640 } 1641 } 1642 1643 rlPopMatrix(); 1644 } 1645 1646 function void 1647 do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect draw_rect, 1648 f32 start_value, f32 end_value, s8 suffix) 1649 { 1650 assert(scale_bar->type == VT_SCALE_BAR); 1651 ScaleBar *sb = &scale_bar->scale_bar; 1652 1653 v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); 1654 1655 Rect tick_rect = draw_rect; 1656 v2 start_pos = tick_rect.pos; 1657 v2 end_pos = tick_rect.pos; 1658 v2 relative_mouse = sub_v2(mouse, tick_rect.pos); 1659 1660 f32 markers[2]; 1661 u32 marker_count = 1; 1662 1663 v2 world_zoom_point = {{sb->zoom_starting_coord, sb->zoom_starting_coord}}; 1664 v2 screen_zoom_point = world_point_to_screen_2d(world_zoom_point, 1665 (v2){{*sb->min_value, *sb->min_value}}, 1666 (v2){{*sb->max_value, *sb->max_value}}, 1667 (v2){0}, tick_rect.size); 1668 u32 tick_count; 1669 if (sb->direction == SB_AXIAL) { 1670 tick_rect.size.x = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x; 1671 tick_count = tick_rect.size.y / (1.5 * ui->small_font.baseSize); 1672 start_pos.y += tick_rect.size.y; 1673 markers[0] = tick_rect.size.y - screen_zoom_point.y; 1674 markers[1] = tick_rect.size.y - relative_mouse.y; 1675 } else { 1676 tick_rect.size.y = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x; 1677 tick_count = tick_rect.size.x / (1.5 * ui->small_font.baseSize); 1678 end_pos.x += tick_rect.size.x; 1679 markers[0] = screen_zoom_point.x; 1680 markers[1] = relative_mouse.x; 1681 } 1682 1683 if (hover_interaction(ui, mouse, auto_interaction(tick_rect, scale_bar))) 1684 marker_count = 2; 1685 1686 draw_ruler(ui, arena, start_pos, end_pos, start_value, end_value, markers, marker_count, 1687 tick_count, suffix, RULER_COLOUR, lerp_v4(FG_COLOUR, HOVERED_COLOUR, scale_bar->hover_t)); 1688 } 1689 1690 function v2 1691 draw_radio_button(BeamformerUI *ui, Variable *var, v2 at, v2 mouse, v4 base_colour, f32 size) 1692 { 1693 assert(var->type == VT_B32 || var->type == VT_BEAMFORMER_VARIABLE); 1694 b32 value; 1695 if (var->type == VT_B32) { 1696 value = var->bool32; 1697 } else { 1698 assert(var->beamformer_variable.store_type == VT_B32); 1699 value = *(b32 *)var->beamformer_variable.store; 1700 } 1701 1702 v2 result = (v2){.x = size, .y = size}; 1703 Rect hover_rect = {.pos = at, .size = result}; 1704 hover_rect.pos.y += 1; 1705 hover_interaction(ui, mouse, auto_interaction(hover_rect, var)); 1706 1707 hover_rect = shrink_rect_centered(hover_rect, (v2){.x = 8, .y = 8}); 1708 Rect inner = shrink_rect_centered(hover_rect, (v2){.x = 4, .y = 4}); 1709 v4 fill = lerp_v4(value? base_colour : (v4){0}, HOVERED_COLOUR, var->hover_t); 1710 DrawRectangleRoundedLinesEx(hover_rect.rl, 0.2, 0, 2, colour_from_normalized(base_colour)); 1711 DrawRectangleRec(inner.rl, colour_from_normalized(fill)); 1712 1713 return result; 1714 } 1715 1716 function v2 1717 draw_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, v4 base_colour, TextSpec text_spec) 1718 { 1719 v2 result; 1720 if (var->flags & V_RADIO_BUTTON) { 1721 result = draw_radio_button(ui, var, at, mouse, base_colour, text_spec.font->baseSize); 1722 } else { 1723 Stream buf = arena_stream(arena); 1724 stream_append_variable(&buf, var); 1725 s8 text = arena_stream_commit(&arena, &buf); 1726 result = measure_text(*text_spec.font, text); 1727 1728 if (var->flags & V_INPUT) { 1729 Rect text_rect = {.pos = at, .size = result}; 1730 text_rect = extend_rect_centered(text_rect, (v2){.x = 8}); 1731 if (hover_interaction(ui, mouse, auto_interaction(text_rect, var)) && (var->flags & V_TEXT)) 1732 ui->text_input_state.hot_font = text_spec.font; 1733 text_spec.colour = lerp_v4(base_colour, HOVERED_COLOUR, var->hover_t); 1734 } 1735 1736 draw_text(text, at, &text_spec); 1737 } 1738 return result; 1739 } 1740 1741 function void 1742 draw_table_cell(BeamformerUI *ui, Arena arena, TableCell *cell, Rect cell_rect, 1743 TextAlignment alignment, TextSpec ts, v2 mouse) 1744 { 1745 f32 x_off = cell_rect.pos.x; 1746 v2 cell_at = table_cell_align(cell, alignment, cell_rect); 1747 ts.limits.size.w -= (cell_at.x - x_off); 1748 cell_rect.size.w = MIN(ts.limits.size.w, cell_rect.size.w); 1749 1750 /* TODO(rnp): push truncated text for hovering */ 1751 switch (cell->kind) { 1752 case TableCellKind_None:{ draw_text(cell->text, cell_at, &ts); }break; 1753 case TableCellKind_Variable:{ 1754 if (cell->var->flags & V_INPUT) { 1755 draw_variable(ui, arena, cell->var, cell_at, mouse, ts.colour, ts); 1756 } else if (cell->text.len) { 1757 draw_text(cell->text, cell_at, &ts); 1758 } 1759 }break; 1760 case TableCellKind_VariableGroup:{ 1761 Variable *v = cell->var->group.first; 1762 f32 dw = draw_text(s8("{"), cell_at, &ts).x; 1763 while (v) { 1764 cell_at.x += dw; 1765 ts.limits.size.w -= dw; 1766 dw = draw_variable(ui, arena, v, cell_at, mouse, ts.colour, ts).x; 1767 1768 v = v->next; 1769 if (v) { 1770 cell_at.x += dw; 1771 ts.limits.size.w -= dw; 1772 dw = draw_text(s8(", "), cell_at, &ts).x; 1773 } 1774 } 1775 cell_at.x += dw; 1776 ts.limits.size.w -= dw; 1777 draw_text(s8("}"), cell_at, &ts); 1778 }break; 1779 } 1780 } 1781 1782 function void 1783 draw_table_borders(Table *t, Rect r, f32 line_height) 1784 { 1785 if (t->column_border_thick > 0) { 1786 v2 start = {.x = r.pos.x, .y = r.pos.y + t->cell_pad.h / 2}; 1787 v2 end = start; 1788 end.y += t->size.y - t->cell_pad.y; 1789 for (i32 i = 0; i < t->columns - 1; i++) { 1790 f32 dx = t->widths[i] + t->cell_pad.w + t->column_border_thick; 1791 start.x += dx; 1792 end.x += dx; 1793 if (t->widths[i + 1] > 0) 1794 DrawLineEx(start.rl, end.rl, t->column_border_thick, fade(BLACK, 0.8)); 1795 } 1796 } 1797 1798 if (t->row_border_thick > 0) { 1799 v2 start = {.x = r.pos.x + t->cell_pad.w / 2, .y = r.pos.y}; 1800 v2 end = start; 1801 end.x += t->size.x - t->cell_pad.x; 1802 for (i32 i = 0; i < t->rows - 1; i++) { 1803 f32 dy = line_height + t->cell_pad.y + t->row_border_thick; 1804 start.y += dy; 1805 end.y += dy; 1806 DrawLineEx(start.rl, end.rl, t->row_border_thick, fade(BLACK, 0.8)); 1807 } 1808 } 1809 } 1810 1811 function v2 1812 draw_table(BeamformerUI *ui, Arena arena, Table *table, Rect draw_rect, TextSpec ts, v2 mouse, b32 skip_rows) 1813 { 1814 ts.flags |= TF_LIMITED; 1815 1816 v2 result = {.x = table_width(table)}; 1817 i32 row_index = skip_rows? table_skip_rows(table, draw_rect.size.h, ts.font->baseSize) : 0; 1818 TableIterator *it = table_iterator_new(table, TIK_CELLS, &arena, row_index, draw_rect.pos, ts.font); 1819 for (TableCell *cell = table_iterator_next(it, &arena); 1820 cell; 1821 cell = table_iterator_next(it, &arena)) 1822 { 1823 ts.limits.size.w = draw_rect.size.w - (it->cell_rect.pos.x - it->start_x); 1824 draw_table_cell(ui, arena, cell, it->cell_rect, it->alignment, ts, mouse); 1825 } 1826 draw_table_borders(table, draw_rect, ts.font->baseSize); 1827 result.y = it->cell_rect.pos.y - draw_rect.pos.y - table->cell_pad.h / 2; 1828 return result; 1829 } 1830 1831 function void 1832 draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts) 1833 { 1834 v2 vr_max_p = add_v2(view_rect.pos, view_rect.size); 1835 v2 start_p = world_point_to_screen_2d(view->ruler.start, XZ(view->min_coordinate), 1836 XZ(view->max_coordinate), view_rect.pos, vr_max_p); 1837 v2 end_p = world_point_to_screen_2d(view->ruler.end, XZ(view->min_coordinate), 1838 XZ(view->max_coordinate), view_rect.pos, vr_max_p); 1839 1840 Color rl_colour = colour_from_normalized(ts.colour); 1841 DrawCircleV(start_p.rl, 3, rl_colour); 1842 DrawLineEx(end_p.rl, start_p.rl, 2, rl_colour); 1843 DrawCircleV(end_p.rl, 3, rl_colour); 1844 1845 Stream buf = arena_stream(a); 1846 stream_append_f64(&buf, 1e3 * magnitude_v2(sub_v2(view->ruler.end, view->ruler.start)), 100); 1847 stream_append_s8(&buf, s8(" mm")); 1848 1849 v2 txt_p = start_p; 1850 v2 txt_s = measure_text(*ts.font, stream_to_s8(&buf)); 1851 v2 pixel_delta = sub_v2(start_p, end_p); 1852 if (pixel_delta.y < 0) txt_p.y -= txt_s.y; 1853 if (pixel_delta.x < 0) txt_p.x -= txt_s.x; 1854 if (txt_p.x < view_rect.pos.x) txt_p.x = view_rect.pos.x; 1855 if (txt_p.x + txt_s.x > vr_max_p.x) txt_p.x -= (txt_p.x + txt_s.x) - vr_max_p.x; 1856 1857 draw_text(stream_to_s8(&buf), txt_p, &ts); 1858 } 1859 1860 function void 1861 draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect display_rect, v2 mouse) 1862 { 1863 assert(var->type == VT_BEAMFORMER_FRAME_VIEW); 1864 BeamformerFrameView *view = var->generic; 1865 BeamformFrame *frame = view->frame; 1866 1867 v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); 1868 f32 scale_bar_size = 1.2 * txt_s.x + RULER_TICK_LENGTH; 1869 1870 v4 min = view->min_coordinate; 1871 v4 max = view->max_coordinate; 1872 v2 requested_dim = sub_v2(XZ(max), XZ(min)); 1873 f32 aspect = requested_dim.w / requested_dim.h; 1874 1875 Rect vr = display_rect; 1876 v2 scale_bar_area = {0}; 1877 if (view->axial_scale_bar_active->bool32) { 1878 vr.pos.y += 0.5 * ui->small_font.baseSize; 1879 scale_bar_area.x += scale_bar_size; 1880 scale_bar_area.y += ui->small_font.baseSize; 1881 } 1882 1883 if (view->lateral_scale_bar_active->bool32) { 1884 vr.pos.x += 0.5 * ui->small_font.baseSize; 1885 scale_bar_area.x += ui->small_font.baseSize; 1886 scale_bar_area.y += scale_bar_size; 1887 } 1888 1889 vr.size = sub_v2(vr.size, scale_bar_area); 1890 if (aspect > 1) vr.size.h = vr.size.w / aspect; 1891 else vr.size.w = vr.size.h * aspect; 1892 1893 v2 occupied = add_v2(vr.size, scale_bar_area); 1894 if (occupied.w > display_rect.size.w) { 1895 vr.size.w -= (occupied.w - display_rect.size.w); 1896 vr.size.h = vr.size.w / aspect; 1897 } else if (occupied.h > display_rect.size.h) { 1898 vr.size.h -= (occupied.h - display_rect.size.h); 1899 vr.size.w = vr.size.h * aspect; 1900 } 1901 occupied = add_v2(vr.size, scale_bar_area); 1902 vr.pos = add_v2(vr.pos, scale_v2(sub_v2(display_rect.size, occupied), 0.5)); 1903 1904 /* TODO(rnp): make this depend on the requested draw orientation (x-z or y-z or x-y) */ 1905 v2 output_dim = { 1906 .x = frame->max_coordinate.x - frame->min_coordinate.x, 1907 .y = frame->max_coordinate.z - frame->min_coordinate.z, 1908 }; 1909 1910 v2 pixels_per_meter = { 1911 .w = (f32)view->texture_dim.w / output_dim.w, 1912 .h = (f32)view->texture_dim.h / output_dim.h, 1913 }; 1914 1915 v2 texture_points = mul_v2(pixels_per_meter, requested_dim); 1916 /* TODO(rnp): this also depends on x-y, y-z, x-z */ 1917 v2 texture_start = { 1918 .x = pixels_per_meter.x * 0.5 * (output_dim.x - requested_dim.x), 1919 .y = pixels_per_meter.y * (frame->max_coordinate.z - max.z), 1920 }; 1921 1922 Rectangle tex_r = {texture_start.x, texture_start.y, texture_points.x, -texture_points.y}; 1923 NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH }; 1924 DrawTextureNPatch(make_raylib_texture(view), tex_np, vr.rl, (Vector2){0}, 0, WHITE); 1925 1926 v2 start_pos = vr.pos; 1927 start_pos.y += vr.size.y; 1928 1929 if (vr.size.w > 0 && view->lateral_scale_bar_active->bool32) { 1930 do_scale_bar(ui, a, &view->lateral_scale_bar, mouse, 1931 (Rect){.pos = start_pos, .size = vr.size}, 1932 *view->lateral_scale_bar.scale_bar.min_value * 1e3, 1933 *view->lateral_scale_bar.scale_bar.max_value * 1e3, s8(" mm")); 1934 } 1935 1936 start_pos = vr.pos; 1937 start_pos.x += vr.size.x; 1938 1939 if (vr.size.h > 0 && view->axial_scale_bar_active->bool32) { 1940 do_scale_bar(ui, a, &view->axial_scale_bar, mouse, 1941 (Rect){.pos = start_pos, .size = vr.size}, 1942 *view->axial_scale_bar.scale_bar.max_value * 1e3, 1943 *view->axial_scale_bar.scale_bar.min_value * 1e3, s8(" mm")); 1944 } 1945 1946 TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED|TF_OUTLINED, 1947 .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1, 1948 .limits.size.x = vr.size.w}; 1949 1950 f32 draw_table_width = vr.size.w; 1951 /* NOTE: avoid hover_t modification */ 1952 Interaction viewer = auto_interaction(vr, var); 1953 if (point_in_rect(mouse, viewer.rect)) { 1954 ui->next_interaction = viewer; 1955 1956 v2 world = screen_point_to_world_2d(mouse, vr.pos, add_v2(vr.pos, vr.size), 1957 XZ(view->min_coordinate), 1958 XZ(view->max_coordinate)); 1959 Stream buf = arena_stream(a); 1960 stream_append_v2(&buf, scale_v2(world, 1e3)); 1961 1962 text_spec.limits.size.w -= 4; 1963 v2 txt_s = measure_text(*text_spec.font, stream_to_s8(&buf)); 1964 v2 txt_p = { 1965 .x = vr.pos.x + vr.size.w - txt_s.w - 4, 1966 .y = vr.pos.y + vr.size.h - txt_s.h - 4, 1967 }; 1968 txt_p.x = MAX(vr.pos.x, txt_p.x); 1969 draw_table_width -= draw_text(stream_to_s8(&buf), txt_p, &text_spec).w; 1970 text_spec.limits.size.w += 4; 1971 } 1972 1973 { 1974 Stream buf = arena_stream(a); 1975 s8 shader = push_das_shader_kind(&buf, frame->das_shader_kind, frame->compound_count); 1976 text_spec.font = &ui->font; 1977 text_spec.limits.size.w -= 16; 1978 v2 txt_s = measure_text(*text_spec.font, shader); 1979 v2 txt_p = { 1980 .x = vr.pos.x + vr.size.w - txt_s.w - 16, 1981 .y = vr.pos.y + 4, 1982 }; 1983 txt_p.x = MAX(vr.pos.x, txt_p.x); 1984 draw_text(stream_to_s8(&buf), txt_p, &text_spec); 1985 text_spec.font = &ui->small_font; 1986 text_spec.limits.size.w += 16; 1987 } 1988 1989 if (view->ruler.state != RulerState_None) draw_view_ruler(view, a, vr, text_spec); 1990 1991 Table *table = table_new(&a, 3, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); 1992 table_push_parameter_row(table, &a, view->gamma.name, &view->gamma, s8("")); 1993 table_push_parameter_row(table, &a, view->threshold.name, &view->threshold, s8("")); 1994 if (view->log_scale->bool32) 1995 table_push_parameter_row(table, &a, view->dynamic_range.name, &view->dynamic_range, s8("[dB]")); 1996 1997 Rect table_rect = vr; 1998 f32 height = table_extent(table, a, text_spec.font).y; 1999 height = MIN(height, vr.size.h); 2000 table_rect.pos.w += 8; 2001 table_rect.pos.y += vr.size.h - height - 8; 2002 table_rect.size.h = height; 2003 table_rect.size.w = draw_table_width - 16; 2004 2005 draw_table(ui, a, table, table_rect, text_spec, mouse, 0); 2006 } 2007 2008 function v2 2009 draw_compute_progress_bar(BeamformerUI *ui, Arena arena, ComputeProgressBar *state, Rect r) 2010 { 2011 if (*state->processing) state->display_t_velocity += 65 * dt_for_frame; 2012 else state->display_t_velocity -= 45 * dt_for_frame; 2013 2014 state->display_t_velocity = CLAMP(state->display_t_velocity, -10, 10); 2015 state->display_t += state->display_t_velocity * dt_for_frame; 2016 state->display_t = CLAMP01(state->display_t); 2017 2018 if (state->display_t > (1.0 / 255.0)) { 2019 Rect outline = {.pos = r.pos, .size = {.w = r.size.w, .h = ui->font.baseSize}}; 2020 outline = scale_rect_centered(outline, (v2){.x = 0.96, .y = 0.7}); 2021 Rect filled = outline; 2022 filled.size.w *= *state->progress; 2023 DrawRectangleRounded(filled.rl, 2, 0, fade(colour_from_normalized(HOVERED_COLOUR), 2024 state->display_t)); 2025 DrawRectangleRoundedLinesEx(outline.rl, 2, 0, 3, fade(BLACK, state->display_t)); 2026 } 2027 2028 v2 result = {.x = r.size.w, .y = ui->font.baseSize}; 2029 return result; 2030 } 2031 2032 function v2 2033 draw_compute_stats_view(BeamformerCtx *ctx, Arena arena, Rect r) 2034 { 2035 #define X(e, n, s, h, pn) [BeamformerShaderKind_##e] = s8_comp(pn ":"), 2036 read_only local_persist s8 labels[BeamformerShaderKind_ComputeCount] = {COMPUTE_SHADERS}; 2037 #undef X 2038 2039 BeamformerSharedMemory *sm = ctx->shared_memory.region; 2040 ComputeShaderStats *stats = ctx->compute_shader_stats; 2041 BeamformerUI *ui = ctx->ui; 2042 f32 compute_time_sum = 0; 2043 u32 stages = sm->compute_stages_count; 2044 TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; 2045 2046 static_assert(countof(labels) <= 32, "shader kind bitfield test"); 2047 u32 seen_shaders = 0; 2048 Table *table = table_new(&arena, stages + 2, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); 2049 for (u32 i = 0; i < stages; i++) { 2050 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 2051 2052 Stream sb = arena_stream(arena); 2053 BeamformerShaderKind index = sm->compute_stages[i]; 2054 if ((seen_shaders & (1 << index)) == 0) { 2055 compute_time_sum += stats->average_times[index]; 2056 stream_append_f64_e(&sb, stats->average_times[index]); 2057 seen_shaders |= (1 << index); 2058 cells[0].text = labels[index]; 2059 cells[1].text = arena_stream_commit(&arena, &sb); 2060 cells[2].text = s8("[s]"); 2061 } 2062 } 2063 2064 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 2065 Stream sb = arena_stream(arena); 2066 stream_append_f64_e(&sb, compute_time_sum); 2067 cells[0].text = s8("Compute Total:"); 2068 cells[1].text = arena_stream_commit(&arena, &sb); 2069 cells[2].text = s8("[s]"); 2070 2071 cells = table_push_row(table, &arena, TRK_CELLS)->data; 2072 sb = arena_stream(arena); 2073 stream_append_f64_e(&sb, stats->rf_time_delta_average); 2074 cells[0].text = s8("RF Upload Delta:"); 2075 cells[1].text = arena_stream_commit(&arena, &sb); 2076 cells[2].text = s8("[s]"); 2077 2078 table_extent(table, arena, text_spec.font); 2079 return draw_table(ui, arena, table, r, text_spec, (v2){0}, 0); 2080 } 2081 2082 struct variable_iterator { Variable *current; }; 2083 function i32 2084 variable_iterator_next(struct variable_iterator *it) 2085 { 2086 i32 result = 0; 2087 2088 if (it->current->type == VT_GROUP && it->current->group.expanded) { 2089 it->current = it->current->group.first; 2090 result++; 2091 } else { 2092 while (it->current) { 2093 if (it->current->next) { 2094 it->current = it->current->next; 2095 break; 2096 } 2097 it->current = it->current->parent; 2098 result--; 2099 } 2100 } 2101 2102 return result; 2103 } 2104 2105 function v2 2106 draw_ui_view_menu(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 mouse, TextSpec text_spec) 2107 { 2108 assert(group->type == VT_GROUP); 2109 Table *table = table_new(&arena, 0, TextAlignment_Left, TextAlignment_Right); 2110 table->row_border_thick = 2.0f; 2111 table->cell_pad = (v2){{16.0f, 8.0f}}; 2112 2113 i32 nesting = 0; 2114 for (struct variable_iterator it = {group->group.first}; 2115 it.current; 2116 nesting = variable_iterator_next(&it)) 2117 { 2118 (void)nesting; 2119 assert(nesting == 0); 2120 Variable *var = it.current; 2121 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 2122 switch (var->type) { 2123 case VT_B32: 2124 case VT_CYCLER: 2125 { 2126 cells[0] = (TableCell){.text = var->name}; 2127 cells[1] = table_variable_cell(&arena, var); 2128 }break; 2129 case VT_UI_BUTTON:{ 2130 cells[0] = (TableCell){.text = var->name, .kind = TableCellKind_Variable, .var = var}; 2131 }break; 2132 InvalidDefaultCase; 2133 } 2134 } 2135 2136 r.size = table_extent(table, arena, text_spec.font); 2137 return draw_table(ui, arena, table, r, text_spec, mouse, 0); 2138 } 2139 2140 function v2 2141 draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 mouse, TextSpec text_spec) 2142 { 2143 assert(group->type == VT_GROUP); 2144 Table *table = table_new(&arena, 0, TextAlignment_Left, TextAlignment_Left, TextAlignment_Right); 2145 /* NOTE(rnp): minimum width for middle column */ 2146 table->widths[1] = 150; 2147 2148 i32 nesting = 0; 2149 for (struct variable_iterator it = {group->group.first}; 2150 it.current; 2151 nesting = variable_iterator_next(&it)) 2152 { 2153 while (nesting > 0) { 2154 table = table_begin_subtable(table, &arena, TextAlignment_Left, 2155 TextAlignment_Center, TextAlignment_Right); 2156 /* NOTE(rnp): minimum width for middle column */ 2157 table->widths[1] = 100; 2158 nesting--; 2159 } 2160 while (nesting < 0) { table = table_end_subtable(table); nesting++; } 2161 2162 Variable *var = it.current; 2163 switch (var->type) { 2164 case VT_CYCLER: 2165 case VT_BEAMFORMER_VARIABLE: 2166 { 2167 s8 suffix = s8(""); 2168 if (var->type == VT_BEAMFORMER_VARIABLE) 2169 suffix = var->beamformer_variable.suffix; 2170 table_push_parameter_row(table, &arena, var->name, var, suffix); 2171 }break; 2172 case VT_GROUP:{ 2173 VariableGroup *g = &var->group; 2174 2175 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 2176 cells[0] = (TableCell){.text = var->name, .kind = TableCellKind_Variable, .var = var}; 2177 2178 if (!g->expanded) { 2179 Stream sb = arena_stream(arena); 2180 stream_append_variable_group(&sb, var); 2181 cells[1].kind = TableCellKind_VariableGroup; 2182 cells[1].text = arena_stream_commit(&arena, &sb); 2183 cells[1].var = var; 2184 2185 Variable *v = g->first; 2186 assert(!v || v->type == VT_BEAMFORMER_VARIABLE); 2187 /* NOTE(rnp): assume the suffix is the same for all elements */ 2188 if (v) cells[2].text = v->beamformer_variable.suffix; 2189 } 2190 }break; 2191 InvalidDefaultCase; 2192 } 2193 } 2194 2195 v2 result = table_extent(table, arena, text_spec.font); 2196 draw_table(ui, arena, table, r, text_spec, mouse, 0); 2197 return result; 2198 } 2199 2200 function void 2201 draw_ui_view_container(BeamformerUI *ui, Variable *var, v2 mouse, Rect bounds) 2202 { 2203 UIView *fw = &var->view; 2204 if (fw->rect.size.x > 0 && fw->rect.size.y > 0) { 2205 f32 line_height = ui->small_font.baseSize; 2206 2207 if (fw->rect.pos.y - line_height < 0) fw->rect.pos.y += line_height - fw->rect.pos.y; 2208 f32 delta_x = add_v2(fw->rect.pos, fw->rect.size).x - add_v2(bounds.size, bounds.pos).x; 2209 if (delta_x > 0) { 2210 fw->rect.pos.x -= delta_x; 2211 fw->rect.pos.x = MAX(0, fw->rect.pos.x); 2212 } 2213 2214 Rect container = fw->rect; 2215 if (fw->close) { 2216 container.pos.y -= 5 + line_height; 2217 container.size.y += 2 + line_height; 2218 Rect handle = {{container.pos, (v2){.x = container.size.w, .y = 2 + line_height}}}; 2219 Rect close; 2220 hover_interaction(ui, mouse, auto_interaction(container, var)); 2221 cut_rect_horizontal(handle, handle.size.w - handle.size.h - 6, 0, &close); 2222 close.size.w = close.size.h; 2223 DrawRectangleRounded(handle.rl, 0.1, 0, colour_from_normalized(BG_COLOUR)); 2224 DrawRectangleRoundedLinesEx(handle.rl, 0.2, 0, 2, BLACK); 2225 draw_close_button(ui, fw->close, mouse, close, (v2){{0.45, 0.45}}); 2226 } else { 2227 hover_interaction(ui, mouse, auto_interaction(container, var)); 2228 } 2229 f32 roundness = 12.0f / fw->rect.size.y; 2230 DrawRectangleRounded(fw->rect.rl, roundness / 2, 0, colour_from_normalized(BG_COLOUR)); 2231 DrawRectangleRoundedLinesEx(fw->rect.rl, roundness, 0, 2, BLACK); 2232 } 2233 } 2234 2235 function void 2236 draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec text_spec) 2237 { 2238 assert(ui_view->type == VT_UI_VIEW || ui_view->type == VT_UI_MENU || ui_view->type == VT_UI_TEXT_BOX); 2239 2240 UIView *view = &ui_view->view; 2241 2242 if (view->flags & UIViewFlag_Floating) { 2243 draw_ui_view_container(ui, ui_view, mouse, r); 2244 /* TODO(rnp): cleanup this jank */ 2245 r = view->rect; 2246 } else { 2247 if (view->rect.size.h - r.size.h < view->rect.pos.h) 2248 view->rect.pos.h = view->rect.size.h - r.size.h; 2249 2250 if (view->rect.size.h - r.size.h < 0) 2251 view->rect.pos.h = 0; 2252 2253 r.pos.y -= view->rect.pos.h; 2254 } 2255 2256 v2 size = {0}; 2257 2258 Variable *var = view->child; 2259 switch (var->type) { 2260 case VT_GROUP:{ 2261 if (ui_view->type == VT_UI_MENU) 2262 size = draw_ui_view_menu(ui, var, ui->arena, r, mouse, text_spec); 2263 else { 2264 size = draw_ui_view_listing(ui, var, ui->arena, r, mouse, text_spec); 2265 } 2266 }break; 2267 case VT_BEAMFORMER_FRAME_VIEW: { 2268 BeamformerFrameView *bv = var->generic; 2269 if (frame_view_ready_to_present(bv)) 2270 draw_beamformer_frame_view(ui, ui->arena, var, r, mouse); 2271 } break; 2272 case VT_COMPUTE_PROGRESS_BAR: { 2273 size = draw_compute_progress_bar(ui, ui->arena, &var->compute_progress_bar, r); 2274 } break; 2275 case VT_COMPUTE_STATS_VIEW:{ size = draw_compute_stats_view(var->generic, ui->arena, r); }break; 2276 InvalidDefaultCase; 2277 } 2278 2279 view->rect.size = size; 2280 } 2281 2282 function void 2283 draw_layout_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse) 2284 { 2285 if (var->type != VT_UI_REGION_SPLIT) { 2286 v2 shrink = {.x = UI_REGION_PAD, .y = UI_REGION_PAD}; 2287 draw_rect = shrink_rect_centered(draw_rect, shrink); 2288 draw_rect.size = floor_v2(draw_rect.size); 2289 BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); 2290 draw_rect = draw_title_bar(ui, ui->arena, var, draw_rect, mouse); 2291 EndScissorMode(); 2292 } 2293 2294 /* TODO(rnp): post order traversal of the ui tree will remove the need for this */ 2295 if (!CheckCollisionPointRec(mouse.rl, draw_rect.rl)) 2296 mouse = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; 2297 2298 draw_rect.size = floor_v2(draw_rect.size); 2299 BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); 2300 switch (var->type) { 2301 case VT_UI_VIEW: { 2302 hover_interaction(ui, mouse, auto_interaction(draw_rect, var)); 2303 TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; 2304 draw_ui_view(ui, var, draw_rect, mouse, text_spec); 2305 } break; 2306 case VT_UI_REGION_SPLIT: { 2307 RegionSplit *rs = &var->region_split; 2308 2309 Rect split, hover; 2310 switch (rs->direction) { 2311 case RSD_VERTICAL: { 2312 split_rect_vertical(draw_rect, rs->fraction, 0, &split); 2313 split.pos.x += UI_REGION_PAD; 2314 split.pos.y -= UI_SPLIT_HANDLE_THICK / 2; 2315 split.size.h = UI_SPLIT_HANDLE_THICK; 2316 split.size.w -= 2 * UI_REGION_PAD; 2317 hover = extend_rect_centered(split, (v2){.y = 0.75 * UI_REGION_PAD}); 2318 } break; 2319 case RSD_HORIZONTAL: { 2320 split_rect_horizontal(draw_rect, rs->fraction, 0, &split); 2321 split.pos.x -= UI_SPLIT_HANDLE_THICK / 2; 2322 split.pos.y += UI_REGION_PAD; 2323 split.size.w = UI_SPLIT_HANDLE_THICK; 2324 split.size.h -= 2 * UI_REGION_PAD; 2325 hover = extend_rect_centered(split, (v2){.x = 0.75 * UI_REGION_PAD}); 2326 } break; 2327 } 2328 2329 Interaction drag = {.kind = InteractionKind_Drag, .rect = hover, .var = var}; 2330 hover_interaction(ui, mouse, drag); 2331 2332 v4 colour = HOVERED_COLOUR; 2333 colour.a = var->hover_t; 2334 DrawRectangleRounded(split.rl, 0.6, 0, colour_from_normalized(colour)); 2335 } break; 2336 InvalidDefaultCase; 2337 } 2338 EndScissorMode(); 2339 } 2340 2341 function void 2342 draw_ui_regions(BeamformerUI *ui, Rect window, v2 mouse) 2343 { 2344 struct region_frame { 2345 Variable *var; 2346 Rect rect; 2347 } init[16]; 2348 2349 struct { 2350 struct region_frame *data; 2351 iz count; 2352 iz capacity; 2353 } stack = {init, 0, ARRAY_COUNT(init)}; 2354 2355 TempArena arena_savepoint = begin_temp_arena(&ui->arena); 2356 2357 *da_push(&ui->arena, &stack) = (struct region_frame){ui->regions, window}; 2358 while (stack.count) { 2359 struct region_frame *top = stack.data + --stack.count; 2360 Rect rect = top->rect; 2361 draw_layout_variable(ui, top->var, rect, mouse); 2362 2363 if (top->var->type == VT_UI_REGION_SPLIT) { 2364 Rect first, second; 2365 RegionSplit *rs = &top->var->region_split; 2366 switch (rs->direction) { 2367 case RSD_VERTICAL: { 2368 split_rect_vertical(rect, rs->fraction, &first, &second); 2369 } break; 2370 case RSD_HORIZONTAL: { 2371 split_rect_horizontal(rect, rs->fraction, &first, &second); 2372 } break; 2373 } 2374 2375 *da_push(&ui->arena, &stack) = (struct region_frame){rs->right, second}; 2376 *da_push(&ui->arena, &stack) = (struct region_frame){rs->left, first}; 2377 } 2378 } 2379 2380 end_temp_arena(arena_savepoint); 2381 } 2382 2383 function void 2384 draw_floating_widgets(BeamformerUI *ui, Rect window_rect, v2 mouse) 2385 { 2386 TextSpec text_spec = {.font = &ui->small_font, .colour = FG_COLOUR}; 2387 for (Variable *var = ui->floating_widget_sentinal.parent; 2388 var != &ui->floating_widget_sentinal; 2389 var = var->parent) 2390 { 2391 if (var->type == VT_UI_TEXT_BOX) { 2392 UIView *fw = &var->view; 2393 InputState *is = &ui->text_input_state; 2394 2395 draw_ui_view_container(ui, var, mouse, fw->rect); 2396 2397 f32 cursor_width = (is->cursor == is->count) ? 0.55 * is->font->baseSize : 4; 2398 s8 text = {.len = is->count, .data = is->buf}; 2399 v2 text_size = measure_text(*is->font, text); 2400 2401 f32 text_pad = 4.0f; 2402 f32 desired_width = text_pad + text_size.w + cursor_width; 2403 fw->rect.size = (v2){{MAX(desired_width, fw->rect.size.w), text_size.h + text_pad}}; 2404 2405 v2 text_position = {{fw->rect.pos.x + text_pad / 2, fw->rect.pos.y + text_pad / 2}}; 2406 f32 cursor_offset = measure_text(*is->font, (s8){is->cursor, text.data}).w; 2407 cursor_offset += text_position.x; 2408 2409 Rect cursor; 2410 cursor.pos = (v2){{cursor_offset, text_position.y}}; 2411 cursor.size = (v2){{cursor_width, text_size.h}}; 2412 2413 v4 cursor_colour = FOCUSED_COLOUR; 2414 cursor_colour.a = CLAMP01(is->cursor_blink_t); 2415 v4 text_colour = lerp_v4(FG_COLOUR, HOVERED_COLOUR, fw->child->hover_t); 2416 2417 TextSpec text_spec = {.font = is->font, .colour = text_colour}; 2418 draw_text(text, text_position, &text_spec); 2419 DrawRectanglePro(cursor.rl, (Vector2){0}, 0, colour_from_normalized(cursor_colour)); 2420 } else { 2421 draw_ui_view(ui, var, window_rect, mouse, text_spec); 2422 } 2423 } 2424 } 2425 2426 function void 2427 scroll_interaction(Variable *var, f32 delta) 2428 { 2429 switch (var->type) { 2430 case VT_B32:{ var->bool32 = !var->bool32; }break; 2431 case VT_F32:{ var->real32 += delta; }break; 2432 case VT_I32:{ var->signed32 += delta; }break; 2433 case VT_U32:{ var->unsigned32 += delta; }break; 2434 case VT_SCALED_F32:{ var->scaled_real32.val += delta * var->scaled_real32.scale; }break; 2435 case VT_BEAMFORMER_FRAME_VIEW:{ 2436 BeamformerFrameView *bv = var->generic; 2437 bv->needs_update = 1; 2438 bv->threshold.real32 += delta; 2439 } break; 2440 case VT_BEAMFORMER_VARIABLE:{ 2441 BeamformerVariable *bv = &var->beamformer_variable; 2442 switch (bv->store_type) { 2443 case VT_F32:{ 2444 f32 val = *(f32 *)bv->store + delta * bv->scroll_scale; 2445 *(f32 *)bv->store = CLAMP(val, bv->limits.x, bv->limits.y); 2446 }break; 2447 InvalidDefaultCase; 2448 } 2449 }break; 2450 case VT_CYCLER:{ 2451 *var->cycler.state += delta > 0? 1 : -1; 2452 *var->cycler.state %= var->cycler.cycle_length; 2453 }break; 2454 case VT_UI_VIEW:{ 2455 var->view.rect.pos.h += UI_SCROLL_SPEED * delta; 2456 var->view.rect.pos.h = MAX(0, var->view.rect.pos.h); 2457 }break; 2458 InvalidDefaultCase; 2459 } 2460 } 2461 2462 function void 2463 begin_text_input(InputState *is, Rect r, Variable *container, v2 mouse) 2464 { 2465 assert(container->type == VT_UI_TEXT_BOX); 2466 Font *font = is->font = is->hot_font; 2467 Stream s = {.cap = countof(is->buf), .data = is->buf}; 2468 stream_append_variable(&s, container->view.child); 2469 is->count = s.widx; 2470 is->container = container; 2471 2472 /* NOTE: extra offset to help with putting a cursor at idx 0 */ 2473 #define TEXT_HALF_CHAR_WIDTH 10 2474 f32 hover_p = CLAMP01((mouse.x - r.pos.x) / r.size.w); 2475 f32 x_off = TEXT_HALF_CHAR_WIDTH, x_bounds = r.size.w * hover_p; 2476 i32 i; 2477 for (i = 0; i < is->count && x_off < x_bounds; i++) { 2478 /* NOTE: assumes font glyphs are ordered ASCII */ 2479 i32 idx = is->buf[i] - 0x20; 2480 x_off += font->glyphs[idx].advanceX; 2481 if (font->glyphs[idx].advanceX == 0) 2482 x_off += font->recs[idx].width; 2483 } 2484 is->cursor = i; 2485 } 2486 2487 function void 2488 end_text_input(InputState *is, Variable *var) 2489 { 2490 f64 value = parse_f64((s8){.len = is->count, .data = is->buf}); 2491 2492 switch (var->type) { 2493 case VT_SCALED_F32:{ var->scaled_real32.val = value; }break; 2494 case VT_F32:{ var->real32 = value; }break; 2495 case VT_BEAMFORMER_VARIABLE:{ 2496 BeamformerVariable *bv = &var->beamformer_variable; 2497 switch (bv->store_type) { 2498 case VT_F32:{ 2499 value = CLAMP(value / bv->display_scale, bv->limits.x, bv->limits.y); 2500 *(f32 *)bv->store = value; 2501 }break; 2502 InvalidDefaultCase; 2503 } 2504 var->hover_t = 0; 2505 }break; 2506 InvalidDefaultCase; 2507 } 2508 } 2509 2510 function b32 2511 update_text_input(InputState *is, Variable *var) 2512 { 2513 assert(is->cursor != -1); 2514 2515 is->cursor_blink_t += is->cursor_blink_scale * dt_for_frame; 2516 if (is->cursor_blink_t >= 1) is->cursor_blink_scale = -1.5f; 2517 if (is->cursor_blink_t <= 0) is->cursor_blink_scale = 1.5f; 2518 2519 var->hover_t -= 2 * HOVER_SPEED * dt_for_frame; 2520 var->hover_t = CLAMP01(var->hover_t); 2521 2522 /* NOTE: handle multiple input keys on a single frame */ 2523 for (i32 key = GetCharPressed(); 2524 is->count < countof(is->buf) && key > 0; 2525 key = GetCharPressed()) 2526 { 2527 b32 allow_key = (BETWEEN(key, '0', '9') || (key == '.') || 2528 (key == '-' && is->cursor == 0)); 2529 if (allow_key) { 2530 mem_move(is->buf + is->cursor + 1, 2531 is->buf + is->cursor, 2532 is->count - is->cursor); 2533 is->buf[is->cursor++] = key; 2534 is->count++; 2535 } 2536 } 2537 2538 is->cursor -= (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) && is->cursor > 0; 2539 is->cursor += (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) && is->cursor < is->count; 2540 2541 if ((IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) && is->cursor > 0) { 2542 is->cursor--; 2543 if (is->cursor < countof(is->buf) - 1) { 2544 mem_move(is->buf + is->cursor, 2545 is->buf + is->cursor + 1, 2546 is->count - is->cursor - 1); 2547 } 2548 is->count--; 2549 } 2550 2551 if ((IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) && is->cursor < is->count) { 2552 mem_move(is->buf + is->cursor, 2553 is->buf + is->cursor + 1, 2554 is->count - is->cursor - 1); 2555 is->count--; 2556 } 2557 2558 b32 result = IsKeyPressed(KEY_ENTER); 2559 return result; 2560 } 2561 2562 function void 2563 scale_bar_interaction(BeamformerUI *ui, ScaleBar *sb, v2 mouse) 2564 { 2565 Interaction *it = &ui->interaction; 2566 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 2567 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 2568 f32 mouse_wheel = GetMouseWheelMoveV().y; 2569 2570 if (mouse_left_pressed) { 2571 v2 world_mouse = screen_point_to_world_2d(mouse, it->rect.pos, 2572 add_v2(it->rect.pos, it->rect.size), 2573 (v2){{*sb->min_value, *sb->min_value}}, 2574 (v2){{*sb->max_value, *sb->max_value}}); 2575 f32 new_coord = F32_INFINITY; 2576 switch (sb->direction) { 2577 case SB_LATERAL: new_coord = world_mouse.x; break; 2578 case SB_AXIAL: new_coord = world_mouse.y; break; 2579 } 2580 if (sb->zoom_starting_coord == F32_INFINITY) { 2581 sb->zoom_starting_coord = new_coord; 2582 } else { 2583 f32 min = sb->zoom_starting_coord; 2584 f32 max = new_coord; 2585 if (min > max) swap(min, max); 2586 2587 v2_sll *savepoint = SLLPop(ui->scale_bar_savepoint_freelist); 2588 if (!savepoint) savepoint = push_struct(&ui->arena, v2_sll); 2589 2590 savepoint->v.x = *sb->min_value; 2591 savepoint->v.y = *sb->max_value; 2592 SLLPush(savepoint, sb->savepoint_stack); 2593 2594 *sb->min_value = min; 2595 *sb->max_value = max; 2596 2597 sb->zoom_starting_coord = F32_INFINITY; 2598 } 2599 } 2600 2601 if (mouse_right_pressed) { 2602 v2_sll *savepoint = sb->savepoint_stack; 2603 if (savepoint) { 2604 *sb->min_value = savepoint->v.x; 2605 *sb->max_value = savepoint->v.y; 2606 sb->savepoint_stack = savepoint->next; 2607 SLLPush(savepoint, ui->scale_bar_savepoint_freelist); 2608 } 2609 sb->zoom_starting_coord = F32_INFINITY; 2610 } 2611 2612 if (mouse_wheel) { 2613 *sb->min_value += mouse_wheel * sb->scroll_scale.x; 2614 *sb->max_value += mouse_wheel * sb->scroll_scale.y; 2615 } 2616 } 2617 2618 function void 2619 ui_widget_bring_to_front(Variable *sentinal, Variable *widget) 2620 { 2621 /* TODO(rnp): clean up the linkage so this can be a macro */ 2622 widget->parent->next = widget->next; 2623 widget->next->parent = widget->parent; 2624 2625 widget->parent = sentinal; 2626 widget->next = sentinal->next; 2627 widget->next->parent = widget; 2628 sentinal->next = widget; 2629 } 2630 2631 function void 2632 ui_view_close(BeamformerUI *ui, Variable *view) 2633 { 2634 switch (view->type) { 2635 case VT_UI_MENU: 2636 case VT_UI_TEXT_BOX: 2637 { 2638 UIView *fw = &view->view; 2639 if (view->type == VT_UI_MENU) { 2640 assert(fw->child->type == VT_GROUP); 2641 fw->child->group.expanded = 0; 2642 fw->child->group.container = 0; 2643 } else { 2644 end_text_input(&ui->text_input_state, fw->child); 2645 } 2646 view->parent->next = view->next; 2647 view->next->parent = view->parent; 2648 if (fw->close) SLLPush(fw->close, ui->variable_freelist); 2649 SLLPush(view, ui->variable_freelist); 2650 }break; 2651 case VT_UI_VIEW:{ 2652 assert(view->parent->type == VT_UI_REGION_SPLIT); 2653 Variable *region = view->parent; 2654 2655 Variable *parent = region->parent; 2656 Variable *remaining = region->region_split.left; 2657 if (remaining == view) remaining = region->region_split.right; 2658 2659 ui_view_free(ui, view); 2660 2661 assert(parent->type == VT_UI_REGION_SPLIT); 2662 if (parent->region_split.left == region) { 2663 parent->region_split.left = remaining; 2664 } else { 2665 parent->region_split.right = remaining; 2666 } 2667 remaining->parent = parent; 2668 2669 SLLPush(region, ui->variable_freelist); 2670 }break; 2671 InvalidDefaultCase; 2672 } 2673 } 2674 2675 function void 2676 ui_button_interaction(BeamformerUI *ui, Variable *button) 2677 { 2678 assert(button->type == VT_UI_BUTTON); 2679 switch (button->button) { 2680 case UI_BID_VIEW_CLOSE:{ ui_view_close(ui, button->parent); }break; 2681 case UI_BID_FV_COPY_HORIZONTAL:{ 2682 ui_copy_frame(ui, button->parent->parent, RSD_HORIZONTAL); 2683 }break; 2684 case UI_BID_FV_COPY_VERTICAL:{ 2685 ui_copy_frame(ui, button->parent->parent, RSD_VERTICAL); 2686 }break; 2687 case UI_BID_GM_OPEN_LIVE_VIEW_RIGHT:{ 2688 ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL); 2689 }break; 2690 case UI_BID_GM_OPEN_LIVE_VIEW_BELOW:{ 2691 ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL); 2692 }break; 2693 } 2694 } 2695 2696 function void 2697 ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) 2698 { 2699 Interaction *hot = &ui->hot_interaction; 2700 if (hot->kind != InteractionKind_None) { 2701 if (hot->kind == InteractionKind_Auto) { 2702 switch (hot->var->type) { 2703 case VT_NULL:{ hot->kind = InteractionKind_Nop; }break; 2704 case VT_B32:{ hot->kind = InteractionKind_Set; }break; 2705 case VT_SCALE_BAR:{ hot->kind = InteractionKind_Set; }break; 2706 case VT_UI_BUTTON:{ hot->kind = InteractionKind_Button; }break; 2707 case VT_GROUP:{ hot->kind = InteractionKind_Set; }break; 2708 case VT_UI_TEXT_BOX: 2709 case VT_UI_MENU: 2710 { 2711 if (hot->var->type == VT_UI_MENU) { 2712 hot->kind = InteractionKind_Drag; 2713 } else { 2714 hot->kind = InteractionKind_Text; 2715 begin_text_input(&ui->text_input_state, hot->rect, hot->var, input->mouse); 2716 } 2717 ui_widget_bring_to_front(&ui->floating_widget_sentinal, hot->var); 2718 }break; 2719 case VT_UI_VIEW:{ 2720 if (scroll) hot->kind = InteractionKind_Scroll; 2721 else hot->kind = InteractionKind_Nop; 2722 }break; 2723 case VT_BEAMFORMER_FRAME_VIEW:{ 2724 if (scroll) { 2725 hot->kind = InteractionKind_Scroll; 2726 } else { 2727 hot->kind = InteractionKind_Nop; 2728 BeamformerFrameView *bv = hot->var->generic; 2729 switch (++bv->ruler.state) { 2730 case RulerState_Start:{ 2731 hot->kind = InteractionKind_Ruler; 2732 v2 r_max = add_v2(hot->rect.pos, hot->rect.size); 2733 v2 p = screen_point_to_world_2d(input->mouse, hot->rect.pos, r_max, 2734 XZ(bv->min_coordinate), 2735 XZ(bv->max_coordinate)); 2736 bv->ruler.start = p; 2737 }break; 2738 case RulerState_Hold:{}break; 2739 default:{ bv->ruler.state = RulerState_None; }break; 2740 } 2741 } 2742 }break; 2743 case VT_CYCLER:{ 2744 if (scroll) hot->kind = InteractionKind_Scroll; 2745 else hot->kind = InteractionKind_Set; 2746 }break; 2747 case VT_BEAMFORMER_VARIABLE:{ 2748 if (hot->var->beamformer_variable.store_type == VT_B32) { 2749 hot->kind = InteractionKind_Set; 2750 break; 2751 } 2752 } /* FALLTHROUGH */ 2753 case VT_F32: 2754 case VT_SCALED_F32: 2755 { 2756 if (scroll) { 2757 hot->kind = InteractionKind_Scroll; 2758 } else if (hot->var->flags & V_TEXT) { 2759 hot->kind = InteractionKind_Text; 2760 Variable *w = add_floating_view(ui, &ui->arena, VT_UI_TEXT_BOX, 2761 hot->rect.pos, hot->var, 0); 2762 w->view.rect = hot->rect; 2763 begin_text_input(&ui->text_input_state, hot->rect, w, input->mouse); 2764 } 2765 }break; 2766 InvalidDefaultCase; 2767 } 2768 } 2769 2770 ui->interaction = ui->hot_interaction; 2771 } else { 2772 ui->interaction.kind = InteractionKind_Nop; 2773 } 2774 } 2775 2776 function void 2777 ui_end_interact(BeamformerUI *ui, v2 mouse) 2778 { 2779 Interaction *it = &ui->interaction; 2780 b32 start_compute = (it->var->flags & V_CAUSES_COMPUTE) != 0; 2781 switch (it->kind) { 2782 case InteractionKind_Nop:{}break; 2783 case InteractionKind_Drag:{}break; 2784 case InteractionKind_Set:{ 2785 switch (it->var->type) { 2786 case VT_B32:{ it->var->bool32 = !it->var->bool32; }break; 2787 case VT_GROUP:{ it->var->group.expanded = !it->var->group.expanded; }break; 2788 case VT_SCALE_BAR:{ scale_bar_interaction(ui, &it->var->scale_bar, mouse); }break; 2789 case VT_CYCLER:{ 2790 *it->var->cycler.state += 1; 2791 *it->var->cycler.state %= it->var->cycler.cycle_length; 2792 }break; 2793 InvalidDefaultCase; 2794 } 2795 }break; 2796 case InteractionKind_Menu:{ 2797 assert(it->var->type == VT_GROUP); 2798 VariableGroup *g = &it->var->group; 2799 if (g->container) { 2800 ui_widget_bring_to_front(&ui->floating_widget_sentinal, g->container); 2801 } else { 2802 g->container = add_floating_view(ui, &ui->arena, VT_UI_MENU, mouse, it->var, 1); 2803 } 2804 }break; 2805 case InteractionKind_Ruler:{ 2806 assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW); 2807 ((BeamformerFrameView *)it->var->generic)->ruler.state = RulerState_None; 2808 }break; 2809 case InteractionKind_Button:{ ui_button_interaction(ui, it->var); }break; 2810 case InteractionKind_Scroll:{ scroll_interaction(it->var, GetMouseWheelMoveV().y); }break; 2811 case InteractionKind_Text:{ ui_view_close(ui, ui->text_input_state.container); }break; 2812 InvalidDefaultCase; 2813 } 2814 2815 if (start_compute) ui->flush_params = 1; 2816 if (it->var->flags & V_UPDATE_VIEW) { 2817 Variable *parent = it->var->parent; 2818 BeamformerFrameView *frame; 2819 /* TODO(rnp): more straight forward way of achieving this */ 2820 if (parent->type == VT_BEAMFORMER_FRAME_VIEW) { 2821 frame = parent->generic; 2822 } else { 2823 assert(parent->parent->group.first->type == VT_BEAMFORMER_FRAME_VIEW); 2824 frame = parent->parent->group.first->generic; 2825 } 2826 frame->needs_update = 1; 2827 } 2828 2829 ui->interaction = (Interaction){.kind = InteractionKind_None}; 2830 } 2831 2832 function void 2833 ui_sticky_interaction_check_end(BeamformerUI *ui, v2 mouse) 2834 { 2835 Interaction *it = &ui->interaction; 2836 switch (it->kind) { 2837 case InteractionKind_Ruler:{ 2838 if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) || !point_in_rect(mouse, it->rect)) 2839 ui_end_interact(ui, mouse); 2840 }break; 2841 case InteractionKind_Text:{ 2842 Interaction text_box = auto_interaction({{0}}, ui->text_input_state.container); 2843 if (!interactions_equal(text_box, ui->hot_interaction)) 2844 ui_end_interact(ui, mouse); 2845 }break; 2846 InvalidDefaultCase; 2847 } 2848 } 2849 2850 function void 2851 ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect) 2852 { 2853 Interaction *it = &ui->interaction; 2854 if (it->kind == InteractionKind_None || interaction_is_sticky(*it)) { 2855 ui->hot_interaction = ui->next_interaction; 2856 2857 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 2858 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 2859 b32 wheel_moved = GetMouseWheelMoveV().y != 0; 2860 if (mouse_right_pressed || mouse_left_pressed || wheel_moved) { 2861 if (it->kind != InteractionKind_None) 2862 ui_sticky_interaction_check_end(ui, input->mouse); 2863 ui_begin_interact(ui, input, wheel_moved); 2864 } 2865 } 2866 2867 switch (it->kind) { 2868 case InteractionKind_Nop:{ it->kind = InteractionKind_None; }break; 2869 case InteractionKind_None:{}break; 2870 case InteractionKind_Text:{ 2871 if (update_text_input(&ui->text_input_state, it->var)) 2872 ui_end_interact(ui, input->mouse); 2873 }break; 2874 case InteractionKind_Ruler:{ 2875 assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW); 2876 BeamformerFrameView *bv = it->var->generic; 2877 v2 r_max = add_v2(it->rect.pos, it->rect.size); 2878 v2 mouse = clamp_v2_rect(input->mouse, it->rect); 2879 bv->ruler.end = screen_point_to_world_2d(mouse, it->rect.pos, r_max, 2880 XZ(bv->min_coordinate), 2881 XZ(bv->max_coordinate)); 2882 }break; 2883 case InteractionKind_Drag:{ 2884 if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { 2885 ui_end_interact(ui, input->mouse); 2886 } else { 2887 v2 ws = window_rect.size; 2888 v2 dMouse = sub_v2(input->mouse, input->last_mouse); 2889 2890 switch (ui->interaction.var->type) { 2891 case VT_UI_MENU:{ 2892 v2 *pos = &ui->interaction.var->view.rect.pos; 2893 *pos = clamp_v2_rect(add_v2(*pos, dMouse), window_rect); 2894 }break; 2895 case VT_UI_REGION_SPLIT:{ 2896 f32 min_fraction = 0; 2897 dMouse = mul_v2(dMouse, (v2){.x = 1.0f / ws.w, .y = 1.0f / ws.h}); 2898 RegionSplit *rs = &ui->interaction.var->region_split; 2899 switch (rs->direction) { 2900 case RSD_VERTICAL: { 2901 min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5 * UI_REGION_PAD) / ws.h; 2902 rs->fraction += dMouse.y; 2903 } break; 2904 case RSD_HORIZONTAL: { 2905 min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5 * UI_REGION_PAD) / ws.w; 2906 rs->fraction += dMouse.x; 2907 } break; 2908 } 2909 rs->fraction = CLAMP(rs->fraction, min_fraction, 1 - min_fraction); 2910 }break; 2911 default:{}break; 2912 } 2913 } 2914 } break; 2915 default:{ ui_end_interact(ui, input->mouse); }break; 2916 } 2917 2918 ui->next_interaction = (Interaction){.kind = InteractionKind_None}; 2919 } 2920 2921 function void 2922 ui_init(BeamformerCtx *ctx, Arena store) 2923 { 2924 /* NOTE(rnp): store the ui at the base of the passed in arena and use the rest for 2925 * temporary allocations within the ui. If needed we can recall this function to 2926 * completely clear the ui state. The is that if we store pointers to static data 2927 * such as embedded font data we will need to reset them when the executable reloads. 2928 * We could also build some sort of ui structure here and store it then iterate over 2929 * it to actually draw the ui. If we reload we may have changed it so we should 2930 * rebuild it */ 2931 2932 BeamformerUI *ui = ctx->ui; 2933 2934 /* NOTE(rnp): unload old data from GPU */ 2935 if (ui) { 2936 UnloadFont(ui->font); 2937 UnloadFont(ui->small_font); 2938 2939 for (BeamformerFrameView *view = ui->views; view; view = view->next) 2940 if (view->texture) 2941 glDeleteTextures(1, &view->texture); 2942 } 2943 2944 ui = ctx->ui = push_struct(&store, typeof(*ui)); 2945 ui->os = &ctx->os; 2946 ui->arena = store; 2947 ui->frame_view_render_context = &ctx->frame_view_render_context; 2948 2949 /* TODO: build these into the binary */ 2950 /* TODO(rnp): better font, this one is jank at small sizes */ 2951 ui->font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 28, 0, 0); 2952 ui->small_font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 20, 0, 0); 2953 2954 ui->floating_widget_sentinal.parent = &ui->floating_widget_sentinal; 2955 ui->floating_widget_sentinal.next = &ui->floating_widget_sentinal; 2956 2957 Variable *split = ui->regions = add_ui_split(ui, 0, &ui->arena, s8("UI Root"), 0.4, 2958 RSD_HORIZONTAL, ui->font); 2959 split->region_split.left = add_ui_split(ui, split, &ui->arena, s8(""), 0.475, 2960 RSD_VERTICAL, ui->font); 2961 split->region_split.right = add_beamformer_frame_view(ui, split, &ui->arena, FVT_LATEST, 0); 2962 2963 ui_fill_live_frame_view(ui, split->region_split.right->view.child->generic); 2964 2965 split = split->region_split.left; 2966 split->region_split.left = add_beamformer_parameters_view(split, ctx); 2967 split->region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.22, 2968 RSD_VERTICAL, ui->font); 2969 split = split->region_split.right; 2970 2971 split->region_split.left = add_compute_progress_bar(split, ctx); 2972 split->region_split.right = add_compute_stats_view(ui, split, &ui->arena, VT_COMPUTE_STATS_VIEW); 2973 /* TODO(rnp): refactor to not need the beamformer ctx */ 2974 split->region_split.right->group.first->generic = ctx; 2975 2976 ctx->ui_read_params = 1; 2977 2978 /* NOTE(rnp): shrink variable size once this fires */ 2979 assert(ui->arena.beg - (u8 *)ui < KB(64)); 2980 } 2981 2982 function void 2983 validate_ui_parameters(BeamformerUIParameters *p) 2984 { 2985 if (p->output_min_coordinate[0] > p->output_max_coordinate[0]) 2986 swap(p->output_min_coordinate[0], p->output_max_coordinate[0]); 2987 if (p->output_min_coordinate[2] > p->output_max_coordinate[2]) 2988 swap(p->output_min_coordinate[2], p->output_max_coordinate[2]); 2989 } 2990 2991 function void 2992 draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw, ImagePlaneTag frame_plane) 2993 { 2994 BeamformerUI *ui = ctx->ui; 2995 BeamformerSharedMemory *sm = ctx->shared_memory.region; 2996 2997 ui->latest_plane[IPT_LAST] = frame_to_draw; 2998 ui->latest_plane[frame_plane] = frame_to_draw; 2999 3000 /* TODO(rnp): there should be a better way of detecting this */ 3001 if (ctx->ui_read_params) { 3002 mem_copy(&ui->params, &sm->parameters.output_min_coordinate, sizeof(ui->params)); 3003 ui->flush_params = 0; 3004 ctx->ui_read_params = 0; 3005 } 3006 3007 /* NOTE: process interactions first because the user interacted with 3008 * the ui that was presented last frame */ 3009 Rect window_rect = {.size = {.w = ctx->window_size.w, .h = ctx->window_size.h}}; 3010 ui_interact(ui, input, window_rect); 3011 3012 if (ui->flush_params) { 3013 i32 lock = BeamformerSharedMemoryLockKind_Parameters; 3014 validate_ui_parameters(&ui->params); 3015 if (ctx->os.shared_memory_region_lock(&ctx->shared_memory, sm->locks, lock, 0)) { 3016 mem_copy(&sm->parameters_ui, &ui->params, sizeof(ui->params)); 3017 ui->flush_params = 0; 3018 atomic_or_u32(&sm->dirty_regions, (1 << (lock - 1))); 3019 b32 dispatch = ctx->os.shared_memory_region_lock(&ctx->shared_memory, sm->locks, 3020 BeamformerSharedMemoryLockKind_DispatchCompute, 3021 0); 3022 sm->start_compute_from_main |= dispatch & ctx->latest_frame->ready_to_present; 3023 ctx->os.shared_memory_region_unlock(&ctx->shared_memory, sm->locks, lock); 3024 } 3025 } 3026 3027 /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */ 3028 update_frame_views(ui, window_rect); 3029 3030 BeginDrawing(); 3031 f32 one = 1; 3032 glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_COLOUR.E); 3033 glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one); 3034 3035 draw_ui_regions(ui, window_rect, input->mouse); 3036 draw_floating_widgets(ui, window_rect, input->mouse); 3037 EndDrawing(); 3038 }