ui.c (182515B)
1 /* See LICENSE for license details. */ 2 /* TODO(rnp): 3 * [ ]: bug: flickering x-scroll bar on switch from ComputeBarGraph to other 4 * [ ]: word scan for text input 5 * [ ]: animation state 6 * [ ]: tooltips 7 * [ ]: extra copy view settings 8 * [ ]: refactor: all drag overlay floating elements can be children of the drag_root. 9 * as long as we layout before chaining them on there won't be an issue. 10 * [ ]: refactor: can the scroll container just use the ViewScroll flags like the tab bar? 11 * [ ]: refactor: it would be nice to have some table building helpers 12 * 13 * [ ]: refactor: cross plane view for non XZ/YZ planes. math needs to be cleaned up 14 * to support this. 15 * - model transform needs to first rotate so that Z is normal, then scale, the rotate from Z to Y. 16 * - ideally the hardcoded +0.25f rotation for YZ should just be a consequence of the math 17 * [ ]: command window 18 * [ ]: 3D data view 19 * - add extra view controls, change view without recompute 20 * - to start just have starting plane/normal, plane uvs, rotation, and offset 21 * - confirmation on recompute 22 * [ ]: rich highlighting for parameters -> X-Plane link 23 * 24 * [ ]: multi-os windows 25 * [ ]: ui color configuration at runtime 26 */ 27 28 #include "assets/generated/assets.c" 29 30 #define NIL_COLOUR (v4){{0.76f, 0.00f, 0.65f, 1.0f}} 31 #define BG_COLOUR (v4){{0.15f, 0.12f, 0.13f, 1.0f}} 32 #define FG_COLOUR (v4){{0.92f, 0.88f, 0.78f, 1.0f}} 33 #define FOCUSED_COLOUR (v4){{0.86f, 0.28f, 0.21f, 1.0f}} 34 #define HOVERED_COLOUR (v4){{0.11f, 0.50f, 0.59f, 1.0f}} 35 #define SELECTION_COLOUR (v4){{0.07f, 0.37f, 0.90f, 0.5f}} 36 #define RULER_COLOUR (v4){{1.00f, 0.70f, 0.00f, 1.0f}} 37 #define BORDER_COLOUR v4_lerp(FG_COLOUR, BG_COLOUR, 0.85f) 38 #define NODE_SPLIT_COLOUR (v4){{0.6f, 0.6f, 0.6f, 0.5f}} 39 40 #define FRAME_VIEW_BB_COLOUR (v4){{0.92f, 0.88f, 0.78f, 1.0f}} 41 #define FRAME_VIEW_BB_FRACTION 0.007f 42 #define FRAME_VIEW_RENDER_TARGET_SIZE 1024, 1024 43 44 #define MENU_PLUS_COLOUR (v4){{0.33f, 0.42f, 1.00f, 1.00f}} 45 #define MENU_CLOSE_COLOUR FOCUSED_COLOUR 46 47 #define UI_NODE_PAD 8.f 48 #define UI_BORDER_THICK 4.f 49 50 #define UI_HASH_TABLE_COUNT 4096 51 52 read_only global v4 g_colour_palette[] = { 53 {{0.32f, 0.20f, 0.50f, 1.00f}}, 54 {{0.14f, 0.39f, 0.61f, 1.00f}}, 55 {{0.61f, 0.14f, 0.25f, 1.00f}}, 56 {{0.20f, 0.60f, 0.24f, 1.00f}}, 57 {{0.80f, 0.60f, 0.20f, 1.00f}}, 58 {{0.15f, 0.51f, 0.74f, 1.00f}}, 59 }; 60 61 #define HOVER_SPEED 5.0f 62 #define BLINK_SPEED 1.5f 63 64 #define TABLE_CELL_PAD_HEIGHT 2.0f 65 #define TABLE_CELL_PAD_WIDTH 8.0f 66 67 #define RULER_TEXT_PAD 6.0f 68 #define RULER_TICK_LENGTH 20.0f 69 70 #define UI_SPLIT_HANDLE_THICK 5.0f 71 #define UI_REGION_PAD 32.0f 72 73 /* TODO(rnp) smooth scroll */ 74 #define UI_SCROLL_SPEED 12.0f 75 76 #define LISTING_LINE_PAD 6.0f 77 #define TITLE_BAR_PAD 6.0f 78 79 typedef enum { 80 UINodeFlag_MouseClickable = 1ull << 0, 81 UINodeFlag_KeyboardClickable = 1ull << 1, 82 UINodeFlag_DropSite = 1ull << 2, 83 UINodeFlag_ClickToFocus = 1ull << 3, 84 UINodeFlag_Scroll = 1ull << 4, 85 UINodeFlag_FocusHot = 1ull << 5, 86 UINodeFlag_FocusActive = 1ull << 6, 87 UINodeFlag_FocusHotDisabled = 1ull << 7, 88 UINodeFlag_FocusActiveDisabled = 1ull << 8, 89 UINodeFlag_Disabled = 1ull << 9, 90 91 UINodeFlag_FloatingX = 1ull << 10, 92 UINodeFlag_FloatingY = 1ull << 11, 93 UINodeFlag_FixedWidth = 1ull << 12, 94 UINodeFlag_FixedHeight = 1ull << 13, 95 UINodeFlag_AllowOverflowX = 1ull << 14, 96 UINodeFlag_AllowOverflowY = 1ull << 15, 97 98 // NOTE(rnp): for scrollable containers 99 UINodeFlag_ViewScrollX = 1ull << 16, 100 UINodeFlag_ViewScrollY = 1ull << 17, 101 102 UINodeFlag_DrawDropShadow = 1ull << 18, 103 UINodeFlag_DrawBackgroundBlur = 1ull << 19, 104 UINodeFlag_DrawBackground = 1ull << 20, 105 UINodeFlag_DrawBorder = 1ull << 21, 106 UINodeFlag_DrawText = 1ull << 22, 107 UINodeFlag_DrawHotEffects = 1ull << 23, 108 UINodeFlag_DrawActiveEffects = 1ull << 24, 109 UINodeFlag_DrawOverlay = 1ull << 25, 110 UINodeFlag_Clip = 1ull << 26, 111 UINodeFlag_DisableTextTrunc = 1ull << 27, 112 UINodeFlag_DisableFocusBorder = 1ull << 28, 113 UINodeFlag_DisableFocusOverlay = 1ull << 29, 114 115 UINodeFlag_TextInput = 1ull << 30, 116 UINodeFlag_TextInputNumeric = 1ull << 31, 117 UINodeFlag_TextInputClearOnStart = 1ull << 32, 118 119 UINodeFlag_CustomDraw = 1ull << 33, 120 121 // TODO(rnp): hack: when text is not drawn with raylib do something smarter 122 UINodeFlag_IconText = 1ull << 34, 123 124 UINodeFlag_Clickable = UINodeFlag_MouseClickable|UINodeFlag_KeyboardClickable, 125 UINodeFlag_Floating = UINodeFlag_FloatingX|UINodeFlag_FloatingY, 126 UINodeFlag_FixedSize = UINodeFlag_FixedWidth|UINodeFlag_FixedHeight, 127 UINodeFlag_AllowOverflow = UINodeFlag_AllowOverflowX|UINodeFlag_AllowOverflowY, 128 UINodeFlag_DisableFocusEffects = UINodeFlag_DisableFocusBorder|UINodeFlag_DisableFocusOverlay, 129 UINodeFlag_ViewScroll = UINodeFlag_ViewScrollX|UINodeFlag_ViewScrollY, 130 } UINodeFlags; 131 132 typedef struct UINodeFlagsNode UINodeFlagsNode; 133 struct UINodeFlagsNode {UINodeFlagsNode *next; UINodeFlags v;}; 134 135 typedef struct Axis2Node Axis2Node; 136 struct Axis2Node {Axis2Node *next; Axis2 v;}; 137 138 typedef enum { 139 UISizeKind_Nil, 140 UISizeKind_Pixels, 141 UISizeKind_TextContent, 142 UISizeKind_PercentOfParent, 143 UISizeKind_ChildrenSum, 144 } UISizeKind; 145 146 typedef struct { 147 UISizeKind kind; 148 f32 value; 149 f32 strictness; 150 } UISize; 151 152 typedef struct UISizeNode UISizeNode; 153 struct UISizeNode {UISizeNode *next; UISize v;}; 154 155 typedef enum { 156 UIAlign_Left, 157 UIAlign_Right, 158 UIAlign_Center, 159 UIAlign_Count, 160 } UIAlign; 161 162 typedef struct UIAlignNode UIAlignNode; 163 struct UIAlignNode {UIAlignNode *next; UIAlign v;}; 164 165 typedef struct {u64 value;} UINodeKey; 166 167 typedef struct UINode UINode; 168 169 #define UI_CUSTOM_DRAW_FUNCTION(name) void name(UINode *node, Rect node_rect) 170 typedef UI_CUSTOM_DRAW_FUNCTION(UICustomDrawFunction); 171 172 struct UINode { 173 UINode *parent; 174 UINode *first_child; 175 UINode *last_child; 176 UINode *previous_sibling; 177 UINode *next_sibling; 178 179 u32 child_count; 180 181 UINodeFlags flags; 182 str8 string; 183 // NOTE(rnp): desired sizing info from build step 184 union { 185 struct { 186 UISize semantic_width; 187 UISize semantic_height; 188 }; 189 UISize semantic_size[Axis2_Count]; 190 }; 191 192 union { 193 struct { 194 UIAlign alignment_x; 195 UIAlign alignment_y; 196 }; 197 UIAlign alignment[Axis2_Count]; 198 }; 199 200 UIAlign text_alignment; 201 202 Axis2 child_layout_axis; 203 f32 font_size; 204 205 u64 last_frame_active_index; 206 UINodeKey key; 207 UINode *hash_prev; 208 UINode *hash_next; 209 210 // NOTE(rnp): recomputed every frame before drawing. also 211 // used on next frame for mouse collision detection. 212 f32 computed_position[Axis2_Count]; 213 f32 computed_size[Axis2_Count]; 214 215 v2 text_size; 216 217 // NOTE(rnp): persistent data 218 f32 active_t; 219 f32 hot_t; 220 221 v2 view_scroll_offset; 222 223 v4 bg_colour; 224 225 v4 text_colour; 226 v4 text_outline_colour; 227 f32 text_outline_thickness; 228 229 v4 border_colour; 230 f32 border_thickness; 231 232 UICustomDrawFunction *custom_draw_function; 233 void *custom_draw_context; 234 }; 235 236 typedef struct {UINode *first, *last;} UINodeHashBucket; 237 238 typedef struct UIParentNode UIParentNode; 239 struct UIParentNode {UIParentNode *next; UINode *v;}; 240 241 typedef enum { 242 UIMouseButtonKind_Left, 243 UIMouseButtonKind_Middle, 244 UIMouseButtonKind_Right, 245 UIMouseButtonKind_Count, 246 } UIMouseButtonKind; 247 248 typedef enum { 249 UISignalFlag_LeftPressed = (1 << 0), 250 UISignalFlag_MiddlePressed = (1 << 1), 251 UISignalFlag_RightPressed = (1 << 2), 252 253 UISignalFlag_LeftDragging = (1 << 3), 254 UISignalFlag_MiddleDragging = (1 << 4), 255 UISignalFlag_RightDragging = (1 << 5), 256 257 UISignalFlag_LeftDoubleDragging = (1 << 6), 258 UISignalFlag_MiddleDoubleDragging = (1 << 7), 259 UISignalFlag_RightDoubleDragging = (1 << 8), 260 261 UISignalFlag_LeftTripleDragging = (1 << 9), 262 UISignalFlag_MiddleTripleDragging = (1 << 10), 263 UISignalFlag_RightTripleDragging = (1 << 11), 264 265 UISignalFlag_LeftReleased = (1 << 12), 266 UISignalFlag_MiddleReleased = (1 << 13), 267 UISignalFlag_RightReleased = (1 << 14), 268 269 UISignalFlag_LeftClicked = (1 << 15), 270 UISignalFlag_MiddleClicked = (1 << 16), 271 UISignalFlag_RightClicked = (1 << 17), 272 273 UISignalFlag_LeftDoubleClicked = (1 << 18), 274 UISignalFlag_MiddleDoubleClicked = (1 << 19), 275 UISignalFlag_RightDoubleClicked = (1 << 20), 276 277 UISignalFlag_LeftTripleClicked = (1 << 21), 278 UISignalFlag_MiddleTripleClicked = (1 << 22), 279 UISignalFlag_RightTripleClicked = (1 << 23), 280 281 UISignalFlag_ScrolledX = (1 << 24), 282 UISignalFlag_ScrolledY = (1 << 25), 283 284 UISignalFlag_KeyboardPressed = (1 << 26), 285 286 UISignalFlag_Hovering = (1 << 27), 287 288 UISignalFlag_TextCommit = (1 << 28), 289 290 UISignalFlag_Scrolled = UISignalFlag_ScrolledX|UISignalFlag_ScrolledY, 291 UISignalFlag_Pressed = UISignalFlag_LeftPressed|UISignalFlag_KeyboardPressed, 292 UISignalFlag_Released = UISignalFlag_LeftReleased, 293 UISignalFlag_Clicked = UISignalFlag_LeftClicked|UISignalFlag_KeyboardPressed, 294 UISignalFlag_DoubleClicked = UISignalFlag_LeftDoubleClicked, 295 UISignalFlag_TripleClicked = UISignalFlag_LeftTripleClicked, 296 UISignalFlag_Dragging = UISignalFlag_LeftDragging, 297 } UISignalFlags; 298 299 typedef struct { 300 UINode *node; 301 v2 scroll; 302 str8 string; 303 UISignalFlags flags; 304 } UISignal; 305 306 typedef struct { 307 UINodeKey node_key; 308 UINodeKey next_node_key; 309 UINodeKey last_node_key; 310 311 i16 cursor; 312 i16 mark; 313 i16 count; 314 i16 last_count; 315 b32 numeric; 316 b32 changed; 317 // TODO(rnp): animation key 318 BeamformerUIBlinker blinker; 319 u8 buffer[256]; 320 u8 last_buffer[256]; 321 } UITextInputState; 322 323 typedef struct F32Node F32Node; 324 struct F32Node {F32Node *next; f32 v;}; 325 326 typedef struct V4Node V4Node; 327 struct V4Node {V4Node *next; v4 v;}; 328 329 #define UI_STACK_LIST \ 330 X(Axis2Node, child_layout_axis, Axis2, 0) \ 331 X(F32Node, font_size, f32, 0) \ 332 X(F32Node, border_thickness, f32, UI_BORDER_THICK) \ 333 X(F32Node, text_outline_thickness, f32, 0) \ 334 X(UINodeFlagsNode, flags, UINodeFlags, 0) \ 335 X(UIParentNode, parent, UINode *, (&ui_node_nil)) \ 336 X(UISizeNode, semantic_height, UISize, {0}) \ 337 X(UISizeNode, semantic_width, UISize, {0}) \ 338 X(UIAlignNode, alignment_y, UIAlign, 0) \ 339 X(UIAlignNode, alignment_x, UIAlign, 0) \ 340 X(UIAlignNode, text_alignment, UIAlign, UIAlign_Left) \ 341 X(V4Node, text_colour, v4, FG_COLOUR) \ 342 X(V4Node, text_outline_colour, v4, NIL_COLOUR) \ 343 X(V4Node, border_colour, v4, NIL_COLOUR) \ 344 X(V4Node, bg_colour, v4, NIL_COLOUR) \ 345 346 347 typedef struct { 348 u64 current_frame_index; 349 Arena arena; 350 351 v2 current_mouse; 352 v2 last_mouse; 353 u64 input_consumed[countof(((BeamformerInput *)0)->event_queue) / 64]; 354 static_assert(countof(((BeamformerInput *)0)->event_queue) % 64 == 0, ""); 355 356 Font font; 357 Font small_font; 358 359 BeamformerFrameView *view_first; 360 BeamformerFrameView *view_last; 361 BeamformerFrameView *view_freelist; 362 363 VulkanHandle pipelines[BeamformerShaderKind_RenderCount]; 364 365 OSHandle render_semaphores_export[2]; 366 VulkanHandle render_semaphores[2]; 367 u32 render_semaphores_gl[2]; 368 369 GPUImage render_3d_image; 370 GPUImage render_3d_depth_image; 371 RenderModel unit_cube_model; 372 373 BeamformerFrame latest_plane[BeamformerViewPlaneTag_Count]; 374 375 BeamformerUIParameters parameters; 376 b32 flush_parameters; 377 u32 selected_parameter_block; 378 379 // TODO(rnp): this should be per parameter block 380 f32 off_axis_position; 381 f32 beamform_plane; 382 383 BeamformerUIPanel *tree; 384 BeamformerUIPanel *tree_node_freelist; 385 386 // NOTE(rnp): context menu 387 UINode *context_menu_root; 388 UINodeKey context_menu_anchor_key; 389 UINodeKey context_menu_next_anchor_key; 390 BeamformerUIPanel *context_menu_panel; 391 BeamformerUIPanel *context_menu_next_panel; 392 f32 context_menu_open_t; 393 b32 context_menu_state_changed; 394 395 // NOTE(rnp): drag info 396 UINodeKey drop_target_key; // alway a stable node 397 UINode *drop_target_node; // may point to a transient node 398 UINode *drag_root; 399 UINode *drag_overlay_root; 400 UINode *drag_overlay_edges_root; 401 UINode *drag_overlay_tab_root; 402 BeamformerUIPanel *drag_panel; 403 f32 drag_open_t; 404 b32 drag_end; 405 406 // NOTE(rnp): User Interaction 407 UINodeKey hot_node_key; 408 UINodeKey active_node_key[UIMouseButtonKind_Count]; 409 // TODO(rnp): click timestamp history (double/triple press) 410 411 // NOTE(rnp): Builder State 412 UINode *node_freelist; 413 UINode *root_node; 414 Arena build_arenas[2]; 415 TempArena build_arena_savepoints[2]; 416 // NOTE(rnp): Builder Stacks 417 #define X(type, name, ...) struct {type *top; type *free; u64 count;} name##_node_stack; 418 UI_STACK_LIST 419 #undef X 420 421 UINodeHashBucket node_hash_table[UI_HASH_TABLE_COUNT]; 422 423 UITextInputState text_input_state; 424 } BeamformerUI; 425 426 typedef enum { 427 TF_NONE = 0, 428 TF_ROTATED = 1 << 0, 429 TF_LIMITED = 1 << 1, 430 TF_OUTLINED = 1 << 2, 431 } TextFlags; 432 433 typedef enum { 434 TextAlignment_Center, 435 TextAlignment_Left, 436 TextAlignment_Right, 437 } TextAlignment; 438 439 typedef struct { 440 Font *font; 441 Rect limits; 442 v4 colour; 443 v4 outline_colour; 444 f32 outline_thick; 445 f32 rotation; 446 TextAlignment align; 447 TextFlags flags; 448 } TextSpec; 449 450 global BeamformerUI *ui_context; 451 global BeamformerInput *beamformer_input; 452 453 read_only global UINode ui_node_nil = { 454 .parent = &ui_node_nil, 455 .first_child = &ui_node_nil, 456 .last_child = &ui_node_nil, 457 .previous_sibling = &ui_node_nil, 458 .next_sibling = &ui_node_nil, 459 }; 460 461 #define X(type, name, _t, impl) read_only global type ui_##name##_node_nil = {.v = impl}; 462 UI_STACK_LIST 463 #undef X 464 465 #define ui_node_is_nil(n) ((n) == 0 || (n) == &ui_node_nil) 466 #define ui_build_arena() (ui_context->build_arenas + (ui_context->current_frame_index % countof(ui_context->build_arenas))) 467 468 #define UIStackPushBody(name_upper, name_lower, type, new_value) \ 469 name_upper *node = SLLPop(ui_context->name_lower##_node_stack.free, next); \ 470 if (!node) node = push_struct_no_zero(ui_build_arena(), name_upper); \ 471 node->v = new_value; \ 472 type result = ui_context->name_lower##_node_stack.top->v; \ 473 SLLStackPush(ui_context->name_lower##_node_stack.top, node, next); \ 474 ui_context->name_lower##_node_stack.count++; \ 475 return result 476 477 #define UIStackPopBody(name_upper, name_lower, type) \ 478 name_upper *node = ui_context->name_lower##_node_stack.top; \ 479 type result = node->v; \ 480 if (node != &ui_##name_lower##_node_nil) { \ 481 node = SLLPop(ui_context->name_lower##_node_stack.top, next); \ 482 SLLStackPush(ui_context->name_lower##_node_stack.free, node, next); \ 483 } \ 484 return result 485 486 #define UIAlign(v) DeferLoop(ui_push_alignment(UIAlign_##v), ui_pop_alignment()) 487 #define UIAxisAlign(axis, v) DeferLoop(ui_push_axis_alignment(axis, UIAlign_##v), ui_pop_axis_alignment(axis)) 488 #define UIAxisSize(axis, v) DeferLoop(ui_push_axis_size(axis, v), ui_pop_axis_size(axis)) 489 #define UIBorderColour(v) DeferLoop(ui_push_border_colour(v), ui_pop_border_colour()) 490 #define UIBorderThickness(v) DeferLoop(ui_push_border_thickness(v), ui_pop_border_thickness()) 491 #define UIBGColour(v) DeferLoop(ui_push_bg_colour(v), ui_pop_bg_colour()) 492 #define UIChildLayoutAxis(v) DeferLoop(ui_push_child_layout_axis(v), ui_pop_child_layout_axis()) 493 #define UIFlags(v) DeferLoop(ui_push_flags(v), ui_pop_flags()) 494 #define UIFontSize(v) DeferLoop(ui_push_font_size(v), ui_pop_font_size()) 495 #define UIParent(v) DeferLoop(ui_push_parent(v), ui_pop_parent()) 496 #define UIPrefHeight(v) DeferLoop(ui_push_semantic_height(v), ui_pop_semantic_height()) 497 #define UIPrefWidth(v) DeferLoop(ui_push_semantic_width(v), ui_pop_semantic_width()) 498 #define UISize(v) DeferLoop(ui_push_size(v), ui_pop_size()) 499 #define UITextAlign(v) DeferLoop(ui_push_text_alignment(UIAlign_##v), ui_pop_text_alignment()) 500 #define UITextOutlineColour(v) DeferLoop(ui_push_text_outline_colour(v), ui_pop_text_outline_colour()) 501 #define UITextOutlineThickness(v) DeferLoop(ui_push_text_outline_thickness(v), ui_pop_text_outline_thickness()) 502 #define UITextColour(v) DeferLoop(ui_push_text_colour(v), ui_pop_text_colour()) 503 504 #define UIScroll(axis) DeferLoop(ui_scroll_begin(axis), ui_scroll_end()) 505 506 #define X(type, name, value_type, ...) \ 507 function value_type ui_push_##name(value_type v) {UIStackPushBody(type, name, value_type, v);} \ 508 function value_type ui_pop_##name(void) {UIStackPopBody(type, name, value_type);} \ 509 function value_type ui_top_##name(void) {return ui_context->name##_node_stack.top->v;} 510 UI_STACK_LIST 511 #undef X 512 513 #define ui_size(k, v, s) (UISize){.kind = UISizeKind_##k, .value = (v), .strictness = (s)} 514 #define ui_em(value, strictness) ui_size(Pixels, (value) * ui_top_font_size(), (strictness)) 515 #define ui_px(value, strictness) ui_size(Pixels, (value), (strictness)) 516 #define ui_pct(value, strictness) ui_size(PercentOfParent, (value), (strictness)) 517 #define ui_children_sum(strictness) ui_size(ChildrenSum, 0.f, (strictness)) 518 #define ui_text_dim(padding, strictness) ui_size(TextContent, (padding), (strictness)) 519 520 #define ui_node_key_zero() (UINodeKey){0} 521 522 #define ui_spacer(flags) ui_build_node_from_key(flags, ui_node_key_zero()) 523 #define ui_padw(v) UIPrefWidth(ui_px(v, 1.f)) ui_spacer(0) 524 #define ui_padh(v) UIPrefHeight(ui_px(v, 1.f)) ui_spacer(0) 525 #define ui_pads(v) UISize(ui_px(v, 1.f)) ui_spacer(0) 526 527 #define ui_dragging(s) (!!((s).flags & UISignalFlag_Dragging)) 528 #define ui_released(s) (!!((s).flags & UISignalFlag_Released)) 529 #define ui_pressed(s) (!!((s).flags & UISignalFlag_Pressed)) 530 #define ui_scrolled(s) (!!((s).flags & UISignalFlag_Scrolled)) 531 532 #define ui_context_menu(p) ((p) == ui_context->context_menu_panel) 533 534 function UIAlign 535 ui_push_axis_alignment(Axis2 axis, UIAlign v) 536 { 537 UIAlign result = 0; 538 switch (axis) { 539 case Axis2_X:{result = ui_push_alignment_x(v);}break; 540 case Axis2_Y:{result = ui_push_alignment_y(v);}break; 541 InvalidDefaultCase; 542 } 543 return result; 544 } 545 546 function UIAlign 547 ui_pop_axis_alignment(Axis2 axis) 548 { 549 UIAlign result = 0; 550 switch (axis) { 551 case Axis2_X:{result = ui_pop_alignment_x();}break; 552 case Axis2_Y:{result = ui_pop_alignment_y();}break; 553 InvalidDefaultCase; 554 } 555 return result; 556 } 557 558 function UIAlign 559 ui_push_alignment(UIAlign v) 560 { 561 UIAlign result = ui_push_axis_alignment(ui_top_child_layout_axis(), v); 562 return result; 563 } 564 565 function UIAlign 566 ui_pop_alignment(void) 567 { 568 UIAlign result = ui_pop_axis_alignment(ui_top_child_layout_axis()); 569 return result; 570 } 571 572 function UISize 573 ui_push_axis_size(Axis2 axis, UISize v) 574 { 575 UISize result = {0}; 576 switch (axis) { 577 case Axis2_X:{result = ui_push_semantic_width(v); }break; 578 case Axis2_Y:{result = ui_push_semantic_height(v);}break; 579 InvalidDefaultCase; 580 } 581 return result; 582 } 583 584 function UISize 585 ui_pop_axis_size(Axis2 axis) 586 { 587 UISize result = {0}; 588 switch (axis) { 589 case Axis2_X:{result = ui_pop_semantic_width(); }break; 590 case Axis2_Y:{result = ui_pop_semantic_height();}break; 591 InvalidDefaultCase; 592 } 593 return result; 594 } 595 596 function UISize 597 ui_push_size(UISize v) 598 { 599 UISize result = ui_push_axis_size(ui_top_child_layout_axis(), v); 600 return result; 601 } 602 603 function UISize 604 ui_pop_size(void) 605 { 606 UISize result = ui_pop_axis_size(ui_top_child_layout_axis()); 607 return result; 608 } 609 610 #define ui_node_key_nil(k) (ui_node_key_equal((k), ui_node_key_zero())) 611 #define ui_node_hot(n) (ui_node_key_equal((n)->key, ui_context->hot_node_key)) 612 function b32 613 ui_node_key_equal(UINodeKey a, UINodeKey b) 614 { 615 b32 result = a.value == b.value; 616 return result; 617 } 618 619 function UINodeKey 620 ui_node_ancestor_key(void) 621 { 622 UINode *node = ui_top_parent(); 623 while (!ui_node_is_nil(node) && ui_node_key_equal(node->key, ui_node_key_zero())) 624 node = node->parent; 625 UINodeKey result = node->key; 626 return result; 627 } 628 629 function Rect 630 ui_node_rect(UINode *node) 631 { 632 Rect result = {0}; 633 result.size = (v2){{node->computed_size[0], node->computed_size[1]}}; 634 result.pos = (v2){{node->computed_position[0], node->computed_position[1]}}; 635 return result; 636 } 637 638 function f32 639 ui_alignment_correction(UIAlign alignment, f32 delta) 640 { 641 f32 result = 0; 642 switch (alignment) { 643 InvalidDefaultCase; 644 case UIAlign_Left:{ result = 0; }break; 645 case UIAlign_Center:{result = 0.5f * delta;}break; 646 case UIAlign_Right:{ result = delta; }break; 647 } 648 return result; 649 } 650 651 function v2 652 ui_node_text_position(UINode *node) 653 { 654 Rect r = ui_node_rect(node); 655 v2 result = r.pos; 656 result.x += ui_alignment_correction(node->text_alignment, r.size.x - node->text_size.x); 657 result.y += (r.size.y - node->text_size.y) / 2.f; 658 return result; 659 } 660 661 function void 662 ui_disable_cursor(void) 663 { 664 HideCursor(); 665 DisableCursor(); 666 /* wtf raylib */ 667 SetMousePosition((i32)ui_context->current_mouse.x, (i32)ui_context->current_mouse.y); 668 } 669 670 function void 671 ui_enable_cursor(void) 672 { 673 EnableCursor(); 674 } 675 676 function Vector2 677 rl_v2(v2 a) 678 { 679 Vector2 result = {a.x, a.y}; 680 return result; 681 } 682 683 function Rectangle 684 rl_rect(Rect a) 685 { 686 Rectangle result = {a.pos.x, a.pos.y, a.size.w, a.size.h}; 687 return result; 688 } 689 690 function f32 691 beamformer_ui_blinker_update(BeamformerUIBlinker *b, f32 scale) 692 { 693 b->t += b->scale * dt_for_frame; 694 if (b->t >= 1.0f) b->scale = -scale; 695 if (b->t <= 0.0f) b->scale = scale; 696 f32 result = b->t; 697 return result; 698 } 699 700 function v2 701 measure_glyph(Font font, u32 glyph) 702 { 703 assert(glyph >= 0x20); 704 v2 result = {.y = (f32)font.baseSize}; 705 /* NOTE: assumes font glyphs are ordered ASCII */ 706 result.x = (f32)font.glyphs[glyph - 0x20].advanceX; 707 if (result.x == 0) 708 result.x = (font.recs[glyph - 0x20].width + (f32)font.glyphs[glyph - 0x20].offsetX); 709 return result; 710 } 711 712 function v2 713 measure_text_tight(Font font, str8 text) 714 { 715 v2 result = {0}; 716 for (i64 i = 0; i < text.length; i++) { 717 assert(text.data[i] >= 0x20); 718 u8 glyph = text.data[i] - 0x20; 719 result.x += font.recs[glyph].width; 720 result.y = Max(font.recs[glyph].height, result.y); 721 } 722 return result; 723 } 724 725 function v2 726 measure_text(Font font, str8 text) 727 { 728 v2 result = {.y = (f32)font.baseSize}; 729 for (i64 i = 0; i < text.length; i++) 730 result.x += measure_glyph(font, text.data[i]).x; 731 return result; 732 } 733 734 function str8 735 clamp_text_to_width(Font font, str8 text, f32 limit) 736 { 737 str8 result = text; 738 f32 width = 0; 739 for (i64 i = 0; i < text.length; i++) { 740 f32 next = measure_glyph(font, text.data[i]).w; 741 if (width + next > limit) { 742 result.length = i; 743 break; 744 } 745 width += next; 746 } 747 return result; 748 } 749 750 function Texture 751 make_raylib_texture(BeamformerFrameView *v) 752 { 753 Texture result; 754 result.id = v->texture; 755 result.width = v->colour_image.width; 756 result.height = v->colour_image.height; 757 result.mipmaps = v->colour_image.mip_map_levels; 758 result.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 759 return result; 760 } 761 762 function str8 763 push_acquisition_kind(Arena *arena, BeamformerAcquisitionKind kind, u32 transmit_count, BeamformerContrastMode contrast_mode) 764 { 765 str8 name = str8("Invalid"); 766 b32 fixed_transmits = 0; 767 if Between(kind, 0, BeamformerAcquisitionKind_Count - 1) { 768 name = beamformer_acquisition_kind_strings[kind]; 769 fixed_transmits = beamformer_acquisition_kind_has_fixed_transmits[kind]; 770 } 771 772 Stream sb = arena_stream(*arena); 773 stream_append_s8(&sb, s8_from_str8(name)); 774 if (!fixed_transmits) { 775 stream_append_byte(&sb, '-'); 776 stream_append_u64(&sb, transmit_count); 777 } 778 779 if (contrast_mode != BeamformerContrastMode_None) 780 stream_append_s8s(&sb, s8(" ("), s8_from_str8(beamformer_contrast_mode_strings[contrast_mode]), s8(")")); 781 782 s8 result = arena_stream_commit(arena, &sb); 783 return str8_from_s8(result); 784 } 785 786 function void 787 resize_frame_view(BeamformerFrameView *view, uv2 dim) 788 { 789 if ValidHandle(view->export_handle) os_release_handle(view->export_handle); 790 791 glDeleteMemoryObjectsEXT(1, &view->memory_object); 792 glCreateMemoryObjectsEXT(1, &view->memory_object); 793 794 glDeleteTextures(1, &view->texture); 795 glCreateTextures(GL_TEXTURE_2D, 1, &view->texture); 796 797 /* TODO(rnp): add some ID for the specific view here */ 798 s8 label = s8("Frame View Texture"); 799 vk_image_allocate(&view->colour_image, dim.w, dim.h, 1, 1, VulkanImageUsage_Colour, 800 VulkanUsageFlag_ImageSampling, &view->export_handle, label); 801 802 glMemoryObjectParameterivEXT(view->memory_object, GL_DEDICATED_MEMORY_OBJECT_EXT, (GLint []){1}); 803 804 if (OS_WINDOWS) { 805 glImportMemoryWin32HandleEXT(view->memory_object, view->colour_image.memory_size, 806 GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, (void *)view->export_handle.value[0]); 807 // NOTE(rnp): w32 does not transfer ownership from handle back to driver 808 } else { 809 glImportMemoryFdEXT(view->memory_object, view->colour_image.memory_size, 810 GL_HANDLE_TYPE_OPAQUE_FD_EXT, view->export_handle.value[0]); 811 view->export_handle.value[0] = OSInvalidHandleValue; 812 } 813 814 glTextureStorageMem2DEXT(view->texture, view->colour_image.mip_map_levels, GL_RGBA8, 815 view->colour_image.width, view->colour_image.height, 816 view->memory_object, 0); 817 818 /* NOTE(rnp): work around raylib's janky texture sampling */ 819 v4 border_colour = {{0, 0, 0, 1}}; 820 if (view->kind != BeamformerFrameViewKind_Copy) border_colour = (v4){0}; 821 glTextureParameteri(view->texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); 822 glTextureParameteri(view->texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); 823 glTextureParameterfv(view->texture, GL_TEXTURE_BORDER_COLOR, border_colour.E); 824 /* TODO(rnp): better choice when depth component is included */ 825 glTextureParameteri(view->texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 826 glTextureParameteri(view->texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 827 828 glObjectLabel(GL_TEXTURE, view->texture, (i32)label.len, (char *)label.data); 829 } 830 831 function void 832 beamformer_ui_frame_view_release_subresources(BeamformerFrameView *bv, BeamformerFrameViewKind kind) 833 { 834 if (kind == BeamformerFrameViewKind_Copy) 835 vk_buffer_release(&bv->copy_buffer); 836 } 837 838 function void 839 beamformer_ui_frame_view_copy_frame(BeamformerFrameView *new, BeamformerFrameView *old) 840 { 841 memory_copy(&new->frame, &old->frame, sizeof(old->frame)); 842 843 iv3 points = new->frame.points; 844 i64 frame_size = points.x * points.y * points.z * beamformer_data_kind_byte_size[new->frame.data_kind]; 845 846 Stream sb = arena_stream(ui_context->arena); 847 stream_append_s8(&sb, s8("Frame Copy [")); 848 stream_append_hex_u64(&sb, new->frame.id); 849 stream_append_s8(&sb, s8("]")); 850 stream_append_byte(&sb, 0); 851 852 GPUBufferAllocateInfo allocate_info = { 853 .size = frame_size, 854 .flags = VulkanUsageFlag_TransferDestination, 855 .label = stream_to_str8(&sb), 856 }; 857 vk_buffer_allocate(&new->copy_buffer, &allocate_info); 858 859 GPUBuffer *backlog = beamformer_context->compute_context.backlog.buffer; 860 VulkanHandle cmd = vk_command_begin(VulkanTimeline_Compute); 861 vk_command_wait_timeline(cmd, VulkanTimeline_Compute, old->frame.timeline_valid_value); 862 vk_command_copy_buffer(cmd, &new->copy_buffer, backlog, old->frame.buffer_offset, frame_size); 863 new->frame.timeline_valid_value = vk_command_end(cmd, (VulkanHandle){0}, (VulkanHandle){0}); 864 } 865 866 function BeamformerFrameView * 867 beamformer_ui_frame_view_new(BeamformerFrameViewKind kind) 868 { 869 BeamformerFrameView *old = (BeamformerFrameView *)beamformer_registers()->frame_view; 870 BeamformerFrameView *result = SLLPopFreelist(ui_context->view_freelist); 871 if (!result) result = push_struct_no_zero(&ui_context->arena, typeof(*result)); 872 zero_struct(result); 873 DLLInsertLast(0, ui_context->view_first, ui_context->view_last, result, next, prev); 874 875 result->export_handle.value[0] = OSInvalidHandleValue; 876 877 result->kind = kind; 878 result->dirty = 1; 879 880 result->log_scale = old? old->log_scale : 0; 881 result->dynamic_range = old? old->dynamic_range : 50.0f; 882 result->threshold = old? old->threshold : 55.0f; 883 result->gamma = old? old->gamma : 1.0f; 884 885 /* TODO(rnp): this is quite dumb. what we actually want is to render directly 886 * into the view region with the appropriate size for that region (scissor) */ 887 resize_frame_view(result, (uv2){{FRAME_VIEW_RENDER_TARGET_SIZE}}); 888 889 switch (kind) { 890 default:{ 891 b32 copy = kind == BeamformerFrameViewKind_Copy; 892 result->scale_bar_active[0] = copy ? old->scale_bar_active[0] : 1; 893 result->scale_bar_active[1] = copy ? old->scale_bar_active[1] : 1; 894 }break; 895 case BeamformerFrameViewKind_3DXPlane:{ 896 glTextureParameteri(result->texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 897 glTextureParameteri(result->texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 898 result->demo = 1; 899 result->plane_drag_index = -1; 900 result->plane_active[BeamformerViewPlaneTag_XZ] = 1; 901 result->plane_active[BeamformerViewPlaneTag_YZ] = 1; 902 }break; 903 } 904 905 if (kind == BeamformerFrameViewKind_Copy) { 906 assert(old != 0); 907 beamformer_ui_frame_view_copy_frame(result, old); 908 } 909 910 if (kind == BeamformerFrameViewKind_Latest) 911 result->view_plane = BeamformerViewPlaneTag_Count; 912 913 return result; 914 } 915 916 function v3 917 x_plane_display_size(BeamformerFrame *frame) 918 { 919 v3 result = {0}; 920 v2 min_2d, max_2d; 921 plane_corners_from_transform(frame->voxel_transform, &min_2d, &max_2d); 922 result.xy = v2_sub(max_2d, min_2d); 923 result.x = Max(1e-3f, result.x); 924 result.y = Max(1e-3f, result.y); 925 result.z = Max(1e-3f, result.z); 926 return result; 927 } 928 929 function f32 930 x_plane_rotation_for_view_plane(BeamformerFrameView *view, BeamformerViewPlaneTag tag) 931 { 932 f32 result = view->rotation; 933 if (tag == BeamformerViewPlaneTag_YZ) 934 result += 0.25f; 935 return result; 936 } 937 938 function v3 939 x_plane_position(BeamformerFrame *frame) 940 { 941 v2 min_2d, max_2d; 942 plane_corners_from_transform(frame->voxel_transform, &min_2d, &max_2d); 943 f32 y_min = min_2d.y; 944 f32 y_max = max_2d.y; 945 v3 result = {.y = y_min + (y_max - y_min) / 2}; 946 return result; 947 } 948 949 function v3 950 x_plane_offset_position(BeamformerFrameView *view, BeamformerFrame *frame, BeamformerViewPlaneTag tag) 951 { 952 BeamformerLiveImagingParameters *li = &beamformer_context->shared_memory->live_imaging_parameters; 953 m4 x_rotation = m4_rotation_about_y(x_plane_rotation_for_view_plane(view, tag)); 954 v3 Z = x_rotation.c[2].xyz; 955 v3 offset = v3_scale(Z, li->image_plane_offsets[tag]); 956 v3 result = v3_add(x_plane_position(frame), offset); 957 return result; 958 } 959 960 function v3 961 x_plane_camera(BeamformerFrame *frame) 962 { 963 v3 size = x_plane_display_size(frame); 964 v3 target = x_plane_position(frame); 965 f32 dist = v2_magnitude(size.xy); 966 v3 result = v3_add(target, (v3){{dist, -0.5f * size.y * tan_f32(50.0f * PI / 180.0f), dist}}); 967 return result; 968 } 969 970 function m4 971 x_plane_view_matrix(BeamformerFrame *frame, v3 camera) 972 { 973 m4 result = camera_look_at(camera, x_plane_position(frame)); 974 return result; 975 } 976 977 function m4 978 x_plane_projection_matrix(f32 aspect) 979 { 980 m4 result = perspective_projection(10e-3f, 500e-3f, 45.0f * PI / 180.0f, aspect); 981 return result; 982 } 983 984 function ray 985 x_plane_raycast(BeamformerFrameView *view, BeamformerFrame *frame, v2 uv) 986 { 987 assert(view->kind == BeamformerFrameViewKind_3DXPlane); 988 ray result = {.origin = x_plane_camera(frame)}; 989 v4 ray_clip = {{uv.x, uv.y, -1.0f, 1.0f}}; 990 991 /* TODO(rnp): combine these so we only do one matrix inversion */ 992 m4 proj_m = x_plane_projection_matrix((f32)view->colour_image.width / (f32)view->colour_image.height); 993 m4 view_m = x_plane_view_matrix(frame, result.origin); 994 m4 proj_inv = m4_inverse(proj_m); 995 m4 view_inv = m4_inverse(view_m); 996 997 v4 ray_eye = {.z = -1}; 998 ray_eye.x = v4_dot(m4_row(proj_inv, 0), ray_clip); 999 ray_eye.y = v4_dot(m4_row(proj_inv, 1), ray_clip); 1000 result.direction = v3_normalize(m4_mul_v4(view_inv, ray_eye).xyz); 1001 1002 return result; 1003 } 1004 1005 function void 1006 render_single_xplane(BeamformerFrameView *view, BeamformerFrame *frame, v3 translate, f32 rotation_turns, 1007 VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc, m4 vp_m, b32 drag_plane) 1008 { 1009 GPUBuffer *beamformed_buffer = beamformer_context->compute_context.backlog.buffer; 1010 pc->input_data = frame->timeline_valid_value ? beamformed_buffer->gpu_pointer + frame->buffer_offset : 0; 1011 pc->input_size_x = frame->points.x; 1012 pc->input_size_y = frame->points.y; 1013 pc->input_size_z = frame->points.z; 1014 pc->data_kind = frame->data_kind; 1015 pc->mvp_matrix = m4_mul(vp_m, y_aligned_volume_transform(x_plane_display_size(frame), translate, rotation_turns)); 1016 1017 vk_command_wait_timeline(command, VulkanTimeline_Compute, frame->timeline_valid_value); 1018 vk_command_push_constants(command, 0, sizeof(*pc), pc); 1019 vk_command_draw(command, &ui_context->unit_cube_model.model); 1020 1021 v3 xp_delta = v3_sub(view->hit_test_point, view->hit_start_point); 1022 if (drag_plane && !f32_equal(v3_magnitude_squared(xp_delta), 0)) { 1023 m4 x_rotation = m4_rotation_about_y(rotation_turns); 1024 v3 Z = x_rotation.c[2].xyz; 1025 v3 f = v3_scale(Z, v3_dot(Z, xp_delta)); 1026 1027 pc->mvp_matrix = m4_mul(vp_m, y_aligned_volume_transform(x_plane_display_size(frame), v3_add(f, translate), rotation_turns)); 1028 pc->bounding_box_colour = HOVERED_COLOUR; 1029 pc->bounding_box_fraction = 1.0f; 1030 pc->input_data = 0; 1031 1032 vk_command_push_constants(command, 0, sizeof(*pc), pc); 1033 vk_command_draw(command, &ui_context->unit_cube_model.model); 1034 } 1035 } 1036 1037 function void 1038 render_3d_xplane(BeamformerFrameView *view, VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc) 1039 { 1040 if (view->demo) { 1041 view->rotation += dt_for_frame * 0.125f; 1042 if (view->rotation > 1.f) view->rotation -= 1.f; 1043 } 1044 1045 u32 largest_plane_index = 0; 1046 f32 largest_magnitude = 0.f; 1047 for EachElement(view->plane_active, plane) if (view->plane_active[plane]) { 1048 BeamformerFrame *frame = ui_context->latest_plane + plane; 1049 f32 m = v3_magnitude_squared(x_plane_display_size(frame)); 1050 if (largest_magnitude < m) { 1051 largest_magnitude = m; 1052 largest_plane_index = plane; 1053 } 1054 } 1055 1056 BeamformerFrame *frame = ui_context->latest_plane + largest_plane_index; 1057 m4 projection = x_plane_projection_matrix((f32)view->colour_image.width / (f32)view->colour_image.height); 1058 m4 view_m = camera_look_at(x_plane_camera(frame), x_plane_position(frame)); 1059 m4 vp_m = m4_mul(projection, view_m); 1060 1061 for EachElement(view->plane_active, plane) { 1062 frame = ui_context->latest_plane + plane; 1063 if (view->plane_active[plane] && frame->timeline_valid_value) { 1064 pc->bounding_box_fraction = FRAME_VIEW_BB_FRACTION; 1065 pc->bounding_box_colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, view->hot_t[plane]); 1066 f32 rotation = x_plane_rotation_for_view_plane(view, plane); 1067 v3 translate = x_plane_offset_position(view, frame, plane); 1068 render_single_xplane(view, frame, translate, rotation, command, pc, vp_m, (i32)plane == view->plane_drag_index); 1069 } 1070 } 1071 } 1072 1073 function void 1074 render_2d_plane(BeamformerFrameView *view, VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc) 1075 { 1076 m4 view_m = m4_identity(); 1077 m4 model = m4_scale((v3){{2.0f, 2.0f, 0.0f}}); 1078 m4 projection = orthographic_projection(0, 1, 1, 1); 1079 1080 GPUBuffer *beamformed_buffer = beamformer_context->compute_context.backlog.buffer; 1081 pc->mvp_matrix = m4_mul(m4_mul(model, view_m), projection); 1082 pc->input_data = beamformed_buffer->gpu_pointer + view->frame.buffer_offset; 1083 pc->input_size_x = view->frame.points.x; 1084 pc->input_size_y = view->frame.points.y; 1085 pc->input_size_z = view->frame.points.z; 1086 pc->data_kind = view->frame.data_kind; 1087 1088 vk_command_wait_timeline(command, VulkanTimeline_Compute, view->frame.timeline_valid_value); 1089 vk_command_push_constants(command, 0, sizeof(*pc), pc); 1090 vk_command_draw(command, &ui_context->unit_cube_model.model); 1091 } 1092 1093 function b32 1094 view_update(BeamformerUI *ui, BeamformerFrameView *view) 1095 { 1096 if (view->kind == BeamformerFrameViewKind_Latest) { 1097 BeamformerFrame *frame; 1098 if (view->view_plane == BeamformerViewPlaneTag_Count) 1099 frame = beamformer_frame_from_index(beamformer_registers()->frame); 1100 else 1101 frame = ui->latest_plane + view->view_plane; 1102 1103 view->dirty |= view->frame.timeline_valid_value != frame->timeline_valid_value; 1104 memory_copy(&view->frame, frame, sizeof(view->frame)); 1105 } 1106 1107 /* TODO(rnp): x-z or y-z */ 1108 // TODO(rnp): how to track this now? use pipeline handle value? 1109 view->dirty |= beamformer_context->render_shader_updated; 1110 view->dirty |= view->kind == BeamformerFrameViewKind_3DXPlane; 1111 1112 b32 result = view->dirty; 1113 return result; 1114 } 1115 1116 function void 1117 update_frame_views(BeamformerUI *ui, Rect window) 1118 { 1119 for (BeamformerFrameView *view = ui->view_first; view; view = view->next) { 1120 if (view_update(ui, view)) { 1121 BeamformerRenderBeamformedPushConstants pc = { 1122 .bounding_box_colour = FRAME_VIEW_BB_COLOUR, 1123 .db_cutoff = view->log_scale ? view->dynamic_range : 0, 1124 .threshold = view->threshold, 1125 .gamma = view->gamma, 1126 .positions = ui->unit_cube_model.model.gpu_pointer, 1127 .normals = ui->unit_cube_model.model.gpu_pointer + ui->unit_cube_model.normals_offset, 1128 }; 1129 1130 //start_renderdoc_capture(); 1131 1132 glSignalSemaphoreEXT(ui->render_semaphores_gl[0], 0, 0, 1, &view->texture, (GLenum []){GL_NONE}); 1133 1134 VulkanHandle cmd = vk_command_begin(VulkanTimeline_Graphics); 1135 vk_command_bind_pipeline(cmd, ui->pipelines[BeamformerShaderKind_RenderBeamformed - BeamformerShaderKind_RenderFirst]); 1136 vk_command_begin_rendering(cmd, &ui->render_3d_image, &ui->render_3d_depth_image, &view->colour_image); 1137 vk_command_viewport(cmd, view->colour_image.width, view->colour_image.height, 0, 0, 0.0f, 1.0f); 1138 vk_command_scissor(cmd, view->colour_image.width, view->colour_image.height, 0, 0); 1139 if (view->kind == BeamformerFrameViewKind_3DXPlane) { 1140 render_3d_xplane(view, cmd, &pc); 1141 } else { 1142 render_2d_plane(view, cmd, &pc); 1143 } 1144 vk_command_end_rendering(cmd); 1145 vk_command_end(cmd, ui->render_semaphores[0], ui->render_semaphores[1]); 1146 1147 glWaitSemaphoreEXT(ui->render_semaphores_gl[1], 0, 0, 1, &view->texture, (GLenum[]){GL_LAYOUT_COLOR_ATTACHMENT_EXT}); 1148 1149 //end_renderdoc_capture(); 1150 view->dirty = 0; 1151 } 1152 } 1153 } 1154 1155 function Color 1156 colour_from_normalized(v4 rgba) 1157 { 1158 Color result = {.r = (u8)(rgba.r * 255.0f), .g = (u8)(rgba.g * 255.0f), 1159 .b = (u8)(rgba.b * 255.0f), .a = (u8)(rgba.a * 255.0f)}; 1160 return result; 1161 } 1162 1163 function void 1164 draw_text_tight(Font font, str8 text, v2 pos, Color colour) 1165 { 1166 v2 off = v2_floor(pos); 1167 for (i64 i = 0; i < text.length; i++) { 1168 /* NOTE: assumes font glyphs are ordered ASCII */ 1169 i32 idx = text.data[i] - 0x20; 1170 Rectangle dst = { 1171 off.x, off.y, 1172 font.recs[idx].width, 1173 font.recs[idx].height, 1174 }; 1175 Rectangle src = { 1176 font.recs[idx].x, 1177 font.recs[idx].y, 1178 font.recs[idx].width, 1179 font.recs[idx].height, 1180 }; 1181 DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); 1182 1183 off.x += (f32)font.recs[idx].width; 1184 } 1185 } 1186 1187 function v2 1188 draw_text_base(Font font, str8 text, v2 pos, Color colour) 1189 { 1190 v2 off = v2_floor(pos); 1191 f32 glyph_pad = (f32)font.glyphPadding; 1192 for (i64 i = 0; i < text.length; i++) { 1193 /* NOTE: assumes font glyphs are ordered ASCII */ 1194 i32 idx = text.data[i] - 0x20; 1195 Rectangle dst = { 1196 off.x + (f32)font.glyphs[idx].offsetX - glyph_pad, 1197 off.y + (f32)font.glyphs[idx].offsetY - glyph_pad, 1198 font.recs[idx].width + 2.0f * glyph_pad, 1199 font.recs[idx].height + 2.0f * glyph_pad 1200 }; 1201 Rectangle src = { 1202 font.recs[idx].x - glyph_pad, 1203 font.recs[idx].y - glyph_pad, 1204 font.recs[idx].width + 2.0f * glyph_pad, 1205 font.recs[idx].height + 2.0f * glyph_pad 1206 }; 1207 DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); 1208 1209 off.x += (f32)font.glyphs[idx].advanceX; 1210 if (font.glyphs[idx].advanceX == 0) 1211 off.x += font.recs[idx].width; 1212 } 1213 v2 result = {{off.x - pos.x, (f32)font.baseSize}}; 1214 return result; 1215 } 1216 1217 /* NOTE(rnp): expensive but of the available options in raylib this gives the best results */ 1218 function v2 1219 draw_outlined_text(str8 text, v2 pos, TextSpec *ts) 1220 { 1221 f32 ow = ts->outline_thick; 1222 Color outline = colour_from_normalized(ts->outline_colour); 1223 Color colour = colour_from_normalized(ts->colour); 1224 draw_text_base(*ts->font, text, v2_sub(pos, (v2){{ ow, ow}}), outline); 1225 draw_text_base(*ts->font, text, v2_sub(pos, (v2){{ ow, -ow}}), outline); 1226 draw_text_base(*ts->font, text, v2_sub(pos, (v2){{-ow, ow}}), outline); 1227 draw_text_base(*ts->font, text, v2_sub(pos, (v2){{-ow, -ow}}), outline); 1228 1229 v2 result = draw_text_base(*ts->font, text, pos, colour); 1230 1231 return result; 1232 } 1233 1234 function v2 1235 draw_text(str8 text, v2 pos, TextSpec *ts) 1236 { 1237 if (ts->flags & TF_ROTATED) { 1238 rlPushMatrix(); 1239 rlTranslatef(pos.x, pos.y, 0); 1240 rlRotatef(ts->rotation, 0, 0, 1); 1241 pos = (v2){0}; 1242 } 1243 1244 v2 result = measure_text(*ts->font, text); 1245 /* TODO(rnp): the size of this should be stored for each font */ 1246 str8 ellipsis = str8("..."); 1247 b32 clamped = ts->flags & TF_LIMITED && result.w > ts->limits.size.w; 1248 if (clamped) { 1249 f32 ellipsis_width = measure_text(*ts->font, ellipsis).x; 1250 if (ellipsis_width < ts->limits.size.w) { 1251 text = clamp_text_to_width(*ts->font, text, ts->limits.size.w - ellipsis_width); 1252 } else { 1253 text.length = 0; 1254 ellipsis.length = 0; 1255 } 1256 } 1257 1258 Color colour = colour_from_normalized(ts->colour); 1259 if (ts->flags & TF_OUTLINED) result.x = draw_outlined_text(text, pos, ts).x; 1260 else result.x = draw_text_base(*ts->font, text, pos, colour).x; 1261 1262 if (clamped) { 1263 pos.x += result.x; 1264 if (ts->flags & TF_OUTLINED) result.x += draw_outlined_text(ellipsis, pos, ts).x; 1265 else result.x += draw_text_base(*ts->font, ellipsis, pos, 1266 colour).x; 1267 } 1268 1269 if (ts->flags & TF_ROTATED) rlPopMatrix(); 1270 1271 return result; 1272 } 1273 1274 function b32 1275 point_in_rect(v2 p, Rect r) 1276 { 1277 v2 end = v2_add(r.pos, r.size); 1278 b32 result = Between(p.x, r.pos.x, end.x) & Between(p.y, r.pos.y, end.y); 1279 return result; 1280 } 1281 1282 function v3 1283 world_point_from_plane_uv(m4 world, v2 uv) 1284 { 1285 v3 U = world.c[0].xyz; 1286 v3 V = world.c[1].xyz; 1287 v3 min = world.c[3].xyz; 1288 v3 result = v3_add(v3_add(v3_scale(U, uv.x), v3_scale(V, uv.y)), min); 1289 return result; 1290 } 1291 1292 function v2 1293 screen_point_to_world_2d(v2 p, v2 screen_min, v2 screen_max, v2 world_min, v2 world_max) 1294 { 1295 v2 pixels_to_m = v2_div(v2_sub(world_max, world_min), v2_sub(screen_max, screen_min)); 1296 v2 result = v2_add(v2_mul(v2_sub(p, screen_min), pixels_to_m), world_min); 1297 return result; 1298 } 1299 1300 function v2 1301 world_point_to_screen_2d(v2 p, v2 world_min, v2 world_max, v2 screen_min, v2 screen_max) 1302 { 1303 v2 m_to_pixels = v2_div(v2_sub(screen_max, screen_min), v2_sub(world_max, world_min)); 1304 v2 result = v2_add(v2_mul(v2_sub(p, world_min), m_to_pixels), screen_min); 1305 return result; 1306 } 1307 1308 function void 1309 draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts) 1310 { 1311 // TODO(rnp): merge this into draw function, tons of duplicate code 1312 v2 vr_max_p = v2_add(view_rect.pos, view_rect.size); 1313 1314 v3 U = view->frame.voxel_transform.c[0].xyz; 1315 v3 V = view->frame.voxel_transform.c[1].xyz; 1316 v3 min = view->frame.voxel_transform.c[3].xyz; 1317 1318 v3 end = view->ruler.end; 1319 if (view->ruler.state != RulerState_Hold) 1320 end = world_point_from_plane_uv(view->frame.voxel_transform, rect_uv(ui_context->current_mouse, view_rect)); 1321 1322 v2 start_uv = plane_uv(v3_sub(view->ruler.start, min), U, V); 1323 v2 end_uv = plane_uv(v3_sub(end, min), U, V); 1324 1325 v2 start_p = v2_add(view_rect.pos, v2_mul(start_uv, view_rect.size)); 1326 v2 end_p = v2_add(view_rect.pos, v2_mul(end_uv, view_rect.size)); 1327 1328 b32 start_in_bounds = point_in_rect(start_p, view_rect); 1329 b32 end_in_bounds = point_in_rect(end_p, view_rect); 1330 1331 // TODO(rnp): this should be a ray intersection not a clamp 1332 start_p = clamp_v2_rect(start_p, view_rect); 1333 end_p = clamp_v2_rect(end_p, view_rect); 1334 1335 Color rl_colour = colour_from_normalized(ts.colour); 1336 DrawLineEx(rl_v2(end_p), rl_v2(start_p), 2, rl_colour); 1337 if (start_in_bounds) DrawCircleV(rl_v2(start_p), 3, rl_colour); 1338 if (end_in_bounds) DrawCircleV(rl_v2(end_p), 3, rl_colour); 1339 1340 Stream buf = arena_stream(a); 1341 stream_append_f64(&buf, 1e3 * v3_magnitude(v3_sub(end, view->ruler.start)), 100); 1342 stream_append_s8(&buf, s8(" mm")); 1343 1344 s8 s = stream_to_s8(&buf); 1345 v2 txt_p = start_p; 1346 v2 txt_s = measure_text(*ts.font, str8_from_s8(s)); 1347 v2 pixel_delta = v2_sub(start_p, end_p); 1348 if (pixel_delta.y < 0) txt_p.y -= txt_s.y; 1349 if (pixel_delta.x < 0) txt_p.x -= txt_s.x; 1350 if (txt_p.x < view_rect.pos.x) txt_p.x = view_rect.pos.x; 1351 if (txt_p.x + txt_s.x > vr_max_p.x) txt_p.x -= (txt_p.x + txt_s.x) - vr_max_p.x; 1352 1353 draw_text(str8_from_s8(s), txt_p, &ts); 1354 } 1355 1356 function void 1357 ui_event_consume(BeamformerInput *input, BeamformerInputEvent *current) 1358 { 1359 BeamformerUI *ui = ui_context; 1360 BeamformerInputEvent *last = input->event_queue + input->event_count - 1; 1361 if Between(current, input->event_queue, last) { 1362 u64 index = current - input->event_queue; 1363 u64 bin = index / (sizeof(ui->input_consumed[0]) * 8); 1364 u64 bit = index % (sizeof(ui->input_consumed[0]) * 8); 1365 ui->input_consumed[bin] |= (1 << bit); 1366 } 1367 } 1368 1369 function BeamformerInputEvent * 1370 ui_event_next(BeamformerInput *input, BeamformerInputEvent *current) 1371 { 1372 BeamformerUI *ui = ui_context; 1373 BeamformerInputEvent *result = 0, *last = input->event_queue + input->event_count - 1; 1374 1375 current++; 1376 current = Max(current, input->event_queue); 1377 1378 for (; !result && Between(current, input->event_queue, last); current++) { 1379 u64 index = current - input->event_queue; 1380 u64 bin = index / (sizeof(ui->input_consumed[0]) * 8); 1381 u64 bit = index % (sizeof(ui->input_consumed[0]) * 8); 1382 1383 if (!(ui->input_consumed[bin] & (1 << bit)) && 1384 (current->kind == BeamformerInputEventKind_ButtonPress || 1385 current->kind == BeamformerInputEventKind_ButtonRelease || 1386 current->kind == BeamformerInputEventKind_MouseScroll)) 1387 { 1388 result = current; 1389 } 1390 } 1391 return result; 1392 } 1393 1394 function UINode * 1395 ui_node_from_key(UINodeKey key) 1396 { 1397 UINodeHashBucket *hb = ui_context->node_hash_table + (key.value % UI_HASH_TABLE_COUNT); 1398 UINode *result = &ui_node_nil; 1399 1400 for (UINode *b = hb->first; !ui_node_is_nil(b); b = b->hash_next) { 1401 if (ui_node_key_equal(b->key, key)) { 1402 result = b; 1403 break; 1404 } 1405 } 1406 1407 return result; 1408 } 1409 1410 function str8 1411 ui_draw_part_from_key_string(str8 string) 1412 { 1413 str8 result = string; 1414 i64 index = str8_find_needle(string, str8("##"), 0); 1415 if (index < string.length) 1416 result.length = index; 1417 return result; 1418 } 1419 1420 function str8 1421 ui_hash_part_from_key_string(str8 string) 1422 { 1423 str8 result = string; 1424 // NOTE(rnp): for xxx###yyy only use the ###yyy otherwise the whole string is hashed 1425 i64 index = str8_find_needle(string, str8("###"), 0); 1426 if (index < string.length) 1427 result = str8_skip(string, index); 1428 return result; 1429 } 1430 1431 function UINodeKey 1432 ui_key_from_string(str8 string, UINodeKey seed) 1433 { 1434 UINodeKey result = {0}; 1435 if (string.length > 0) { 1436 str8 hash_string = ui_hash_part_from_key_string(string); 1437 result.value = u64_hash_from_str8_seed(hash_string, seed.value); 1438 } 1439 return result; 1440 } 1441 1442 function Font 1443 ui_font_for_node(UINode *node) 1444 { 1445 Font result = node->font_size > 28.0f ? ui_context->font : ui_context->small_font; 1446 return result; 1447 } 1448 1449 function b32 1450 ui_number_conversion_f64(str8 s, f64 *out_value) 1451 { 1452 b32 result = 0; 1453 NumberConversion number = number_from_str8(s); 1454 if (number.result == NumberConversionResult_Success) { 1455 result = 1; 1456 if (number.kind == NumberConversionKind_Float) 1457 *out_value = number.F64; 1458 else 1459 *out_value = (f64)number.S64; 1460 } 1461 return result; 1462 } 1463 1464 function iv2 1465 ui_text_input_cursor_range(void) 1466 { 1467 UITextInputState *tis = &ui_context->text_input_state; 1468 iv2 range; 1469 range.x = Min(tis->cursor, tis->mark); 1470 range.y = Max(tis->cursor, tis->mark); 1471 return range; 1472 } 1473 1474 function str8 1475 ui_text_input_string(void) 1476 { 1477 UITextInputState *tis = &ui_context->text_input_state; 1478 str8 result = {.data = tis->buffer, .length = tis->count}; 1479 return result; 1480 } 1481 1482 function str8 1483 ui_text_input_last_string(void) 1484 { 1485 UITextInputState *tis = &ui_context->text_input_state; 1486 str8 result = {.data = tis->last_buffer, .length = tis->last_count}; 1487 return result; 1488 } 1489 1490 function Rect 1491 ui_text_input_rect(void) 1492 { 1493 Rect result = ui_node_rect(ui_node_from_key(ui_context->text_input_state.node_key)); 1494 f32 text_box_slop = 4.0f; 1495 result.pos.x -= text_box_slop; 1496 result.size.x += 2 * text_box_slop; 1497 return result; 1498 } 1499 1500 function i32 1501 ui_text_input_index_from_point(f32 point) 1502 { 1503 i32 result = 0; 1504 1505 // TODO(rnp): visible range, extended virtual rect which exactly fits the visible text 1506 UITextInputState *tis = &ui_context->text_input_state; 1507 Rect r = ui_text_input_rect(); 1508 1509 Font font = ui_font_for_node(ui_node_from_key(tis->node_key)); 1510 1511 /* NOTE: extra offset to help with putting a cursor at idx 0 */ 1512 f32 pct = Clamp01((point - r.pos.x) / r.size.w); 1513 f32 x_off = 10.0f, x_bounds = r.size.w * pct; 1514 for (; result < tis->count && x_off < x_bounds; result++) { 1515 /* NOTE: assumes font glyphs are ordered ASCII */ 1516 i32 idx = tis->buffer[result] - 0x20; 1517 x_off += (f32)font.glyphs[idx].advanceX; 1518 if (font.glyphs[idx].advanceX == 0) 1519 x_off += font.recs[idx].width; 1520 } 1521 1522 return result; 1523 } 1524 1525 function void 1526 ui_text_input_end(void) 1527 { 1528 UITextInputState *tis = &ui_context->text_input_state; 1529 1530 UINode *next_node = ui_node_from_key(tis->next_node_key); 1531 str8 new_input_string = str8(""); 1532 if ((next_node->flags & UINodeFlag_TextInputClearOnStart) == 0) 1533 new_input_string = ui_draw_part_from_key_string(next_node->string); 1534 1535 tis->cursor = tis->mark = 0; 1536 tis->last_count = tis->count; 1537 tis->count = Min(new_input_string.length, countof(tis->buffer)); 1538 tis->numeric = (next_node->flags & UINodeFlag_TextInputNumeric) != 0; 1539 memory_copy(tis->last_buffer, tis->buffer, tis->last_count); 1540 memory_copy(tis->buffer, new_input_string.data, tis->count); 1541 1542 tis->last_node_key = tis->node_key; 1543 tis->node_key = ui_node_key_zero(); 1544 } 1545 1546 function void 1547 ui_text_input_insert(str8 text) 1548 { 1549 UITextInputState *tis = &ui_context->text_input_state; 1550 iv2 cursor_range = ui_text_input_cursor_range(); 1551 i64 bytes_after_cursor = tis->count - cursor_range.y; 1552 i64 remaining_length = ((i32)countof(tis->buffer) - cursor_range.x) - bytes_after_cursor; 1553 i64 truncated_length = Min(remaining_length, text.length); 1554 1555 memory_move(tis->buffer + cursor_range.x + truncated_length, 1556 tis->buffer + cursor_range.y, bytes_after_cursor); 1557 memory_copy(tis->buffer + cursor_range.x, text.data, truncated_length); 1558 1559 tis->count -= cursor_range.y - cursor_range.x; 1560 tis->count += truncated_length; 1561 tis->cursor = tis->mark = cursor_range.x + truncated_length; 1562 } 1563 1564 function b32 1565 ui_text_input_update(BeamformerInput *input) 1566 { 1567 UITextInputState *tis = &ui_context->text_input_state; 1568 1569 Arena scratch = *ui_build_arena(); 1570 Stream sb = arena_stream(scratch); 1571 1572 enum { 1573 DeltaPicksSide = (1 << 0), 1574 WordScan = (1 << 1), 1575 Delete = (1 << 2), 1576 KeepMark = (1 << 3), 1577 Copy = (1 << 4), 1578 Paste = (1 << 5), 1579 }; 1580 1581 i32 delta = 0; 1582 u32 flags = 0; 1583 1584 b32 result = 0; 1585 1586 // NOTE(rnp): first pass, non uniform inputs 1587 for (BeamformerInputEvent *event = ui_event_next(input, 0); 1588 event; 1589 event = ui_event_next(input, event)) 1590 { 1591 b32 taken = 0; 1592 1593 BeamformerInputModifiers mods = event->modifiers; 1594 if (event->kind == BeamformerInputEventKind_ButtonPress) { 1595 if (mods & BeamformerInputModifier_Control) 1596 flags |= WordScan; 1597 1598 if (mods & BeamformerInputModifier_Shift) 1599 flags |= KeepMark; 1600 1601 switch (event->button_id) { 1602 default:{}break; 1603 case BeamformerButtonID_Escape: 1604 case BeamformerButtonID_Enter: 1605 { 1606 taken = 1; 1607 result = 1; 1608 }break; 1609 1610 case BeamformerButtonID_A: if (mods & BeamformerInputModifier_Control) { 1611 tis->cursor = 0; 1612 tis->mark = tis->count; 1613 taken = 1; 1614 }break; 1615 1616 case BeamformerButtonID_C: if (mods & BeamformerInputModifier_Control) { 1617 flags |= Copy; 1618 taken = 1; 1619 }break; 1620 1621 case BeamformerButtonID_V: if (mods & BeamformerInputModifier_Control) { 1622 flags |= Paste; 1623 taken = 1; 1624 }break; 1625 1626 case BeamformerButtonID_X: if (mods & BeamformerInputModifier_Control) { 1627 flags |= Copy|Delete|KeepMark; 1628 taken = 1; 1629 }break; 1630 1631 case BeamformerButtonID_Backspace:{ 1632 delta -= 1; 1633 flags |= Delete|KeepMark; 1634 taken = 1; 1635 }break; 1636 1637 case BeamformerButtonID_Delete:{ 1638 delta += 1; 1639 flags |= Delete|KeepMark; 1640 taken = 1; 1641 }break; 1642 1643 case BeamformerButtonID_Left:{ 1644 delta -= 1; 1645 flags |= DeltaPicksSide; 1646 taken = 1; 1647 }break; 1648 1649 case BeamformerButtonID_Right:{ 1650 delta += 1; 1651 flags |= DeltaPicksSide; 1652 taken = 1; 1653 }break; 1654 1655 } 1656 1657 if (!taken && event->codepoint) { 1658 u32 cp = event->codepoint; 1659 taken = !tis->numeric || (Between(cp, '0', '9') || (cp == '.') || (cp == '-' && tis->cursor == 0)); 1660 if (taken) stream_append_codepoint(&sb, event->codepoint); 1661 } 1662 } 1663 1664 if (taken) ui_event_consume(input, event); 1665 } 1666 1667 if (flags & Paste) { 1668 str8 string; 1669 string.data = os_get_clipboard_text(&string.length); 1670 for (i64 it = 0; it < string.length; it++) { 1671 u8 cp = string.data[it]; 1672 if (!tis->numeric || (Between(cp, '0', '9') || (cp == '.') || (cp == '-' && tis->cursor == 0))) 1673 stream_append_byte(&sb, cp); 1674 } 1675 } 1676 1677 if (flags & Copy) { 1678 str8 string = ui_text_input_string(); 1679 os_set_clipboard_text(string.data, string.length); 1680 } 1681 1682 if ((flags & Delete) && tis->mark != tis->cursor) 1683 delta = 0; 1684 1685 // TODO(rnp): word selection 1686 tis->mark += delta; 1687 tis->mark = Clamp(tis->mark, 0, tis->count); 1688 1689 if (!(flags & KeepMark) && delta) { 1690 i32 new_cursor = tis->mark; 1691 if (flags & DeltaPicksSide) { 1692 if (delta < 0) new_cursor = Min(tis->mark, tis->cursor); 1693 if (delta > 0) new_cursor = Max(tis->mark, tis->cursor); 1694 } 1695 tis->mark = tis->cursor = new_cursor; 1696 } 1697 1698 if ((flags & Delete) || sb.widx) 1699 ui_text_input_insert(stream_to_str8(&sb)); 1700 1701 if (flags || delta || sb.widx) 1702 tis->blinker.t = 1.0; 1703 1704 return result; 1705 } 1706 1707 function void 1708 ui_context_menu_close(void) 1709 { 1710 ui_context->context_menu_next_anchor_key = ui_node_key_zero(); 1711 ui_context->context_menu_state_changed = 1; 1712 ui_context->context_menu_next_panel = 0; 1713 } 1714 1715 function void 1716 ui_context_menu_open(UINodeKey anchor_node_key, BeamformerUIPanel *panel) 1717 { 1718 if (ui_node_key_equal(ui_context->context_menu_anchor_key, anchor_node_key)) { 1719 ui_context_menu_close(); 1720 } else { 1721 ui_context->context_menu_next_anchor_key = anchor_node_key; 1722 ui_context->context_menu_next_panel = panel; 1723 ui_context->context_menu_state_changed = 1; 1724 ui_context->context_menu_open_t = 0; 1725 } 1726 } 1727 1728 function void 1729 ui_drag_end(void) 1730 { 1731 if ((beamformer_registers()->split_left_tree != beamformer_registers()->split_right_tree) && 1732 ui_context->drag_panel) 1733 { 1734 beamformer_command(beamformer_command_infos[BeamformerCommandKind_SplitTree].string, 1735 .tree_node = (u64)ui_context->drag_panel); 1736 } else if (beamformer_registers()->drop_target_tree && ui_context->drag_panel) { 1737 beamformer_command(beamformer_command_infos[BeamformerCommandKind_MoveTab].string, 1738 .tree_node = (u64)ui_context->drag_panel); 1739 } 1740 ui_context->drag_panel = 0; 1741 ui_context->drag_end = 0; 1742 } 1743 1744 function void 1745 ui_drag_begin(BeamformerUIPanel *panel) 1746 { 1747 if (!ui_context->drag_panel) { 1748 ui_context->drag_panel = panel; 1749 ui_context->drag_open_t = 0; 1750 ui_context->drop_target_key = ui_node_key_zero(); 1751 beamformer_registers()->drop_target_tree = 0; 1752 } 1753 } 1754 1755 function v2 1756 ui_node_final_position(UINode *node) 1757 { 1758 v2 result = ui_node_rect(node).pos; 1759 for (UINode *p = node->parent; !ui_node_is_nil(p); p = p->parent) 1760 if (p->flags & UINodeFlag_ViewScroll) 1761 result = v2_sub(result, p->view_scroll_offset); 1762 return result; 1763 } 1764 1765 function UISignal 1766 ui_signal_from_node(UINode *node) 1767 { 1768 BeamformerUI *ui = ui_context; 1769 BeamformerInput *input = beamformer_input; 1770 1771 UISignal result = {.node = node}; 1772 Rect nr = ui_node_rect(node); 1773 1774 // NOTE(rnp): use the last mouse as this matches what the user saw when they positioned 1775 v2 mouse = ui->last_mouse; 1776 1777 // NOTE(rnp): apply offset 1778 nr.pos = ui_node_final_position(node); 1779 1780 // NOTE(rnp): apply clipping 1781 for (UINode *p = node->parent; !ui_node_is_nil(p); p = p->parent) 1782 if (p->flags & UINodeFlag_Clip) 1783 nr = rect_intersect(nr, ui_node_rect(p)); 1784 1785 // NOTE(rnp): filter when node is under context menu 1786 b32 context_menu_descendent = 0; 1787 for (UINode *p = node->parent; !ui_node_is_nil(p); p = p->parent) 1788 if (p == ui->context_menu_root) 1789 context_menu_descendent = 1; 1790 1791 Rect filter_rect = {0}; 1792 if (!context_menu_descendent && !ui_node_key_nil(ui->context_menu_anchor_key)) 1793 filter_rect = ui_node_rect(ui->context_menu_root); 1794 1795 b32 disabled = (node->flags & UINodeFlag_Disabled) != 0; 1796 b32 collides = point_in_rect(mouse, nr) && !point_in_rect(mouse, filter_rect); 1797 1798 result.flags |= collides * UISignalFlag_Hovering; 1799 1800 if (!disabled) 1801 for (BeamformerInputEvent *event = ui_event_next(input, 0); 1802 event; 1803 event = ui_event_next(input, event)) 1804 { 1805 b32 taken = 0; 1806 b32 press = event->kind == BeamformerInputEventKind_ButtonPress; 1807 b32 release = event->kind == BeamformerInputEventKind_ButtonRelease; 1808 b32 event_is_mouse = (press || release) && ( 1809 event->button_id == BeamformerButtonID_MouseLeft || 1810 event->button_id == BeamformerButtonID_MouseRight || 1811 event->button_id == BeamformerButtonID_MouseMiddle || 1812 (0)); 1813 UIMouseButtonKind mouse_button = (event->button_id == BeamformerButtonID_MouseLeft ? UIMouseButtonKind_Left : 1814 event->button_id == BeamformerButtonID_MouseRight ? UIMouseButtonKind_Right : 1815 event->button_id == BeamformerButtonID_MouseMiddle ? UIMouseButtonKind_Middle : 1816 UIMouseButtonKind_Left); 1817 1818 if ((node->flags & UINodeFlag_MouseClickable) && event_is_mouse && press && collides) { 1819 ui->hot_node_key = node->key; 1820 ui->active_node_key[mouse_button] = node->key; 1821 1822 // TODO(rnp): store timestamp 1823 // TODO(rnp): check with timestamp for double/triple click 1824 1825 result.flags |= UISignalFlag_LeftPressed << mouse_button; 1826 1827 taken = 1; 1828 } 1829 1830 // NOTE(rnp): release, applies whenever this node is active regardless of in bounds or not. 1831 if ((node->flags & UINodeFlag_MouseClickable) && event_is_mouse && release && 1832 ui_node_key_equal(ui->active_node_key[mouse_button], node->key)) 1833 { 1834 ui->hot_node_key = ui_node_key_zero(); 1835 ui->active_node_key[mouse_button] = ui_node_key_zero(); 1836 result.flags |= UISignalFlag_LeftReleased << mouse_button; 1837 1838 taken = 1; 1839 } 1840 1841 // NOTE(rnp): custom scroll handling 1842 if (node->flags & UINodeFlag_Scroll && event->kind == BeamformerInputEventKind_MouseScroll && collides) { 1843 v2 delta = {{event->scroll.x, event->scroll.y}}; 1844 // TODO(rnp): glfw doesn't pass these through 1845 if (event->modifiers & BeamformerInputModifier_Shift) 1846 swap(delta.x, delta.y); 1847 result.scroll = v2_add(result.scroll, delta); 1848 1849 taken = 1; 1850 } 1851 1852 // NOTE(rnp): scrollable container handling 1853 if (node->flags & UINodeFlag_ViewScroll && collides) { 1854 v2 delta = {{event->scroll.x, event->scroll.y}}; 1855 // TODO(rnp): glfw doesn't pass these through 1856 if (event->modifiers & BeamformerInputModifier_Shift) 1857 swap(delta.x, delta.y); 1858 1859 // NOTE(rnp): if the view only has scroll in one direction we ignore the delta's direction 1860 1861 if ((node->flags & UINodeFlag_ViewScrollX) == 0) { 1862 if f32_equal(delta.y, 0) 1863 delta.y = delta.x; 1864 delta.x = 0; 1865 } 1866 1867 if ((node->flags & UINodeFlag_ViewScrollY) == 0) { 1868 if f32_equal(delta.x, 0) 1869 delta.x = delta.y; 1870 delta.y = 0; 1871 } 1872 1873 node->view_scroll_offset = v2_add(node->view_scroll_offset, v2_scale(delta, -10.f)); 1874 taken = 1; 1875 } 1876 1877 if (taken) ui_event_consume(input, event); 1878 } 1879 1880 // NOTE(rnp): single click dragging 1881 if (node->flags & UINodeFlag_MouseClickable) { 1882 for EachEnumValue(UIMouseButtonKind, k) { 1883 if (ui_node_key_equal(ui->active_node_key[k], node->key) || 1884 result.flags & (UISignalFlag_LeftPressed << k)) 1885 { 1886 result.flags |= (UISignalFlag_LeftDragging << k); 1887 } 1888 } 1889 } 1890 1891 // NOTE(rnp): drop handling 1892 if (node->flags & UINodeFlag_DropSite && collides 1893 && ui_node_key_equal(ui->drop_target_key, ui_node_key_zero())) 1894 { 1895 ui->drop_target_key = node->key; 1896 } 1897 1898 if (node->flags & UINodeFlag_DropSite && !collides 1899 && ui_node_key_equal(ui->drop_target_key, node->key)) 1900 { 1901 ui->drop_target_key = ui_node_key_zero(); 1902 } 1903 1904 // TODO(rnp): double click dragging 1905 1906 // TODO(rnp): triple click dragging 1907 1908 result.flags |= (!f32_equal(0, result.scroll.x) * UISignalFlag_ScrolledX); 1909 result.flags |= (!f32_equal(0, result.scroll.y) * UISignalFlag_ScrolledY); 1910 1911 if (node->flags & UINodeFlag_MouseClickable && collides && 1912 (ui_node_key_nil(ui->hot_node_key) || ui_node_key_equal(ui->hot_node_key, node->key)) && 1913 (ui_node_key_nil(ui->active_node_key[UIMouseButtonKind_Left]) || ui_node_key_equal(ui->active_node_key[UIMouseButtonKind_Left], node->key)) && 1914 (ui_node_key_nil(ui->active_node_key[UIMouseButtonKind_Middle]) || ui_node_key_equal(ui->active_node_key[UIMouseButtonKind_Middle], node->key)) && 1915 (ui_node_key_nil(ui->active_node_key[UIMouseButtonKind_Right]) || ui_node_key_equal(ui->active_node_key[UIMouseButtonKind_Right], node->key))) 1916 { 1917 ui->hot_node_key = node->key; 1918 } 1919 1920 if (node->flags & UINodeFlag_ViewScroll) { 1921 v2 offset = node->view_scroll_offset; 1922 f32 clamp_x = Max(0, node->computed_size[Axis2_X] - node->parent->computed_size[Axis2_X]); 1923 f32 clamp_y = Max(0, node->computed_size[Axis2_Y] - node->parent->computed_size[Axis2_Y]); 1924 node->view_scroll_offset.x = Max(0, Sign(offset.x) * Min(Abs(offset.x), clamp_x)); 1925 node->view_scroll_offset.y = Max(0, Sign(offset.y) * Min(Abs(offset.y), clamp_y)); 1926 } 1927 1928 // NOTE(rnp): activate text input 1929 if (ui_pressed(result) && !ui_node_key_equal(ui->text_input_state.node_key, node->key)) { 1930 ui->text_input_state.changed = 1; 1931 ui->text_input_state.next_node_key = node->flags & UINodeFlag_TextInput ? node->key : ui_node_key_zero(); 1932 } 1933 1934 // NOTE(rnp): signal ended text input 1935 if (node->flags & UINodeFlag_TextInput && 1936 ui_node_key_equal(ui->text_input_state.last_node_key, node->key)) 1937 { 1938 result.flags |= UISignalFlag_TextCommit; 1939 result.string = (str8){.length = ui->text_input_state.last_count, 1940 .data = ui->text_input_state.last_buffer}; 1941 } 1942 1943 if (ui_pressed(result) && !context_menu_descendent) 1944 ui_context_menu_close(); 1945 1946 if (!disabled) { 1947 b32 hot = ui_node_key_equal(ui->hot_node_key, node->key); 1948 if (hot) node->hot_t += HOVER_SPEED * dt_for_frame; 1949 else node->hot_t -= HOVER_SPEED * dt_for_frame; 1950 node->hot_t = Clamp01(node->hot_t); 1951 } 1952 1953 return result; 1954 } 1955 1956 function UINode * 1957 ui_build_node_from_key(UINodeFlags flags, UINodeKey key) 1958 { 1959 UINode *result = ui_node_from_key(key); 1960 1961 b32 first_frame = ui_node_is_nil(result); 1962 b32 transient = ui_node_key_equal(key, ui_node_key_zero()); 1963 1964 assert(first_frame || result->last_frame_active_index != ui_context->current_frame_index); 1965 1966 if (first_frame) { 1967 result = transient ? 0 : ui_context->node_freelist; 1968 if (!ui_node_is_nil(result)) { 1969 SLLStackPop(ui_context->node_freelist, next_sibling); 1970 } else { 1971 result = push_struct_no_zero(transient ? ui_build_arena() : &ui_context->arena, UINode); 1972 } 1973 zero_struct(result); 1974 } 1975 1976 // NOTE(rnp): reassigned per frame 1977 { 1978 result->parent = result->first_child = result->last_child = &ui_node_nil; 1979 result->next_sibling = result->previous_sibling = &ui_node_nil; 1980 result->child_count = 0; 1981 } 1982 1983 if (first_frame && !transient) { 1984 UINodeHashBucket *hb = ui_context->node_hash_table + (key.value % UI_HASH_TABLE_COUNT); 1985 DLLInsert(&ui_node_nil, hb->first, hb->last, result, hash_next, hash_prev); 1986 } 1987 1988 #define X(type, name, value_type, ...) result->name = ui_top_##name(); 1989 UI_STACK_LIST 1990 #undef X 1991 1992 result->last_frame_active_index = ui_context->current_frame_index; 1993 result->key = key; 1994 result->flags |= flags; 1995 1996 if (!ui_node_is_nil(result->parent)) { 1997 DLLInsertLast(&ui_node_nil, result->parent->first_child, result->parent->last_child, 1998 result, next_sibling, previous_sibling); 1999 result->parent->child_count++; 2000 } 2001 2002 return result; 2003 } 2004 2005 function UINode * 2006 ui_node_from_string(UINodeFlags flags, str8 string) 2007 { 2008 UINode *result = ui_build_node_from_key(flags, ui_key_from_string(string, ui_node_ancestor_key())); 2009 if (flags & UINodeFlag_DrawText) { 2010 if (ui_node_key_equal(ui_context->text_input_state.node_key, result->key)) 2011 result->string = ui_text_input_string(); 2012 else if (ui_node_key_equal(ui_context->text_input_state.last_node_key, result->key)) 2013 result->string = ui_text_input_last_string(); 2014 else 2015 result->string = string; 2016 } 2017 return result; 2018 } 2019 2020 function print_format(2, 3) UINode * 2021 ui_node_from_stringf(UINodeFlags flags, const char *format, ...) 2022 { 2023 va_list args; 2024 va_start(args, format); 2025 str8 string = push_str8_fv(ui_build_arena(), format, args); 2026 va_end(args); 2027 UINode *result = ui_node_from_string(flags, string); 2028 return result; 2029 } 2030 2031 typedef struct { 2032 f32 percent; 2033 } UIDrawSliderData; 2034 2035 function UI_CUSTOM_DRAW_FUNCTION(ui_custom_draw_slider) 2036 { 2037 UIDrawSliderData *data = node->custom_draw_context; 2038 2039 f32 pct = data->percent; 2040 f32 border_thick = 3.0f; 2041 f32 bar_height_frac = 0.8f; 2042 v2 bar_size = {{6.0f, bar_height_frac * node_rect.size.y}}; 2043 2044 Rect inner = rect_shrink_centered(node_rect, (v2){{2.0f * border_thick, // NOTE(rnp): raylib jank 2045 Max(0, 2.0f * (node_rect.size.y - bar_size.y))}}); 2046 Rect filled = inner; 2047 filled.size.w *= pct; 2048 2049 Rect bar; 2050 2051 bar.pos = v2_add(node_rect.pos, (v2){{pct * (node_rect.size.w - bar_size.w), 2052 (1 - bar_height_frac) * 0.5f * node_rect.size.y}}); 2053 bar.size = bar_size; 2054 v4 bar_colour = v4_lerp(FG_COLOUR, FOCUSED_COLOUR, node->hot_t); 2055 2056 DrawRectangleRec(rl_rect(filled), colour_from_normalized(node->bg_colour)); 2057 DrawRectangleRoundedLinesEx(rl_rect(inner), 0.2f, 0, border_thick, BLACK); 2058 DrawRectangleRounded(rl_rect(bar), 0.6f, 1, colour_from_normalized(bar_colour)); 2059 } 2060 2061 function UISignal 2062 ui_slider(f32 percent, str8 tag) 2063 { 2064 UINode *slider = ui_node_from_string(UINodeFlag_Clickable| 2065 UINodeFlag_Scroll| 2066 UINodeFlag_CustomDraw, tag); 2067 // TODO(rnp): don't need custom draw for this when individual borders can be specified 2068 slider->custom_draw_function = ui_custom_draw_slider; 2069 slider->custom_draw_context = push_struct(ui_build_arena(), UIDrawSliderData); 2070 UIDrawSliderData *data = slider->custom_draw_context; 2071 data->percent = percent; 2072 2073 UISignal result = ui_signal_from_node(slider); 2074 return result; 2075 } 2076 2077 function print_format(2, 3) UISignal 2078 ui_sliderf(f32 percent, const char *format, ...) 2079 { 2080 va_list args; 2081 va_start(args, format); 2082 str8 string = push_str8_fv(ui_build_arena(), format, args); 2083 va_end(args); 2084 UISignal result = ui_slider(percent, string); 2085 return result; 2086 } 2087 2088 function UISignal 2089 ui_button(str8 string) 2090 { 2091 UINode *node = ui_node_from_string(UINodeFlag_Clickable| 2092 UINodeFlag_DrawBackground| 2093 UINodeFlag_DrawBorder| 2094 UINodeFlag_DrawText| 2095 UINodeFlag_DrawHotEffects| 2096 UINodeFlag_DrawActiveEffects, 2097 string); 2098 UISignal result = ui_signal_from_node(node); 2099 return result; 2100 } 2101 2102 function print_format(1, 2) UISignal 2103 ui_buttonf(const char *format, ...) 2104 { 2105 va_list args; 2106 va_start(args, format); 2107 str8 string = push_str8_fv(ui_build_arena(), format, args); 2108 va_end(args); 2109 UISignal result = ui_button(string); 2110 return result; 2111 } 2112 2113 function UISignal 2114 ui_toggle_button(b32 state, str8 string) 2115 { 2116 UINode *node, *outer; 2117 2118 UIAxisAlign(Axis2_Y, Center) 2119 UIAxisAlign(Axis2_X, Center) 2120 UIParent(ui_spacer(0)) 2121 { 2122 UIPrefHeight(ui_pct(0.75f, 1.f)) 2123 UIPrefWidth(ui_pct(0.75f, 1.f)) 2124 UIBorderThickness(2.f) 2125 UIBorderColour(FG_COLOUR) 2126 outer = ui_node_from_string(UINodeFlag_Clickable|UINodeFlag_DrawBorder, 2127 push_str8_from_parts(ui_build_arena(), str8(""), string, str8("_outer"))); 2128 2129 UIParent(outer) 2130 UIPrefHeight(ui_pct(0.46f, 1.f)) 2131 UIPrefWidth(ui_pct(0.46f, 1.f)) 2132 UIBGColour(state ? FG_COLOUR : (v4){0}) 2133 { 2134 node = ui_node_from_string(UINodeFlag_DrawBackground| 2135 UINodeFlag_DrawHotEffects| 2136 UINodeFlag_DrawActiveEffects, 2137 string); 2138 node->hot_t = outer->hot_t; 2139 } 2140 } 2141 2142 UISignal result = ui_signal_from_node(outer); 2143 return result; 2144 } 2145 2146 function print_format(2, 3) UISignal 2147 ui_toggle_buttonf(b32 state, const char *format, ...) 2148 { 2149 va_list args; 2150 va_start(args, format); 2151 str8 string = push_str8_fv(ui_build_arena(), format, args); 2152 va_end(args); 2153 UISignal result = ui_toggle_button(state, string); 2154 return result; 2155 } 2156 2157 function UISignal 2158 ui_label(str8 string) 2159 { 2160 UINode *node = ui_node_from_string(UINodeFlag_DrawText, string); 2161 UISignal result = ui_signal_from_node(node); 2162 return result; 2163 } 2164 2165 function print_format(1, 2) UISignal 2166 ui_labelf(const char *format, ...) 2167 { 2168 va_list args; 2169 va_start(args, format); 2170 str8 string = push_str8_fv(ui_build_arena(), format, args); 2171 va_end(args); 2172 UISignal result = ui_label(string); 2173 return result; 2174 } 2175 2176 function UISignal 2177 ui_label_button(str8 string) 2178 { 2179 UINode *node = ui_node_from_string(UINodeFlag_DrawText| 2180 UINodeFlag_Clickable| 2181 UINodeFlag_DrawHotEffects| 2182 UINodeFlag_DrawActiveEffects, 2183 string); 2184 UISignal result = ui_signal_from_node(node); 2185 return result; 2186 } 2187 2188 function print_format(1, 2) UISignal 2189 ui_label_buttonf(const char *format, ...) 2190 { 2191 va_list args; 2192 va_start(args, format); 2193 str8 string = push_str8_fv(ui_build_arena(), format, args); 2194 va_end(args); 2195 UISignal result = ui_label_button(string); 2196 return result; 2197 } 2198 2199 function UISignal 2200 ui_text_box(str8 string) 2201 { 2202 UINode *node = ui_node_from_string(UINodeFlag_TextInput| 2203 UINodeFlag_DrawText| 2204 UINodeFlag_Clickable| 2205 UINodeFlag_DrawHotEffects| 2206 UINodeFlag_DrawActiveEffects, 2207 string); 2208 UISignal result = ui_signal_from_node(node); 2209 return result; 2210 } 2211 2212 function print_format(1, 2) UISignal 2213 ui_text_boxf(const char *format, ...) 2214 { 2215 va_list args; 2216 va_start(args, format); 2217 str8 string = push_str8_fv(ui_build_arena(), format, args); 2218 va_end(args); 2219 UISignal result = ui_text_box(string); 2220 return result; 2221 } 2222 2223 function b32 2224 ui_tweak_f32_compute_variable(UISignal signal, f32 *value, f32 text_scale, f32 scroll_scale, v2 limits) 2225 { 2226 b32 result = 0; 2227 if (signal.flags) { 2228 f64 new_value = *value; 2229 if (signal.flags & UISignalFlag_TextCommit && ui_number_conversion_f64(signal.string, &new_value)) 2230 new_value *= text_scale; 2231 2232 if (signal.flags & UISignalFlag_ScrolledY) 2233 new_value += scroll_scale * signal.scroll.y; 2234 2235 new_value = Clamp(new_value, limits.x, limits.y); 2236 2237 result = !f32_equal(*value, (f32)new_value); 2238 *value = (f32)new_value; 2239 } 2240 return result; 2241 } 2242 2243 typedef struct { 2244 v2 uv_start; 2245 v2 uv_end; 2246 BeamformerFrameView *view; 2247 } BeamformerCustomDrawFrameViewData; 2248 2249 function UI_CUSTOM_DRAW_FUNCTION(beamformer_custom_draw_frame_view) 2250 { 2251 // TODO(rnp): we should always just draw inline, requires no raylib 2252 BeamformerCustomDrawFrameViewData *data = node->custom_draw_context; 2253 BeamformerFrameView *view = data->view; 2254 Rectangle tex_r = { 2255 data->uv_start.x * view->colour_image.width, 2256 data->uv_start.y * view->colour_image.height, 2257 data->uv_end.x * view->colour_image.width, 2258 data->uv_end.y * view->colour_image.height, 2259 }; 2260 NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH }; 2261 DrawTextureNPatch(make_raylib_texture(view), tex_np, rl_rect(node_rect), (Vector2){0}, 0, WHITE); 2262 2263 TextSpec text_spec = {.font = &ui_context->small_font, .flags = TF_LIMITED|TF_OUTLINED, 2264 .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1, 2265 .limits.size.x = node_rect.size.w}; 2266 if (view->kind != BeamformerFrameViewKind_3DXPlane && view->ruler.state != RulerState_None) 2267 draw_view_ruler(view, *ui_build_arena(), node_rect, text_spec); 2268 } 2269 2270 function b32 2271 ui_rebuild_das_transform(u32 parameter_block, i32 dimension, v3 min, v3 max) 2272 { 2273 BeamformerUI *ui = ui_context; 2274 2275 b32 result = 0; 2276 m4 new_transform = m4_identity(); 2277 2278 BeamformerParameterBlock *pb = beamformer_parameter_block(beamformer_context->shared_memory, parameter_block); 2279 2280 m4 das_transform = pb->parameters.das_voxel_transform; 2281 2282 switch (dimension) { 2283 case 1:{new_transform = das_transform_1d(min, max);}break; 2284 case 3:{new_transform = das_transform_3d(min, max);}break; 2285 case 2:{ 2286 v3 U = v3_normalize(das_transform.c[0].xyz); 2287 v3 V = v3_normalize(das_transform.c[1].xyz); 2288 v3 N = cross(V, U); 2289 2290 v2 min_2d = {{min.E[0], min.E[1]}}; 2291 v2 max_2d = {{max.E[0], max.E[1]}}; 2292 2293 new_transform = das_transform_2d_with_normal(N, min_2d, max_2d, 0); 2294 2295 v3 rotation_axis = cross(v3_normalize(new_transform.c[0].xyz), N); 2296 2297 m4 R = m4_rotation_about_axis(rotation_axis, ui->beamform_plane); 2298 m4 T = m4_translation(v3_scale(m4_mul_v3(R, N), ui->off_axis_position)); 2299 2300 new_transform = m4_mul(T, m4_mul(R, new_transform)); 2301 }break; 2302 } 2303 2304 new_transform = m4_mul(new_transform, m4_inverse(das_transform)); 2305 2306 BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[parameter_block]; 2307 if (cp) { 2308 result |= !m4_equal(new_transform, cp->ui_voxel_transform); 2309 memory_copy(cp->ui_voxel_transform.E, new_transform.E, sizeof(new_transform)); 2310 } 2311 2312 if (result) { 2313 mark_parameter_block_region_dirty(beamformer_context->shared_memory, parameter_block, 2314 BeamformerParameterBlockRegion_Parameters); 2315 } 2316 2317 return result; 2318 } 2319 2320 function void 2321 ui_scroll_begin(Axis2 scroll_axis) 2322 { 2323 UINode *outer, *inner, *clip, *child; 2324 2325 UIChildLayoutAxis(Axis2_Y) 2326 UIParent(ui_spacer(0)) 2327 { 2328 UIChildLayoutAxis(Axis2_X) 2329 UIFontSize(30.f) 2330 outer = ui_node_from_string(UINodeFlag_Scroll, str8("###scroll_box")); 2331 2332 ui_padh(UI_NODE_PAD); 2333 } 2334 2335 UIParent(outer) 2336 { 2337 ui_padw(UI_NODE_PAD); 2338 UIChildLayoutAxis(Axis2_Y) 2339 inner = ui_node_from_string(0, str8("###scroll_inner")); 2340 } 2341 2342 UINodeFlags axis_flags; 2343 switch (scroll_axis) { 2344 InvalidDefaultCase; 2345 case Axis2_Count:{axis_flags = UINodeFlag_ViewScroll; }break; 2346 case Axis2_X:{ axis_flags = UINodeFlag_ViewScrollX;}break; 2347 case Axis2_Y:{ axis_flags = UINodeFlag_ViewScrollY;}break; 2348 } 2349 UIParent(inner) 2350 clip = ui_node_from_string(axis_flags| 2351 UINodeFlag_Clip| 2352 UINodeFlag_AllowOverflow| 2353 0, str8("###scroll_clip")); 2354 2355 UIParent(clip) 2356 { 2357 UIPrefWidth(ui_children_sum(1.f)) 2358 UIPrefHeight(ui_children_sum(1.f)) 2359 child = ui_node_from_string(0, str8("###scroll_child")); 2360 } 2361 2362 ui_push_parent(child); 2363 } 2364 2365 function void 2366 ui_scroll_end(void) 2367 { 2368 BeamformerUI *ui = ui_context; 2369 UINode *child = ui_pop_parent(); 2370 UINode *clip = child->parent; 2371 UINode *inner = clip->parent; 2372 UINode *outer = inner->parent; 2373 2374 v2 scroll_offset = clip->view_scroll_offset; 2375 2376 str8 labels[2][2] = { 2377 [Axis2_X] = {str8_comp("<"), str8_comp(">")}, 2378 [Axis2_Y] = {str8_comp("^"), str8_comp("v")}, 2379 }; 2380 2381 f32 btn_size = (f32)ui_font_for_node(outer).baseSize; 2382 2383 UINode *axis_parents[] = {[Axis2_X] = inner, [Axis2_Y] = outer}; 2384 for EachElement(axis_parents, axis) 2385 if (clip->flags & (UINodeFlag_ViewScrollX << axis)) 2386 UIParent(axis_parents[axis]) 2387 { 2388 b32 build_scrollbar = 2.f * btn_size < clip->computed_size[axis] && 2389 child->computed_size[axis] > clip->computed_size[axis]; 2390 2391 // NOTE(rnp): vertical scroll bar shares padding on bottom with horizontal 2392 // scroll bar so padding was already pushed, if we aren't drawing the horizontal 2393 // scroll bar we need to avoid a double pad 2394 if (axis == Axis2_Y || build_scrollbar) { 2395 UIChildLayoutAxis(axis2_flip(axis)) 2396 ui_pads(UI_NODE_PAD); 2397 } 2398 2399 if (build_scrollbar) 2400 UIAxisSize(axis2_flip(axis), ui_px(12.f, 1.f)) 2401 UIChildLayoutAxis(axis) 2402 UIParent(axis_parents[axis]) 2403 { 2404 UINode *parent = axis_parents[axis]; 2405 f32 d_size = child->computed_size[axis] - clip->computed_size[axis]; 2406 f32 used_pct = clip->computed_size[axis] / child->computed_size[axis]; 2407 f32 rem_pct = 1.f - used_pct; 2408 f32 before_pct = rem_pct - (d_size - scroll_offset.E[axis]) / child->computed_size[axis]; 2409 f32 after_pct = rem_pct - before_pct; 2410 2411 UINode *scroll_container; 2412 UIAxisAlign(axis2_flip(axis), Center) 2413 UIAxisSize(axis, ui_px(parent->computed_size[axis], 1.f)) 2414 scroll_container = ui_spacer(0); 2415 2416 UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f)) 2417 UIFontSize(outer->font_size) 2418 UIParent(scroll_container) 2419 { 2420 UISignal signal; 2421 // TODO(rnp): icons 2422 UIFlags(UINodeFlag_IconText) 2423 UIAxisSize(axis2_flip(axis), ui_text_dim(1.f, 1.f)) 2424 UIAxisSize(axis, ui_text_dim(1.f, 1.f)) 2425 signal = ui_label_button(labels[axis][0]); 2426 if (signal.flags & UISignalFlag_LeftPressed) { 2427 // TODO(rnp): handle repeat 2428 scroll_offset.E[axis] -= btn_size * 0.5f; 2429 } 2430 2431 ui_pads(3.f); 2432 2433 UISignalFlags bar_flags = 0; 2434 2435 UIBorderColour((v4){0}) 2436 UIFlags(UINodeFlag_Clickable|UINodeFlag_DrawBorder|UINodeFlag_DrawHotEffects) 2437 UIAxisSize(axis, ui_pct(before_pct, 0.5f)) 2438 bar_flags |= ui_signal_from_node(ui_node_from_string(0, str8("###before"))).flags; 2439 2440 UIBGColour(FG_COLOUR) 2441 UIAxisSize(axis, ui_pct(used_pct, 0.5f)) 2442 UIFlags(UINodeFlag_Clickable|UINodeFlag_DrawBackground|UINodeFlag_DrawHotEffects) 2443 signal = ui_signal_from_node(ui_node_from_string(0, str8("###used"))); 2444 bar_flags |= signal.flags; 2445 2446 UIBorderColour((v4){0}) 2447 UIFlags(UINodeFlag_Clickable|UINodeFlag_DrawBorder|UINodeFlag_DrawHotEffects) 2448 UIAxisSize(axis, ui_pct(after_pct , 0.5f)) 2449 bar_flags |= ui_signal_from_node(ui_node_from_string(0, str8("###after"))).flags; 2450 2451 if (bar_flags & (UISignalFlag_Dragging|UISignalFlag_Pressed)) { 2452 f32 off_pct = rect_uv(ui->last_mouse, ui_node_rect(clip)).E[axis] - 0.5f * used_pct; 2453 scroll_offset.E[axis] = Clamp01(off_pct) * child->computed_size[axis]; 2454 } 2455 2456 ui_pads(3.f); 2457 2458 UIFlags(UINodeFlag_IconText) 2459 UIAxisSize(axis2_flip(axis), ui_text_dim(1.f, 1.f)) 2460 UIAxisSize(axis, ui_text_dim(1.f, 1.f)) 2461 signal = ui_label_button(labels[axis][1]); 2462 if (signal.flags & UISignalFlag_LeftPressed) { 2463 // TODO(rnp): handle repeat 2464 scroll_offset.E[axis] += btn_size * 0.5f; 2465 } 2466 } 2467 2468 // NOTE(rnp): vertical scroll bar needs padding next to it but must share 2469 // padding on the bottom with the horizontal scrollbar 2470 if (axis == Axis2_Y) ui_padw(UI_NODE_PAD); 2471 } 2472 } 2473 2474 // TODO(rnp): view scroll is being added to ViewScroll node in ui_signal_from_node maybe we are ignoring it? 2475 UISignal signal = ui_signal_from_node(outer); 2476 scroll_offset = v2_sub(scroll_offset, v2_scale(signal.scroll, btn_size * 0.5f)); 2477 2478 scroll_offset.x = Max(0, Min(scroll_offset.x, child->computed_size[Axis2_X] - clip->computed_size[Axis2_X])); 2479 scroll_offset.y = Max(0, Min(scroll_offset.y, child->computed_size[Axis2_Y] - clip->computed_size[Axis2_Y])); 2480 clip->view_scroll_offset = scroll_offset; 2481 } 2482 2483 typedef struct { 2484 Axis2 axis; 2485 f32 start_value; 2486 f32 end_value; 2487 u32 segments; 2488 } UIDrawScaleBarData; 2489 2490 function UI_CUSTOM_DRAW_FUNCTION(ui_custom_draw_scale_bar) 2491 { 2492 UIDrawScaleBarData *info = node->custom_draw_context; 2493 2494 b32 draw_plus = Sign(info->end_value) != Sign(info->start_value); 2495 2496 Font font = ui_font_for_node(node); 2497 v2 start_point = node_rect.pos; 2498 v2 end_point = node_rect.pos; 2499 2500 if (info->axis == Axis2_Y) start_point.y += node_rect.size.y; 2501 else end_point.x += node_rect.size.x; 2502 2503 end_point = v2_sub(end_point, start_point); 2504 2505 rlPushMatrix(); 2506 rlTranslatef(start_point.x, start_point.y, 0); 2507 rlRotatef(atan2_f32(end_point.y, end_point.x) * 180 / PI, 0, 0, 1); 2508 2509 Stream buf = arena_stream(*ui_build_arena()); 2510 f32 inc = v2_magnitude(end_point) / (f32)info->segments; 2511 f32 value_inc = (info->end_value - info->start_value) / (f32)info->segments; 2512 f32 value = info->start_value; 2513 2514 v2 sp = {0}, ep = {.y = RULER_TICK_LENGTH}; 2515 v2 tp = {{(f32)font.baseSize / 2.0f, ep.y + RULER_TEXT_PAD}}; 2516 2517 TextSpec text_spec = {.font = &font, .rotation = 90.0f, .colour = node->text_colour, .flags = TF_ROTATED}; 2518 if (node->flags & UINodeFlag_DrawHotEffects) 2519 text_spec.colour = v4_lerp(text_spec.colour, HOVERED_COLOUR, node->hot_t); 2520 2521 Color rl_txt_colour = colour_from_normalized(node->text_colour); 2522 for (u32 j = 0; j <= info->segments; j++) { 2523 DrawLineEx(rl_v2(sp), rl_v2(ep), 4.f, rl_txt_colour); 2524 2525 stream_reset(&buf, 0); 2526 if (draw_plus && value > 0) stream_append_byte(&buf, '+'); 2527 stream_append_f64(&buf, value, Abs(value_inc) < 1 ? 100 : 10); 2528 stream_append_s8(&buf, s8("mm")); 2529 draw_text(stream_to_str8(&buf), tp, &text_spec); 2530 2531 value += value_inc; 2532 sp.x += inc; 2533 ep.x += inc; 2534 tp.x += inc; 2535 } 2536 2537 rlPopMatrix(); 2538 } 2539 2540 function UISignal 2541 ui_build_scale_bar(Axis2 axis, v2 min, v2 max) 2542 { 2543 Font font = ui_font_for_node(ui_top_parent()); 2544 f32 label_size = measure_text(font, str8("-288.88mm")).w; 2545 2546 UISignal result; 2547 UIAxisSize(axis2_flip(axis), ui_px(RULER_TICK_LENGTH + RULER_TEXT_PAD + label_size, 1.f)) 2548 UIFlags(UINodeFlag_Clickable|UINodeFlag_Scroll|UINodeFlag_DrawHotEffects|UINodeFlag_CustomDraw) 2549 { 2550 UINode *node = ui_node_from_string(0, str8("###scale_bar")); 2551 result = ui_signal_from_node(node); 2552 2553 UIDrawScaleBarData *info = push_struct(ui_build_arena(), UIDrawScaleBarData); 2554 node->custom_draw_function = ui_custom_draw_scale_bar; 2555 node->custom_draw_context = info; 2556 2557 Rect tick_rect = ui_node_rect(node); 2558 if (tick_rect.size.E[axis] > 0) { 2559 info->axis = axis; 2560 info->segments = (u32)(tick_rect.size.E[axis] / (1.5f * font.baseSize)); 2561 info->start_value = min.E[axis] * 1e3; 2562 info->end_value = max.E[axis] * 1e3; 2563 if (axis == Axis2_Y) swap(info->start_value, info->end_value); 2564 } 2565 } 2566 return result; 2567 } 2568 2569 function void 2570 ui_build_frame_view_overlay(UINode *frame_view, BeamformerFrameView *view, v2 min_2d, v2 max_2d) 2571 { 2572 BeamformerUI *ui = ui_context; 2573 UIParent(frame_view) 2574 UIChildLayoutAxis(Axis2_X) 2575 UIPrefHeight(ui_children_sum(1.f)) 2576 UIPrefWidth(ui_pct(1.f, 0.5f)) 2577 UITextOutlineColour((v4){.a = 1.f}) 2578 UITextOutlineThickness(1.f) 2579 UITextColour(RULER_COLOUR) 2580 { 2581 ui_padh(UI_NODE_PAD); 2582 2583 if (view->kind != BeamformerFrameViewKind_3DXPlane) 2584 UIFontSize(30.f) 2585 UIParent(ui_spacer(0)) 2586 { 2587 ui_spacer(0); 2588 2589 UIPrefHeight(ui_text_dim(1.f, 1.f)) 2590 UIPrefWidth(ui_text_dim(1.f, 1.f)) 2591 ui_label(push_acquisition_kind(ui_build_arena(), view->frame.acquisition_kind, 2592 view->frame.compound_count, view->frame.contrast_mode)); 2593 2594 ui_padw(2.f * UI_NODE_PAD); 2595 } 2596 2597 UIPrefHeight(ui_pct(1.f, 0.5f)) ui_spacer(0); 2598 2599 UIFontSize(24.f) 2600 UIAxisAlign(Axis2_Y, Right) 2601 UIParent(ui_spacer(0)) 2602 { 2603 ui_padw(2.f * UI_NODE_PAD); 2604 2605 UINode *label_column, *value_column, *unit_column; 2606 UIAxisAlign(Axis2_X, Left) 2607 UIAxisAlign(Axis2_Y, Left) 2608 UIPrefWidth(ui_children_sum(1.f)) 2609 UIParent(ui_spacer(0)) 2610 UIChildLayoutAxis(Axis2_Y) 2611 { 2612 label_column = ui_node_from_string(0, str8("###labels")); 2613 ui_padw(UI_NODE_PAD); 2614 value_column = ui_node_from_string(0, str8("###values")); 2615 ui_padw(UI_NODE_PAD); 2616 unit_column = ui_node_from_string(0, str8("###units")); 2617 } 2618 2619 UIPrefWidth(ui_text_dim(1.f, 1.f)) 2620 UIPrefHeight(ui_text_dim(1.f, 1.f)) 2621 { 2622 if (view->log_scale) { 2623 UIParent(label_column) ui_label(str8("Dynamic Range:")); 2624 UIParent(unit_column) ui_label(str8("[dB]")); 2625 UIParent(value_column) 2626 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 2627 { 2628 UISignal signal = ui_text_boxf("%0.2f###dynamic_range", view->dynamic_range); 2629 view->dirty |= ui_tweak_f32_compute_variable(signal, &view->dynamic_range, 1.f, 0.5f, V2_INFINITY); 2630 } 2631 } 2632 2633 // TODO(rnp): ui_em after text height matches correctly 2634 f32 spacer_height; 2635 UIParent(label_column) spacer_height = ui_label(str8("Gamma:")).node->computed_size[Axis2_Y]; 2636 UIParent(unit_column) ui_padh(spacer_height); 2637 UIParent(value_column) 2638 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 2639 { 2640 UISignal signal = ui_text_boxf("%0.2f###gamma", view->gamma); 2641 view->dirty |= ui_tweak_f32_compute_variable(signal, &view->gamma, 1.f, 0.025f, V2_INFINITY); 2642 } 2643 2644 UIParent(label_column) spacer_height = ui_label(str8("Threshold:")).node->computed_size[Axis2_Y]; 2645 UIParent(unit_column) ui_padh(spacer_height); 2646 UIParent(value_column) 2647 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 2648 { 2649 UISignal signal = ui_text_boxf("%0.2f###threshold", view->threshold); 2650 view->dirty |= ui_tweak_f32_compute_variable(signal, &view->threshold, 1.f, 1.f, V2_INFINITY); 2651 } 2652 } 2653 2654 UIPrefWidth(ui_pct(1.f, 0.5f)) ui_spacer(0); 2655 2656 Rect nr = ui_node_rect(frame_view); 2657 if (view->kind != BeamformerFrameViewKind_3DXPlane && point_in_rect(ui->last_mouse, nr) && ui->drag_panel == 0) { 2658 b32 is_1d = iv3_dimension(view->frame.points) == 1; 2659 v2 world = screen_point_to_world_2d(ui->last_mouse, nr.pos, v2_add(nr.pos, nr.size), 2660 min_2d, max_2d); 2661 world = v2_scale(world, 1e3f); 2662 if (is_1d) world.y = ((nr.pos.y + nr.size.y) - ui->last_mouse.y) / nr.size.y; 2663 2664 UIPrefWidth(ui_text_dim(1.f, 1.f)) 2665 UIPrefHeight(ui_text_dim(1.f, 1.f)) 2666 ui_labelf("{%0.2f%s, %0.2f}", world.x, is_1d ? " mm" : "", world.y); 2667 } 2668 2669 ui_padw(2.f * UI_NODE_PAD); 2670 } 2671 2672 ui_padh(UI_NODE_PAD); 2673 } 2674 } 2675 2676 function void 2677 ui_build_3d_xplane_context_menu(BeamformerFrameView *view) 2678 { 2679 UINode *label_column, *button_column; 2680 UIParent(ui_context->context_menu_root) 2681 UIChildLayoutAxis(Axis2_X) 2682 UIPrefHeight(ui_children_sum(1.f)) 2683 UIPrefWidth(ui_children_sum(1.f)) 2684 UIParent(ui_spacer(0)) 2685 UIChildLayoutAxis(Axis2_Y) 2686 { 2687 ui_padw(UI_NODE_PAD); 2688 UIAxisAlign(Axis2_X, Left) label_column = ui_node_from_string(0, str8("###labels")); 2689 ui_padw(UI_NODE_PAD * 2.f); 2690 UIAxisAlign(Axis2_X, Center) 2691 button_column = ui_node_from_string(0, str8("###buttons")); 2692 ui_padw(UI_NODE_PAD); 2693 } 2694 2695 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 2696 UIPrefWidth(ui_text_dim(1.f, 1.f)) 2697 { 2698 { 2699 f32 row_height; 2700 UIParent(label_column) 2701 row_height = ui_label(str8("Log Scale")).node->computed_size[Axis2_Y]; 2702 2703 UIParent(button_column) 2704 // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly 2705 UIPrefHeight(ui_px(row_height, 1.f)) 2706 UIPrefWidth(ui_px(row_height, 1.f)) 2707 { 2708 UISignal signal = ui_toggle_button(view->log_scale, str8("###log_scale")); 2709 if ui_pressed(signal) { 2710 view->log_scale = !view->log_scale; 2711 view->dirty = 1; 2712 } 2713 } 2714 } 2715 2716 { 2717 f32 row_height; 2718 UIParent(label_column) 2719 row_height = ui_label(str8("Demo Mode")).node->computed_size[Axis2_Y]; 2720 2721 UIParent(button_column) 2722 // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly 2723 UIPrefHeight(ui_px(row_height, 1.f)) 2724 UIPrefWidth(ui_px(row_height, 1.f)) 2725 { 2726 UISignal signal = ui_toggle_button(view->demo, str8("###demo_mode")); 2727 if ui_pressed(signal) 2728 view->demo = !view->demo; 2729 } 2730 } 2731 2732 UIParent(label_column) 2733 { 2734 f32 row_height = ui_label(str8("Planes:")).node->computed_size[Axis2_Y]; 2735 // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly 2736 UIParent(button_column) ui_padh(row_height); 2737 } 2738 for EachElement(view->plane_active, plane) { 2739 f32 row_height; 2740 UIParent(label_column) 2741 { 2742 str8 label = push_str8_from_parts(ui_build_arena(), str8(""), str8(" "), 2743 beamformer_view_plane_tag_strings[plane]); 2744 row_height = ui_label(label).node->computed_size[Axis2_Y]; 2745 } 2746 2747 UIParent(button_column) 2748 UIPrefHeight(ui_px(row_height, 1.f)) 2749 UIPrefWidth(ui_px(row_height, 1.f)) 2750 { 2751 UISignal signal = ui_toggle_button(view->plane_active[plane], 2752 beamformer_view_plane_tag_strings[plane]); 2753 if ui_pressed(signal) 2754 view->plane_active[plane] = !view->plane_active[plane]; 2755 } 2756 } 2757 } 2758 } 2759 2760 function void 2761 ui_build_3d_xplane_frame_view(UINode *container, BeamformerFrameView *view) 2762 { 2763 assert(view->kind == BeamformerFrameViewKind_3DXPlane); 2764 Rect display_rect = ui_node_rect(container); 2765 Rect vr = rect_shrink_centered(display_rect, (v2){{UI_NODE_PAD, UI_NODE_PAD}}); 2766 2767 f32 aspect = (f32)view->colour_image.width / (f32)view->colour_image.height; 2768 if (aspect > 1.0f) vr.size.w = vr.size.h; 2769 else vr.size.h = vr.size.w; 2770 2771 if (vr.size.w > display_rect.size.w) { 2772 vr.size.w -= (vr.size.w - display_rect.size.w); 2773 vr.size.h = vr.size.w / aspect; 2774 } else if (vr.size.h > display_rect.size.h) { 2775 vr.size.h -= (vr.size.h - display_rect.size.h); 2776 vr.size.w = vr.size.h * aspect; 2777 } 2778 2779 // TODO(rnp): probably we don't need frame_top in this path 2780 UINode *frame_top, *frame_view; 2781 UIParent(container) 2782 { 2783 ui_padh(UI_NODE_PAD); 2784 2785 UIChildLayoutAxis(Axis2_X) 2786 UIPrefHeight(ui_children_sum(1.f)) 2787 UIPrefWidth(ui_children_sum(1.f)) 2788 frame_top = ui_node_from_string(0, str8("###frame_view_top")); 2789 2790 UIParent(frame_top) 2791 UIPrefHeight(ui_px(vr.size.h, 1.f)) 2792 { 2793 UIChildLayoutAxis(Axis2_Y) 2794 UIPrefWidth(ui_px(vr.size.w, 1.f)) 2795 frame_view = ui_node_from_string(UINodeFlag_Clickable| 2796 UINodeFlag_CustomDraw| 2797 UINodeFlag_Clip| 2798 UINodeFlag_Scroll| 2799 0, str8("###frame_view")); 2800 frame_view->custom_draw_function = beamformer_custom_draw_frame_view; 2801 frame_view->custom_draw_context = push_struct(ui_build_arena(), BeamformerCustomDrawFrameViewData); 2802 { 2803 BeamformerCustomDrawFrameViewData *data = frame_view->custom_draw_context; 2804 data->uv_start = (v2){0}; 2805 data->uv_end = (v2){{1.f, 1.f}}; 2806 data->view = view; 2807 } 2808 2809 ui_build_frame_view_overlay(frame_view, view, (v2){0}, (v2){0}); 2810 } 2811 } 2812 2813 UISignal signal = ui_signal_from_node(frame_view); 2814 if (ui_tweak_f32_compute_variable(signal, &view->threshold, 1.f, 1.f, V2_INFINITY)) 2815 view->dirty = 1; 2816 2817 f32 test[countof(view->plane_active)] = {0}; 2818 ray mouse_rays[countof(view->plane_active)] = {0}; 2819 v2 mouse_uv = rect_uv_ndc(ui_context->last_mouse, vr); 2820 2821 i32 hovered_plane = -1; 2822 if ui_node_hot(frame_view) { 2823 for EachElement(test, it) if (view->plane_active[it]) { 2824 BeamformerFrame *frame = ui_context->latest_plane + it; 2825 v2 min_2d, max_2d; 2826 plane_corners_from_transform(frame->voxel_transform, &min_2d, &max_2d); 2827 v3 x_size = v3_scale(x_plane_display_size(frame), 0.5f); 2828 m4 x_rotation = m4_rotation_about_y(x_plane_rotation_for_view_plane(view, it)); 2829 v3 x_position = x_plane_offset_position(view, frame, it); 2830 mouse_rays[it] = x_plane_raycast(view, frame, mouse_uv); 2831 test[it] = obb_raycast(x_rotation, x_size, x_position, mouse_rays[it]); 2832 } 2833 2834 f32 min_valid_t = inf32(); 2835 for EachElement(test, it) { 2836 if (view->plane_active[it] && Between(test[it], 0, min_valid_t)) { 2837 hovered_plane = (i32)it; 2838 min_valid_t = test[it]; 2839 } 2840 } 2841 } 2842 2843 if ui_pressed(signal) { 2844 view->plane_drag_index = hovered_plane; 2845 if (hovered_plane != -1) { 2846 v3 origin = mouse_rays[hovered_plane].origin; 2847 v3 p = v3_scale(mouse_rays[hovered_plane].direction, test[hovered_plane]); 2848 view->hit_start_point = view->hit_test_point = v3_add(origin, p); 2849 } 2850 } 2851 2852 b32 active = ui_node_key_equal(ui_context->active_node_key[UIMouseButtonKind_Left], frame_view->key); 2853 for EachElement(view->hot_t, it) { 2854 b32 hot = active ? (view->plane_drag_index == (i32)it) : (hovered_plane == (i32)it); 2855 if (hot) view->hot_t[it] += HOVER_SPEED * dt_for_frame; 2856 else view->hot_t[it] -= HOVER_SPEED * dt_for_frame; 2857 view->hot_t[it] = Clamp01(view->hot_t[it]); 2858 } 2859 2860 if ui_dragging(signal) { 2861 ui_disable_cursor(); 2862 // TODO(rnp): hide mouse 2863 if (view->plane_drag_index != -1) { 2864 /* NOTE(rnp): project start point onto ray */ 2865 BeamformerFrame *frame = ui_context->latest_plane + view->plane_drag_index; 2866 ray mouse_ray = x_plane_raycast(view, frame, rect_uv_ndc(clamp_v2_rect(ui_context->last_mouse, vr), vr)); 2867 v3 s = v3_sub(view->hit_start_point, mouse_ray.origin); 2868 v3 r = v3_sub(mouse_ray.direction, mouse_ray.origin); 2869 f32 scale = v3_dot(s, r) / v3_magnitude_squared(r); 2870 view->hit_test_point = v3_add(mouse_ray.origin, v3_scale(r, scale)); 2871 } else { 2872 f32 dMouseX = ui_context->current_mouse.x - ui_context->last_mouse.x; 2873 view->rotation -= dMouseX / (f32)beamformer_context->window_size.w; 2874 if (view->rotation > 1.0f) view->rotation -= 1.0f; 2875 if (view->rotation < 0.0f) view->rotation += 1.0f; 2876 } 2877 } 2878 2879 if ui_released(signal) { 2880 ui_enable_cursor(); 2881 2882 if (view->plane_drag_index != -1) { 2883 m4 x_rotation = m4_rotation_about_y(x_plane_rotation_for_view_plane(view, view->plane_drag_index)); 2884 v3 Z = x_rotation.c[2].xyz; 2885 f32 delta = v3_dot(Z, v3_sub(view->hit_test_point, view->hit_start_point)); 2886 2887 BeamformerSharedMemory *sm = beamformer_context->shared_memory; 2888 BeamformerLiveImagingParameters *li = &sm->live_imaging_parameters; 2889 li->image_plane_offsets[view->plane_drag_index] += delta; 2890 atomic_or_u32(&sm->live_imaging_dirty_flags, BeamformerLiveImagingDirtyFlags_ImagePlaneOffsets); 2891 } 2892 2893 view->plane_drag_index = -1; 2894 view->hit_start_point = view->hit_test_point = (v3){0}; 2895 } 2896 } 2897 2898 function void 2899 ui_build_frame_view_context_menu(BeamformerUIPanel *panel, BeamformerFrameView *view) 2900 { 2901 UINode *label_column, *button_column; 2902 UIParent(ui_context->context_menu_root) 2903 UIChildLayoutAxis(Axis2_X) 2904 UIPrefHeight(ui_children_sum(1.f)) 2905 UIPrefWidth(ui_children_sum(1.f)) 2906 UIParent(ui_spacer(0)) 2907 UIChildLayoutAxis(Axis2_Y) 2908 { 2909 ui_padw(UI_NODE_PAD); 2910 UIAxisAlign(Axis2_X, Left) label_column = ui_node_from_string(0, str8("###labels")); 2911 ui_padw(UI_NODE_PAD * 2.f); 2912 UIAxisAlign(Axis2_X, Center) 2913 button_column = ui_node_from_string(0, str8("###buttons")); 2914 ui_padw(UI_NODE_PAD); 2915 } 2916 2917 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 2918 UIPrefWidth(ui_text_dim(1.f, 1.f)) 2919 { 2920 read_only local_persist str8 dimension_strings[2][2] = { 2921 {str8_comp("Extent Scale Bar"), str8_comp("Magnitude Scale Bar")}, 2922 {str8_comp("Lateral Scale Bar"), str8_comp("Axial Scale Bar") }, 2923 }; 2924 2925 UIParent(label_column) ui_label(str8("Plane Tag")); 2926 UIParent(button_column) 2927 UIFlags(UINodeFlag_Scroll) 2928 { 2929 str8 tag = str8("Any"); 2930 if (view->view_plane != BeamformerViewPlaneTag_Count) 2931 tag = beamformer_view_plane_tag_strings[view->view_plane]; 2932 UISignal signal = ui_label_button(push_str8_from_parts(ui_build_arena(), str8(""), 2933 tag, str8("###PlaneTagButton"))); 2934 i32 delta = signal.scroll.y + ui_pressed(signal); 2935 view->view_plane = circular_add(view->view_plane, delta, BeamformerViewPlaneTag_Count + 1); 2936 if (ui_pressed(signal) || ui_scrolled(signal)) 2937 view->dirty = 1; 2938 } 2939 2940 i32 dimension = iv3_dimension(view->frame.points); 2941 dimension = Min(dimension, 2); 2942 if (dimension > 0) { 2943 for EachEnumValue(Axis2, axis) { 2944 f32 row_height; 2945 UIParent(label_column) 2946 row_height = ui_label(dimension_strings[dimension - 1][axis]).node->computed_size[Axis2_Y]; 2947 2948 UIParent(button_column) 2949 // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly 2950 UIPrefHeight(ui_px(row_height, 1.f)) 2951 UIPrefWidth(ui_px(row_height, 1.f)) 2952 { 2953 UISignal signal = ui_toggle_buttonf(view->scale_bar_active[axis], "###axis_%u", axis); 2954 if ui_pressed(signal) 2955 view->scale_bar_active[axis] = !view->scale_bar_active[axis]; 2956 } 2957 } 2958 } 2959 2960 { 2961 f32 row_height; 2962 UIParent(label_column) 2963 row_height = ui_label(str8("Log Scale")).node->computed_size[Axis2_Y]; 2964 2965 UIParent(button_column) 2966 // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly 2967 UIPrefHeight(ui_px(row_height, 1.f)) 2968 UIPrefWidth(ui_px(row_height, 1.f)) 2969 { 2970 UISignal signal = ui_toggle_button(view->log_scale, str8("###log_scale")); 2971 if ui_pressed(signal) { 2972 view->log_scale = !view->log_scale; 2973 view->dirty = 1; 2974 } 2975 } 2976 } 2977 2978 if (dimension > 0 && panel->kind != BeamformerPanelKind_FrameViewCopy) { 2979 f32 row_height; 2980 UIParent(label_column) 2981 { 2982 UISignal signal = ui_label_button(str8("Copy Frame")); 2983 row_height = signal.node->computed_size[Axis2_Y]; 2984 if ui_pressed(signal) { 2985 ui_context_menu_close(); 2986 beamformer_command(beamformer_command_infos[BeamformerCommandKind_OpenTab].string, 2987 .tree_node = (u64)panel->parent, 2988 .frame_view = (u64)view, 2989 .string = beamformer_panel_infos[BeamformerPanelKind_FrameViewCopy].string); 2990 } 2991 } 2992 2993 // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly 2994 UIParent(button_column) ui_padh(row_height); 2995 } 2996 2997 // TODO(rnp): extra frame view copy settings 2998 if (panel->kind == BeamformerPanelKind_FrameViewCopy) { 2999 } 3000 } 3001 } 3002 3003 function void 3004 ui_build_frame_view(UINode *container, BeamformerFrameView *view) 3005 { 3006 assert(view->kind != BeamformerFrameViewKind_3DXPlane); 3007 3008 BeamformerUI *ui = ui_context; 3009 BeamformerFrame *frame = &view->frame; 3010 b32 is_1d = iv3_dimension(frame->points) == 1; 3011 f32 txt_w = measure_text(ui->small_font, str8(" -288.8 mm")).w; 3012 f32 scale_bar_size = 1.2f * txt_w + RULER_TICK_LENGTH; 3013 3014 v3 U = frame->voxel_transform.c[0].xyz; 3015 v3 V = frame->voxel_transform.c[1].xyz; 3016 3017 v2 output_dim; 3018 output_dim.x = v3_magnitude(U); 3019 output_dim.y = v3_magnitude(V); 3020 3021 U = v3_scale(U, 1.f / output_dim.x); 3022 V = v3_scale(V, 1.f / output_dim.y); 3023 3024 v3 min_coordinate = m4_mul_v3(frame->voxel_transform, (v3){{0.f, 0.f, 0.f}}); 3025 v3 max_coordinate = m4_mul_v3(frame->voxel_transform, (v3){{1.f, 1.f, 1.f}}); 3026 3027 v2 min_2d = {{v3_dot(U, min_coordinate), v3_dot(V, min_coordinate)}}; 3028 v2 max_2d = {{v3_dot(U, max_coordinate), v3_dot(V, max_coordinate)}}; 3029 3030 f32 aspect = is_1d ? 1.0f : output_dim.w / output_dim.h; 3031 3032 Rect display_rect = ui_node_rect(container); 3033 Rect vr = rect_shrink_centered(display_rect, (v2){{UI_NODE_PAD, UI_NODE_PAD}}); 3034 3035 v2 scale_bar_area = {0}; 3036 if (view->scale_bar_active[Axis2_Y]) { 3037 vr.pos.y += 0.5f * (f32)ui->small_font.baseSize; 3038 scale_bar_area.x += scale_bar_size; 3039 scale_bar_area.y += (f32)ui->small_font.baseSize; 3040 } 3041 if (view->scale_bar_active[Axis2_X]) { 3042 vr.pos.x += 0.5f * (f32)ui->small_font.baseSize; 3043 scale_bar_area.x += (f32)ui->small_font.baseSize; 3044 scale_bar_area.y += scale_bar_size; 3045 } 3046 3047 vr.size = v2_sub(vr.size, scale_bar_area); 3048 if (aspect > 1) vr.size.h = vr.size.w / aspect; 3049 else vr.size.w = vr.size.h * aspect; 3050 3051 v2 occupied = v2_add(vr.size, scale_bar_area); 3052 if (occupied.w > display_rect.size.w) { 3053 vr.size.w -= (occupied.w - display_rect.size.w); 3054 vr.size.h = vr.size.w / aspect; 3055 } else if (occupied.h > display_rect.size.h) { 3056 vr.size.h -= (occupied.h - display_rect.size.h); 3057 vr.size.w = vr.size.h * aspect; 3058 } 3059 3060 b32 rebuild_transform = 0; 3061 3062 UIParent(container) 3063 { 3064 ui_padh(UI_NODE_PAD); 3065 3066 UINode *frame_top, *frame_view; 3067 UIChildLayoutAxis(Axis2_X) 3068 UIPrefHeight(ui_children_sum(1.f)) 3069 UIPrefWidth(ui_children_sum(1.f)) 3070 frame_top = ui_node_from_string(0, str8("###frame_view_top")); 3071 3072 UIParent(frame_top) 3073 UIPrefHeight(ui_px(vr.size.h, 1.f)) 3074 { 3075 UIChildLayoutAxis(Axis2_Y) 3076 UIPrefWidth(ui_px(vr.size.w, 1.f)) 3077 frame_view = ui_node_from_string(UINodeFlag_Clickable| 3078 UINodeFlag_CustomDraw| 3079 UINodeFlag_Clip| 3080 UINodeFlag_Scroll| 3081 0, str8("###frame_view")); 3082 frame_view->custom_draw_function = beamformer_custom_draw_frame_view; 3083 frame_view->custom_draw_context = push_struct(ui_build_arena(), BeamformerCustomDrawFrameViewData); 3084 { 3085 BeamformerCustomDrawFrameViewData *data = frame_view->custom_draw_context; 3086 data->uv_start = (v2){0}; 3087 data->uv_end = (v2){{1.f, 1.f}}; 3088 data->view = view; 3089 } 3090 3091 ui_build_frame_view_overlay(frame_view, view, min_2d, max_2d); 3092 3093 UISignal signal = ui_signal_from_node(frame_view); 3094 // TODO(rnp): is this correct for x-plane? 3095 if (ui_tweak_f32_compute_variable(signal, &view->threshold, 1.f, 1.f, V2_INFINITY)) 3096 view->dirty = 1; 3097 3098 if ui_pressed(signal) { 3099 view->ruler.state = circular_add(view->ruler.state, 1, RulerState_Count); 3100 // TODO(rnp): cleanup: this 3101 v3 p = world_point_from_plane_uv(frame->voxel_transform, rect_uv(ui->last_mouse, ui_node_rect(frame_view))); 3102 switch (view->ruler.state) { 3103 InvalidDefaultCase; 3104 case RulerState_None:{}break; 3105 case RulerState_Start:{view->ruler.start = p;}break; 3106 case RulerState_Hold:{ view->ruler.end = p;}break; 3107 } 3108 } 3109 3110 if (view->scale_bar_active[Axis2_Y]) { 3111 signal = ui_build_scale_bar(Axis2_Y, min_2d, max_2d); 3112 if ui_scrolled(signal) { 3113 max_2d.y += signal.scroll.y * 1e-3f; 3114 rebuild_transform = 1; 3115 } 3116 } 3117 } 3118 3119 if (view->scale_bar_active[Axis2_X]) 3120 UIChildLayoutAxis(Axis2_X) 3121 UIPrefHeight(ui_children_sum(1.0f)) 3122 UIPrefWidth(ui_children_sum(1.0f)) 3123 UIParent(ui_node_from_string(0, str8("###frame_view_bot"))) 3124 UIPrefWidth(ui_px(vr.size.w, 1.f)) 3125 { 3126 f32 top_position_offset = frame_view->computed_position[Axis2_X] - display_rect.pos.x; 3127 ui_padw(top_position_offset); 3128 3129 UISignal signal = ui_build_scale_bar(Axis2_X, min_2d, max_2d); 3130 if ui_scrolled(signal) { 3131 min_2d.x += signal.scroll.y * 0.5e-3f; 3132 max_2d.x -= signal.scroll.y * 0.5e-3f; 3133 rebuild_transform = 1; 3134 } 3135 3136 ui_padw(display_rect.size.x - top_position_offset); 3137 } 3138 } 3139 3140 if (rebuild_transform) { 3141 min_coordinate.E[0] = min_2d.E[0]; min_coordinate.E[1] = min_2d.E[1]; 3142 max_coordinate.E[0] = max_2d.E[0]; max_coordinate.E[1] = max_2d.E[1]; 3143 if (ui_rebuild_das_transform(frame->parameter_block, iv3_dimension(frame->points), min_coordinate, max_coordinate)) 3144 ui->flush_parameters = 1; 3145 } 3146 } 3147 3148 function UI_CUSTOM_DRAW_FUNCTION(beamformer_ui_custom_draw_compute_bar_graph) 3149 { 3150 ComputeShaderStats *stats = beamformer_context->compute_shader_stats; 3151 3152 UINode *labels = node->previous_sibling->previous_sibling; 3153 3154 u32 label_count = labels->child_count; 3155 f32 *total_times = push_array(ui_build_arena(), f32, label_count); 3156 f32 compute_time_sum = 0; 3157 3158 u32 stages = stats->table.shader_count; 3159 for (u32 index = 0; index < stages; index++) 3160 compute_time_sum += stats->average_times[index]; 3161 for EachIndex(label_count, frame) { 3162 u32 frame_index = (stats->latest_frame_index - frame - 1) % countof(stats->table.times); 3163 for EachIndex(stages, stage) 3164 total_times[frame] += stats->table.times[frame_index][stage]; 3165 } 3166 3167 f32 remaining_width = node_rect.size.w; 3168 f32 average_width = 0.8f * remaining_width; 3169 3170 s8 mouse_text = s8(""); 3171 v2 text_pos; 3172 3173 u32 row_index = 0; 3174 for (UINode *ln = labels->first_child; !ui_node_is_nil(ln); ln = ln->next_sibling, row_index++) { 3175 u32 frame_index = (stats->latest_frame_index - row_index - 1) % countof(stats->table.times); 3176 f32 total_width = average_width * total_times[row_index] / compute_time_sum; 3177 Rect rect; 3178 rect.pos = (v2){{node_rect.pos.x, ln->computed_position[Axis2_Y]}}; 3179 rect.size = (v2){.y = ln->computed_size[Axis2_Y]}; 3180 rect = rect_squish_centered(rect, (v2){.y = 0.4f}); 3181 3182 for (u32 i = 0; i < stages; i++) { 3183 rect.size.w = total_width * stats->table.times[frame_index][i] / total_times[row_index]; 3184 Color color = colour_from_normalized(g_colour_palette[i % countof(g_colour_palette)]); 3185 DrawRectangleRec(rl_rect(rect), color); 3186 if (point_in_rect(ui_context->last_mouse, rect)) { 3187 // TODO(rnp): tooltips 3188 text_pos = v2_add(rect.pos, (v2){{UI_NODE_PAD, 3.f}}); 3189 Stream sb = arena_stream(*ui_build_arena()); 3190 stream_append_s8s(&sb, beamformer_shader_names[stats->table.shader_ids[i]], s8(": ")); 3191 stream_append_f64_e(&sb, stats->table.times[frame_index][i]); 3192 mouse_text = arena_stream_commit(ui_build_arena(), &sb); 3193 } 3194 rect.pos.x += rect.size.w; 3195 } 3196 } 3197 3198 v2 start = v2_add(node_rect.pos, (v2){.x = average_width, .y = 0.01f * node_rect.size.y}); 3199 v2 end = v2_add(start, (v2){.y = node_rect.size.y - 0.02f * node_rect.size.y}); 3200 DrawLineEx(rl_v2(start), rl_v2(end), 4, colour_from_normalized(FG_COLOUR)); 3201 3202 if (mouse_text.len) { 3203 TextSpec ts = {.font = &ui_context->small_font, .flags = TF_OUTLINED, .colour = FG_COLOUR, 3204 .outline_colour = {.a = 1.f}, .outline_thick = 1.f}; 3205 draw_text(str8_from_s8(mouse_text), text_pos, &ts); 3206 } 3207 } 3208 3209 function void 3210 ui_build_compute_stats(BeamformerComputePlan *cp, f32 broken_shader_t) 3211 { 3212 ComputeShaderStats *stats = beamformer_context->compute_shader_stats; 3213 f32 compute_time_sum = 0; 3214 u32 stages = stats->table.shader_count; 3215 3216 for (u32 index = 0; index < stages; index++) 3217 compute_time_sum += stats->average_times[index]; 3218 3219 UIFontSize(30.f) 3220 UIScroll(Axis2_Count) 3221 { 3222 ui_top_parent()->child_layout_axis = Axis2_X; 3223 3224 UINode *label_column, *value_column, *unit_column; 3225 UIAxisAlign(Axis2_X, Left) 3226 UIChildLayoutAxis(Axis2_Y) 3227 UIPrefWidth(ui_children_sum(1.0f)) 3228 UIPrefHeight(ui_children_sum(1.0f)) 3229 { 3230 label_column = ui_node_from_string(0, str8("###labels")); 3231 ui_padw(UI_NODE_PAD); 3232 value_column = ui_node_from_string(0, str8("###values")); 3233 ui_padw(UI_NODE_PAD); 3234 unit_column = ui_node_from_string(0, str8("###units")); 3235 } 3236 3237 UIPrefWidth(ui_text_dim(1.0f, 1.0f)) 3238 UIPrefHeight(ui_text_dim(1.05f, 1.0f)) 3239 { 3240 for EachIndex(stages, it) { 3241 v4 label_colour = FG_COLOUR; 3242 if (vk_pipeline_valid(cp->vulkan_pipelines[it]) == 0 && 3243 stats->table.shader_ids[it] != BeamformerShaderKind_Hilbert) 3244 { 3245 label_colour = v4_lerp(FG_COLOUR, FOCUSED_COLOUR, ease_in_out_quartic(broken_shader_t)); 3246 } 3247 3248 str8 shader = str8_from_s8(beamformer_shader_names[stats->table.shader_ids[it]]); 3249 3250 UITextColour(label_colour) 3251 UIParent(label_column) ui_labelf("%.*s:###csl%u", (i32)shader.length, shader.data, (u32)it); 3252 UIParent(value_column) ui_labelf("%0.2e###csv%u", stats->average_times[it], (u32)it); 3253 UIParent(unit_column) ui_labelf("[s]###csu%u", (u32)it); 3254 } 3255 3256 UIParent(label_column) ui_label(str8("Compute Total:")); 3257 UIParent(value_column) ui_labelf("%0.2e (%0.2f)###csv_total", compute_time_sum, 3258 compute_time_sum > 0.f ? 1.0f / compute_time_sum : 0.f); 3259 UIParent(unit_column) ui_label(str8("[s] (FPS)###csv_total")); 3260 3261 UIParent(label_column) ui_label(str8("RF Upload Delta:")); 3262 UIParent(value_column) ui_labelf("%0.2e (%0.2f)###csv_upload", stats->rf_time_delta_average, 3263 stats->rf_time_delta_average > 0.f ? 1.0f / stats->rf_time_delta_average 3264 : 0.f); 3265 UIParent(unit_column) ui_label(str8("[s] (FPS)###csv_upload")); 3266 3267 u32 rf_size = beamformer_context->compute_context.rf_buffer.active_rf_size; 3268 UIParent(label_column) ui_label(str8("Input RF Size:")); 3269 UIParent(value_column) ui_labelf("%u###csv_rf_size", rf_size); 3270 UIParent(unit_column) ui_label(str8("[B/F]###csv_rf_size")); 3271 3272 UIParent(label_column) ui_label(str8("DAS RF Size:")); 3273 UIParent(value_column) ui_labelf("%u###csv_das_size", cp->rf_size); 3274 UIParent(unit_column) ui_label(str8("[B/F]###csv_das_size")); 3275 } 3276 } 3277 } 3278 3279 function void 3280 ui_build_parameters_listing(BeamformerUIPanel *panel) 3281 { 3282 BeamformerUI *ui = ui_context; 3283 3284 if ui_context_menu(panel) { 3285 UINode *label_column, *button_column; 3286 UIParent(ui->context_menu_root) 3287 UIChildLayoutAxis(Axis2_X) 3288 UIPrefHeight(ui_children_sum(1.f)) 3289 UIPrefWidth(ui_children_sum(1.f)) 3290 UIParent(ui_spacer(0)) 3291 UIChildLayoutAxis(Axis2_Y) 3292 { 3293 ui_padw(UI_NODE_PAD); 3294 UIAxisAlign(Axis2_X, Left) label_column = ui_node_from_string(0, str8("###labels")); 3295 ui_padw(UI_NODE_PAD * 2.f); 3296 UIAxisAlign(Axis2_X, Center) 3297 button_column = ui_node_from_string(0, str8("###buttons")); 3298 ui_padw(UI_NODE_PAD); 3299 } 3300 3301 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 3302 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3303 { 3304 UIParent(label_column) ui_label(str8("Block")); 3305 UIParent(button_column) 3306 { 3307 UISignal signal; 3308 u32 cycle = beamformer_context->shared_memory->reserved_parameter_blocks; 3309 u32 block = panel->u.parameter_listing.parameter_block; 3310 UIFlags(cycle <= 1 ? UINodeFlag_Disabled : 0) 3311 signal = ui_label_buttonf("%u", block); 3312 if (ui_pressed(signal) || ui_scrolled(signal)) { 3313 i32 delta = signal.scroll.y + ui_pressed(signal); 3314 panel->u.parameter_listing.parameter_block = circular_add(block, delta, cycle); 3315 } 3316 } 3317 } 3318 } 3319 3320 UIFontSize(30.f) 3321 UIScroll(Axis2_Count) 3322 { 3323 ui_top_parent()->child_layout_axis = Axis2_X; 3324 3325 UINode *label_column, *value_column, *unit_column; 3326 UIChildLayoutAxis(Axis2_Y) 3327 UIPrefWidth(ui_children_sum(1.0f)) 3328 UIPrefHeight(ui_children_sum(1.0f)) 3329 { 3330 UIAxisAlign(Axis2_X, Left) label_column = ui_node_from_string(0, str8("###labels")); 3331 ui_padw(UI_NODE_PAD); 3332 UIAxisAlign(Axis2_X, Center) value_column = ui_node_from_string(0, str8("###values")); 3333 ui_padw(UI_NODE_PAD); 3334 UIAxisAlign(Axis2_X, Right) unit_column = ui_node_from_string(0, str8("###units")); 3335 } 3336 3337 f32 line_pad_pct = 1.05f; 3338 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3339 UIPrefHeight(ui_text_dim(line_pad_pct, 1.f)) 3340 { 3341 BeamformerUIParameters *bp = &ui_context->parameters; 3342 UIParent(label_column) ui_label(str8("Sampling Frequency")); 3343 UIParent(value_column) ui_labelf("%0.2f##sampling", bp->sampling_frequency * 1e-6); 3344 UIParent(unit_column) ui_label(str8("[MHz]##sampling")); 3345 3346 UIParent(label_column) ui_label(str8("Demodulation Frequency")); 3347 UIParent(value_column) ui_labelf("%0.2f###demod", bp->demodulation_frequency * 1e-6); 3348 UIParent(unit_column) ui_label(str8("[MHz]##demod")); 3349 3350 UIParent(label_column) ui_label(str8("Speed of Sound")); 3351 UIParent(unit_column) ui_label(str8("[m/s]")); 3352 UIParent(value_column) 3353 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3354 { 3355 UISignal signal = ui_text_boxf("%0.2f###sound", bp->speed_of_sound); 3356 if (ui_tweak_f32_compute_variable(signal, &bp->speed_of_sound, 1.f, 10.f, (v2){{0, inf32()}})) 3357 ui->flush_parameters = 1; 3358 } 3359 3360 u32 parameter_block = panel->u.parameter_listing.parameter_block; 3361 b32 rebuild_transform = 0; 3362 3363 BeamformerParameterBlock *pb = beamformer_parameter_block(beamformer_context->shared_memory, parameter_block); 3364 BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[parameter_block]; 3365 m4 das_transform = pb->parameters.das_voxel_transform; 3366 if (cp) das_transform = m4_mul(cp->ui_voxel_transform, das_transform); 3367 v3 coordinates[2] = { 3368 m4_mul_v3(das_transform, (v3){{0.0f, 0.0f, 0.0f}}), 3369 m4_mul_v3(das_transform, (v3){{1.0f, 1.0f, 1.0f}}), 3370 }; 3371 3372 i32 dimension = iv3_dimension(bp->output_points.xyz); 3373 if (dimension > 0) { 3374 read_only local_persist str8 dimension_strings[3][2] = { 3375 {str8_comp("Start Point"), str8_comp("End Point") }, 3376 {str8_comp("Lateral Extent"), str8_comp("Axial Extent")}, 3377 {str8_comp("Min Corner"), str8_comp("Max Corner") }, 3378 }; 3379 3380 for (u32 index = 0; index < 2; index++) { 3381 UIParent(label_column) 3382 { 3383 UISignal signal = ui_button(dimension_strings[dimension - 1][index]); 3384 signal.node->flags &= ~(UINodeFlag_DrawBackground|UINodeFlag_DrawBorder); 3385 if ui_pressed(signal) 3386 panel->u.parameter_listing.expand_coordinate[index] ^= 1u; 3387 } 3388 3389 f32 values[3] = {coordinates[index].x, coordinates[index].y, coordinates[index].z}; 3390 u32 value_count = dimension == 2 ? 2 : 3; 3391 v3 normalized_axis = v3_normalize(das_transform.c[index].xyz); 3392 if (dimension == 2) { 3393 values[0] = v3_dot(normalized_axis, coordinates[0]); 3394 values[1] = v3_dot(normalized_axis, coordinates[1]); 3395 } 3396 3397 if (panel->u.parameter_listing.expand_coordinate[index]) { 3398 UIPrefHeight(ui_px((f32)ui_font_for_node(value_column).baseSize * line_pad_pct, 1.f)) 3399 { 3400 UIParent(value_column) ui_spacer(0); 3401 UIParent(unit_column) ui_spacer(0); 3402 } 3403 3404 read_only local_persist str8 axis_strings[2][3] = { 3405 {str8_comp(" X:"), str8_comp(" Y:"), str8_comp(" Z:")}, 3406 {str8_comp(" Min:"), str8_comp(" Max:"), }, 3407 }; 3408 str8 *strs = dimension == 2 ? axis_strings[1] : axis_strings[0]; 3409 for EachIndex(value_count, it) { 3410 UIParent(label_column) ui_labelf(" %.*s##label%u_%u", 3411 (i32)strs[it].length, strs[it].data, 3412 index, (u32)it); 3413 UIParent(unit_column) ui_labelf("[mm]##%u_%u", index, (u32)it); 3414 UIParent(value_column) 3415 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3416 { 3417 UISignal signal = ui_text_boxf("%0.2f###%u_%u", values[it] * 1e3f, index, (u32)it); 3418 rebuild_transform |= ui_tweak_f32_compute_variable(signal, values + it, 3419 1e-3f, 0.5e-3f, V2_INFINITY); 3420 } 3421 } 3422 } else { 3423 UIParent(unit_column) ui_labelf("[mm]##dim%u", index); 3424 3425 UINode *group; 3426 UIParent(value_column) 3427 UIChildLayoutAxis(Axis2_X) 3428 UIPrefWidth(ui_children_sum(1.f)) 3429 UIPrefHeight(ui_children_sum(1.f)) 3430 group = ui_spacer(0); 3431 3432 UIParent(group) 3433 { 3434 ui_labelf("{##%u", index); 3435 for EachIndex(value_count, it) { 3436 if (it != 0) ui_labelf(", ##%u_%u", index, (u32)it); 3437 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3438 { 3439 UISignal signal = ui_text_boxf("%0.2f###%u_%u", values[it] * 1e3f, index, (u32)it); 3440 rebuild_transform |= ui_tweak_f32_compute_variable(signal, values + it, 3441 1e-3f, 0.5e-3f, V2_INFINITY); 3442 } 3443 } 3444 ui_labelf("}##%u", index); 3445 } 3446 } 3447 3448 if (dimension == 2) { 3449 coordinates[0].E[index] = values[0]; 3450 coordinates[1].E[index] = values[1]; 3451 } 3452 } 3453 } 3454 3455 if (dimension == 2) { 3456 UIParent(label_column) ui_label(str8("Off Axis Position")); 3457 UIParent(unit_column) ui_label(str8("[mm]##off_axis")); 3458 UIParent(value_column) 3459 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3460 { 3461 UISignal signal = ui_text_boxf("%0.2f###off_axis", plane_offset_from_transform(das_transform) * 1e3f); 3462 rebuild_transform |= ui_tweak_f32_compute_variable(signal, &ui->off_axis_position, 3463 1e-3f, 0.1e-3f, V2_INFINITY); 3464 } 3465 3466 UIParent(label_column) ui_label(str8("Beamform Plane")); 3467 UIParent(unit_column) UIPrefHeight(ui_em(1.f, 1.f)) ui_spacer(0); 3468 UIParent(value_column) 3469 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3470 { 3471 UISignal signal = ui_text_boxf("%0.2f###beamform_plane", ui->beamform_plane); 3472 rebuild_transform |= ui_tweak_f32_compute_variable(signal, &ui->beamform_plane, 3473 1.f, 0.025f, (v2){{-1.f, 1.f}}); 3474 } 3475 } 3476 3477 UIParent(label_column) ui_label(str8("F#")); 3478 UIParent(unit_column) UIPrefHeight(ui_em(1.f, 1.f)) ui_spacer(0); 3479 UIParent(value_column) 3480 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3481 { 3482 UISignal signal = ui_text_boxf("%0.2f###f_number", bp->f_number); 3483 if (ui_tweak_f32_compute_variable(signal, &bp->f_number, 1.f, 0.05f, (v2){{0, inf32()}})) 3484 ui->flush_parameters = 1; 3485 } 3486 3487 UIParent(label_column) ui_label(str8("Interpolation")); 3488 UIParent(unit_column) ui_build_node_from_key(0, ui_node_key_zero()); 3489 UIParent(value_column) 3490 UIFlags(UINodeFlag_Scroll) 3491 { 3492 str8 label = str8_from_s8(beamformer_interpolation_mode_strings[bp->interpolation_mode]); 3493 UISignal signal = ui_label_button(label); 3494 if (ui_pressed(signal) || ui_scrolled(signal)) { 3495 i32 delta = signal.scroll.y + ui_pressed(signal); 3496 bp->interpolation_mode = circular_add(bp->interpolation_mode, delta, 3497 BeamformerInterpolationMode_Count); 3498 ui->flush_parameters = 1; 3499 } 3500 } 3501 3502 UIParent(label_column) ui_label(str8("Coherency Weighting")); 3503 UIParent(unit_column) UIPrefHeight(ui_em(1.0f, 1.0f)) ui_spacer(0); 3504 UIParent(value_column) 3505 UIFlags(UINodeFlag_Scroll) 3506 { 3507 UISignal signal = ui_label_button(bp->coherency_weighting ? 3508 str8("True###coherency_weighting") : 3509 str8("False###coherency_weighting")); 3510 if (signal.flags & (UISignalFlag_Pressed|UISignalFlag_Scrolled)) { 3511 bp->coherency_weighting = !bp->coherency_weighting; 3512 ui->flush_parameters = 1; 3513 } 3514 } 3515 3516 if (rebuild_transform) { 3517 if (ui_rebuild_das_transform(parameter_block, dimension, coordinates[0], coordinates[1])) 3518 ui->flush_parameters = 1; 3519 } 3520 } 3521 } 3522 } 3523 3524 function f32 3525 ui_slider_update_from_signal(f32 percent, UISignal signal) 3526 { 3527 f32 result = percent; 3528 result += 0.05f * signal.scroll.y; 3529 if ui_dragging(signal) 3530 result = rect_uv(ui_context->last_mouse, ui_node_rect(signal.node)).E[signal.node->parent->child_layout_axis]; 3531 result = Clamp01(result); 3532 return result; 3533 } 3534 3535 function void 3536 ui_build_live_imaging_controls(BeamformerUIPanel *panel) 3537 { 3538 BeamformerLiveImagingParameters *lip = &beamformer_context->shared_memory->live_imaging_parameters; 3539 3540 UIFontSize(30.f) 3541 UIScroll(Axis2_Count) 3542 { 3543 ui_top_parent()->child_layout_axis = Axis2_Y; 3544 3545 if (popcount_u64(lip->acquisition_kind_enabled_flags) > 1) 3546 UIPrefWidth(ui_children_sum(1.f)) 3547 UIPrefHeight(ui_children_sum(1.f)) 3548 UIChildLayoutAxis(Axis2_X) 3549 UIParent(ui_spacer(0)) 3550 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 3551 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3552 { 3553 u32 kind = lip->acquisition_kind; 3554 ui_label(str8("Acquisition: ")); 3555 str8 kind_string = kind < BeamformerAcquisitionKind_Count ? beamformer_acquisition_kind_strings[kind] 3556 : str8("Invalid"); 3557 3558 UISignal signal = ui_label_button(kind_string); 3559 if ui_pressed(signal) 3560 ui_context_menu_open(signal.node->key, panel); 3561 3562 if ui_context_menu(panel) { 3563 u64 enabled_kinds = atomic_load_u64(&lip->acquisition_kind_enabled_flags); 3564 3565 UIParent(ui_context->context_menu_root) 3566 UIFontSize(24.f) 3567 UIChildLayoutAxis(Axis2_X) 3568 UIPrefHeight(ui_children_sum(1.f)) 3569 UIPrefWidth(ui_children_sum(1.f)) 3570 for EachBit(enabled_kinds, kind) 3571 UIParent(ui_spacer(0)) 3572 { 3573 ui_padw(UI_NODE_PAD); 3574 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 3575 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3576 signal = ui_label_button(beamformer_acquisition_kind_strings[kind]); 3577 ui_padw(UI_NODE_PAD); 3578 3579 if ui_pressed(signal) { 3580 ui_context_menu_close(); 3581 lip->acquisition_kind = kind; 3582 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3583 BeamformerLiveImagingDirtyFlags_AcquisitionKind); 3584 } 3585 } 3586 } 3587 } 3588 3589 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 3590 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3591 { 3592 UINode *spacer; 3593 UISignal signal; 3594 3595 f32 row_height = ui_label(str8("Power:")).node->computed_size[Axis2_Y]; 3596 3597 UIPrefWidth(ui_pct(1.f, 1.f)) 3598 UIPrefHeight(ui_px(row_height, 1.f)) 3599 UIChildLayoutAxis(Axis2_X) 3600 spacer = ui_spacer(0); 3601 UIParent(spacer) 3602 { 3603 ui_padw(2 * UI_NODE_PAD); 3604 v4 hsv_power_slider = {{0.35f * ease_in_out_cubic(1.0f - lip->transmit_power), 0.65f, 0.65f, 1}}; 3605 UIBGColour(hsv_to_rgb(hsv_power_slider)) 3606 UIPrefHeight(ui_px(row_height, 1.f)) 3607 UIPrefWidth(ui_pct(1.f, 0.5f)) 3608 signal = ui_slider(lip->transmit_power, str8("###transmit_power")); 3609 if (signal.flags) { 3610 lip->transmit_power = ui_slider_update_from_signal(lip->transmit_power, signal); 3611 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3612 BeamformerLiveImagingDirtyFlags_TransmitPower); 3613 } 3614 ui_padw(2 * UI_NODE_PAD); 3615 } 3616 3617 row_height = ui_label(str8("TGC:")).node->computed_size[Axis2_Y]; 3618 for EachElement(lip->tgc_control_points, it) { 3619 UIPrefWidth(ui_pct(1.f, 1.f)) 3620 UIPrefHeight(ui_px(row_height, 1.f)) 3621 UIChildLayoutAxis(Axis2_X) 3622 spacer = ui_spacer(0); 3623 UIParent(spacer) 3624 { 3625 ui_padw(2 * UI_NODE_PAD); 3626 UIBGColour(g_colour_palette[1]) 3627 UIPrefHeight(ui_px(row_height, 1.f)) 3628 UIPrefWidth(ui_pct(1.f, 0.5f)) 3629 signal = ui_sliderf(lip->tgc_control_points[it], "###tgc_%u", (u32)it); 3630 if (signal.flags) { 3631 lip->tgc_control_points[it] = ui_slider_update_from_signal(lip->tgc_control_points[it], signal); 3632 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3633 BeamformerLiveImagingDirtyFlags_TGCControlPoints); 3634 } 3635 ui_padw(2 * UI_NODE_PAD); 3636 } 3637 } 3638 3639 if (lip->save_enabled) { 3640 ui_label(str8("File Name Tag:")); 3641 str8 save_name = (str8){.data = (u8 *)lip->save_name_tag, 3642 .length = Clamp(lip->save_name_tag_length, 0, countof(lip->save_name_tag))}; 3643 3644 3645 v4 save_text_colour = FG_COLOUR; 3646 u64 text_input_flags = 0; 3647 if (lip->save_name_tag_length <= 0) { 3648 save_text_colour.a = 0.6f; 3649 save_name = str8("Insert Text..."); 3650 text_input_flags = UINodeFlag_TextInputClearOnStart; 3651 } 3652 3653 UIPrefWidth(ui_children_sum(1.f)) 3654 UIPrefHeight(ui_children_sum(1.f)) 3655 UIChildLayoutAxis(Axis2_X) 3656 spacer = ui_spacer(0); 3657 UIParent(spacer) 3658 { 3659 ui_padw(2 * UI_NODE_PAD); 3660 UITextColour(save_text_colour) 3661 UIFlags(text_input_flags) 3662 signal = ui_text_box(push_str8_from_parts(ui_build_arena(), str8(""), save_name, 3663 str8("###save_name_field"))); 3664 if (ui_node_key_equal(signal.node->key, ui_context->text_input_state.node_key)) 3665 signal.node->text_colour = FG_COLOUR; 3666 3667 if (signal.flags & UISignalFlag_TextCommit) { 3668 str8 string = signal.string; 3669 lip->save_name_tag_length = Min(string.length, countof(lip->save_name_tag)); 3670 memory_copy(lip->save_name_tag, string.data, lip->save_name_tag_length); 3671 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3672 BeamformerLiveImagingDirtyFlags_SaveNameTag); 3673 } 3674 } 3675 3676 ui_padh(UI_NODE_PAD); 3677 3678 UIChildLayoutAxis(Axis2_Y) 3679 UIAxisAlign(Axis2_X, Center) 3680 UIPrefWidth(ui_children_sum(1.f)) 3681 UIPrefHeight(ui_children_sum(1.f)) 3682 spacer = ui_spacer(0); 3683 3684 UIParent(spacer) 3685 UITextAlign(Center) 3686 UIBGColour((v4){0}) 3687 UIPrefWidth(ui_text_dim(1.3f, 1.f)) 3688 UIPrefHeight(ui_text_dim(1.3f, 1.f)) 3689 { 3690 b32 active = lip->save_active; 3691 str8 label = active ? str8("Saving...###save_button") : str8("Save Data###save_button"); 3692 f32 save_t = beamformer_ui_blinker_update(&panel->u.live_imaging_save_button_blinker, BLINK_SPEED); 3693 v4 border_colour = (v4){.a = 0.6f}; 3694 if (active) border_colour = v4_lerp(BORDER_COLOUR, FOCUSED_COLOUR, ease_in_out_cubic(save_t)); 3695 UIBorderColour(border_colour) 3696 signal = ui_button(label); 3697 if ui_pressed(signal) { 3698 lip->save_active = !active; 3699 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3700 BeamformerLiveImagingDirtyFlags_SaveData); 3701 } 3702 3703 ui_padh(UI_NODE_PAD); 3704 3705 UIBorderColour((v4){.a = 0.6f}) 3706 signal = ui_button(str8("Stop Imaging")); 3707 if ui_pressed(signal) 3708 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3709 BeamformerLiveImagingDirtyFlags_StopImaging); 3710 } 3711 } 3712 } 3713 } 3714 } 3715 3716 function UISignal 3717 ui_panel_label(BeamformerUIPanel *panel) 3718 { 3719 Stream sb = arena_stream(*ui_build_arena()); 3720 switch (panel->kind) { 3721 InvalidDefaultCase; 3722 case BeamformerPanelKind_ComputeBarGraph:{stream_append_s8(&sb, s8("Compute Bar Graph"));}break; 3723 case BeamformerPanelKind_ComputeStats:{stream_append_s8(&sb, s8("Compute Stats"));}break; 3724 case BeamformerPanelKind_FrameViewLive:{stream_append_s8(&sb, s8("Frame View"));}break; 3725 case BeamformerPanelKind_FrameViewXPlane:{stream_append_s8(&sb, s8("X-Plane View"));}break; 3726 case BeamformerPanelKind_LiveImagingControls:{stream_append_s8(&sb, s8("Live Controls"));}break; 3727 case BeamformerPanelKind_FrameViewCopy:{ 3728 stream_append_s8(&sb, s8("Frame Copy [")); 3729 stream_append_hex_u64(&sb, panel->u.frame_view->frame.id); 3730 stream_append_s8(&sb, s8("]#")); 3731 }break; 3732 case BeamformerPanelKind_ParameterListing:{ 3733 stream_append_s8(&sb, s8("Parameter Listing [")); 3734 stream_append_u64(&sb, panel->u.parameter_listing.parameter_block); 3735 stream_append_s8(&sb, s8("]#")); 3736 }break; 3737 } 3738 stream_append_s8(&sb, s8("##")); 3739 stream_append_hex_u64(&sb, (u64)panel); 3740 s8 title_s8 = arena_stream_commit(ui_build_arena(), &sb); 3741 3742 UISignal result; 3743 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3744 UIPrefHeight(ui_text_dim(1.4f, 1.f)) 3745 result = ui_label(str8_from_s8(title_s8)); 3746 3747 return result; 3748 } 3749 3750 function void 3751 ui_insert_drop_site_spacer_before(UINode *before, f32 pad_node_width) 3752 { 3753 UIParent(0) 3754 { 3755 UINode *spacer, *spacer_gap; 3756 UIPrefHeight(ui_pct(1.f, 0.5f)) 3757 UIPrefWidth(ui_px(24.f, 1.f)) 3758 UIBorderColour((v4){.a = 0.9f}) 3759 UIBGColour((v4){.a = 0.6f}) 3760 spacer = ui_spacer(UINodeFlag_DrawBackground|UINodeFlag_DrawBorder); 3761 3762 UIPrefWidth(ui_px(pad_node_width, 1.f)) 3763 spacer_gap = ui_spacer(0); 3764 3765 spacer->parent = before->parent; 3766 spacer_gap->parent = before->parent; 3767 spacer->parent->child_count += 2; 3768 3769 spacer->previous_sibling = before->previous_sibling; 3770 spacer->next_sibling = spacer_gap; 3771 before->previous_sibling->next_sibling = spacer; 3772 3773 spacer_gap->previous_sibling = spacer; 3774 spacer_gap->next_sibling = before; 3775 3776 before->previous_sibling = spacer_gap; 3777 } 3778 } 3779 3780 function UINode * 3781 ui_box_pad(UINode *container, UISize pad, str8 tag) 3782 { 3783 UINode *result; 3784 UIParent(container) 3785 UIAxisSize(Axis2_X, ui_pct(1.f, 0.5f)) 3786 { 3787 UIAxisSize(Axis2_Y, pad) ui_spacer(0); 3788 3789 UIAxisSize(Axis2_Y, ui_pct(1.f, 0.5f)) 3790 UIChildLayoutAxis(Axis2_X) 3791 UIParent(ui_spacer(0)) 3792 { 3793 UIAxisSize(Axis2_X, pad) ui_spacer(0); 3794 UIChildLayoutAxis(container->child_layout_axis) 3795 result = ui_node_from_string(0, tag); 3796 UIAxisSize(Axis2_X, pad) ui_spacer(0); 3797 } 3798 3799 UIAxisSize(Axis2_Y, pad) ui_spacer(0); 3800 } 3801 return result; 3802 } 3803 3804 function print_format(3, 4) UINode * 3805 ui_box_padf(UINode *container, UISize pad, const char *format, ...) 3806 { 3807 va_list args; 3808 va_start(args, format); 3809 UINode *result = ui_box_pad(container, pad, push_str8_fv(ui_build_arena(), format, args)); 3810 va_end(args); 3811 return result; 3812 } 3813 3814 function BeamformerUIPanel * 3815 ui_panel_group_equip(UINode *node, BeamformerUIPanel *group) 3816 { 3817 BeamformerUIPanel *result = group; 3818 3819 if (group->kind != BeamformerPanelKind_Split) 3820 UIPrefWidth(ui_children_sum(1.f)) 3821 UIPrefHeight(ui_children_sum(1.f)) 3822 { 3823 assert(group->kind == BeamformerPanelKind_TabGroup); 3824 BeamformerUIPanel *focus = result = group->u.tab_focus; 3825 3826 node->flags |= UINodeFlag_DropSite; 3827 3828 UINode *tab_bar_node, *tab_clip_node; 3829 UIParent(node) 3830 UIChildLayoutAxis(Axis2_X) 3831 UIPrefWidth(ui_pct(1.f, 1.f)) 3832 tab_bar_node = ui_node_from_string(UINodeFlag_Clip, str8("###tab_scroll")); 3833 3834 UIParent(tab_bar_node) 3835 UIAxisAlign(Axis2_Y, Center) 3836 UIChildLayoutAxis(Axis2_X) 3837 tab_clip_node = ui_node_from_string(UINodeFlag_ViewScrollX, str8("###tab_clip")); 3838 3839 b32 drop_site = ui_node_key_equal(node->key, ui_context->drop_target_key) && 3840 ui_context->drag_panel && 3841 !beamformer_registers()->split_left_tree && 3842 !beamformer_registers()->split_right_tree; 3843 b32 drop_site_handled = 0; 3844 u32 drop_site_index = 0; 3845 f32 tab_pad = 6.f; 3846 UIParent(tab_clip_node) 3847 UIFontSize(24.f) 3848 { 3849 for (BeamformerUIPanel *tab = group->first_child; tab; tab = tab->next_sibling) { 3850 ui_padw(tab_pad); 3851 3852 // NOTE(rnp): push tab 3853 UINode *tab_node; 3854 UIAxisAlign(Axis2_Y, Center) 3855 UIChildLayoutAxis(Axis2_X) 3856 // TODO(rnp): per edge border colour 3857 UIBorderColour((v4){.a = 0.9f}) 3858 UIBGColour(tab == focus ? BG_COLOUR : (v4){.a = 0.6f}) 3859 UIFlags(UINodeFlag_Clickable| 3860 UINodeFlag_DrawBackground| 3861 UINodeFlag_DrawBorder| 3862 UINodeFlag_DrawHotEffects| 3863 UINodeFlag_DrawActiveEffects) 3864 tab_node = ui_node_from_stringf(tab == focus ? UINodeFlag_FocusActive : 0, "###tab%p", tab); 3865 3866 if (drop_site && !drop_site_handled && 3867 ui_context->last_mouse.x < (tab_node->computed_position[Axis2_X] + 0.5f * tab_node->computed_size[Axis2_X])) 3868 { 3869 drop_site_handled = 1; 3870 if (ui_context->drag_panel != tab && ui_context->drag_panel != tab->previous_sibling) 3871 ui_insert_drop_site_spacer_before(tab_node, tab_pad); 3872 } 3873 3874 UISignal signal = {0}; 3875 UIParent(tab_node) 3876 { 3877 ui_padw(UI_BORDER_THICK + tab_pad); 3878 3879 v4 fg_colour = FG_COLOUR; 3880 if (tab != focus) fg_colour.a = 0.8f; 3881 UITextColour(fg_colour) 3882 ui_panel_label(tab); 3883 3884 b32 has_settings = (beamformer_panel_infos[tab->kind].flags & BeamformerPanelFlags_HasSettings) != 0; 3885 if (tab == focus && has_settings) 3886 UIPrefWidth(ui_text_dim(2.f, 1.f)) 3887 UIPrefHeight(ui_pct(1.f, 1.f)) 3888 UITextAlign(Right) 3889 UIFlags(UINodeFlag_IconText) 3890 { 3891 ui_padw(0.5f * UI_NODE_PAD); 3892 3893 signal = ui_label_button(str8("+")); 3894 if ui_pressed(signal) 3895 ui_context_menu_open(signal.node->key, tab); 3896 } 3897 3898 ui_padw(0.5f * UI_NODE_PAD); 3899 3900 UIPrefWidth(ui_text_dim(2.f, 1.f)) 3901 UIPrefHeight(ui_pct(1.f, 1.f)) 3902 UITextAlign(Center) 3903 UIFlags(UINodeFlag_IconText) 3904 signal = ui_label_button(str8("x")); 3905 if (ui_pressed(signal) || signal.flags & UISignalFlag_MiddlePressed) 3906 beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)tab); 3907 3908 ui_padw(0.5f * UI_NODE_PAD); 3909 } 3910 3911 if (!drop_site_handled) drop_site_index++; 3912 3913 signal = ui_signal_from_node(tab_node); 3914 if ui_pressed(signal) 3915 beamformer_command(beamformer_command_infos[BeamformerCommandKind_FocusTab].string, .tree_node = (u64)tab); 3916 if (signal.flags & UISignalFlag_MiddlePressed) 3917 beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)tab); 3918 if (ui_dragging(signal) && !point_in_rect(ui_context->last_mouse, ui_node_rect(signal.node))) 3919 ui_drag_begin(tab); 3920 if ui_released(signal) 3921 ui_context->drag_end = 1; 3922 } 3923 3924 ui_padw(tab_pad); 3925 3926 // NOTE(rnp): context menu opener 3927 UISignal signal; 3928 UIPrefWidth(ui_text_dim(3.f, 1.f)) 3929 UIPrefHeight(ui_text_dim(3.f, 1.f)) 3930 UITextAlign(Center) 3931 UIFlags(UINodeFlag_IconText) 3932 signal = ui_label_button(str8("+")); 3933 if ui_pressed(signal) 3934 ui_context_menu_open(signal.node->key, group); 3935 3936 if ui_context_menu(group) { 3937 UIParent(ui_context->context_menu_root) 3938 UIChildLayoutAxis(Axis2_X) 3939 UIPrefHeight(ui_children_sum(1.f)) 3940 UIPrefWidth(ui_children_sum(1.f)) 3941 for EachElement(beamformer_panel_infos, it) 3942 { 3943 BeamformerPanelInfo *info = beamformer_panel_infos + it; 3944 b32 list = (info->flags & BeamformerPanelFlags_List) != 0; 3945 b32 needs_frame = (info->flags & BeamformerPanelFlags_NeedsFrame) != 0; 3946 if (list && (!needs_frame || beamformer_frame_valid(beamformer_registers()->frame))) { 3947 UIParent(ui_spacer(0)) 3948 { 3949 ui_padw(UI_NODE_PAD); 3950 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 3951 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3952 signal = ui_label_button(info->display); 3953 ui_padw(UI_NODE_PAD); 3954 3955 if ui_pressed(signal) { 3956 ui_context_menu_close(); 3957 beamformer_command(beamformer_command_infos[BeamformerCommandKind_OpenTab].string, 3958 .tree_node = (u64)group, 3959 .string = info->string); 3960 } 3961 } 3962 } 3963 } 3964 } 3965 3966 if (drop_site && !drop_site_handled && ui_context->drag_panel != group->last_child) { 3967 drop_site_handled = 1; 3968 ui_insert_drop_site_spacer_before(signal.node, tab_pad); 3969 } 3970 3971 ui_padw(tab_pad); 3972 } 3973 3974 ui_signal_from_node(tab_clip_node); 3975 3976 if (drop_site || drop_site_handled) { 3977 beamformer_registers()->drop_target_tree = (u64)group; 3978 beamformer_registers()->drop_child_index = drop_site_handled ? drop_site_index : group->child_count; 3979 } 3980 } 3981 3982 // NOTE(rnp): close tabgroup button 3983 if (!result && group != ui_context->tree) 3984 UIParent(ui_box_pad(node, ui_pct(0.5f, 0.5f), str8(""))) 3985 { 3986 ui_top_parent()->semantic_size[Axis2_X] = ui_children_sum(1.f); 3987 3988 UISignal signal; 3989 UIPrefWidth(ui_text_dim(1.5f, 1.f)) 3990 UIPrefHeight(ui_text_dim(2.f, 1.f)) 3991 UIBGColour((v4){.a = 0.3f}) 3992 UIBorderColour((v4){.a = 0.6f}) 3993 UITextAlign(Center) 3994 signal = ui_button(str8("Close Panel")); 3995 if ui_pressed(signal) 3996 beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)group); 3997 } 3998 3999 ui_signal_from_node(node); 4000 4001 return result; 4002 } 4003 4004 function void 4005 ui_build_regions(UINode *root_node, BeamformerUIPanel *tree_root) 4006 { 4007 BeamformerUI *ui = ui_context; 4008 4009 struct tree_frame { 4010 BeamformerUIPanel *tree; 4011 UINode *node; 4012 } init[64]; 4013 4014 struct { 4015 struct tree_frame *data; 4016 da_count count; 4017 da_count capacity; 4018 } stack = {init, 0, countof(init)}; 4019 4020 *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4021 .node = ui_box_padf(root_node, ui_px(UI_NODE_PAD, 1.f), "%p_padded", root_node), 4022 .tree = tree_root, 4023 }; 4024 while (stack.count) { 4025 struct tree_frame *top = stack.data + --stack.count; 4026 4027 BeamformerUIPanel *panel = top->tree; 4028 UINode *top_node = top->node; 4029 4030 UIParent(top_node) 4031 switch (panel->kind) { 4032 4033 case BeamformerPanelKind_TabGroup:{ 4034 UINode *node; 4035 UIChildLayoutAxis(Axis2_Y) 4036 node = ui_node_from_stringf(UINodeFlag_Clip, "###%p_group", panel); 4037 BeamformerUIPanel *next = ui_panel_group_equip(node, panel); 4038 if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4039 .tree = next, 4040 .node = node, 4041 }; 4042 }break; 4043 4044 case BeamformerPanelKind_Split:{ 4045 assert(panel->child_count == 2); 4046 4047 Axis2 axis = panel->u.split.axis; 4048 top_node->child_layout_axis = axis; 4049 4050 UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f)) 4051 { 4052 f32 split_pct = panel->u.split.fraction; 4053 4054 UINode *left; 4055 UIAxisSize(axis, ui_pct(split_pct, 0.5f)) 4056 UIChildLayoutAxis(Axis2_Y) 4057 left = ui_node_from_stringf(UINodeFlag_Clip, "###%p_left", panel); 4058 4059 BeamformerUIPanel *next = ui_panel_group_equip(left, panel->first_child); 4060 if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4061 .tree = next, 4062 .node = left, 4063 }; 4064 4065 UIAxisSize(axis, ui_children_sum(1.f)) 4066 UIChildLayoutAxis(axis) 4067 UIParent(ui_node_from_stringf(UINodeFlag_Clickable, "###%p_split", panel)) 4068 { 4069 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4070 UIAxisSize(axis, ui_px(UI_SPLIT_HANDLE_THICK, 1.f)) 4071 UIBGColour((v4){.a = 0.6f}) 4072 { 4073 UINode *rn = ui_spacer(UINodeFlag_DrawBackground|UINodeFlag_DrawHotEffects|UINodeFlag_DrawActiveEffects); 4074 rn->hot_t = ui_top_parent()->hot_t; 4075 } 4076 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4077 4078 UISignal signal = ui_signal_from_node(ui_top_parent()); 4079 if ui_dragging(signal) { 4080 Rect nr = ui_node_rect(top_node); 4081 v2 uv = rect_uv(clamp_v2_rect(ui->last_mouse, nr), nr); 4082 panel->u.split.fraction = Clamp(uv.E[panel->u.split.axis], 0.03f, 0.97f); 4083 } 4084 } 4085 4086 UINode *right; 4087 UIAxisSize(axis, ui_pct(1.f - split_pct, 0.5f)) 4088 UIChildLayoutAxis(Axis2_Y) 4089 right = ui_node_from_stringf(UINodeFlag_Clip, "###%p_right", panel); 4090 4091 next = ui_panel_group_equip(right, panel->last_child); 4092 if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4093 .tree = next, 4094 .node = right, 4095 }; 4096 } 4097 }break; 4098 4099 case BeamformerPanelKind_ComputeBarGraph:{ 4100 UIFontSize(30.f) 4101 UIScroll(Axis2_Y) 4102 { 4103 ui_top_parent()->child_layout_axis = Axis2_X; 4104 4105 UINode *label_column, *bar_column; 4106 UIAxisAlign(Axis2_X, Left) 4107 UIChildLayoutAxis(Axis2_Y) 4108 UIPrefWidth(ui_children_sum(1.f)) 4109 UIPrefHeight(ui_children_sum(1.f)) 4110 { 4111 UIAxisAlign(Axis2_X, Right) 4112 label_column = ui_node_from_string(0, str8("###labels")); 4113 ui_padw(UI_NODE_PAD); 4114 4115 f32 bar_width = ui_top_parent()->parent->parent->parent->computed_size[Axis2_X] 4116 - label_column->computed_size[Axis2_X] - UI_NODE_PAD; 4117 bar_column = ui_node_from_string(UINodeFlag_CustomDraw, str8("###bars")); 4118 bar_column->semantic_size[Axis2_X] = ui_px(bar_width, 0.1f); 4119 bar_column->semantic_size[Axis2_Y] = ui_px(label_column->computed_size[Axis2_Y], 1.f); 4120 bar_column->custom_draw_function = beamformer_ui_custom_draw_compute_bar_graph; 4121 } 4122 UIParent(label_column) 4123 UIPrefHeight(ui_text_dim(1.3f, 1.f)) 4124 UIPrefWidth(ui_text_dim(1.f, 1.f)) 4125 for (i32 i = 0; i < 4; i++) 4126 ui_labelf("%d:", -i); 4127 } 4128 4129 }break; 4130 4131 case BeamformerPanelKind_ComputeStats:{ 4132 u32 selected_plan = ui->selected_parameter_block % BeamformerMaxParameterBlocks; 4133 BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[selected_plan]; 4134 if (!cp) cp = &beamformer_nil_compute_plan; 4135 f32 t = beamformer_ui_blinker_update(&panel->u.compute_stats_broken_shader_blinker, BLINK_SPEED); 4136 ui_build_compute_stats(cp, t); 4137 }break; 4138 4139 case BeamformerPanelKind_FrameViewXPlane: 4140 { 4141 BeamformerFrameView *view = panel->u.frame_view; 4142 b32 any_valid = 0; 4143 for EachElement(view->plane_active, plane) 4144 any_valid |= (view->plane_active[plane] && ui_context->latest_plane[plane].timeline_valid_value); 4145 if (any_valid) { 4146 UINode *container; 4147 UIChildLayoutAxis(Axis2_Y) 4148 UIPrefWidth(ui_pct(1.f, 0.5f)) 4149 UIPrefHeight(ui_pct(1.f, 0.5f)) 4150 UIAxisAlign(Axis2_X, Center) 4151 container = ui_node_from_string(0, str8("###frame_view_container")); 4152 ui_build_3d_xplane_frame_view(container, view); 4153 } 4154 if ui_context_menu(panel) ui_build_3d_xplane_context_menu(view); 4155 }break; 4156 4157 case BeamformerPanelKind_FrameViewCopy: 4158 case BeamformerPanelKind_FrameViewLive: 4159 { 4160 BeamformerFrameView *view = panel->u.frame_view; 4161 if (iv3_dimension(view->frame.points) != 0) { 4162 // TODO(rnp): cleanup, why do we need this extra container 4163 UINode *container; 4164 UIChildLayoutAxis(Axis2_Y) 4165 UIPrefWidth(ui_pct(1.f, 0.5f)) 4166 UIPrefHeight(ui_pct(1.f, 0.5f)) 4167 UIAxisAlign(Axis2_X, Center) 4168 container = ui_node_from_string(0, str8("###frame_view_container")); 4169 ui_build_frame_view(container, view); 4170 } 4171 if ui_context_menu(panel) ui_build_frame_view_context_menu(panel, view); 4172 }break; 4173 4174 case BeamformerPanelKind_ParameterListing:{ ui_build_parameters_listing(panel); }break; 4175 4176 case BeamformerPanelKind_LiveImagingControls:{ ui_build_live_imaging_controls(panel); }break; 4177 4178 InvalidDefaultCase; 4179 } 4180 4181 ui_signal_from_node(top_node); 4182 } 4183 } 4184 4185 function UINode * 4186 ui_build_drag_hover_node(void) 4187 { 4188 BeamformerUI *ui = ui_context; 4189 4190 BeamformerUIPanel *tree = (BeamformerUIPanel *)beamformer_registers()->drop_target_tree; 4191 UINode *target = (UINode *)ui->drop_target_node; 4192 Axis2 axis = beamformer_registers()->split_axis; 4193 Axis2 flip = axis2_flip(axis); 4194 Rect nr = ui_node_rect(target); 4195 f32 pct = 4.0f; 4196 f32 off_pct = 0.95f; 4197 if (target == ui->root_node) 4198 pct = 0.1f; 4199 if (tree->kind == BeamformerPanelKind_TabGroup) { 4200 off_pct = 0.8f; 4201 pct = 0.3f; 4202 } 4203 if (beamformer_registers()->split_left_tree == beamformer_registers()->split_right_tree) 4204 off_pct = pct = 0.8f; 4205 4206 UINode *result = push_struct(ui_build_arena(), UINode); 4207 result->flags = UINodeFlag_DrawBackground; 4208 result->bg_colour = NODE_SPLIT_COLOUR; 4209 result->computed_size[flip] = off_pct * nr.size.E[flip]; 4210 result->computed_size[axis] = pct * nr.size.E[axis]; 4211 result->computed_position[axis] = nr.pos.E[axis]; 4212 result->computed_position[flip] = nr.pos.E[flip]; 4213 4214 if (target == ui->root_node) { 4215 result->computed_position[flip] += 0.5f * (nr.size.E[flip] - result->computed_size[flip]); 4216 if (beamformer_registers()->split_left_tree == (u64)ui->tree) 4217 result->computed_position[axis] = nr.pos.E[axis] + nr.size.E[axis] - result->computed_size[axis]; 4218 } else { 4219 result->computed_position[axis] += 0.5f * (nr.size.E[axis] - result->computed_size[axis]); 4220 result->computed_position[flip] += 0.5f * (nr.size.E[flip] - result->computed_size[flip]); 4221 4222 if (tree->kind == BeamformerPanelKind_TabGroup) { 4223 if (beamformer_registers()->split_left_tree == (u64)tree) 4224 result->computed_position[axis] += 0.45f * (nr.size.E[axis] - result->computed_size[axis]); 4225 if (beamformer_registers()->split_right_tree == (u64)tree) 4226 result->computed_position[axis] -= 0.45f * (nr.size.E[axis] - result->computed_size[axis]); 4227 } 4228 } 4229 4230 return result; 4231 } 4232 4233 function b32 4234 ui_build_drag_split_box(Axis2 axis, b32 two_way, i32 highlight_index, str8 tag) 4235 { 4236 UINode *split_box; 4237 UIAxisAlign(axis2_flip(axis), Center) 4238 UIAxisSize(axis2_flip(axis), ui_px(60.f, 1.f)) 4239 UIAxisSize(axis, ui_children_sum(1.f)) 4240 UIChildLayoutAxis(axis) 4241 UIBorderColour((v4){.a = 0.8f}) 4242 UIBGColour(BG_COLOUR) 4243 split_box = ui_node_from_string(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground, 4244 push_str8_from_parts(ui_build_arena(), str8(""), 4245 str8("drag_split_box_"), tag)); 4246 b32 result = point_in_rect(ui_context->last_mouse, ui_node_rect(split_box)); 4247 4248 UIParent(split_box) 4249 UIAxisSize(axis2_flip(axis), ui_pct(0.7f, 0.5f)) 4250 UIAxisSize(axis, ui_px(1.5f * UI_NODE_PAD, 1.f)) 4251 { 4252 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4253 4254 UIBorderColour((v4){.a = highlight_index <= 0 ? 0.0f : 0.4f}) 4255 UIBGColour(highlight_index <= 0 ? NODE_SPLIT_COLOUR : (v4){0}) 4256 ui_spacer(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground); 4257 4258 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4259 4260 if (two_way) { 4261 UIBorderColour((v4){.a = highlight_index != 0 ? 0.0f : 0.4f}) 4262 UIBGColour(highlight_index != 0 ? NODE_SPLIT_COLOUR : (v4){0}) 4263 ui_spacer(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground); 4264 4265 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4266 } 4267 } 4268 return result; 4269 } 4270 4271 function b32 4272 ui_build_drag_overlay_splitter(Axis2 axis, b32 two_way, i32 highlight_index, str8 tag) 4273 { 4274 b32 result = 0; 4275 4276 UINode *container; 4277 UIChildLayoutAxis(axis2_flip(axis)) 4278 UIAxisAlign(axis2_flip(axis), Center) 4279 UIAxisSize(axis, ui_children_sum(1.f)) 4280 UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f)) 4281 { 4282 container = ui_spacer(0); 4283 } 4284 4285 UIParent(container) 4286 result = ui_build_drag_split_box(axis, two_way, highlight_index, tag); 4287 return result; 4288 } 4289 4290 function void 4291 ui_build_drag_overlay(Rect window_rect) 4292 { 4293 BeamformerUI *ui = ui_context; 4294 4295 UIChildLayoutAxis(Axis2_Y) 4296 UIPrefWidth(ui_px(window_rect.size.x, 1.f)) 4297 UIPrefHeight(ui_px(window_rect.size.y, 1.f)) 4298 ui->drag_overlay_edges_root = ui_node_from_string(0, str8("drag_overlay_edges_root")); 4299 4300 UIParent(ui->drag_overlay_edges_root) 4301 { 4302 if (ui_build_drag_overlay_splitter(Axis2_Y, 0, 0, str8("top"))) 4303 { 4304 beamformer_registers()->split_axis = Axis2_Y; 4305 beamformer_registers()->split_left_tree = 0; 4306 beamformer_registers()->split_right_tree = (u64)ui->tree; 4307 beamformer_registers()->drop_target_tree = (u64)ui->tree; 4308 ui->drop_target_node = ui->root_node; 4309 } 4310 4311 UIPrefHeight(ui_pct(1.f, 0.5f)) 4312 UIParent(ui_spacer(0)) 4313 { 4314 if (ui_build_drag_overlay_splitter(Axis2_X, 0, 0, str8("left"))) 4315 { 4316 beamformer_registers()->split_axis = Axis2_X; 4317 beamformer_registers()->split_left_tree = 0; 4318 beamformer_registers()->split_right_tree = (u64)ui->tree; 4319 beamformer_registers()->drop_target_tree = (u64)ui->tree; 4320 ui->drop_target_node = ui->root_node; 4321 } 4322 4323 UIPrefWidth(ui_pct(1.f, 0.5f)) ui_spacer(0); 4324 4325 if (ui_build_drag_overlay_splitter(Axis2_X, 0, 0, str8("right"))) 4326 { 4327 beamformer_registers()->split_axis = Axis2_X; 4328 beamformer_registers()->split_left_tree = (u64)ui->tree; 4329 beamformer_registers()->split_right_tree = 0; 4330 beamformer_registers()->drop_target_tree = (u64)ui->tree; 4331 ui->drop_target_node = ui->root_node; 4332 } 4333 } 4334 4335 if (ui_build_drag_overlay_splitter(Axis2_Y, 0, 0, str8("bottom"))) 4336 { 4337 beamformer_registers()->split_axis = Axis2_Y; 4338 beamformer_registers()->split_left_tree = (u64)ui->tree; 4339 beamformer_registers()->split_right_tree = 0; 4340 beamformer_registers()->drop_target_tree = (u64)ui->tree; 4341 ui->drop_target_node = ui->root_node; 4342 } 4343 } 4344 4345 struct tree_frame { 4346 BeamformerUIPanel *tree; 4347 UINode *node; 4348 } init[64]; 4349 4350 struct { 4351 struct tree_frame *data; 4352 da_count count; 4353 da_count capacity; 4354 } stack = {init, 0, countof(init)}; 4355 4356 UIChildLayoutAxis(Axis2_Y) 4357 UIPrefWidth(ui_px(window_rect.size.x, 1.f)) 4358 UIPrefHeight(ui_px(window_rect.size.y, 1.f)) 4359 ui->drag_overlay_root = ui_node_from_string(0, str8("drag_overlay_root")); 4360 4361 *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4362 .node = ui->drag_overlay_root, 4363 .tree = ui->tree, 4364 }; 4365 while (stack.count) { 4366 struct tree_frame *top = stack.data + --stack.count; 4367 4368 BeamformerUIPanel *panel = top->tree; 4369 UINode *top_node = top->node; 4370 4371 UIParent(top_node) 4372 switch (panel->kind) { 4373 default:{}break; 4374 case BeamformerPanelKind_Split:{ 4375 Axis2 axis = panel->u.split.axis; 4376 top_node->child_layout_axis = axis; 4377 4378 UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f)) 4379 { 4380 f32 split_pct = panel->u.split.fraction; 4381 4382 UINode *spacer; 4383 UIAxisSize(axis, ui_pct(split_pct, 0.5f)) spacer = ui_spacer(0); 4384 4385 if (panel->first_child->kind == BeamformerPanelKind_Split) { 4386 *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4387 .tree = panel->first_child, 4388 .node = spacer, 4389 }; 4390 } 4391 4392 UIChildLayoutAxis(axis2_flip(axis)) 4393 UIAxisAlign(axis2_flip(axis), Center) 4394 UIAxisSize(axis, ui_children_sum(1.f)) 4395 UIParent(ui_spacer(0)) 4396 { 4397 // TODO(rnp): cleanup 4398 Stream sb = arena_stream(*ui_build_arena()); 4399 stream_appendf(&sb, "###%p_split", panel); 4400 s8 tag = arena_stream_commit(ui_build_arena(), &sb); 4401 4402 if (ui_build_drag_split_box(axis, 1, -1, str8_from_s8(tag))) { 4403 beamformer_registers()->split_axis = axis; 4404 beamformer_registers()->split_left_tree = (u64)panel; 4405 beamformer_registers()->split_right_tree = (u64)ui->drag_panel; 4406 beamformer_registers()->drop_target_tree = (u64)panel; 4407 ui->drop_target_node = ui_top_parent(); 4408 } 4409 } 4410 4411 UIAxisSize(axis, ui_pct(1.f - split_pct, 0.5f)) spacer = ui_spacer(0); 4412 4413 if (panel->last_child->kind == BeamformerPanelKind_Split) { 4414 *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4415 .tree = panel->last_child, 4416 .node = spacer, 4417 }; 4418 } 4419 } 4420 }break; 4421 } 4422 } 4423 4424 ui->drag_overlay_tab_root = 0; 4425 if (beamformer_registers()->drop_target_tree && 4426 !beamformer_registers()->split_left_tree && 4427 !beamformer_registers()->split_right_tree) 4428 { 4429 BeamformerUIPanel *group = (BeamformerUIPanel *)beamformer_registers()->drop_target_tree; 4430 UINode *target = ui_node_from_key(ui->drop_target_key); 4431 4432 assert(!group->parent || (group->parent && group->parent->kind == BeamformerPanelKind_Split)); 4433 Axis2 parent_axis = group->parent ? group->parent->u.split.axis : Axis2_Count; 4434 4435 Rect tr = ui_node_rect(target); 4436 if (point_in_rect(ui->last_mouse, tr)) { 4437 UIPrefWidth(ui_px(tr.size.x, 1.f)) 4438 UIPrefHeight(ui_px(tr.size.h, 1.f)) 4439 UIAxisAlign(Axis2_X, Center) 4440 UIAxisAlign(Axis2_Y, Center) 4441 UIChildLayoutAxis(Axis2_Y) 4442 { 4443 ui->drag_overlay_tab_root = ui_node_from_string(0, str8("drag_overlay_tab_root")); 4444 } 4445 4446 ui->drag_overlay_tab_root->computed_position[Axis2_X] = tr.pos.x; 4447 ui->drag_overlay_tab_root->computed_position[Axis2_Y] = tr.pos.y; 4448 4449 UINode *inner; 4450 UIAxisAlign(Axis2_X, Center) 4451 UIChildLayoutAxis(Axis2_Y) 4452 UIPrefHeight(ui_children_sum(1.f)) 4453 UIPrefWidth(ui_children_sum(1.f)) 4454 UIParent(ui->drag_overlay_tab_root) 4455 inner = ui_spacer(0); 4456 4457 UIParent(inner) 4458 { 4459 if (parent_axis != Axis2_Y && ui_build_drag_split_box(Axis2_Y, 1, 0, str8("top"))) 4460 { 4461 beamformer_registers()->split_axis = Axis2_Y; 4462 beamformer_registers()->split_left_tree = (u64)ui->drag_panel; 4463 beamformer_registers()->split_right_tree = (u64)group; 4464 ui->drop_target_node = target; 4465 } 4466 4467 ui_padh(UI_NODE_PAD); 4468 4469 UIPrefHeight(ui_children_sum(1.f)) 4470 UIPrefWidth(ui_children_sum(1.f)) 4471 UIParent(ui_spacer(0)) 4472 { 4473 if (parent_axis != Axis2_X && ui_build_drag_split_box(Axis2_X, 1, 0, str8("left"))) 4474 { 4475 beamformer_registers()->split_axis = Axis2_X; 4476 beamformer_registers()->split_left_tree = (u64)ui->drag_panel; 4477 beamformer_registers()->split_right_tree = (u64)group; 4478 ui->drop_target_node = target; 4479 } 4480 4481 ui_padw(UI_NODE_PAD); 4482 4483 if (parent_axis != Axis2_Count && 4484 ui_build_drag_split_box(axis2_flip(parent_axis), 0, 0, str8("center"))) 4485 { 4486 beamformer_registers()->split_left_tree = (u64)group; 4487 beamformer_registers()->split_right_tree = (u64)group; 4488 beamformer_registers()->drop_target_tree = (u64)group; 4489 beamformer_registers()->drop_child_index = group->child_count; 4490 ui->drop_target_node = target; 4491 } 4492 4493 ui_padw(UI_NODE_PAD); 4494 4495 if (parent_axis != Axis2_X && ui_build_drag_split_box(Axis2_X, 1, 1, str8("right"))) 4496 { 4497 beamformer_registers()->split_axis = Axis2_X; 4498 beamformer_registers()->split_left_tree = (u64)group; 4499 beamformer_registers()->split_right_tree = (u64)ui->drag_panel; 4500 ui->drop_target_node = target; 4501 } 4502 } 4503 4504 ui_padh(UI_NODE_PAD); 4505 4506 if (parent_axis != Axis2_Y && ui_build_drag_split_box(Axis2_Y, 1, 1, str8("bottom"))) 4507 { 4508 beamformer_registers()->split_axis = Axis2_Y; 4509 beamformer_registers()->split_left_tree = (u64)group; 4510 beamformer_registers()->split_right_tree = (u64)ui->drag_panel; 4511 ui->drop_target_node = target; 4512 } 4513 } 4514 } 4515 } 4516 } 4517 4518 function void 4519 ui_layout_constrain(UINode *root) 4520 { 4521 assert(!ui_node_is_nil(root->first_child)); 4522 4523 // NOTE(rnp): for violations in non-layout axis all we can do is clamp 4524 { 4525 Axis2 axis = axis2_flip(root->child_layout_axis); 4526 if ((root->flags & (UINodeFlag_AllowOverflowX << axis)) == 0) { 4527 for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4528 child->computed_size[axis] = Min(child->computed_size[axis], root->computed_size[axis]); 4529 } 4530 } 4531 4532 Axis2 axis = root->child_layout_axis; 4533 if ((root->flags & (UINodeFlag_AllowOverflowX << axis)) == 0) { 4534 f32 allowed_size = root->computed_size[axis]; 4535 f32 total_size = 0; 4536 f32 total_weighted_size = 0; 4537 4538 for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4539 total_size += child->computed_size[axis]; 4540 total_weighted_size += child->computed_size[axis] * (1.0f - child->semantic_size[axis].strictness); 4541 } 4542 4543 f32 remaining_size = root->computed_size[axis]; 4544 f32 violation = total_size - allowed_size; 4545 if (violation > 0 && total_weighted_size > 0) { 4546 f32 fixup_fraction = Clamp01(violation / total_weighted_size); 4547 for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4548 f32 fixup = Max(0, child->computed_size[axis] * (1.0f - child->semantic_size[axis].strictness)); 4549 child->computed_size[axis] -= fixup * fixup_fraction; 4550 4551 if (child->semantic_size[axis].kind != UISizeKind_PercentOfParent) 4552 remaining_size -= child->computed_size[axis]; 4553 } 4554 } 4555 4556 // NOTE(rnp): fixup sizes dependant on parent 4557 for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4558 if (child->semantic_size[axis].kind == UISizeKind_PercentOfParent) 4559 child->computed_size[axis] = remaining_size * child->semantic_size[axis].value; 4560 } 4561 } 4562 4563 function void 4564 ui_layout_nodes(UINode *root) 4565 { 4566 struct node_frame { 4567 UINode *node; 4568 // NOTE(rnp): for post order traversal 4569 b32 visited; 4570 } init[64] = {0}; 4571 4572 struct { 4573 struct node_frame *data; 4574 da_count count; 4575 da_count capacity; 4576 } stack = {init, 0, countof(init)}; 4577 4578 /////////////////////// 4579 // NOTE(rnp): First Pass: non dependant sizes 4580 da_push(ui_build_arena(), &stack)->node = root; 4581 while (stack.count) { 4582 struct node_frame *top = stack.data + --stack.count; 4583 UINode *node = top->node; 4584 4585 if (node->flags & UINodeFlag_DrawText) { 4586 Font font = ui_font_for_node(node); 4587 str8 string = ui_draw_part_from_key_string(node->string); 4588 if (node->flags & UINodeFlag_IconText) 4589 node->text_size = measure_text_tight(font, string); 4590 else 4591 node->text_size = measure_text(font, string); 4592 } 4593 4594 for EachElement(node->semantic_size, it) { 4595 switch (node->semantic_size[it].kind) { 4596 case UISizeKind_Pixels:{node->computed_size[it] = node->semantic_size[it].value;}break; 4597 4598 case UISizeKind_TextContent:{ 4599 node->computed_size[it] = node->semantic_size[it].value * node->text_size.E[it]; 4600 }break; 4601 4602 default:{}break; 4603 } 4604 } 4605 4606 // NOTE(rnp): push children 4607 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4608 da_push(ui_build_arena(), &stack)->node = child; 4609 } 4610 4611 /////////////////////// 4612 // NOTE(rnp): Second Pass (Pre Order): parent dependant sizes 4613 da_push(ui_build_arena(), &stack)->node = root; 4614 while (stack.count) { 4615 struct node_frame *top = stack.data + --stack.count; 4616 UINode *node = top->node; 4617 4618 for EachElement(node->semantic_size, it) { 4619 if (node->semantic_size[it].kind == UISizeKind_PercentOfParent) { 4620 f32 parent_size = node->parent->computed_size[it]; 4621 node->computed_size[it] = node->semantic_size[it].value * parent_size; 4622 } 4623 } 4624 4625 // NOTE(rnp): push children 4626 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4627 da_push(ui_build_arena(), &stack)->node = child; 4628 } 4629 4630 /////////////////////// 4631 // NOTE(rnp): Third Pass (Post Order): child dependant sizes 4632 da_push(ui_build_arena(), &stack)->node = root; 4633 while (stack.count) { 4634 struct node_frame *top = stack.data + stack.count - 1; 4635 4636 UINode *node = top->node; 4637 if (!top->visited && node->child_count) { 4638 top->visited = 1; 4639 4640 // NOTE(rnp): push children 4641 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4642 da_push(ui_build_arena(), &stack)->node = child; 4643 } else { 4644 // NOTE(rnp): pop 4645 stack.count--; 4646 4647 for EachElement(node->semantic_size, it) { 4648 if (node->semantic_size[it].kind == UISizeKind_ChildrenSum) { 4649 f32 size_sum = 0; 4650 for (UINode *child = node->first_child; 4651 !ui_node_is_nil(child); 4652 child = child->next_sibling) 4653 { 4654 if (it == node->child_layout_axis) { 4655 size_sum += child->computed_size[it]; 4656 } else { 4657 size_sum = Max(size_sum, child->computed_size[it]); 4658 } 4659 } 4660 node->computed_size[it] = size_sum; 4661 } 4662 } 4663 } 4664 } 4665 4666 /////////////////////// 4667 // NOTE(rnp): Fourth Pass (Pre Order): solve violations 4668 da_push(ui_build_arena(), &stack)->node = root; 4669 while (stack.count) { 4670 struct node_frame *top = stack.data + --stack.count; 4671 4672 UINode *node = top->node; 4673 if (node->child_count) 4674 ui_layout_constrain(node); 4675 4676 // NOTE(rnp): push children 4677 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4678 da_push(ui_build_arena(), &stack)->node = child; 4679 } 4680 4681 /////////////////////// 4682 // NOTE(rnp): Final Pass (Pre Order): fill positions 4683 da_push(ui_build_arena(), &stack)->node = root; 4684 while (stack.count) { 4685 struct node_frame *top = stack.data + --stack.count; 4686 4687 UINode *node = top->node; 4688 Axis2 layout_axis = node->child_layout_axis; 4689 Axis2 flipped_axis = axis2_flip(layout_axis); 4690 f32 offset = 0; 4691 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4692 child->computed_position[flipped_axis] = node->computed_position[flipped_axis]; 4693 child->computed_position[layout_axis] = offset + node->computed_position[layout_axis]; 4694 offset += child->computed_size[layout_axis]; 4695 } 4696 4697 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4698 for EachElement(node->alignment, axis) { 4699 f32 size_delta = node->computed_size[axis] - child->computed_size[axis]; 4700 child->computed_position[axis] += ui_alignment_correction(node->alignment[axis], size_delta); 4701 } 4702 } 4703 4704 // NOTE(rnp): push children 4705 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4706 if (child->child_count > 0) 4707 da_push(ui_build_arena(), &stack)->node = child; 4708 } 4709 } 4710 4711 function void 4712 ui_draw_nodes(UINode *root, Rect window_rect) 4713 { 4714 BeamformerUI *ui = ui_context; 4715 4716 struct node_frame { 4717 b32 visited; 4718 UINode *node; 4719 } init[64]; 4720 4721 struct { 4722 struct node_frame *data; 4723 da_count count; 4724 da_count capacity; 4725 } stack = {init, 0, countof(init)}; 4726 4727 u32 colour_index = 0; 4728 (void)colour_index; 4729 4730 da_push(ui_build_arena(), &stack)->node = root; 4731 while (stack.count) { 4732 struct node_frame *top = stack.data + stack.count - 1; 4733 4734 UINode *node = top->node; 4735 if (!top->visited) { 4736 top->visited = 1; 4737 4738 Rect r = ui_node_rect(node); 4739 if (node->flags & UINodeFlag_Clip) 4740 BeginScissorMode(r.pos.x, r.pos.y, r.size.w, r.size.h); 4741 4742 if (node->flags & UINodeFlag_ViewScroll) { 4743 v2 view_off = node->view_scroll_offset; 4744 rlPushMatrix(); 4745 rlTranslatef(-view_off.x, -view_off.y, 0); 4746 } 4747 4748 //v4 colour = g_colour_palette[(colour_index++) % countof(g_colour_palette)]; 4749 //DrawRectangleLinesEx(rl_rect(r), 4.0f, colour_from_normalized(colour)); 4750 4751 v4 bg_colour = node->bg_colour; 4752 if (node->flags & UINodeFlag_DrawHotEffects) 4753 bg_colour = v4_lerp(bg_colour, HOVERED_COLOUR, node->hot_t); 4754 4755 if (node->flags & UINodeFlag_DrawBackground) 4756 DrawRectangleRec(rl_rect(r), colour_from_normalized(bg_colour)); 4757 4758 if (node->flags & UINodeFlag_DrawBorder) { 4759 v4 colour = node->border_colour; 4760 u64 masked = node->flags & (UINodeFlag_DrawBackground|UINodeFlag_DrawHotEffects); 4761 if (masked == UINodeFlag_DrawHotEffects) 4762 colour = v4_lerp(colour, HOVERED_COLOUR, node->hot_t); 4763 4764 DrawRectangleLinesEx(rl_rect(r), node->border_thickness, colour_from_normalized(colour)); 4765 } 4766 4767 if (node->flags & UINodeFlag_CustomDraw) { 4768 node->custom_draw_function(node, r); 4769 } else { 4770 if (node->flags & UINodeFlag_DrawText) { 4771 Font font = ui_font_for_node(node); 4772 4773 TextSpec text_spec = { 4774 .font = &font, 4775 .flags = TF_LIMITED, 4776 .colour = node->text_colour, 4777 .outline_colour = node->text_outline_colour, 4778 .outline_thick = node->text_outline_thickness, 4779 .limits.size = r.size, 4780 }; 4781 if (node->text_outline_thickness > 0) 4782 text_spec.flags |= TF_OUTLINED; 4783 4784 v2 pos = ui_node_text_position(node); 4785 4786 UITextInputState *tis = &ui_context->text_input_state; 4787 b32 input = ui_node_key_equal(node->key, tis->node_key); 4788 // TODO(rnp): cleanup: visible part 4789 str8 string = ui_draw_part_from_key_string(node->string); 4790 if (!input && node->flags & UINodeFlag_DrawHotEffects && (node->flags & UINodeFlag_DrawBackground) == 0) 4791 text_spec.colour = v4_lerp(text_spec.colour, HOVERED_COLOUR, node->hot_t); 4792 4793 if (node->flags & UINodeFlag_IconText) 4794 draw_text_tight(*text_spec.font, string, pos, colour_from_normalized(text_spec.colour)); 4795 else 4796 draw_text(string, pos, &text_spec); 4797 4798 if (input) { 4799 iv2 range = ui_text_input_cursor_range(); 4800 str8 parts[2]; 4801 parts[0] = (str8){.data = string.data, .length = range.x}; 4802 parts[1] = (str8){.data = string.data + range.x, .length = range.y - range.x}; 4803 4804 Rect cursor = {.pos = pos}; 4805 cursor.pos.x += measure_text(font, parts[0]).x; 4806 4807 v4 cursor_colour = FOCUSED_COLOUR; 4808 if (parts[1].length > 0) { 4809 cursor_colour = SELECTION_COLOUR; 4810 cursor.size = measure_text(font, parts[1]); 4811 4812 if (range.x == 0) { 4813 cursor.pos.x -= 2.f; 4814 cursor.size.x += 2.f; 4815 } 4816 4817 if (range.y == tis->count) 4818 cursor.size.x += 2.f; 4819 } else { 4820 cursor_colour.a = ease_in_out_cubic(ui->text_input_state.blinker.t); 4821 cursor.size.x = string.length - range.y > 0 ? 4.0f : 0.55f * (f32)font.baseSize; 4822 cursor.size.y = font.baseSize; 4823 } 4824 4825 if (cursor.size.x > 0) 4826 DrawRectanglePro(rl_rect(cursor), (Vector2){0}, 0, colour_from_normalized(cursor_colour)); 4827 } 4828 } 4829 } 4830 4831 // NOTE(rnp): push children 4832 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4833 Rect cr = ui_node_rect(child); 4834 if ((cr.size.x > 0 && cr.size.y > 0) || ui_node_key_equal(child->key, ui->text_input_state.node_key)) 4835 da_push(ui_build_arena(), &stack)->node = child; 4836 } 4837 } else { 4838 // NOTE(rnp): pop 4839 stack.count--; 4840 4841 if (node->flags & UINodeFlag_ViewScroll) { 4842 rlPopMatrix(); 4843 } 4844 4845 if (node->flags & UINodeFlag_Clip) 4846 EndScissorMode(); 4847 } 4848 } 4849 4850 // TODO(rnp): can we make the mouse latency not shit? 4851 //if (ui->current_mouse.x > 0) DrawCircle(ui->current_mouse.x, ui->current_mouse.y, 6, GREEN); 4852 } 4853 4854 function void 4855 beamformer_ui_panel_unlink(BeamformerUIPanel *node) 4856 { 4857 BeamformerUIPanel *parent = node->parent; 4858 if (parent->kind == BeamformerPanelKind_TabGroup && parent->u.tab_focus == node) 4859 parent->u.tab_focus = node->previous_sibling ? node->previous_sibling : node->next_sibling; 4860 DLLRemove(0, parent->first_child, parent->last_child, node, next_sibling, previous_sibling); 4861 parent->child_count--; 4862 } 4863 4864 function void 4865 ui_kill_panel(BeamformerUIPanel *node) 4866 { 4867 BeamformerUI *ui = ui_context; 4868 BeamformerUIPanel *parent = node->parent; 4869 4870 if (node->kind == BeamformerPanelKind_FrameViewLive || 4871 node->kind == BeamformerPanelKind_FrameViewCopy || 4872 node->kind == BeamformerPanelKind_FrameViewXPlane) 4873 { 4874 BeamformerFrameView *bv = node->u.frame_view; 4875 beamformer_ui_frame_view_release_subresources(bv, bv->kind); 4876 DLLRemove(0, ui->view_first, ui->view_last, bv, next, prev); 4877 SLLStackPush(ui->view_freelist, bv, next); 4878 } 4879 4880 beamformer_ui_panel_unlink(node); 4881 4882 if (node->kind == BeamformerPanelKind_TabGroup) { 4883 assert(parent->kind == BeamformerPanelKind_Split); 4884 4885 BeamformerUIPanel *old_child = parent->first_child; 4886 parent->kind = old_child->kind; 4887 parent->first_child = old_child->first_child; 4888 parent->last_child = old_child->last_child; 4889 parent->child_count = old_child->child_count; 4890 memory_copy(&parent->u, &old_child->u, sizeof(parent->u)); 4891 4892 for (BeamformerUIPanel *child = parent->first_child; child; child = child->next_sibling) 4893 child->parent = parent; 4894 4895 SLLStackPush(ui->tree_node_freelist, old_child, next_sibling); 4896 } 4897 4898 SLLStackPush(ui->tree_node_freelist, node, next_sibling); 4899 } 4900 4901 function BeamformerUIPanel * 4902 beamformer_ui_push_panel_node(BeamformerUIPanel *parent) 4903 { 4904 BeamformerUI *ui = ui_context; 4905 BeamformerUIPanel *result = ui->tree_node_freelist; 4906 if (result) SLLStackPop(ui->tree_node_freelist, next_sibling); 4907 else result = push_struct_no_zero(&ui->arena, BeamformerUIPanel); 4908 zero_struct(result); 4909 4910 result->parent = parent; 4911 if (parent) { 4912 DLLInsertLast(0, parent->first_child, parent->last_child, result, next_sibling, previous_sibling); 4913 parent->child_count++; 4914 } 4915 4916 return result; 4917 } 4918 4919 function BeamformerUIPanel * 4920 beamformer_ui_push_panel(BeamformerUIPanel *parent, BeamformerPanelKind kind) 4921 { 4922 BeamformerUIPanel *result = beamformer_ui_push_panel_node(parent); 4923 result->kind = kind; 4924 if (parent && parent->kind == BeamformerPanelKind_TabGroup) 4925 parent->u.tab_focus = result; 4926 4927 if (kind == BeamformerPanelKind_FrameViewLive || 4928 kind == BeamformerPanelKind_FrameViewCopy || 4929 kind == BeamformerPanelKind_FrameViewXPlane) 4930 { 4931 BeamformerFrameViewKind view_kind = BeamformerFrameViewKind_Latest; 4932 if (kind == BeamformerPanelKind_FrameViewCopy) 4933 view_kind = BeamformerFrameViewKind_Copy; 4934 if (kind == BeamformerPanelKind_FrameViewXPlane) 4935 view_kind = BeamformerFrameViewKind_3DXPlane; 4936 result->u.frame_view = beamformer_ui_frame_view_new(view_kind); 4937 } 4938 4939 return result; 4940 } 4941 4942 /* NOTE(rnp): this only exists to make asan less annoying. do not waste 4943 * people's time by freeing, closing, etc... */ 4944 DEBUG_EXPORT BEAMFORMER_DEBUG_UI_DEINIT_FN(beamformer_debug_ui_deinit) 4945 { 4946 #if ASAN_ACTIVE 4947 BeamformerUI *ui = ctx->ui; 4948 UnloadFont(ui->font); 4949 UnloadFont(ui->small_font); 4950 CloseWindow(); 4951 #endif 4952 } 4953 4954 function void 4955 ui_init(BeamformerCtx *ctx, Arena store) 4956 { 4957 BeamformerUI *ui = ui_context = ctx->ui; 4958 if (!ui) { 4959 ui = ui_context = ctx->ui = push_struct(&store, typeof(*ui)); 4960 ui->arena = store; 4961 4962 for EachElement(ui->build_arenas, it) { 4963 ui->build_arenas[it] = sub_arena(&ui->arena, KB(128), KB(4)); 4964 ui->build_arena_savepoints[it] = begin_temp_arena(ui->build_arenas + it); 4965 } 4966 ui->node_freelist = &ui_node_nil; 4967 4968 /* TODO(rnp): better font, this one is jank at small sizes */ 4969 ui->font = LoadFontFromMemory(".ttf", beamformer_base_font, sizeof(beamformer_base_font), 28, 0, 0); 4970 ui->small_font = LoadFontFromMemory(".ttf", beamformer_base_font, sizeof(beamformer_base_font), 20, 0, 0); 4971 4972 // NOTE(rnp): push default UI layout 4973 // TODO(rnp): load last layout from file and only load default if not present 4974 { 4975 BeamformerUIPanel *node = ui->tree = beamformer_ui_push_panel(0, BeamformerPanelKind_Split); 4976 node->u.split.fraction = 0.35f; 4977 node->u.split.axis = Axis2_X; 4978 4979 DeferLoop(node = beamformer_ui_push_panel(node, BeamformerPanelKind_Split), node = node->parent) 4980 { 4981 node->u.split.fraction = 0.65f; 4982 node->u.split.axis = Axis2_Y; 4983 4984 BeamformerUIPanel *left = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup); 4985 BeamformerUIPanel *right = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup); 4986 beamformer_ui_push_panel(left, BeamformerPanelKind_ParameterListing); 4987 beamformer_ui_push_panel(right, BeamformerPanelKind_ComputeBarGraph); 4988 beamformer_ui_push_panel(right, BeamformerPanelKind_ComputeStats); 4989 } 4990 4991 DeferLoop(node = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup), node = node->parent) 4992 { 4993 beamformer_ui_push_panel(node, BeamformerPanelKind_FrameViewLive); 4994 } 4995 } 4996 4997 u32 samples = vk_gpu_info()->max_msaa_samples; 4998 vk_image_allocate(&ui->render_3d_image, FRAME_VIEW_RENDER_TARGET_SIZE, 1, samples, VulkanImageUsage_Colour, 0, 0, s8("Render Target Colour")); 4999 vk_image_allocate(&ui->render_3d_depth_image, FRAME_VIEW_RENDER_TARGET_SIZE, 1, samples, VulkanImageUsage_DepthStencil, 0, 0, s8("Render Target Depth")); 5000 5001 glGenSemaphoresEXT(countof(ui->render_semaphores_gl), ui->render_semaphores_gl); 5002 for EachElement(ui->render_semaphores, it) 5003 ui->render_semaphores[it] = vk_create_semaphore(ui->render_semaphores_export + it); 5004 5005 if (OS_WINDOWS) { 5006 glImportSemaphoreWin32HandleEXT(ui->render_semaphores_gl[0], GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, (void *)ui->render_semaphores_export[0].value[0]); 5007 glImportSemaphoreWin32HandleEXT(ui->render_semaphores_gl[1], GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, (void *)ui->render_semaphores_export[1].value[0]); 5008 } else { 5009 glImportSemaphoreFdEXT(ui->render_semaphores_gl[0], GL_HANDLE_TYPE_OPAQUE_FD_EXT, ui->render_semaphores_export[0].value[0]); 5010 glImportSemaphoreFdEXT(ui->render_semaphores_gl[1], GL_HANDLE_TYPE_OPAQUE_FD_EXT, ui->render_semaphores_export[1].value[0]); 5011 ui->render_semaphores_export[0].value[0] = OSInvalidHandleValue; 5012 ui->render_semaphores_export[1].value[0] = OSInvalidHandleValue; 5013 } 5014 5015 if (!BakeShaders) 5016 { 5017 for EachElement(beamformer_reloadable_render_shader_info_indices, it) { 5018 i32 index = beamformer_reloadable_render_shader_info_indices[it]; 5019 for (u32 i = 0; i < 2; i++) { 5020 BeamformerFileReloadContext *frc = push_struct(&ui->arena, typeof(*frc)); 5021 frc->kind = BeamformerFileReloadKind_RenderShader; 5022 frc->shader_reload.shader = beamformer_reloadable_shader_kinds[index]; 5023 frc->shader_reload.pipeline = ui->pipelines + it; 5024 5025 Arena scratch = ui->arena; 5026 s8 file = push_s8_from_parts(&scratch, os_path_separator(), s8("shaders"), 5027 beamformer_reloadable_shader_files[index][i]); 5028 5029 os_add_file_watch((char *)file.data, file.len, frc); 5030 } 5031 } 5032 } 5033 5034 f32 unit_cube_vertices[] = { 5035 0.5f, 0.5f, -0.5f, 0.0f, 5036 0.5f, 0.5f, -0.5f, 0.0f, 5037 0.5f, 0.5f, -0.5f, 0.0f, 5038 0.5f, -0.5f, -0.5f, 0.0f, 5039 0.5f, -0.5f, -0.5f, 0.0f, 5040 0.5f, -0.5f, -0.5f, 0.0f, 5041 0.5f, 0.5f, 0.5f, 0.0f, 5042 0.5f, 0.5f, 0.5f, 0.0f, 5043 0.5f, 0.5f, 0.5f, 0.0f, 5044 0.5f, -0.5f, 0.5f, 0.0f, 5045 0.5f, -0.5f, 0.5f, 0.0f, 5046 0.5f, -0.5f, 0.5f, 0.0f, 5047 -0.5f, 0.5f, -0.5f, 0.0f, 5048 -0.5f, 0.5f, -0.5f, 0.0f, 5049 -0.5f, 0.5f, -0.5f, 0.0f, 5050 -0.5f, -0.5f, -0.5f, 0.0f, 5051 -0.5f, -0.5f, -0.5f, 0.0f, 5052 -0.5f, -0.5f, -0.5f, 0.0f, 5053 -0.5f, 0.5f, 0.5f, 0.0f, 5054 -0.5f, 0.5f, 0.5f, 0.0f, 5055 -0.5f, 0.5f, 0.5f, 0.0f, 5056 -0.5f, -0.5f, 0.5f, 0.0f, 5057 -0.5f, -0.5f, 0.5f, 0.0f, 5058 -0.5f, -0.5f, 0.5f, 0.0f, 5059 }; 5060 f32 unit_cube_normals[] = { 5061 0.0f, 0.0f, -1.0f, 0.0f, 5062 0.0f, 1.0f, 0.0f, 0.0f, 5063 1.0f, 0.0f, 0.0f, 0.0f, 5064 0.0f, 0.0f, -1.0f, 0.0f, 5065 0.0f, -1.0f, 0.0f, 0.0f, 5066 1.0f, 0.0f, 0.0f, 0.0f, 5067 0.0f, 0.0f, 1.0f, 0.0f, 5068 0.0f, 1.0f, 0.0f, 0.0f, 5069 1.0f, 0.0f, 0.0f, 0.0f, 5070 0.0f, 0.0f, 1.0f, 0.0f, 5071 0.0f, -1.0f, 0.0f, 0.0f, 5072 1.0f, 0.0f, 0.0f, 0.0f, 5073 0.0f, 0.0f, -1.0f, 0.0f, 5074 0.0f, 1.0f, 0.0f, 0.0f, 5075 -1.0f, 0.0f, 0.0f, 0.0f, 5076 0.0f, 0.0f, -1.0f, 0.0f, 5077 0.0f, -1.0f, 0.0f, 0.0f, 5078 -1.0f, 0.0f, 0.0f, 0.0f, 5079 0.0f, 0.0f, 1.0f, 0.0f, 5080 0.0f, 1.0f, 0.0f, 0.0f, 5081 -1.0f, 0.0f, 0.0f, 0.0f, 5082 0.0f, 0.0f, 1.0f, 0.0f, 5083 0.0f, -1.0f, 0.0f, 0.0f, 5084 -1.0f, 0.0f, 0.0f, 0.0f, 5085 }; 5086 u16 unit_cube_indices[] = { 5087 1, 13, 19, 5088 1, 19, 7, 5089 9, 6, 18, 5090 9, 18, 21, 5091 23, 20, 14, 5092 23, 14, 17, 5093 16, 4, 10, 5094 16, 10, 22, 5095 5, 2, 8, 5096 5, 8, 11, 5097 15, 12, 0, 5098 15, 0, 3 5099 }; 5100 5101 static_assert(countof(unit_cube_normals) == countof(unit_cube_vertices), ""); 5102 5103 RenderModel *rm = &ui->unit_cube_model; 5104 rm->vertex_count = countof(unit_cube_vertices) / 4; 5105 rm->normals_offset = round_up_to(sizeof(unit_cube_vertices), 16); 5106 5107 u64 model_size = 2 * round_up_to(sizeof(unit_cube_vertices), 16); 5108 vk_render_model_allocate(&rm->model, unit_cube_indices, countof(unit_cube_indices), model_size, s8("unit_cube_model")); 5109 vk_render_model_range_upload(&rm->model, unit_cube_vertices, 0, sizeof(unit_cube_vertices), 0); 5110 vk_render_model_range_upload(&rm->model, unit_cube_normals, rm->normals_offset, sizeof(unit_cube_normals), 0); 5111 } 5112 5113 for EachElement(beamformer_reloadable_render_shader_info_indices, it) { 5114 i32 index = beamformer_reloadable_render_shader_info_indices[it]; 5115 BeamformerShaderKind shader = beamformer_reloadable_shader_kinds[index]; 5116 beamformer_reload_render_pipeline(ui->pipelines + it, shader, ui->arena); 5117 } 5118 } 5119 5120 function void 5121 beamformer_ui_frame(void) 5122 { 5123 BeamformerUI *ui = ui_context = beamformer_context->ui; 5124 5125 { 5126 BeamformerFrame *frame = beamformer_frame_from_index(beamformer_registers()->frame); 5127 memory_copy(ui->latest_plane + frame->view_plane_tag, frame, sizeof(*frame)); 5128 } 5129 5130 BeamformerInput *input = beamformer_input; 5131 for EachIndex(input->event_count, it) { 5132 if (input->event_queue[it].kind == BeamformerInputEventKind_WindowResize) { 5133 // TODO(rnp): match window against window list 5134 beamformer_context->window_size.w = input->event_queue[it].window_resize.width; 5135 beamformer_context->window_size.h = input->event_queue[it].window_resize.height; 5136 } 5137 } 5138 5139 asan_poison_region(ui->arena.beg, ui->arena.end - ui->arena.beg); 5140 5141 u32 selected_block = ui->selected_parameter_block % BeamformerMaxParameterBlocks; 5142 u32 selected_mask = 1 << selected_block; 5143 if (beamformer_context->ui_dirty_parameter_blocks & selected_mask) { 5144 BeamformerParameterBlock *pb = beamformer_parameter_block_lock(beamformer_context->shared_memory, selected_block, 0); 5145 if (pb) { 5146 ui->flush_parameters = 0; 5147 5148 m4 das_transform; 5149 memory_copy(&ui->parameters, &pb->parameters_ui, sizeof(ui->parameters)); 5150 memory_copy(das_transform.E, pb->parameters.das_voxel_transform.E, sizeof(das_transform)); 5151 5152 atomic_and_u32(&beamformer_context->ui_dirty_parameter_blocks, ~selected_mask); 5153 beamformer_parameter_block_unlock(beamformer_context->shared_memory, selected_block); 5154 5155 BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[selected_block]; 5156 m4 identity = m4_identity(); 5157 b32 recompute = !m4_equal(identity, cp->ui_voxel_transform); 5158 memory_copy(cp->ui_voxel_transform.E, identity.E, sizeof(identity)); 5159 5160 if (recompute) { 5161 mark_parameter_block_region_dirty(beamformer_context->shared_memory, selected_block, 5162 BeamformerParameterBlockRegion_Parameters); 5163 beamformer_queue_compute(beamformer_context, 5164 beamformer_frame_from_index(beamformer_registers()->frame), 5165 selected_block); 5166 } 5167 5168 ui->off_axis_position = plane_offset_from_transform(das_transform); 5169 ui->beamform_plane = 0; 5170 } 5171 } 5172 5173 /* NOTE: process interactions first because the user interacted with 5174 * the ui that was presented last frame */ 5175 Rect window_rect = {.size = {{(f32)beamformer_context->window_size.w, (f32)beamformer_context->window_size.h}}}; 5176 5177 ui->last_mouse = ui->current_mouse; 5178 ui->current_mouse.x = input->mouse_x; 5179 ui->current_mouse.y = input->mouse_y; 5180 for EachElement(ui->input_consumed, it) 5181 ui->input_consumed[it] = 0; 5182 5183 if (ui->flush_parameters && beamformer_frame_valid(beamformer_registers()->frame)) { 5184 BeamformerParameterBlock *pb = beamformer_parameter_block_lock(beamformer_context->shared_memory, selected_block, 0); 5185 if (pb) { 5186 ui->flush_parameters = 0; 5187 memory_copy(&pb->parameters_ui, &ui->parameters, sizeof(ui->parameters)); 5188 mark_parameter_block_region_dirty(beamformer_context->shared_memory, selected_block, 5189 BeamformerParameterBlockRegion_Parameters); 5190 beamformer_parameter_block_unlock(beamformer_context->shared_memory, selected_block); 5191 beamformer_queue_compute(beamformer_context, 5192 beamformer_frame_from_index(beamformer_registers()->frame), 5193 selected_block); 5194 } 5195 } 5196 5197 /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */ 5198 update_frame_views(ui, window_rect); 5199 5200 //////////////////////////// 5201 // NOTE(rnp): Text Input 5202 { 5203 UITextInputState *tis = &ui->text_input_state; 5204 // NOTE(rnp): transition to new node 5205 tis->last_node_key = ui_node_key_zero(); 5206 tis->last_count = 0; 5207 if (tis->changed) { 5208 tis->changed = 0; 5209 ui_text_input_end(); 5210 if (!ui_node_key_nil(tis->next_node_key)) { 5211 tis->node_key = tis->next_node_key; 5212 tis->next_node_key = ui_node_key_zero(); 5213 if (point_in_rect(ui->current_mouse, ui_text_input_rect())) 5214 tis->cursor = tis->mark = ui_text_input_index_from_point(ui->last_mouse.x); 5215 tis->blinker.t = 1.0f; 5216 } 5217 } 5218 5219 if (!ui_node_key_nil(tis->node_key)) { 5220 beamformer_ui_blinker_update(&tis->blinker, BLINK_SPEED); 5221 5222 UISignal signal = ui_signal_from_node(ui_node_from_key(tis->node_key)); 5223 5224 if (signal.flags & UISignalFlag_LeftPressed) { 5225 if (point_in_rect(ui->current_mouse, ui_text_input_rect())) 5226 tis->cursor = tis->mark = ui_text_input_index_from_point(ui->last_mouse.x); 5227 tis->blinker.t = 1.0f; 5228 } 5229 5230 if (signal.flags & UISignalFlag_LeftDragging) 5231 tis->mark = ui_text_input_index_from_point(ui->last_mouse.x); 5232 5233 if (signal.flags & UISignalFlag_DoubleClicked) { 5234 // TODO(rnp): select word 5235 } 5236 5237 if (signal.flags & UISignalFlag_TripleClicked) { 5238 tis->cursor = 0; 5239 tis->mark = tis->count; 5240 } 5241 5242 if (ui_text_input_update(input)) 5243 ui_text_input_end(); 5244 } 5245 } 5246 5247 if (ui->context_menu_state_changed) { 5248 ui->context_menu_state_changed = 0; 5249 ui->context_menu_anchor_key = ui->context_menu_next_anchor_key; 5250 ui->context_menu_panel = ui->context_menu_next_panel; 5251 } 5252 5253 { 5254 //////////////////////////// 5255 // NOTE(rnp): Build Pass 5256 end_temp_arena(ui->build_arena_savepoints[ui->current_frame_index % countof(ui->build_arenas)]); 5257 // NOTE(rnp): reset last frame's build stacks 5258 { 5259 #define X(type, name, ...) \ 5260 ui->name##_node_stack.top = &ui_##name##_node_nil; \ 5261 ui->name##_node_stack.free = 0; \ 5262 ui->name##_node_stack.count = 0; 5263 UI_STACK_LIST 5264 #undef X 5265 5266 UIPrefWidth(ui_px(window_rect.size.x, 1.f)) 5267 UIPrefHeight(ui_px(window_rect.size.y, 1.f)) 5268 UIChildLayoutAxis(Axis2_Y) 5269 ui->root_node = ui_node_from_string(0, str8("UI Root Node")); 5270 ui_push_semantic_width(ui_pct(1.f, 0.5f)); 5271 ui_push_semantic_height(ui_pct(1.f, 0.5f)); 5272 } 5273 5274 ui->drag_root = 0; 5275 ui->drop_target_node = 0; 5276 ui->drag_overlay_root = 0; 5277 ui->drag_overlay_edges_root = 0; 5278 ui->drag_overlay_tab_root = 0; 5279 5280 beamformer_registers()->split_left_tree = 0; 5281 beamformer_registers()->split_right_tree = 0; 5282 beamformer_registers()->drop_child_index = 0; 5283 5284 // NOTE(rnp): check for active nodes 5285 { 5286 b32 active = 0; 5287 for EachEnumValue(UIMouseButtonKind, k) 5288 active |= !ui_node_key_equal(ui->active_node_key[k], ui_node_key_zero()); 5289 // NOTE(rnp): clear hot node if there are no active nodes 5290 if (!active) ui->hot_node_key = ui_node_key_zero(); 5291 } 5292 5293 // NOTE(rnp): context menu 5294 if (!ui_node_key_nil(ui->context_menu_anchor_key)) { 5295 // TODO(rnp): context_menu_open_t 5296 UIPrefWidth(ui_children_sum(1.f)) 5297 UIPrefHeight(ui_children_sum(1.f)) 5298 UIChildLayoutAxis(Axis2_Y) 5299 UIBGColour((v4){.a = 0.8f}) 5300 { 5301 // TODO(rnp): this should be tied to the window state 5302 ui->context_menu_root = ui_node_from_string(UINodeFlag_DrawBackground, str8("context_menu_root")); 5303 } 5304 5305 UIParent(ui->context_menu_root) UIAxisSize(Axis2_X, ui_px(0.f, 0.5f)) ui_padh(0.8f * UI_NODE_PAD); 5306 } 5307 5308 // NOTE(rnp): drag panel 5309 if (ui->drag_panel) { 5310 ui_build_drag_overlay(window_rect); 5311 5312 UIPrefWidth(ui_px(640.f, 1.f)) 5313 UIPrefHeight(ui_px(480.f, 1.f)) 5314 UIChildLayoutAxis(Axis2_Y) 5315 UIBGColour((v4){.a = 0.8f}) 5316 { 5317 ui->drag_root = ui_node_from_string(UINodeFlag_DrawBackground, str8("drag_panel_root")); 5318 } 5319 5320 UIParent(ui->drag_root) 5321 { 5322 UIChildLayoutAxis(Axis2_X) 5323 UIPrefHeight(ui_children_sum(1.f)) 5324 UIPrefWidth(ui_children_sum(1.f)) 5325 UIParent(ui_spacer(0)) 5326 { 5327 ui_padw(UI_NODE_PAD); 5328 ui_panel_label(ui->drag_panel); 5329 } 5330 } 5331 5332 ui_build_regions(ui->drag_root, ui->drag_panel); 5333 } 5334 5335 ui_build_regions(ui->root_node, ui->tree); 5336 5337 //////////////////////////// 5338 // NOTE(rnp): Prune Dead UI Nodes 5339 for EachElement(ui->node_hash_table, it) { 5340 UINodeHashBucket *hb = ui->node_hash_table + it; 5341 UINode *next = hb->first; 5342 for (UINode *b = next; !ui_node_is_nil(b); b = next) { 5343 next = b == b->hash_next ? 0 : b->hash_next; 5344 if (b->last_frame_active_index != ui->current_frame_index) { 5345 for EachEnumValue(UIMouseButtonKind, k) 5346 if (ui_node_key_equal(ui->active_node_key[k], b->key)) 5347 ui->active_node_key[k] = ui_node_key_zero(); 5348 5349 DLLRemove(&ui_node_nil, hb->first, hb->last, b, hash_next, hash_prev); 5350 SLLStackPush(ui->node_freelist, b, next_sibling); 5351 } 5352 } 5353 } 5354 5355 for (BeamformerInputEvent *event = ui_event_next(input, 0); 5356 event; 5357 event = ui_event_next(input, event)) 5358 { 5359 if (event->kind == BeamformerInputEventKind_ButtonPress) { 5360 if (event->button_id == BeamformerButtonID_Escape) 5361 beamformer_context->state = BeamformerState_ShouldClose; 5362 5363 if (!Between(event->button_id, BeamformerButtonID_ModifierFirst, BeamformerButtonID_ModifierLast)) { 5364 ui_context_menu_close(); 5365 ui->text_input_state.changed = 1; 5366 ui->text_input_state.next_node_key = ui_node_key_zero(); 5367 } 5368 } 5369 } 5370 5371 //////////////////////////// 5372 // NOTE(rnp): Layout Pass 5373 if (ui->drag_root) { 5374 ui->drag_root->computed_position[Axis2_X] = ui->last_mouse.x; 5375 ui->drag_root->computed_position[Axis2_Y] = ui->last_mouse.y; 5376 ui_layout_nodes(ui->drag_root); 5377 ui_layout_nodes(ui->drag_overlay_edges_root); 5378 if (ui->drag_overlay_tab_root) 5379 ui_layout_nodes(ui->drag_overlay_tab_root); 5380 ui_layout_nodes(ui->drag_overlay_root); 5381 } 5382 5383 ui_layout_nodes(ui->root_node); 5384 5385 if (!ui_node_key_nil(ui->context_menu_anchor_key)) { 5386 UIParent(ui->context_menu_root) UIAxisSize(Axis2_X, ui_px(0.f, 0.5f)) ui_padh(0.8f * UI_NODE_PAD); 5387 5388 UINode *anchor = ui_node_from_key(ui->context_menu_anchor_key); 5389 v2 anchor_p = ui_node_final_position(anchor); 5390 ui->context_menu_root->computed_position[Axis2_X] = anchor_p.x; 5391 ui->context_menu_root->computed_position[Axis2_Y] = anchor_p.y + anchor->computed_size[Axis2_Y]; 5392 5393 ui_layout_nodes(ui->context_menu_root); 5394 } 5395 5396 BeginDrawing(); 5397 glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_COLOUR.E); 5398 glClearNamedFramebufferfv(0, GL_DEPTH, 0, (f32 []){1}); 5399 ui_draw_nodes(ui->root_node, window_rect); 5400 5401 if (!ui_node_key_nil(ui->context_menu_anchor_key)) 5402 ui_draw_nodes(ui->context_menu_root, window_rect); 5403 5404 if (ui->drag_root) { 5405 if (beamformer_registers()->split_left_tree || beamformer_registers()->split_right_tree) 5406 ui_draw_nodes(ui_build_drag_hover_node(), window_rect); 5407 ui_draw_nodes(ui->drag_overlay_root, window_rect); 5408 ui_draw_nodes(ui->drag_overlay_edges_root, window_rect); 5409 if (ui->drag_overlay_tab_root) 5410 ui_draw_nodes(ui->drag_overlay_tab_root, window_rect); 5411 ui_draw_nodes(ui->drag_root, window_rect); 5412 } 5413 5414 // TODO(rnp): hack: until raylib is removed this happens in ui since raylib will cause 5415 // glfw to call the input callbacks during EndDrawing() 5416 input->event_count = 0; 5417 EndDrawing(); 5418 5419 if (ui->drag_end) 5420 ui_drag_end(); 5421 5422 ui->current_frame_index++; 5423 } 5424 }