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