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