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