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