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