ui.c (93160B)
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 static 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 static 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 static 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 static 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 static 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 static 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 interpolate_labels[] = {s8("False"), s8("True")}; 959 add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Interpolate:"), 960 &bp->interpolate, interpolate_labels, countof(interpolate_labels)); 961 962 return result; 963 } 964 965 function Variable * 966 add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, 967 BeamformerFrameViewType type, b32 closable) 968 { 969 /* TODO(rnp): this can be always closable once we have a way of opening new views */ 970 Variable *result = add_ui_view(ui, parent, arena, s8(""), UI_VIEW_CUSTOM_TEXT, closable); 971 Variable *var = result->u.view.child = add_variable(ui, result, arena, s8(""), 0, 972 VT_BEAMFORMER_FRAME_VIEW, ui->small_font); 973 974 BeamformerFrameView *bv = SLLPop(ui->view_freelist); 975 if (bv) zero_struct(bv); 976 else bv = push_struct(arena, typeof(*bv)); 977 DLLPushDown(bv, ui->views); 978 979 var->u.generic = bv; 980 bv->type = type; 981 982 fill_variable(&bv->dynamic_range, var, s8("Dynamic Range:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 983 VT_F32, ui->small_font); 984 fill_variable(&bv->threshold, var, s8("Threshold:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 985 VT_F32, ui->small_font); 986 fill_variable(&bv->gamma, var, s8("Gamma:"), V_INPUT|V_TEXT|V_UPDATE_VIEW, 987 VT_SCALED_F32, ui->small_font); 988 989 bv->dynamic_range.u.f32 = 50.0f; 990 bv->threshold.u.f32 = 55.0f; 991 bv->gamma.u.scaled_f32.val = 1.0f; 992 bv->gamma.u.scaled_f32.scale = 0.05f; 993 994 fill_variable(&bv->lateral_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); 995 fill_variable(&bv->axial_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); 996 ScaleBar *lateral = &bv->lateral_scale_bar.u.scale_bar; 997 ScaleBar *axial = &bv->axial_scale_bar.u.scale_bar; 998 lateral->direction = SB_LATERAL; 999 axial->direction = SB_AXIAL; 1000 lateral->scroll_scale = (v2){.x = -0.5e-3, .y = 0.5e-3}; 1001 axial->scroll_scale = (v2){.x = 0, .y = 1e-3}; 1002 lateral->zoom_starting_coord = F32_INFINITY; 1003 axial->zoom_starting_coord = F32_INFINITY; 1004 1005 Variable *menu = result->u.view.menu; 1006 /* TODO(rnp): push to head of list? */ 1007 Variable *old_menu_first = menu->u.group.first; 1008 Variable *old_menu_last = menu->u.group.last; 1009 menu->u.group.first = menu->u.group.last = 0; 1010 1011 #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, V_CLOSES_MENU, ui->small_font); 1012 FRAME_VIEW_BUTTONS 1013 #undef X 1014 1015 switch (type) { 1016 case FVT_LATEST: { 1017 #define X(_type, _id, pretty) s8(pretty), 1018 local_persist s8 labels[] = { IMAGE_PLANE_TAGS s8("Any") }; 1019 #undef X 1020 bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Live: "), 1021 &bv->cycler_state, labels, countof(labels)); 1022 bv->cycler_state = IPT_LAST; 1023 } break; 1024 case FVT_INDEXED: { 1025 bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Index: "), 1026 &bv->cycler_state, 0, MAX_BEAMFORMED_SAVED_FRAMES); 1027 } break; 1028 default: break; 1029 } 1030 1031 bv->log_scale = add_variable(ui, menu, arena, s8("Log Scale"), 1032 V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON, VT_B32, 1033 ui->small_font); 1034 bv->axial_scale_bar_active = add_variable(ui, menu, arena, s8("Axial Scale Bar"), 1035 V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); 1036 bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"), 1037 V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); 1038 1039 menu->u.group.last->next = old_menu_first; 1040 menu->u.group.last = old_menu_last; 1041 1042 return result; 1043 } 1044 1045 function Variable * 1046 add_compute_progress_bar(Variable *parent, BeamformerCtx *ctx) 1047 { 1048 BeamformerUI *ui = ctx->ui; 1049 /* TODO(rnp): this can be closable once we have a way of opening new views */ 1050 Variable *result = add_ui_view(ui, parent, &ui->arena, s8(""), UI_VIEW_CUSTOM_TEXT, 0); 1051 result->u.view.child = add_variable(ui, result, &ui->arena, s8(""), 0, 1052 VT_COMPUTE_PROGRESS_BAR, ui->small_font); 1053 ComputeProgressBar *bar = &result->u.view.child->u.compute_progress_bar; 1054 bar->progress = &ctx->csctx.processing_progress; 1055 bar->processing = &ctx->csctx.processing_compute; 1056 1057 return result; 1058 } 1059 1060 function Variable * 1061 add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, VariableType type) 1062 { 1063 /* TODO(rnp): this can be closable once we have a way of opening new views */ 1064 Variable *result = add_ui_view(ui, parent, arena, s8(""), UI_VIEW_CUSTOM_TEXT, 0); 1065 result->u.view.child = add_variable(ui, result, &ui->arena, s8(""), 0, type, ui->small_font); 1066 return result; 1067 } 1068 1069 function Variable * 1070 ui_split_region(BeamformerUI *ui, Variable *region, Variable *split_side, RegionSplitDirection direction) 1071 { 1072 Variable *result = add_ui_split(ui, region, &ui->arena, s8(""), 0.5, direction, ui->small_font); 1073 if (split_side == region->u.region_split.left) { 1074 region->u.region_split.left = result; 1075 } else { 1076 region->u.region_split.right = result; 1077 } 1078 split_side->parent = result; 1079 result->u.region_split.left = split_side; 1080 return result; 1081 } 1082 1083 function void 1084 ui_fill_live_frame_view(BeamformerUI *ui, BeamformerFrameView *bv) 1085 { 1086 ScaleBar *lateral = &bv->lateral_scale_bar.u.scale_bar; 1087 ScaleBar *axial = &bv->axial_scale_bar.u.scale_bar; 1088 lateral->min_value = ui->params.output_min_coordinate + 0; 1089 lateral->max_value = ui->params.output_max_coordinate + 0; 1090 axial->min_value = ui->params.output_min_coordinate + 2; 1091 axial->max_value = ui->params.output_max_coordinate + 2; 1092 bv->axial_scale_bar_active->u.b32 = 1; 1093 bv->lateral_scale_bar_active->u.b32 = 1; 1094 bv->ctx = ui->frame_view_render_context; 1095 bv->axial_scale_bar.flags |= V_CAUSES_COMPUTE; 1096 bv->lateral_scale_bar.flags |= V_CAUSES_COMPUTE; 1097 } 1098 1099 function void 1100 ui_add_live_frame_view(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) 1101 { 1102 Variable *region = view->parent; 1103 ASSERT(region->type == VT_UI_REGION_SPLIT); 1104 ASSERT(view->type == VT_UI_VIEW); 1105 1106 Variable *new_region = ui_split_region(ui, region, view, direction); 1107 new_region->u.region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_LATEST, 1); 1108 1109 ui_fill_live_frame_view(ui, new_region->u.region_split.right->u.group.first->u.generic); 1110 } 1111 1112 function void 1113 ui_copy_frame(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) 1114 { 1115 Variable *region = view->parent; 1116 ASSERT(region->type == VT_UI_REGION_SPLIT); 1117 ASSERT(view->type == VT_UI_VIEW); 1118 1119 BeamformerFrameView *old = view->u.group.first->u.generic; 1120 /* TODO(rnp): hack; it would be better if this was unreachable with a 0 old->frame */ 1121 if (!old->frame) 1122 return; 1123 1124 Variable *new_region = ui_split_region(ui, region, view, direction); 1125 new_region->u.region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_COPY, 1); 1126 1127 BeamformerFrameView *bv = new_region->u.region_split.right->u.group.first->u.generic; 1128 ScaleBar *lateral = &bv->lateral_scale_bar.u.scale_bar; 1129 ScaleBar *axial = &bv->axial_scale_bar.u.scale_bar; 1130 lateral->min_value = &bv->min_coordinate.x; 1131 lateral->max_value = &bv->max_coordinate.x; 1132 axial->min_value = &bv->min_coordinate.z; 1133 axial->max_value = &bv->max_coordinate.z; 1134 1135 bv->ctx = old->ctx; 1136 bv->needs_update = 1; 1137 bv->threshold.u.f32 = old->threshold.u.f32; 1138 bv->dynamic_range.u.f32 = old->dynamic_range.u.f32; 1139 bv->gamma.u.f32 = old->gamma.u.f32; 1140 bv->log_scale->u.b32 = old->log_scale->u.b32; 1141 bv->min_coordinate = old->frame->min_coordinate; 1142 bv->max_coordinate = old->frame->max_coordinate; 1143 1144 bv->frame = SLLPop(ui->frame_freelist); 1145 if (!bv->frame) bv->frame = push_struct(&ui->arena, typeof(*bv->frame)); 1146 1147 mem_copy(bv->frame, old->frame, sizeof(*bv->frame)); 1148 bv->frame->texture = 0; 1149 bv->frame->next = 0; 1150 alloc_beamform_frame(0, bv->frame, 0, old->frame->dim, s8("Frame Copy: "), ui->arena); 1151 1152 glCopyImageSubData(old->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0, 1153 bv->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0, 1154 bv->frame->dim.x, bv->frame->dim.y, bv->frame->dim.z); 1155 glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT); 1156 /* TODO(rnp): x vs y here */ 1157 resize_frame_view(bv, (uv2){.x = bv->frame->dim.x, .y = bv->frame->dim.z}); 1158 } 1159 1160 function b32 1161 view_update(BeamformerUI *ui, BeamformerFrameView *view) 1162 { 1163 if (view->type == FVT_LATEST) { 1164 u32 index = *view->cycler->u.cycler.state; 1165 view->needs_update |= view->frame != ui->latest_plane[index]; 1166 view->frame = ui->latest_plane[index]; 1167 if (view->needs_update) { 1168 view->min_coordinate = v4_from_f32_array(ui->params.output_min_coordinate); 1169 view->max_coordinate = v4_from_f32_array(ui->params.output_max_coordinate); 1170 } 1171 } 1172 1173 /* TODO(rnp): x-z or y-z */ 1174 /* TODO(rnp): add method of setting a target size in frame view */ 1175 uv2 current = view->texture_dim; 1176 uv2 target = {.w = ui->params.output_points[0], .h = ui->params.output_points[2]}; 1177 if (view->type != FVT_COPY && !uv2_equal(current, target) && !uv2_equal(target, (uv2){0})) { 1178 resize_frame_view(view, target); 1179 view->needs_update = 1; 1180 } 1181 1182 return (view->ctx->updated || view->needs_update) && view->frame; 1183 } 1184 1185 function void 1186 update_frame_views(BeamformerUI *ui, Rect window) 1187 { 1188 b32 fbo_bound = 0; 1189 for (BeamformerFrameView *view = ui->views; view; view = view->next) { 1190 if (view_update(ui, view)) { 1191 if (!fbo_bound) { 1192 fbo_bound = 1; 1193 glBindFramebuffer(GL_FRAMEBUFFER, view->ctx->framebuffer); 1194 glUseProgram(view->ctx->shader); 1195 glBindVertexArray(view->ctx->vao); 1196 glClearColor(0.79, 0.46, 0.77, 1); 1197 } 1198 glViewport(0, 0, view->texture_dim.w, view->texture_dim.h); 1199 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 1200 GL_TEXTURE_2D, view->texture, 0); 1201 glClear(GL_COLOR_BUFFER_BIT); 1202 glBindTextureUnit(0, view->frame->texture); 1203 glUniform1f(FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC, view->dynamic_range.u.f32); 1204 glUniform1f(FRAME_VIEW_RENDER_THRESHOLD_LOC, view->threshold.u.f32); 1205 glUniform1f(FRAME_VIEW_RENDER_GAMMA_LOC, view->gamma.u.scaled_f32.val); 1206 glUniform1ui(FRAME_VIEW_RENDER_LOG_SCALE_LOC, view->log_scale->u.b32); 1207 1208 glDrawArrays(GL_TRIANGLES, 0, 6); 1209 glGenerateTextureMipmap(view->texture); 1210 view->needs_update = 0; 1211 } 1212 } 1213 if (fbo_bound) { 1214 glBindFramebuffer(GL_FRAMEBUFFER, 0); 1215 glViewport(window.pos.x, window.pos.y, window.size.w, window.size.h); 1216 /* NOTE(rnp): I don't trust raylib to not mess with us */ 1217 glBindVertexArray(0); 1218 } 1219 } 1220 1221 function b32 1222 frame_view_ready_to_present(BeamformerFrameView *view) 1223 { 1224 return !uv2_equal((uv2){0}, view->texture_dim) && view->frame; 1225 } 1226 1227 static Color 1228 colour_from_normalized(v4 rgba) 1229 { 1230 return (Color){.r = rgba.r * 255.0f, .g = rgba.g * 255.0f, 1231 .b = rgba.b * 255.0f, .a = rgba.a * 255.0f}; 1232 } 1233 1234 static Color 1235 fade(Color a, f32 visibility) 1236 { 1237 a.a = (u8)((f32)a.a * visibility); 1238 return a; 1239 } 1240 1241 static v4 1242 lerp_v4(v4 a, v4 b, f32 t) 1243 { 1244 return (v4){ 1245 .x = a.x + t * (b.x - a.x), 1246 .y = a.y + t * (b.y - a.y), 1247 .z = a.z + t * (b.z - a.z), 1248 .w = a.w + t * (b.w - a.w), 1249 }; 1250 } 1251 1252 static s8 1253 push_das_shader_id(Stream *s, DASShaderID shader, u32 transmit_count) 1254 { 1255 #define X(type, id, pretty, fixed_tx) s8(pretty), 1256 static s8 pretty_names[] = { DAS_TYPES }; 1257 #undef X 1258 #define X(type, id, pretty, fixed_tx) fixed_tx, 1259 static u8 fixed_transmits[] = { DAS_TYPES }; 1260 #undef X 1261 1262 if ((u32)shader < (u32)DAS_LAST) { 1263 stream_append_s8(s, pretty_names[shader]); 1264 if (!fixed_transmits[shader]) { 1265 stream_append_byte(s, '-'); 1266 stream_append_u64(s, transmit_count); 1267 } 1268 } 1269 1270 return stream_to_s8(s); 1271 } 1272 1273 static s8 1274 push_custom_view_title(Stream *s, Variable *var) 1275 { 1276 switch (var->type) { 1277 case VT_COMPUTE_STATS_VIEW: 1278 case VT_COMPUTE_LATEST_STATS_VIEW: { 1279 stream_append_s8(s, s8("Compute Stats")); 1280 if (var->type == VT_COMPUTE_LATEST_STATS_VIEW) 1281 stream_append_s8(s, s8(": Live")); 1282 } break; 1283 case VT_COMPUTE_PROGRESS_BAR: { 1284 stream_append_s8(s, s8("Compute Progress: ")); 1285 stream_append_f64(s, 100 * *var->u.compute_progress_bar.progress, 100); 1286 stream_append_byte(s, '%'); 1287 } break; 1288 case VT_BEAMFORMER_FRAME_VIEW: { 1289 BeamformerFrameView *bv = var->u.generic; 1290 stream_append_s8(s, s8("Frame View")); 1291 switch (bv->type) { 1292 case FVT_COPY: stream_append_s8(s, s8(": Copy [")); break; 1293 case FVT_LATEST: { 1294 #define X(plane, id, pretty) s8(": " pretty " ["), 1295 local_persist s8 labels[IPT_LAST + 1] = { IMAGE_PLANE_TAGS s8(": Live [") }; 1296 #undef X 1297 stream_append_s8(s, labels[*bv->cycler->u.cycler.state % (IPT_LAST + 1)]); 1298 } break; 1299 case FVT_INDEXED: { 1300 stream_append_s8(s, s8(": Index {")); 1301 stream_append_u64(s, *bv->cycler->u.cycler.state % MAX_BEAMFORMED_SAVED_FRAMES); 1302 stream_append_s8(s, s8("} [")); 1303 } break; 1304 } 1305 stream_append_hex_u64(s, bv->frame? bv->frame->id : 0); 1306 stream_append_byte(s, ']'); 1307 } break; 1308 default: INVALID_CODE_PATH; 1309 } 1310 return stream_to_s8(s); 1311 } 1312 1313 static v2 1314 draw_text_base(Font font, s8 text, v2 pos, Color colour) 1315 { 1316 v2 off = pos; 1317 for (iz i = 0; i < text.len; i++) { 1318 /* NOTE: assumes font glyphs are ordered ASCII */ 1319 i32 idx = text.data[i] - 0x20; 1320 Rectangle dst = { 1321 off.x + font.glyphs[idx].offsetX - font.glyphPadding, 1322 off.y + font.glyphs[idx].offsetY - font.glyphPadding, 1323 font.recs[idx].width + 2.0f * font.glyphPadding, 1324 font.recs[idx].height + 2.0f * font.glyphPadding 1325 }; 1326 Rectangle src = { 1327 font.recs[idx].x - font.glyphPadding, 1328 font.recs[idx].y - font.glyphPadding, 1329 font.recs[idx].width + 2.0f * font.glyphPadding, 1330 font.recs[idx].height + 2.0f * font.glyphPadding 1331 }; 1332 DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); 1333 1334 off.x += font.glyphs[idx].advanceX; 1335 if (font.glyphs[idx].advanceX == 0) 1336 off.x += font.recs[idx].width; 1337 } 1338 v2 result = {.x = off.x - pos.x, .y = font.baseSize}; 1339 return result; 1340 } 1341 1342 /* NOTE(rnp): expensive but of the available options in raylib this gives the best results */ 1343 static v2 1344 draw_outlined_text(s8 text, v2 pos, TextSpec *ts) 1345 { 1346 f32 ow = ts->outline_thick; 1347 Color outline = colour_from_normalized(ts->outline_colour); 1348 Color colour = colour_from_normalized(ts->colour); 1349 draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = ow, .y = ow}), outline); 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 1354 v2 result = draw_text_base(*ts->font, text, pos, colour); 1355 1356 return result; 1357 } 1358 1359 static v2 1360 draw_text(s8 text, v2 pos, TextSpec *ts) 1361 { 1362 if (ts->flags & TF_ROTATED) { 1363 rlPushMatrix(); 1364 rlTranslatef(pos.x, pos.y, 0); 1365 rlRotatef(ts->rotation, 0, 0, 1); 1366 pos = (v2){0}; 1367 } 1368 1369 v2 result = measure_text(*ts->font, text); 1370 /* TODO(rnp): the size of this should be stored for each font */ 1371 s8 ellipsis = s8("..."); 1372 b32 clamped = ts->flags & TF_LIMITED && result.w > ts->limits.size.w; 1373 if (clamped) { 1374 f32 ellipsis_width = measure_text(*ts->font, ellipsis).x; 1375 if (ellipsis_width < ts->limits.size.w) { 1376 text = clamp_text_to_width(*ts->font, text, ts->limits.size.w - ellipsis_width); 1377 } else { 1378 text.len = 0; 1379 ellipsis.len = 0; 1380 } 1381 } 1382 1383 Color colour = colour_from_normalized(ts->colour); 1384 if (ts->flags & TF_OUTLINED) result.x = draw_outlined_text(text, pos, ts).x; 1385 else result.x = draw_text_base(*ts->font, text, pos, colour).x; 1386 1387 if (clamped) { 1388 pos.x += result.x; 1389 if (ts->flags & TF_OUTLINED) result.x += draw_outlined_text(ellipsis, pos, ts).x; 1390 else result.x += draw_text_base(*ts->font, ellipsis, pos, 1391 colour).x; 1392 } 1393 1394 if (ts->flags & TF_ROTATED) rlPopMatrix(); 1395 1396 return result; 1397 } 1398 1399 static Rect 1400 extend_rect_centered(Rect r, v2 delta) 1401 { 1402 r.size.w += delta.x; 1403 r.size.h += delta.y; 1404 r.pos.x -= delta.x / 2; 1405 r.pos.y -= delta.y / 2; 1406 return r; 1407 } 1408 1409 static Rect 1410 shrink_rect_centered(Rect r, v2 delta) 1411 { 1412 delta.x = MIN(delta.x, r.size.w); 1413 delta.y = MIN(delta.y, r.size.h); 1414 r.size.w -= delta.x; 1415 r.size.h -= delta.y; 1416 r.pos.x += delta.x / 2; 1417 r.pos.y += delta.y / 2; 1418 return r; 1419 } 1420 1421 static Rect 1422 scale_rect_centered(Rect r, v2 scale) 1423 { 1424 Rect or = r; 1425 r.size.w *= scale.x; 1426 r.size.h *= scale.y; 1427 r.pos.x += (or.size.w - r.size.w) / 2; 1428 r.pos.y += (or.size.h - r.size.h) / 2; 1429 return r; 1430 } 1431 1432 function b32 1433 point_in_rect(v2 p, Rect r) 1434 { 1435 v2 end = add_v2(r.pos, r.size); 1436 b32 result = BETWEEN(p.x, r.pos.x, end.x) & BETWEEN(p.y, r.pos.y, end.y); 1437 return result; 1438 } 1439 1440 function v2 1441 screen_point_to_world_2d(v2 p, v2 screen_min, v2 screen_max, v2 world_min, v2 world_max) 1442 { 1443 v2 pixels_to_m = div_v2(sub_v2(world_max, world_min), sub_v2(screen_max, screen_min)); 1444 v2 result = add_v2(mul_v2(sub_v2(p, screen_min), pixels_to_m), world_min); 1445 return result; 1446 } 1447 1448 function v2 1449 world_point_to_screen_2d(v2 p, v2 world_min, v2 world_max, v2 screen_min, v2 screen_max) 1450 { 1451 v2 m_to_pixels = div_v2(sub_v2(screen_max, screen_min), sub_v2(world_max, world_min)); 1452 v2 result = add_v2(mul_v2(sub_v2(p, world_min), m_to_pixels), screen_min); 1453 return result; 1454 } 1455 1456 function b32 1457 hover_rect(v2 mouse, Rect rect, f32 *hover_t) 1458 { 1459 b32 hovering = point_in_rect(mouse, rect); 1460 if (hovering) *hover_t += HOVER_SPEED * dt_for_frame; 1461 else *hover_t -= HOVER_SPEED * dt_for_frame; 1462 *hover_t = CLAMP01(*hover_t); 1463 return hovering; 1464 } 1465 1466 function b32 1467 hover_var(BeamformerUI *ui, v2 mouse, Rect rect, Variable *var) 1468 { 1469 b32 result = 0; 1470 if (ui->interaction.type != IT_DRAG || ui->interaction.active == var) { 1471 result = hover_rect(mouse, rect, &var->hover_t); 1472 if (result) { 1473 ui->interaction.hot_rect = rect; 1474 ui->interaction.hot = var; 1475 } 1476 } 1477 return result; 1478 } 1479 1480 static Rect 1481 draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mouse) 1482 { 1483 ASSERT(ui_view->type == VT_UI_VIEW); 1484 UIView *view = &ui_view->u.view; 1485 1486 s8 title = ui_view->name; 1487 if (view->flags & UI_VIEW_CUSTOM_TEXT) { 1488 Stream buf = arena_stream(arena); 1489 push_custom_view_title(&buf, ui_view->u.group.first); 1490 title = arena_stream_commit(&arena, &buf); 1491 } 1492 1493 Rect result, title_rect; 1494 cut_rect_vertical(r, ui->small_font.baseSize + TITLE_BAR_PAD, &title_rect, &result); 1495 cut_rect_vertical(result, LISTING_LINE_PAD, 0, &result); 1496 1497 DrawRectangleRec(title_rect.rl, BLACK); 1498 1499 title_rect = shrink_rect_centered(title_rect, (v2){.x = 1.5 * TITLE_BAR_PAD}); 1500 DrawRectangleRounded(title_rect.rl, 0.5, 0, fade(colour_from_normalized(BG_COLOUR), 0.55)); 1501 title_rect = shrink_rect_centered(title_rect, (v2){.x = 3 * TITLE_BAR_PAD}); 1502 1503 if (view->close) { 1504 Rect close; 1505 cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &close); 1506 hover_var(ui, mouse, close, view->close); 1507 1508 Color colour = colour_from_normalized(lerp_v4(MENU_CLOSE_COLOUR, FG_COLOUR, view->close->hover_t)); 1509 close = shrink_rect_centered(close, (v2){.x = 16, .y = 16}); 1510 DrawLineEx(close.pos.rl, add_v2(close.pos, close.size).rl, 4, colour); 1511 DrawLineEx(add_v2(close.pos, (v2){.x = close.size.w}).rl, 1512 add_v2(close.pos, (v2){.y = close.size.h}).rl, 4, colour); 1513 } 1514 1515 if (view->menu) { 1516 Rect menu; 1517 cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &menu); 1518 if (hover_var(ui, mouse, menu, view->menu)) 1519 ui->interaction.hot_font = &ui->small_font; 1520 1521 Color colour = colour_from_normalized(lerp_v4(MENU_PLUS_COLOUR, FG_COLOUR, view->menu->hover_t)); 1522 menu = shrink_rect_centered(menu, (v2){.x = 14, .y = 14}); 1523 DrawLineEx(add_v2(menu.pos, (v2){.x = menu.size.w / 2}).rl, 1524 add_v2(menu.pos, (v2){.x = menu.size.w / 2, .y = menu.size.h}).rl, 4, colour); 1525 DrawLineEx(add_v2(menu.pos, (v2){.y = menu.size.h / 2}).rl, 1526 add_v2(menu.pos, (v2){.x = menu.size.w, .y = menu.size.h / 2}).rl, 4, colour); 1527 } 1528 1529 v2 title_pos = title_rect.pos; 1530 title_pos.y += 0.5 * TITLE_BAR_PAD; 1531 TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED, .colour = FG_COLOUR, 1532 .limits.size = title_rect.size}; 1533 draw_text(title, title_pos, &text_spec); 1534 1535 return result; 1536 } 1537 1538 /* TODO(rnp): once this has more callers decide if it would be better for this to take 1539 * an orientation rather than force CCW/right-handed */ 1540 function void 1541 draw_ruler(BeamformerUI *ui, Arena arena, v2 start_point, v2 end_point, 1542 f32 start_value, f32 end_value, f32 *markers, u32 marker_count, 1543 u32 segments, s8 suffix, v4 marker_colour, v4 txt_colour) 1544 { 1545 b32 draw_plus = SIGN(start_value) != SIGN(end_value); 1546 1547 end_point = sub_v2(end_point, start_point); 1548 f32 rotation = atan2_f32(end_point.y, end_point.x) * 180 / PI; 1549 1550 rlPushMatrix(); 1551 rlTranslatef(start_point.x, start_point.y, 0); 1552 rlRotatef(rotation, 0, 0, 1); 1553 1554 f32 inc = magnitude_v2(end_point) / segments; 1555 f32 value_inc = (end_value - start_value) / segments; 1556 f32 value = start_value; 1557 1558 Stream buf = arena_stream(arena); 1559 v2 sp = {0}, ep = {.y = RULER_TICK_LENGTH}; 1560 v2 tp = {.x = ui->small_font.baseSize / 2, .y = ep.y + RULER_TEXT_PAD}; 1561 TextSpec text_spec = {.font = &ui->small_font, .rotation = 90, .colour = txt_colour, .flags = TF_ROTATED}; 1562 Color rl_txt_colour = colour_from_normalized(txt_colour); 1563 for (u32 j = 0; j <= segments; j++) { 1564 DrawLineEx(sp.rl, ep.rl, 3, rl_txt_colour); 1565 1566 stream_reset(&buf, 0); 1567 if (draw_plus && value > 0) stream_append_byte(&buf, '+'); 1568 stream_append_f64(&buf, value, 10); 1569 stream_append_s8(&buf, suffix); 1570 draw_text(stream_to_s8(&buf), tp, &text_spec); 1571 1572 value += value_inc; 1573 sp.x += inc; 1574 ep.x += inc; 1575 tp.x += inc; 1576 } 1577 1578 Color rl_marker_colour = colour_from_normalized(marker_colour); 1579 ep.y += RULER_TICK_LENGTH; 1580 for (u32 i = 0; i < marker_count; i++) { 1581 if (markers[i] < F32_INFINITY) { 1582 ep.x = sp.x = markers[i]; 1583 DrawLineEx(sp.rl, ep.rl, 3, rl_marker_colour); 1584 DrawCircleV(ep.rl, 3, rl_marker_colour); 1585 } 1586 } 1587 1588 rlPopMatrix(); 1589 } 1590 1591 function void 1592 do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect draw_rect, 1593 f32 start_value, f32 end_value, s8 suffix) 1594 { 1595 ASSERT(scale_bar->type == VT_SCALE_BAR); 1596 ScaleBar *sb = &scale_bar->u.scale_bar; 1597 1598 v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); 1599 1600 Rect tick_rect = draw_rect; 1601 v2 start_pos = tick_rect.pos; 1602 v2 end_pos = tick_rect.pos; 1603 v2 relative_mouse = sub_v2(mouse, tick_rect.pos); 1604 1605 f32 markers[2]; 1606 u32 marker_count = 1; 1607 1608 v2 world_zoom_point = {{sb->zoom_starting_coord, sb->zoom_starting_coord}}; 1609 v2 screen_zoom_point = world_point_to_screen_2d(world_zoom_point, 1610 (v2){{*sb->min_value, *sb->min_value}}, 1611 (v2){{*sb->max_value, *sb->max_value}}, 1612 (v2){0}, tick_rect.size); 1613 u32 tick_count; 1614 if (sb->direction == SB_AXIAL) { 1615 tick_rect.size.x = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x; 1616 tick_count = tick_rect.size.y / (1.5 * ui->small_font.baseSize); 1617 start_pos.y += tick_rect.size.y; 1618 markers[0] = tick_rect.size.y - screen_zoom_point.y; 1619 markers[1] = tick_rect.size.y - relative_mouse.y; 1620 } else { 1621 tick_rect.size.y = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x; 1622 tick_count = tick_rect.size.x / (1.5 * ui->small_font.baseSize); 1623 end_pos.x += tick_rect.size.x; 1624 markers[0] = screen_zoom_point.x; 1625 markers[1] = relative_mouse.x; 1626 } 1627 1628 if (hover_var(ui, mouse, tick_rect, scale_bar)) 1629 marker_count = 2; 1630 1631 draw_ruler(ui, arena, start_pos, end_pos, start_value, end_value, markers, marker_count, 1632 tick_count, suffix, RULER_COLOUR, lerp_v4(FG_COLOUR, HOVERED_COLOUR, scale_bar->hover_t)); 1633 } 1634 1635 function v2 1636 draw_radio_button(BeamformerUI *ui, Variable *var, v2 at, v2 mouse, v4 base_colour, f32 size) 1637 { 1638 ASSERT(var->type == VT_B32 || var->type == VT_BEAMFORMER_VARIABLE); 1639 b32 value; 1640 if (var->type == VT_B32) { 1641 value = var->u.b32; 1642 } else { 1643 ASSERT(var->u.beamformer_variable.store_type == VT_B32); 1644 value = *(b32 *)var->u.beamformer_variable.store; 1645 } 1646 1647 v2 result = (v2){.x = size, .y = size}; 1648 Rect hover_rect = {.pos = at, .size = result}; 1649 hover_rect.pos.y += 1; 1650 hover_var(ui, mouse, hover_rect, var); 1651 1652 hover_rect = shrink_rect_centered(hover_rect, (v2){.x = 8, .y = 8}); 1653 Rect inner = shrink_rect_centered(hover_rect, (v2){.x = 4, .y = 4}); 1654 v4 fill = lerp_v4(value? base_colour : (v4){0}, HOVERED_COLOUR, var->hover_t); 1655 DrawRectangleRoundedLinesEx(hover_rect.rl, 0.2, 0, 2, colour_from_normalized(base_colour)); 1656 DrawRectangleRec(inner.rl, colour_from_normalized(fill)); 1657 1658 return result; 1659 } 1660 1661 static v2 1662 draw_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, v4 base_colour, TextSpec text_spec) 1663 { 1664 v2 result; 1665 if (var->flags & V_RADIO_BUTTON) { 1666 result = draw_radio_button(ui, var, at, mouse, base_colour, text_spec.font->baseSize); 1667 } else { 1668 Stream buf = arena_stream(arena); 1669 stream_append_variable(&buf, var); 1670 s8 text = arena_stream_commit(&arena, &buf); 1671 result = measure_text(*text_spec.font, text); 1672 1673 if (var->flags & V_INPUT) { 1674 Rect text_rect = {.pos = at, .size = result}; 1675 text_rect = extend_rect_centered(text_rect, (v2){.x = 8}); 1676 if (hover_var(ui, mouse, text_rect, var) && (var->flags & V_TEXT)) 1677 ui->interaction.hot_font = text_spec.font; 1678 text_spec.colour = lerp_v4(base_colour, HOVERED_COLOUR, var->hover_t); 1679 } 1680 1681 draw_text(text, at, &text_spec); 1682 } 1683 return result; 1684 } 1685 1686 function v2 1687 draw_table_cell(BeamformerUI *ui, TableCell *cell, Rect cell_rect, TextAlignment alignment, 1688 TextSpec ts, v2 mouse) 1689 { 1690 /* NOTE(rnp): use desired width for alignment and clamped width for drawing */ 1691 f32 start_x = cell_rect.pos.x; 1692 v2 cell_at = table_cell_align(cell, alignment, cell_rect); 1693 ts.limits.size.w -= (cell_at.x - start_x); 1694 cell_rect.size.w = MIN(ts.limits.size.w, cell_rect.size.w); 1695 1696 v4 base_colour = ts.colour; 1697 if (cell->kind == TCK_VARIABLE && cell->var->flags & V_INPUT) { 1698 Rect hover = {.pos = cell_at, .size = {.w = cell->width, .h = cell_rect.size.h}}; 1699 if (hover_var(ui, mouse, hover, cell->var) && (cell->var->flags & V_TEXT)) 1700 ui->interaction.hot_font = ts.font; 1701 ts.colour = lerp_v4(ts.colour, HOVERED_COLOUR, cell->var->hover_t); 1702 } 1703 1704 /* TODO(rnp): push truncated text for hovering */ 1705 if (cell->kind == TCK_VARIABLE && cell->var->flags & V_RADIO_BUTTON) 1706 draw_radio_button(ui, cell->var, cell_at, mouse, base_colour, ts.font->baseSize); 1707 else if (cell->text.len) 1708 draw_text(cell->text, cell_at, &ts); 1709 /* TODO(rnp): draw column border */ 1710 1711 return cell_rect.size; 1712 } 1713 1714 function v2 1715 draw_table_row(BeamformerUI *ui, Arena arena, TableCell *cells, TextAlignment *cell_alignments, 1716 f32 *widths, i32 cell_count, Rect draw_rect, TextSpec ts, v2 mouse) 1717 { 1718 Rect cell_rect = {.pos = draw_rect.pos, .size.h = draw_rect.size.h}; 1719 for (i32 i = 0; i < cell_count; i++) { 1720 TableCell *cell = cells + i; 1721 cell_rect.size.w = widths[i]; 1722 1723 f32 dw = draw_table_cell(ui, cell, cell_rect, cell_alignments[i], ts, mouse).w; 1724 cell_rect.pos.x += dw; 1725 ts.limits.size.w -= dw; 1726 } 1727 return (v2){.x = draw_rect.pos.x - cell_rect.pos.x, .y = draw_rect.size.h}; 1728 } 1729 1730 function v2 1731 draw_table(BeamformerUI *ui, Arena arena, Table *table, Rect draw_rect, TextSpec ts, v2 mouse) 1732 { 1733 ts.flags |= TF_LIMITED; 1734 ts.limits.size.w = draw_rect.size.w; 1735 1736 f32 start_height = draw_rect.size.h; 1737 i32 row_index = table_skip_rows(table, draw_rect.size.h, ts.font->baseSize); 1738 TableIterator *it = table_iterator_new(table, TIK_ROWS, &arena, row_index, (v2){0}, ts.font); 1739 for (TableRow *row = table_iterator_next(it, &arena); 1740 row; 1741 row = table_iterator_next(it, &arena)) 1742 { 1743 Table *table = it->frame.table; 1744 Rect row_rect = draw_rect; 1745 row_rect.size.h = ts.font->baseSize + TABLE_CELL_PAD_HEIGHT; 1746 f32 h = draw_table_row(ui, arena, row->data, table->alignment, table->widths, 1747 table->columns, row_rect, ts, mouse).y; 1748 draw_rect.pos.y += h; 1749 draw_rect.size.y -= h; 1750 /* TODO(rnp): draw row border */ 1751 } 1752 v2 result = {.x = table_width(table), .y = start_height - draw_rect.size.h}; 1753 return result; 1754 } 1755 1756 function void 1757 draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect display_rect, v2 mouse) 1758 { 1759 ASSERT(var->type == VT_BEAMFORMER_FRAME_VIEW); 1760 InteractionState *is = &ui->interaction; 1761 BeamformerFrameView *view = var->u.generic; 1762 BeamformFrame *frame = view->frame; 1763 1764 v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); 1765 f32 scale_bar_size = 1.2 * txt_s.x + RULER_TICK_LENGTH; 1766 1767 v4 min = view->min_coordinate; 1768 v4 max = view->max_coordinate; 1769 v2 requested_dim = sub_v2(XZ(max), XZ(min)); 1770 f32 aspect = requested_dim.w / requested_dim.h; 1771 1772 Rect vr = display_rect; 1773 v2 scale_bar_area = {0}; 1774 if (view->axial_scale_bar_active->u.b32) { 1775 vr.pos.y += 0.5 * ui->small_font.baseSize; 1776 scale_bar_area.x += scale_bar_size; 1777 scale_bar_area.y += ui->small_font.baseSize; 1778 } 1779 1780 if (view->lateral_scale_bar_active->u.b32) { 1781 vr.pos.x += 0.5 * ui->small_font.baseSize; 1782 scale_bar_area.x += ui->small_font.baseSize; 1783 scale_bar_area.y += scale_bar_size; 1784 } 1785 1786 vr.size = sub_v2(vr.size, scale_bar_area); 1787 if (aspect > 1) vr.size.h = vr.size.w / aspect; 1788 else vr.size.w = vr.size.h * aspect; 1789 1790 v2 occupied = add_v2(vr.size, scale_bar_area); 1791 if (occupied.w > display_rect.size.w) { 1792 vr.size.w -= (occupied.w - display_rect.size.w); 1793 vr.size.h = vr.size.w / aspect; 1794 } else if (occupied.h > display_rect.size.h) { 1795 vr.size.h -= (occupied.h - display_rect.size.h); 1796 vr.size.w = vr.size.h * aspect; 1797 } 1798 occupied = add_v2(vr.size, scale_bar_area); 1799 vr.pos = add_v2(vr.pos, scale_v2(sub_v2(display_rect.size, occupied), 0.5)); 1800 1801 /* TODO(rnp): make this depend on the requested draw orientation (x-z or y-z or x-y) */ 1802 v2 output_dim = { 1803 .x = frame->max_coordinate.x - frame->min_coordinate.x, 1804 .y = frame->max_coordinate.z - frame->min_coordinate.z, 1805 }; 1806 1807 v2 pixels_per_meter = { 1808 .w = (f32)view->texture_dim.w / output_dim.w, 1809 .h = (f32)view->texture_dim.h / output_dim.h, 1810 }; 1811 1812 v2 texture_points = mul_v2(pixels_per_meter, requested_dim); 1813 /* TODO(rnp): this also depends on x-y, y-z, x-z */ 1814 v2 texture_start = { 1815 .x = pixels_per_meter.x * 0.5 * (output_dim.x - requested_dim.x), 1816 .y = pixels_per_meter.y * (frame->max_coordinate.z - max.z), 1817 }; 1818 1819 Rectangle tex_r = {texture_start.x, texture_start.y, texture_points.x, -texture_points.y}; 1820 NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH }; 1821 DrawTextureNPatch(make_raylib_texture(view), tex_np, vr.rl, (Vector2){0}, 0, WHITE); 1822 1823 v2 start_pos = vr.pos; 1824 start_pos.y += vr.size.y; 1825 1826 if (vr.size.w > 0 && view->lateral_scale_bar_active->u.b32) { 1827 do_scale_bar(ui, a, &view->lateral_scale_bar, mouse, 1828 (Rect){.pos = start_pos, .size = vr.size}, 1829 *view->lateral_scale_bar.u.scale_bar.min_value * 1e3, 1830 *view->lateral_scale_bar.u.scale_bar.max_value * 1e3, s8(" mm")); 1831 } 1832 1833 start_pos = vr.pos; 1834 start_pos.x += vr.size.x; 1835 1836 if (vr.size.h > 0 && view->axial_scale_bar_active->u.b32) { 1837 do_scale_bar(ui, a, &view->axial_scale_bar, mouse, 1838 (Rect){.pos = start_pos, .size = vr.size}, 1839 *view->axial_scale_bar.u.scale_bar.max_value * 1e3, 1840 *view->axial_scale_bar.u.scale_bar.min_value * 1e3, s8(" mm")); 1841 } 1842 1843 TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED|TF_OUTLINED, 1844 .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1, 1845 .limits.size.x = vr.size.w}; 1846 1847 f32 draw_table_width = vr.size.w; 1848 if (point_in_rect(mouse, vr)) { 1849 is->hot = var; 1850 is->hot_rect = vr; 1851 1852 v2 world = screen_point_to_world_2d(mouse, vr.pos, add_v2(vr.pos, vr.size), 1853 XZ(view->min_coordinate), 1854 XZ(view->max_coordinate)); 1855 Stream buf = arena_stream(a); 1856 stream_append_v2(&buf, scale_v2(world, 1e3)); 1857 1858 text_spec.limits.size.w -= 4; 1859 v2 txt_s = measure_text(*text_spec.font, stream_to_s8(&buf)); 1860 v2 txt_p = { 1861 .x = vr.pos.x + vr.size.w - txt_s.w - 4, 1862 .y = vr.pos.y + vr.size.h - txt_s.h - 4, 1863 }; 1864 txt_p.x = MAX(vr.pos.x, txt_p.x); 1865 draw_table_width -= draw_text(stream_to_s8(&buf), txt_p, &text_spec).w; 1866 text_spec.limits.size.w += 4; 1867 } 1868 1869 { 1870 Stream buf = arena_stream(a); 1871 s8 shader = push_das_shader_id(&buf, frame->das_shader_id, frame->compound_count); 1872 text_spec.font = &ui->font; 1873 text_spec.limits.size.w -= 16; 1874 v2 txt_s = measure_text(*text_spec.font, shader); 1875 v2 txt_p = { 1876 .x = vr.pos.x + vr.size.w - txt_s.w - 16, 1877 .y = vr.pos.y + 4, 1878 }; 1879 txt_p.x = MAX(vr.pos.x, txt_p.x); 1880 draw_text(stream_to_s8(&buf), txt_p, &text_spec); 1881 text_spec.font = &ui->small_font; 1882 text_spec.limits.size.w += 16; 1883 } 1884 1885 if (view->ruler.state != RS_NONE) { 1886 v2 vr_max_p = add_v2(vr.pos, vr.size); 1887 v2 start_p = world_point_to_screen_2d(view->ruler.start, XZ(view->min_coordinate), 1888 XZ(view->max_coordinate), vr.pos, vr_max_p); 1889 v2 end_p = clamp_v2_rect(mouse, vr); 1890 1891 if (view->ruler.state == RS_HOLD) { 1892 end_p = world_point_to_screen_2d(view->ruler.end, XZ(view->min_coordinate), 1893 XZ(view->max_coordinate), vr.pos, vr_max_p); 1894 } 1895 1896 v2 start_p_world = view->ruler.start; 1897 v2 end_p_world = screen_point_to_world_2d(end_p, vr.pos, vr_max_p, 1898 XZ(view->min_coordinate), 1899 XZ(view->max_coordinate)); 1900 v2 pixel_delta = sub_v2(start_p, end_p); 1901 v2 m_delta = sub_v2(end_p_world, start_p_world); 1902 1903 Color rl_colour = colour_from_normalized(text_spec.colour); 1904 DrawCircleV(start_p.rl, 3, rl_colour); 1905 DrawLineEx(end_p.rl, start_p.rl, 2, rl_colour); 1906 DrawCircleV(end_p.rl, 3, rl_colour); 1907 1908 Stream buf = arena_stream(a); 1909 stream_append_f64(&buf, 1e3 * magnitude_v2(m_delta), 100); 1910 stream_append_s8(&buf, s8(" mm")); 1911 1912 v2 txt_p = start_p; 1913 v2 txt_s = measure_text(*text_spec.font, stream_to_s8(&buf)); 1914 if (pixel_delta.y < 0) txt_p.y -= txt_s.y; 1915 if (pixel_delta.x < 0) txt_p.x -= txt_s.x; 1916 draw_text(stream_to_s8(&buf), txt_p, &text_spec); 1917 } 1918 1919 Table *table = table_new(&a, 3, 3, (TextAlignment []){TA_LEFT, TA_LEFT, TA_LEFT}); 1920 table_push_parameter_row(table, &a, view->gamma.name, &view->gamma, s8("")); 1921 table_push_parameter_row(table, &a, view->threshold.name, &view->threshold, s8("")); 1922 table_push_parameter_row(table, &a, view->dynamic_range.name, &view->dynamic_range, s8("[dB]")); 1923 1924 Rect table_rect = vr; 1925 f32 height = table_extent(table, a, text_spec.font).y; 1926 height = MIN(height, vr.size.h); 1927 table_rect.pos.w += 8; 1928 table_rect.pos.y += vr.size.h - height - 8; 1929 table_rect.size.h = height; 1930 table_rect.size.w = draw_table_width - 16; 1931 1932 draw_table(ui, a, table, table_rect, text_spec, mouse); 1933 } 1934 1935 static v2 1936 draw_compute_progress_bar(BeamformerUI *ui, Arena arena, ComputeProgressBar *state, Rect r) 1937 { 1938 if (*state->processing) state->display_t_velocity += 65 * dt_for_frame; 1939 else state->display_t_velocity -= 45 * dt_for_frame; 1940 1941 state->display_t_velocity = CLAMP(state->display_t_velocity, -10, 10); 1942 state->display_t += state->display_t_velocity * dt_for_frame; 1943 state->display_t = CLAMP01(state->display_t); 1944 1945 if (state->display_t > (1.0 / 255.0)) { 1946 Rect outline = {.pos = r.pos, .size = {.w = r.size.w, .h = ui->font.baseSize}}; 1947 outline = scale_rect_centered(outline, (v2){.x = 0.96, .y = 0.7}); 1948 Rect filled = outline; 1949 filled.size.w *= *state->progress; 1950 DrawRectangleRounded(filled.rl, 2, 0, fade(colour_from_normalized(HOVERED_COLOUR), 1951 state->display_t)); 1952 DrawRectangleRoundedLinesEx(outline.rl, 2, 0, 3, fade(BLACK, state->display_t)); 1953 } 1954 1955 v2 result = {.x = r.size.w, .y = ui->font.baseSize}; 1956 return result; 1957 } 1958 1959 function v2 1960 draw_compute_stats_view(BeamformerCtx *ctx, Arena arena, ComputeShaderStats *stats, Rect r) 1961 { 1962 #define X(e, n, s, h, pn) [CS_##e] = s8(pn ":"), 1963 local_persist s8 labels[CS_LAST] = { COMPUTE_SHADERS }; 1964 #undef X 1965 1966 BeamformerUI *ui = ctx->ui; 1967 f32 compute_time_sum = 0; 1968 u32 stages = ctx->shared_memory->compute_stages_count; 1969 TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; 1970 1971 Table *table = table_new(&arena, stages + 1, 3, (TextAlignment []){TA_LEFT, TA_LEFT, TA_LEFT}); 1972 for (u32 i = 0; i < stages; i++) { 1973 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 1974 1975 1976 Stream sb = arena_stream(arena); 1977 u32 index = ctx->shared_memory->compute_stages[i]; 1978 compute_time_sum += stats->times[index]; 1979 stream_append_f64_e(&sb, stats->times[index]); 1980 1981 cells[0].text = labels[index]; 1982 cells[1].text = arena_stream_commit(&arena, &sb); 1983 cells[2].text = s8("[s]"); 1984 } 1985 1986 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 1987 Stream sb = arena_stream(arena); 1988 stream_append_f64_e(&sb, compute_time_sum); 1989 cells[0].text = s8("Compute Total:"); 1990 cells[1].text = arena_stream_commit(&arena, &sb); 1991 cells[2].text = s8("[s]"); 1992 1993 table_extent(table, arena, text_spec.font); 1994 return draw_table(ui, arena, table, r, text_spec, (v2){0}); 1995 } 1996 1997 function v2 1998 draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 mouse, TextSpec text_spec) 1999 { 2000 ASSERT(group->type == VT_GROUP); 2001 Table *table = table_new(&arena, 0, 3, (TextAlignment []){TA_LEFT, TA_LEFT, TA_RIGHT}); 2002 /* NOTE(rnp): minimum width for middle column */ 2003 table->widths[1] = 150; 2004 2005 Variable *var = group->u.group.first; 2006 while (var) { 2007 switch (var->type) { 2008 case VT_CYCLER: 2009 case VT_BEAMFORMER_VARIABLE: { 2010 s8 suffix = s8(""); 2011 if (var->type == VT_BEAMFORMER_VARIABLE) 2012 suffix = var->u.beamformer_variable.suffix; 2013 table_push_parameter_row(table, &arena, var->name, var, suffix); 2014 while (var) { 2015 if (var->next) { 2016 var = var->next; 2017 break; 2018 } 2019 var = var->parent; 2020 table = table_end_subtable(table); 2021 } 2022 } break; 2023 case VT_GROUP: { 2024 VariableGroup *g = &var->u.group; 2025 2026 TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; 2027 cells[0] = (TableCell){.text = var->name, .kind = TCK_VARIABLE, .var = var}; 2028 2029 if (g->expanded) { 2030 var = g->first; 2031 table = table_begin_subtable(table, &arena, table->columns, 2032 (TextAlignment []){TA_LEFT, TA_CENTER, TA_RIGHT}); 2033 table->widths[1] = 100; 2034 } else { 2035 Variable *v = g->first; 2036 2037 ASSERT(!v || v->type == VT_BEAMFORMER_VARIABLE); 2038 /* NOTE(rnp): assume the suffix is the same for all elements */ 2039 if (v) cells[2].text = v->u.beamformer_variable.suffix; 2040 2041 Stream sb = arena_stream(arena); 2042 switch (g->type) { 2043 case VG_LIST: break; 2044 case VG_V2: 2045 case VG_V4: { 2046 stream_append_s8(&sb, s8("{")); 2047 while (v) { 2048 stream_append_variable(&sb, v); 2049 v = v->next; 2050 if (v) stream_append_s8(&sb, s8(", ")); 2051 } 2052 stream_append_s8(&sb, s8("}")); 2053 } break; 2054 } 2055 cells[1].kind = TCK_VARIABLE_GROUP; 2056 cells[1].text = arena_stream_commit(&arena, &sb); 2057 cells[1].var = var; 2058 2059 var = var->next; 2060 } 2061 } break; 2062 INVALID_DEFAULT_CASE; 2063 } 2064 } 2065 2066 text_spec.flags |= TF_LIMITED; 2067 v2 result = table_extent(table, arena, text_spec.font); 2068 TableIterator *it = table_iterator_new(table, TIK_CELLS, &arena, 0, r.pos, text_spec.font); 2069 for (TableCell *cell = table_iterator_next(it, &arena); 2070 cell; 2071 cell = table_iterator_next(it, &arena)) 2072 { 2073 text_spec.limits.size.w = r.size.w - (it->cell_rect.pos.x - it->start_x); 2074 /* TODO(rnp): ensure this doesn't exceed r.size */ 2075 Rect rect; 2076 rect.pos = add_v2(it->cell_rect.pos, scale_v2((v2){.x = text_spec.font->baseSize}, it->sub_table_depth)); 2077 rect.size = it->cell_rect.size; 2078 if (cell->kind == TCK_VARIABLE_GROUP) { 2079 Variable *v = cell->var->u.group.first; 2080 v2 at = table_cell_align(cell, it->alignment, rect); 2081 text_spec.limits.size.w = r.size.w - (at.x - it->start_x); 2082 f32 dw = draw_text(s8("{"), at, &text_spec).x; 2083 while (v) { 2084 at.x += dw; 2085 text_spec.limits.size.w -= dw; 2086 dw = draw_variable(ui, arena, v, at, mouse, text_spec.colour, text_spec).x; 2087 2088 v = v->next; 2089 if (v) { 2090 at.x += dw; 2091 text_spec.limits.size.w -= dw; 2092 dw = draw_text(s8(", "), at, &text_spec).x; 2093 } 2094 } 2095 at.x += dw; 2096 text_spec.limits.size.w -= dw; 2097 draw_text(s8("}"), at, &text_spec); 2098 } else { 2099 draw_table_cell(ui, cell, rect, it->alignment, text_spec, mouse); 2100 } 2101 } 2102 2103 return result; 2104 } 2105 2106 function void 2107 draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec text_spec) 2108 { 2109 ASSERT(ui_view->type == VT_UI_VIEW); 2110 UIView *view = &ui_view->u.view; 2111 2112 if (view->needed_height - r.size.h < view->offset) 2113 view->offset = view->needed_height - r.size.h; 2114 2115 if (view->needed_height - r.size.h < 0) 2116 view->offset = 0; 2117 2118 r.pos.y -= view->offset; 2119 2120 v2 size = {0}; 2121 2122 Variable *var = view->child; 2123 switch (var->type) { 2124 case VT_GROUP: size = draw_ui_view_listing(ui, var, ui->arena, r, mouse, text_spec); break; 2125 case VT_BEAMFORMER_FRAME_VIEW: { 2126 BeamformerFrameView *bv = var->u.generic; 2127 if (frame_view_ready_to_present(bv)) 2128 draw_beamformer_frame_view(ui, ui->arena, var, r, mouse); 2129 } break; 2130 case VT_COMPUTE_PROGRESS_BAR: { 2131 size = draw_compute_progress_bar(ui, ui->arena, &var->u.compute_progress_bar, r); 2132 } break; 2133 case VT_COMPUTE_LATEST_STATS_VIEW: 2134 case VT_COMPUTE_STATS_VIEW: { 2135 ComputeShaderStats *stats = var->u.compute_stats_view.stats; 2136 if (var->type == VT_COMPUTE_LATEST_STATS_VIEW) 2137 stats = *(ComputeShaderStats **)stats; 2138 size = draw_compute_stats_view(var->u.compute_stats_view.ctx, ui->arena, stats, r); 2139 } break; 2140 default: INVALID_CODE_PATH; 2141 } 2142 2143 view->needed_height = size.y; 2144 } 2145 2146 function void 2147 draw_active_text_box(BeamformerUI *ui, Variable *var) 2148 { 2149 InputState *is = &ui->text_input_state; 2150 Rect box = ui->interaction.rect; 2151 Font *font = ui->interaction.font; 2152 2153 s8 text = {.len = is->count, .data = is->buf}; 2154 v2 text_size = measure_text(*font, text); 2155 v2 text_position = {.x = box.pos.x, .y = box.pos.y + (box.size.h - text_size.h) / 2}; 2156 2157 f32 cursor_width = (is->cursor == is->count) ? 0.55 * font->baseSize : 4; 2158 f32 cursor_offset = measure_text(*font, (s8){.data = text.data, .len = is->cursor}).w; 2159 cursor_offset += text_position.x; 2160 2161 box.size.w = MAX(box.size.w, text_size.w + cursor_width); 2162 Rect background = extend_rect_centered(box, (v2){.x = 12, .y = 8}); 2163 box = extend_rect_centered(box, (v2){.x = 8, .y = 4}); 2164 2165 Rect cursor = { 2166 .pos = {.x = cursor_offset, .y = text_position.y}, 2167 .size = {.w = cursor_width, .h = text_size.h}, 2168 }; 2169 2170 v4 cursor_colour = FOCUSED_COLOUR; 2171 cursor_colour.a = CLAMP01(is->cursor_blink_t); 2172 2173 TextSpec text_spec = {.font = font, .colour = lerp_v4(FG_COLOUR, HOVERED_COLOUR, var->hover_t)}; 2174 2175 DrawRectangleRounded(background.rl, 0.2, 0, fade(BLACK, 0.8)); 2176 DrawRectangleRounded(box.rl, 0.2, 0, colour_from_normalized(BG_COLOUR)); 2177 draw_text(text, text_position, &text_spec); 2178 DrawRectanglePro(cursor.rl, (Vector2){0}, 0, colour_from_normalized(cursor_colour)); 2179 } 2180 2181 static void 2182 draw_active_menu(BeamformerUI *ui, Arena arena, Variable *menu, v2 mouse, Rect window) 2183 { 2184 ASSERT(menu->type == VT_GROUP); 2185 2186 Font *font = ui->interaction.font; 2187 f32 font_height = font->baseSize; 2188 f32 max_label_width = 0; 2189 2190 Variable *item = menu->u.group.first; 2191 i32 item_count = 0; 2192 b32 radio = 0; 2193 while (item) { 2194 max_label_width = MAX(max_label_width, item->name_width); 2195 radio |= (item->flags & V_RADIO_BUTTON) != 0; 2196 item_count++; 2197 item = item->next; 2198 } 2199 2200 f32 radio_button_width = radio? font_height : 0; 2201 v2 at = ui->interaction.rect.pos; 2202 f32 menu_width = max_label_width + radio_button_width + 8; 2203 f32 menu_height = item_count * font_height + (item_count - 1) * 2; 2204 menu_height = MAX(menu_height, 0); 2205 2206 if (at.x + menu_width > window.size.w) 2207 at.x = window.size.w - menu_width - 16; 2208 if (at.y + menu_height > window.size.h) 2209 at.y = window.size.h - menu_height - 12; 2210 /* TODO(rnp): scroll menu if it doesn't fit on screen */ 2211 2212 Rect menu_rect = {.pos = at, .size = {.w = menu_width, .h = menu_height}}; 2213 Rect bg_rect = extend_rect_centered(menu_rect, (v2){.x = 12, .y = 8}); 2214 menu_rect = extend_rect_centered(menu_rect, (v2){.x = 6, .y = 4}); 2215 DrawRectangleRounded(bg_rect.rl, 0.1, 0, fade(BLACK, 0.8)); 2216 DrawRectangleRounded(menu_rect.rl, 0.1, 0, colour_from_normalized(BG_COLOUR)); 2217 v2 start = at; 2218 for (i32 i = 0; i < item_count - 1; i++) { 2219 at.y += 2 + font_height; 2220 DrawLineEx((v2){.x = at.x - 3, .y = at.y}.rl, 2221 add_v2(at, (v2){.w = menu_width + 3}).rl, 2, fade(BLACK, 0.8)); 2222 } 2223 2224 item = menu->u.group.first; 2225 TextSpec text_spec = {.font = font, .colour = FG_COLOUR, .limits.size.w = menu_width}; 2226 at = start; 2227 while (item) { 2228 at.x = start.x; 2229 if (item->type == VT_CYCLER) { 2230 at.x += draw_text(item->name, at, &text_spec).x; 2231 } else if (item->flags & V_RADIO_BUTTON) { 2232 draw_text(item->name, at, &text_spec); 2233 at.x += max_label_width + 8; 2234 } 2235 at.y += draw_variable(ui, arena, item, at, mouse, FG_COLOUR, text_spec).y + 2; 2236 item = item->next; 2237 } 2238 } 2239 2240 static void 2241 draw_layout_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse) 2242 { 2243 if (var->type != VT_UI_REGION_SPLIT) { 2244 v2 shrink = {.x = UI_REGION_PAD, .y = UI_REGION_PAD}; 2245 draw_rect = shrink_rect_centered(draw_rect, shrink); 2246 draw_rect.size = floor_v2(draw_rect.size); 2247 BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); 2248 draw_rect = draw_title_bar(ui, ui->arena, var, draw_rect, mouse); 2249 EndScissorMode(); 2250 } 2251 2252 /* TODO(rnp): post order traversal of the ui tree will remove the need for this */ 2253 if (!CheckCollisionPointRec(mouse.rl, draw_rect.rl)) 2254 mouse = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; 2255 2256 draw_rect.size = floor_v2(draw_rect.size); 2257 BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); 2258 switch (var->type) { 2259 case VT_UI_VIEW: { 2260 hover_var(ui, mouse, draw_rect, var); 2261 TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; 2262 draw_ui_view(ui, var, draw_rect, mouse, text_spec); 2263 } break; 2264 case VT_UI_REGION_SPLIT: { 2265 RegionSplit *rs = &var->u.region_split; 2266 2267 Rect split, hover; 2268 switch (rs->direction) { 2269 case RSD_VERTICAL: { 2270 split_rect_vertical(draw_rect, rs->fraction, 0, &split); 2271 split.pos.x += UI_REGION_PAD; 2272 split.pos.y -= UI_SPLIT_HANDLE_THICK / 2; 2273 split.size.h = UI_SPLIT_HANDLE_THICK; 2274 split.size.w -= 2 * UI_REGION_PAD; 2275 hover = extend_rect_centered(split, (v2){.y = 0.75 * UI_REGION_PAD}); 2276 } break; 2277 case RSD_HORIZONTAL: { 2278 split_rect_horizontal(draw_rect, rs->fraction, 0, &split); 2279 split.pos.x -= UI_SPLIT_HANDLE_THICK / 2; 2280 split.pos.y += UI_REGION_PAD; 2281 split.size.w = UI_SPLIT_HANDLE_THICK; 2282 split.size.h -= 2 * UI_REGION_PAD; 2283 hover = extend_rect_centered(split, (v2){.x = 0.75 * UI_REGION_PAD}); 2284 } break; 2285 } 2286 2287 hover_var(ui, mouse, hover, var); 2288 2289 v4 colour = HOVERED_COLOUR; 2290 colour.a = var->hover_t; 2291 DrawRectangleRounded(split.rl, 0.6, 0, colour_from_normalized(colour)); 2292 } break; 2293 default: INVALID_CODE_PATH; break; 2294 } 2295 EndScissorMode(); 2296 } 2297 2298 static void 2299 draw_ui_regions(BeamformerUI *ui, Rect window, v2 mouse) 2300 { 2301 struct region_frame { 2302 Variable *var; 2303 Rect rect; 2304 } init[16]; 2305 2306 struct { 2307 struct region_frame *data; 2308 iz count; 2309 iz capacity; 2310 } stack = {init, 0, ARRAY_COUNT(init)}; 2311 2312 TempArena arena_savepoint = begin_temp_arena(&ui->arena); 2313 2314 *da_push(&ui->arena, &stack) = (struct region_frame){ui->regions, window}; 2315 while (stack.count) { 2316 struct region_frame *top = stack.data + --stack.count; 2317 Rect rect = top->rect; 2318 draw_layout_variable(ui, top->var, rect, mouse); 2319 2320 if (top->var->type == VT_UI_REGION_SPLIT) { 2321 Rect first, second; 2322 RegionSplit *rs = &top->var->u.region_split; 2323 switch (rs->direction) { 2324 case RSD_VERTICAL: { 2325 split_rect_vertical(rect, rs->fraction, &first, &second); 2326 } break; 2327 case RSD_HORIZONTAL: { 2328 split_rect_horizontal(rect, rs->fraction, &first, &second); 2329 } break; 2330 } 2331 2332 *da_push(&ui->arena, &stack) = (struct region_frame){rs->right, second}; 2333 *da_push(&ui->arena, &stack) = (struct region_frame){rs->left, first}; 2334 } 2335 } 2336 2337 end_temp_arena(arena_savepoint); 2338 } 2339 2340 function void 2341 scroll_interaction(Variable *var, f32 delta) 2342 { 2343 switch (var->type) { 2344 case VT_B32: var->u.b32 = !var->u.b32; break; 2345 case VT_F32: var->u.f32 += delta; break; 2346 case VT_I32: var->u.i32 += delta; break; 2347 case VT_U32: var->u.u32 += delta; break; 2348 case VT_SCALED_F32: var->u.scaled_f32.val += delta * var->u.scaled_f32.scale; break; 2349 case VT_BEAMFORMER_FRAME_VIEW: { 2350 BeamformerFrameView *bv = var->u.generic; 2351 bv->needs_update = 1; 2352 bv->threshold.u.f32 += delta; 2353 } break; 2354 case VT_BEAMFORMER_VARIABLE: { 2355 BeamformerVariable *bv = &var->u.beamformer_variable; 2356 switch (bv->store_type) { 2357 case VT_F32: { 2358 f32 val = *(f32 *)bv->store + delta * bv->scroll_scale; 2359 *(f32 *)bv->store = CLAMP(val, bv->limits.x, bv->limits.y); 2360 } break; 2361 INVALID_DEFAULT_CASE; 2362 } 2363 } break; 2364 case VT_CYCLER: { 2365 *var->u.cycler.state += delta > 0? 1 : -1; 2366 *var->u.cycler.state %= var->u.cycler.cycle_length; 2367 } break; 2368 case VT_UI_VIEW: { 2369 var->u.view.offset += UI_SCROLL_SPEED * delta; 2370 var->u.view.offset = MAX(0, var->u.view.offset); 2371 } break; 2372 INVALID_DEFAULT_CASE; 2373 } 2374 } 2375 2376 function void 2377 begin_text_input(InputState *is, Font *font, Rect r, Variable *var, v2 mouse) 2378 { 2379 Stream s = {.cap = ARRAY_COUNT(is->buf), .data = is->buf}; 2380 stream_append_variable(&s, var); 2381 is->count = s.widx; 2382 2383 /* NOTE: extra offset to help with putting a cursor at idx 0 */ 2384 #define TEXT_HALF_CHAR_WIDTH 10 2385 f32 hover_p = CLAMP01((mouse.x - r.pos.x) / r.size.w); 2386 f32 x_off = TEXT_HALF_CHAR_WIDTH, x_bounds = r.size.w * hover_p; 2387 i32 i; 2388 for (i = 0; i < is->count && x_off < x_bounds; i++) { 2389 /* NOTE: assumes font glyphs are ordered ASCII */ 2390 i32 idx = is->buf[i] - 0x20; 2391 x_off += font->glyphs[idx].advanceX; 2392 if (font->glyphs[idx].advanceX == 0) 2393 x_off += font->recs[idx].width; 2394 } 2395 is->cursor = i; 2396 } 2397 2398 function void 2399 end_text_input(InputState *is, Variable *var) 2400 { 2401 f64 value = parse_f64((s8){.len = is->count, .data = is->buf}); 2402 2403 switch (var->type) { 2404 case VT_SCALED_F32: var->u.scaled_f32.val = value; break; 2405 case VT_F32: var->u.f32 = value; break; 2406 case VT_BEAMFORMER_VARIABLE: { 2407 BeamformerVariable *bv = &var->u.beamformer_variable; 2408 switch (bv->store_type) { 2409 case VT_F32: { 2410 value = CLAMP(value / bv->display_scale, bv->limits.x, bv->limits.y); 2411 *(f32 *)bv->store = value; 2412 } break; 2413 INVALID_DEFAULT_CASE; 2414 } 2415 var->hover_t = 0; 2416 } break; 2417 INVALID_DEFAULT_CASE; 2418 } 2419 } 2420 2421 function void 2422 update_text_input(InputState *is, Variable *var) 2423 { 2424 ASSERT(is->cursor != -1); 2425 2426 is->cursor_blink_t += is->cursor_blink_scale * dt_for_frame; 2427 if (is->cursor_blink_t >= 1) is->cursor_blink_scale = -1.5f; 2428 if (is->cursor_blink_t <= 0) is->cursor_blink_scale = 1.5f; 2429 2430 var->hover_t -= 2 * HOVER_SPEED * dt_for_frame; 2431 var->hover_t = CLAMP01(var->hover_t); 2432 2433 /* NOTE: handle multiple input keys on a single frame */ 2434 for (i32 key = GetCharPressed(); 2435 is->count < countof(is->buf) && key > 0; 2436 key = GetCharPressed()) 2437 { 2438 b32 allow_key = (BETWEEN(key, '0', '9') || (key == '.') || 2439 (key == '-' && is->cursor == 0)); 2440 if (allow_key) { 2441 mem_move(is->buf + is->cursor + 1, 2442 is->buf + is->cursor, 2443 is->count - is->cursor); 2444 is->buf[is->cursor++] = key; 2445 is->count++; 2446 } 2447 } 2448 2449 is->cursor -= (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) && is->cursor > 0; 2450 is->cursor += (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) && is->cursor < is->count; 2451 2452 if ((IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) && is->cursor > 0) { 2453 is->cursor--; 2454 if (is->cursor < countof(is->buf) - 1) { 2455 mem_move(is->buf + is->cursor, 2456 is->buf + is->cursor + 1, 2457 is->count - is->cursor - 1); 2458 } 2459 is->count--; 2460 } 2461 2462 if ((IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) && is->cursor < is->count) { 2463 mem_move(is->buf + is->cursor, 2464 is->buf + is->cursor + 1, 2465 is->count - is->cursor - 1); 2466 is->count--; 2467 } 2468 } 2469 2470 function void 2471 scale_bar_interaction(BeamformerUI *ui, ScaleBar *sb, v2 mouse) 2472 { 2473 InteractionState *is = &ui->interaction; 2474 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 2475 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 2476 f32 mouse_wheel = GetMouseWheelMoveV().y; 2477 2478 if (mouse_left_pressed) { 2479 v2 world_mouse = screen_point_to_world_2d(mouse, is->rect.pos, 2480 add_v2(is->rect.pos, is->rect.size), 2481 (v2){{*sb->min_value, *sb->min_value}}, 2482 (v2){{*sb->max_value, *sb->max_value}}); 2483 f32 new_coord = F32_INFINITY; 2484 switch (sb->direction) { 2485 case SB_LATERAL: new_coord = world_mouse.x; break; 2486 case SB_AXIAL: new_coord = world_mouse.y; break; 2487 } 2488 if (sb->zoom_starting_coord == F32_INFINITY) { 2489 sb->zoom_starting_coord = new_coord; 2490 } else { 2491 f32 min = sb->zoom_starting_coord; 2492 f32 max = new_coord; 2493 if (min > max) SWAP(min, max) 2494 2495 v2_sll *savepoint = SLLPop(ui->scale_bar_savepoint_freelist); 2496 if (!savepoint) savepoint = push_struct(&ui->arena, v2_sll); 2497 2498 savepoint->v.x = *sb->min_value; 2499 savepoint->v.y = *sb->max_value; 2500 SLLPush(savepoint, sb->savepoint_stack); 2501 2502 *sb->min_value = min; 2503 *sb->max_value = max; 2504 2505 sb->zoom_starting_coord = F32_INFINITY; 2506 } 2507 } 2508 2509 if (mouse_right_pressed) { 2510 v2_sll *savepoint = sb->savepoint_stack; 2511 if (savepoint) { 2512 *sb->min_value = savepoint->v.x; 2513 *sb->max_value = savepoint->v.y; 2514 sb->savepoint_stack = savepoint->next; 2515 SLLPush(savepoint, ui->scale_bar_savepoint_freelist); 2516 } 2517 sb->zoom_starting_coord = F32_INFINITY; 2518 } 2519 2520 if (mouse_wheel) { 2521 *sb->min_value += mouse_wheel * sb->scroll_scale.x; 2522 *sb->max_value += mouse_wheel * sb->scroll_scale.y; 2523 } 2524 } 2525 2526 static void 2527 ui_button_interaction(BeamformerUI *ui, Variable *button) 2528 { 2529 ASSERT(button->type == VT_UI_BUTTON); 2530 switch (button->u.button) { 2531 case UI_BID_FV_COPY_HORIZONTAL: { 2532 ui_copy_frame(ui, button->parent->parent, RSD_HORIZONTAL); 2533 } break; 2534 case UI_BID_FV_COPY_VERTICAL: { 2535 ui_copy_frame(ui, button->parent->parent, RSD_VERTICAL); 2536 } break; 2537 case UI_BID_GM_OPEN_LIVE_VIEW_RIGHT: { 2538 ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL); 2539 } break; 2540 case UI_BID_GM_OPEN_LIVE_VIEW_BELOW: { 2541 ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL); 2542 } break; 2543 case UI_BID_CLOSE_VIEW: { 2544 Variable *view = button->parent; 2545 Variable *region = view->parent; 2546 ASSERT(view->type == VT_UI_VIEW && region->type == VT_UI_REGION_SPLIT); 2547 2548 Variable *parent = region->parent; 2549 Variable *remaining = region->u.region_split.left; 2550 if (remaining == view) remaining = region->u.region_split.right; 2551 2552 ui_view_free(ui, view); 2553 2554 ASSERT(parent->type == VT_UI_REGION_SPLIT); 2555 if (parent->u.region_split.left == region) { 2556 parent->u.region_split.left = remaining; 2557 } else { 2558 parent->u.region_split.right = remaining; 2559 } 2560 remaining->parent = parent; 2561 2562 SLLPush(region, ui->variable_freelist); 2563 } break; 2564 } 2565 } 2566 2567 static void 2568 ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll, b32 mouse_left_pressed) 2569 { 2570 InteractionState *is = &ui->interaction; 2571 if (is->hot) { 2572 switch (is->hot->type) { 2573 case VT_NULL: is->type = IT_NOP; break; 2574 case VT_B32: is->type = IT_SET; break; 2575 case VT_UI_REGION_SPLIT: { is->type = IT_DRAG; } break; 2576 case VT_UI_VIEW: { if (scroll) is->type = IT_SCROLL; } break; 2577 case VT_UI_BUTTON: { ui_button_interaction(ui, is->hot); } break; 2578 case VT_SCALE_BAR: { is->type = IT_SET; } break; 2579 case VT_BEAMFORMER_FRAME_VIEW: 2580 case VT_CYCLER: { 2581 if (scroll) is->type = IT_SCROLL; 2582 else is->type = IT_SET; 2583 } break; 2584 case VT_GROUP: { 2585 if (mouse_left_pressed && is->hot->flags & V_MENU) { 2586 is->type = IT_MENU; 2587 } else { 2588 is->type = IT_SET; 2589 } 2590 } break; 2591 case VT_BEAMFORMER_VARIABLE: { 2592 if (is->hot->u.beamformer_variable.store_type == VT_B32) { 2593 is->type = IT_SET; 2594 break; 2595 } 2596 } /* FALLTHROUGH */ 2597 case VT_SCALED_F32: 2598 case VT_F32: { 2599 if (scroll) { 2600 is->type = IT_SCROLL; 2601 } else if (mouse_left_pressed && is->hot->flags & V_TEXT) { 2602 is->type = IT_TEXT; 2603 begin_text_input(&ui->text_input_state, is->hot_font, is->hot_rect, 2604 is->hot, input->mouse); 2605 } 2606 } break; 2607 default: INVALID_CODE_PATH; 2608 } 2609 } 2610 if (is->type != IT_NONE) { 2611 is->last_rect = is->rect; 2612 is->active = is->hot; 2613 is->rect = is->hot_rect; 2614 is->font = is->hot_font; 2615 } 2616 } 2617 2618 function void 2619 ui_end_interact(BeamformerUI *ui, v2 mouse) 2620 { 2621 InteractionState *is = &ui->interaction; 2622 switch (is->type) { 2623 case IT_NOP: break; 2624 case IT_MENU: break; 2625 case IT_DRAG: break; 2626 case IT_SET: { 2627 switch (is->active->type) { 2628 case VT_B32: { is->active->u.b32 = !is->active->u.b32; } break; 2629 case VT_GROUP: { 2630 is->active->u.group.expanded = !is->active->u.group.expanded; 2631 } break; 2632 case VT_CYCLER: { 2633 *is->active->u.cycler.state += 1; 2634 *is->active->u.cycler.state %= is->active->u.cycler.cycle_length; 2635 } break; 2636 case VT_SCALE_BAR: { 2637 scale_bar_interaction(ui, &is->active->u.scale_bar, mouse); 2638 } break; 2639 case VT_BEAMFORMER_FRAME_VIEW: { 2640 BeamformerFrameView *bv = is->hot->u.generic; 2641 bv->ruler.state++; 2642 switch (bv->ruler.state) { 2643 case RS_START: 2644 case RS_HOLD: { 2645 v2 r_max = add_v2(is->rect.pos, is->rect.size); 2646 v2 p = screen_point_to_world_2d(mouse, is->rect.pos, r_max, 2647 XZ(bv->min_coordinate), 2648 XZ(bv->max_coordinate)); 2649 if (bv->ruler.state == RS_START) bv->ruler.start = p; 2650 else bv->ruler.end = p; 2651 } break; 2652 default: bv->ruler.state = RS_NONE; break; 2653 } 2654 } break; 2655 default: INVALID_CODE_PATH; 2656 } 2657 } break; 2658 case IT_SCROLL: scroll_interaction(is->active, GetMouseWheelMoveV().y); break; 2659 case IT_TEXT: end_text_input(&ui->text_input_state, is->active); break; 2660 default: INVALID_CODE_PATH; 2661 } 2662 2663 b32 menu_child = is->active->parent && is->active->parent->flags & V_MENU; 2664 2665 /* TODO(rnp): better way of clearing the state when the parent is a menu */ 2666 if (menu_child) is->active->hover_t = 0; 2667 2668 if (is->active->flags & V_CAUSES_COMPUTE) 2669 ui->flush_params = 1; 2670 if (is->active->flags & V_UPDATE_VIEW) { 2671 Variable *parent = is->active->parent; 2672 BeamformerFrameView *frame; 2673 /* TODO(rnp): more straight forward way of achieving this */ 2674 if (parent->type == VT_BEAMFORMER_FRAME_VIEW) { 2675 frame = parent->u.generic; 2676 } else { 2677 ASSERT(parent->flags & V_MENU); 2678 ASSERT(parent->parent->u.group.first->type == VT_BEAMFORMER_FRAME_VIEW); 2679 frame = parent->parent->u.group.first->u.generic; 2680 } 2681 frame->needs_update = 1; 2682 } 2683 2684 if (menu_child && (is->active->flags & V_CLOSES_MENU) == 0) { 2685 is->type = IT_MENU; 2686 is->rect = is->last_rect; 2687 is->active = is->active->parent; 2688 } else { 2689 is->type = IT_NONE; 2690 is->active = 0; 2691 } 2692 } 2693 2694 function void 2695 ui_interact(BeamformerUI *ui, BeamformerInput *input, uv2 window_size) 2696 { 2697 InteractionState *is = &ui->interaction; 2698 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 2699 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 2700 b32 wheel_moved = GetMouseWheelMoveV().y != 0; 2701 if (mouse_right_pressed || mouse_left_pressed || wheel_moved) { 2702 if (is->type != IT_NONE) 2703 ui_end_interact(ui, input->mouse); 2704 ui_begin_interact(ui, input, wheel_moved, mouse_left_pressed); 2705 } 2706 2707 if (IsKeyPressed(KEY_ENTER) && is->type == IT_TEXT) 2708 ui_end_interact(ui, input->mouse); 2709 2710 switch (is->type) { 2711 case IT_NONE: break; 2712 case IT_NOP: break; 2713 case IT_MENU: break; 2714 case IT_SCROLL: ui_end_interact(ui, input->mouse); break; 2715 case IT_SET: ui_end_interact(ui, input->mouse); break; 2716 case IT_TEXT: update_text_input(&ui->text_input_state, is->active); break; 2717 case IT_DRAG: { 2718 if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { 2719 ui_end_interact(ui, input->mouse); 2720 } else { 2721 v2 ws = {.w = window_size.w, .h = window_size.h}; 2722 v2 dMouse = sub_v2(input->mouse, input->last_mouse); 2723 dMouse = mul_v2(dMouse, (v2){.x = 1.0f / ws.w, .y = 1.0f / ws.h}); 2724 2725 switch (is->active->type) { 2726 case VT_UI_REGION_SPLIT: { 2727 f32 min_fraction = 0; 2728 RegionSplit *rs = &is->active->u.region_split; 2729 switch (rs->direction) { 2730 case RSD_VERTICAL: { 2731 min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5 * UI_REGION_PAD) / ws.h; 2732 rs->fraction += dMouse.y; 2733 } break; 2734 case RSD_HORIZONTAL: { 2735 min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5 * UI_REGION_PAD) / ws.w; 2736 rs->fraction += dMouse.x; 2737 } break; 2738 } 2739 rs->fraction = CLAMP(rs->fraction, min_fraction, 1 - min_fraction); 2740 } break; 2741 default: break; 2742 } 2743 } 2744 } break; 2745 } 2746 2747 is->hot = 0; 2748 } 2749 2750 static void 2751 ui_init(BeamformerCtx *ctx, Arena store) 2752 { 2753 /* NOTE(rnp): store the ui at the base of the passed in arena and use the rest for 2754 * temporary allocations within the ui. If needed we can recall this function to 2755 * completely clear the ui state. The is that if we store pointers to static data 2756 * such as embedded font data we will need to reset them when the executable reloads. 2757 * We could also build some sort of ui structure here and store it then iterate over 2758 * it to actually draw the ui. If we reload we may have changed it so we should 2759 * rebuild it */ 2760 2761 BeamformerUI *ui = ctx->ui; 2762 2763 /* NOTE(rnp): unload old data from GPU */ 2764 if (ui) { 2765 UnloadFont(ui->font); 2766 UnloadFont(ui->small_font); 2767 2768 for (BeamformerFrameView *view = ui->views; view; view = view->next) 2769 if (view->texture) 2770 glDeleteTextures(1, &view->texture); 2771 } 2772 2773 ui = ctx->ui = push_struct(&store, typeof(*ui)); 2774 ui->os = &ctx->os; 2775 ui->arena = store; 2776 ui->frame_view_render_context = &ctx->frame_view_render_context; 2777 2778 /* TODO: build these into the binary */ 2779 /* TODO(rnp): better font, this one is jank at small sizes */ 2780 ui->font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 28, 0, 0); 2781 ui->small_font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 20, 0, 0); 2782 2783 Variable *split = ui->regions = add_ui_split(ui, 0, &ui->arena, s8("UI Root"), 0.4, 2784 RSD_HORIZONTAL, ui->font); 2785 split->u.region_split.left = add_ui_split(ui, split, &ui->arena, s8(""), 0.475, 2786 RSD_VERTICAL, ui->font); 2787 split->u.region_split.right = add_beamformer_frame_view(ui, split, &ui->arena, FVT_LATEST, 0); 2788 2789 ui_fill_live_frame_view(ui, split->u.region_split.right->u.view.child->u.generic); 2790 2791 split = split->u.region_split.left; 2792 split->u.region_split.left = add_beamformer_parameters_view(split, ctx); 2793 split->u.region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.22, 2794 RSD_VERTICAL, ui->font); 2795 split = split->u.region_split.right; 2796 2797 split->u.region_split.left = add_compute_progress_bar(split, ctx); 2798 split->u.region_split.right = add_compute_stats_view(ui, split, &ui->arena, 2799 VT_COMPUTE_LATEST_STATS_VIEW); 2800 2801 ComputeStatsView *compute_stats = &split->u.region_split.right->u.group.first->u.compute_stats_view; 2802 compute_stats->ctx = ctx; 2803 compute_stats->stats = &ui->latest_compute_stats; 2804 2805 ctx->ui_read_params = 1; 2806 2807 /* NOTE(rnp): shrink variable size once this fires */ 2808 ASSERT(ui->arena.beg - (u8 *)ui < KB(64)); 2809 } 2810 2811 function void 2812 validate_ui_parameters(BeamformerUIParameters *p) 2813 { 2814 if (p->output_min_coordinate[0] > p->output_max_coordinate[0]) 2815 SWAP(p->output_min_coordinate[0], p->output_max_coordinate[0]) 2816 if (p->output_min_coordinate[2] > p->output_max_coordinate[2]) 2817 SWAP(p->output_min_coordinate[2], p->output_max_coordinate[2]) 2818 } 2819 2820 function void 2821 draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw, ImagePlaneTag frame_plane, 2822 ComputeShaderStats *latest_compute_stats) 2823 { 2824 BeamformerUI *ui = ctx->ui; 2825 2826 ui->latest_plane[IPT_LAST] = frame_to_draw; 2827 ui->latest_plane[frame_plane] = frame_to_draw; 2828 ui->latest_compute_stats = latest_compute_stats; 2829 2830 /* TODO(rnp): there should be a better way of detecting this */ 2831 if (ctx->ui_read_params) { 2832 mem_copy(&ui->params, &ctx->shared_memory->parameters.output_min_coordinate, sizeof(ui->params)); 2833 ui->flush_params = 0; 2834 ctx->ui_read_params = 0; 2835 } 2836 2837 /* NOTE: process interactions first because the user interacted with 2838 * the ui that was presented last frame */ 2839 ui_interact(ui, input, ctx->window_size); 2840 2841 if (ui->flush_params) { 2842 validate_ui_parameters(&ui->params); 2843 BeamformWork *work = beamform_work_queue_push(ctx->beamform_work_queue); 2844 if (work && try_wait_sync(&ctx->shared_memory->parameters_sync, 0, ctx->os.wait_on_value)) { 2845 BeamformerUploadContext *uc = &work->upload_context; 2846 uc->shared_memory_offset = offsetof(BeamformerSharedMemory, parameters); 2847 uc->size = sizeof(ctx->shared_memory->parameters); 2848 uc->kind = BU_KIND_PARAMETERS; 2849 work->type = BW_UPLOAD_BUFFER; 2850 work->completion_barrier = (iptr)&ctx->shared_memory->parameters_sync; 2851 mem_copy(&ctx->shared_memory->parameters_ui, &ui->params, sizeof(ui->params)); 2852 beamform_work_queue_push_commit(ctx->beamform_work_queue); 2853 ui->flush_params = 0; 2854 ctx->start_compute = 1; 2855 } 2856 } 2857 2858 /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */ 2859 Rect window_rect = {.size = {.w = ctx->window_size.w, .h = ctx->window_size.h}}; 2860 update_frame_views(ui, window_rect); 2861 2862 BeginDrawing(); 2863 glClearColor(BG_COLOUR.r, BG_COLOUR.g, BG_COLOUR.b, BG_COLOUR.a); 2864 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 2865 2866 draw_ui_regions(ui, window_rect, input->mouse); 2867 if (ui->interaction.type == IT_TEXT) 2868 draw_active_text_box(ui, ui->interaction.active); 2869 if (ui->interaction.type == IT_MENU) 2870 draw_active_menu(ui, ui->arena, ui->interaction.active, input->mouse, window_rect); 2871 EndDrawing(); 2872 }