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