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