ui.c (188816B)
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 u32 parameter_block = panel->u.parameter_listing.parameter_block; 3424 3425 BeamformerParameterBlock *pb = beamformer_parameter_block(beamformer_context->shared_memory, parameter_block); 3426 BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[parameter_block]; 3427 3428 // NOTE(rnp): emission info 3429 { 3430 BeamformerEmissionParameters *ep = &pb->parameters.emission_parameters; 3431 str8 kind = str8("Unknown"); 3432 b32 valid = ep->kind <= BeamformerEmissionKind_Count; 3433 if (valid) kind = beamformer_emission_kind_strings[ep->kind]; 3434 3435 UIFlags(ui_top_flags()|(!valid * UINodeFlag_Disabled)) 3436 UIParent(label_column) 3437 { 3438 UISignal signal = ui_label_button(str8("Emission")); 3439 if ui_pressed(signal) 3440 panel->u.parameter_listing.expand_emission ^= 1u; 3441 } 3442 UIParent(unit_column) UIPrefHeight(ui_em(1.0f, 1.0f)) ui_spacer(0); 3443 UIParent(value_column) ui_label(kind); 3444 if (valid && panel->u.parameter_listing.expand_emission) { 3445 switch (ep->kind) { 3446 case BeamformerEmissionKind_Sine:{ 3447 UIParent(label_column) 3448 { 3449 ui_label(str8(" Cycles")); 3450 ui_label(str8(" Frequency")); 3451 } 3452 3453 UIParent(value_column) 3454 { 3455 ui_labelf("%0.2f###sine_cycles", ep->sine.cycles); 3456 ui_labelf("%0.2f###sine_freq", ep->sine.frequency * 1e-6f); 3457 } 3458 3459 UIParent(unit_column) 3460 { 3461 UIPrefHeight(ui_em(1.0f, 1.0f)) ui_spacer(0); 3462 ui_label(str8("[MHz]###sine_freq")); 3463 } 3464 }break; 3465 3466 case BeamformerEmissionKind_Chirp:{ 3467 UIParent(label_column) 3468 { 3469 ui_label(str8(" Duration")); 3470 ui_label(str8(" Min Frequency")); 3471 ui_label(str8(" Max Frequency")); 3472 } 3473 3474 UIParent(value_column) 3475 { 3476 ui_labelf("%0.2f###chirp_duration", ep->chirp.duration * 1e6f); 3477 ui_labelf("%0.2f###chirp_min", ep->chirp.min_frequency * 1e-6f); 3478 ui_labelf("%0.2f###chirp_max", ep->chirp.max_frequency * 1e-6f); 3479 } 3480 3481 UIParent(unit_column) 3482 { 3483 ui_label(str8("[us]###chirp_duration")); 3484 ui_label(str8("[MHz]###chirp_min")); 3485 ui_label(str8("[MHz]###chirp_max")); 3486 } 3487 }break; 3488 3489 InvalidDefaultCase; 3490 } 3491 } 3492 } 3493 3494 UIParent(label_column) ui_label(str8("Speed of Sound")); 3495 UIParent(unit_column) ui_label(str8("[m/s]")); 3496 UIParent(value_column) 3497 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3498 { 3499 UISignal signal = ui_text_boxf("%0.2f###sound", bp->speed_of_sound); 3500 if (ui_tweak_f32_compute_variable(signal, &bp->speed_of_sound, 1.f, 10.f, (v2){{0, inf32()}})) 3501 ui->flush_parameters = 1; 3502 } 3503 3504 b32 rebuild_transform = 0; 3505 m4 das_transform = pb->parameters.das_voxel_transform; 3506 if (cp) das_transform = m4_mul(cp->ui_voxel_transform, das_transform); 3507 v3 coordinates[2] = { 3508 m4_mul_v3(das_transform, (v3){{0.0f, 0.0f, 0.0f}}), 3509 m4_mul_v3(das_transform, (v3){{1.0f, 1.0f, 1.0f}}), 3510 }; 3511 3512 i32 dimension = iv3_dimension(bp->output_points.xyz); 3513 if (dimension > 0) { 3514 read_only str8 dimension_strings[3][2] = { 3515 {str8_comp("Start Point"), str8_comp("End Point") }, 3516 {str8_comp("Lateral Extent"), str8_comp("Axial Extent")}, 3517 {str8_comp("Min Corner"), str8_comp("Max Corner") }, 3518 }; 3519 3520 for (u32 index = 0; index < 2; index++) { 3521 UIParent(label_column) 3522 { 3523 UISignal signal = ui_label_button(dimension_strings[dimension - 1][index]); 3524 if ui_pressed(signal) 3525 panel->u.parameter_listing.expand_coordinate[index] ^= 1u; 3526 } 3527 3528 f32 values[3] = {coordinates[index].x, coordinates[index].y, coordinates[index].z}; 3529 u32 value_count = dimension == 2 ? 2 : 3; 3530 v3 normalized_axis = v3_normalize(das_transform.c[index].xyz); 3531 if (dimension == 2) { 3532 values[0] = v3_dot(normalized_axis, coordinates[0]); 3533 values[1] = v3_dot(normalized_axis, coordinates[1]); 3534 } 3535 3536 if (panel->u.parameter_listing.expand_coordinate[index]) { 3537 UIPrefHeight(ui_px((f32)ui_font_for_node(value_column).baseSize * line_pad_pct, 1.f)) 3538 { 3539 UIParent(value_column) ui_spacer(0); 3540 UIParent(unit_column) ui_spacer(0); 3541 } 3542 3543 read_only str8 axis_strings[2][3] = { 3544 {str8_comp(" X:"), str8_comp(" Y:"), str8_comp(" Z:")}, 3545 {str8_comp(" Min:"), str8_comp(" Max:"), }, 3546 }; 3547 const str8 *strs = dimension == 2 ? axis_strings[1] : axis_strings[0]; 3548 for EachIndex(value_count, it) { 3549 UIParent(label_column) ui_labelf(" %.*s##label%u_%u", 3550 (i32)strs[it].length, strs[it].data, 3551 index, (u32)it); 3552 UIParent(unit_column) ui_labelf("[mm]##%u_%u", index, (u32)it); 3553 UIParent(value_column) 3554 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3555 { 3556 UISignal signal = ui_text_boxf("%0.2f###%u_%u", values[it] * 1e3f, index, (u32)it); 3557 rebuild_transform |= ui_tweak_f32_compute_variable(signal, values + it, 3558 1e-3f, 0.5e-3f, V2_INFINITY); 3559 } 3560 } 3561 } else { 3562 UIParent(unit_column) ui_labelf("[mm]##dim%u", index); 3563 3564 UINode *group; 3565 UIParent(value_column) 3566 UIChildLayoutAxis(Axis2_X) 3567 UIPrefWidth(ui_children_sum(1.f)) 3568 UIPrefHeight(ui_children_sum(1.f)) 3569 group = ui_spacer(0); 3570 3571 UIParent(group) 3572 { 3573 ui_labelf("{##%u", index); 3574 for EachIndex(value_count, it) { 3575 if (it != 0) ui_labelf(", ##%u_%u", index, (u32)it); 3576 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3577 { 3578 UISignal signal = ui_text_boxf("%0.2f###%u_%u", values[it] * 1e3f, index, (u32)it); 3579 rebuild_transform |= ui_tweak_f32_compute_variable(signal, values + it, 3580 1e-3f, 0.5e-3f, V2_INFINITY); 3581 } 3582 } 3583 ui_labelf("}##%u", index); 3584 } 3585 } 3586 3587 if (dimension == 2) { 3588 coordinates[0].E[index] = values[0]; 3589 coordinates[1].E[index] = values[1]; 3590 } 3591 } 3592 } 3593 3594 if (dimension == 2) { 3595 UIParent(label_column) ui_label(str8("Off Axis Position")); 3596 UIParent(unit_column) ui_label(str8("[mm]##off_axis")); 3597 UIParent(value_column) 3598 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3599 { 3600 UISignal signal = ui_text_boxf("%0.2f###off_axis", plane_offset_from_transform(das_transform) * 1e3f); 3601 rebuild_transform |= ui_tweak_f32_compute_variable(signal, &ui->off_axis_position, 3602 1e-3f, 0.1e-3f, V2_INFINITY); 3603 } 3604 3605 UIParent(label_column) ui_label(str8("Beamform Plane")); 3606 UIParent(unit_column) UIPrefHeight(ui_em(1.f, 1.f)) ui_spacer(0); 3607 UIParent(value_column) 3608 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3609 { 3610 UISignal signal = ui_text_boxf("%0.2f###beamform_plane", ui->beamform_plane); 3611 rebuild_transform |= ui_tweak_f32_compute_variable(signal, &ui->beamform_plane, 3612 1.f, 0.025f, (v2){{-1.f, 1.f}}); 3613 } 3614 } 3615 3616 UIParent(label_column) ui_label(str8("F#")); 3617 UIParent(unit_column) UIPrefHeight(ui_em(1.f, 1.f)) ui_spacer(0); 3618 UIParent(value_column) 3619 UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric) 3620 { 3621 UISignal signal = ui_text_boxf("%0.2f###f_number", bp->f_number); 3622 if (ui_tweak_f32_compute_variable(signal, &bp->f_number, 1.f, 0.05f, (v2){{0, inf32()}})) 3623 ui->flush_parameters = 1; 3624 } 3625 3626 UIParent(label_column) ui_label(str8("Interpolation")); 3627 UIParent(unit_column) UIPrefHeight(ui_em(1.f, 1.f)) ui_spacer(0); 3628 UIParent(value_column) 3629 UIFlags(UINodeFlag_Scroll) 3630 { 3631 str8 label = beamformer_interpolation_mode_strings[bp->interpolation_mode]; 3632 UISignal signal = ui_label_button(label); 3633 if (ui_pressed(signal) || ui_scrolled(signal)) { 3634 i32 delta = signal.scroll.y + ui_pressed(signal); 3635 bp->interpolation_mode = circular_add(bp->interpolation_mode, delta, 3636 BeamformerInterpolationMode_Count); 3637 ui->flush_parameters = 1; 3638 } 3639 } 3640 3641 UIParent(label_column) ui_label(str8("Coherency Weighting")); 3642 UIParent(unit_column) UIPrefHeight(ui_em(1.0f, 1.0f)) ui_spacer(0); 3643 UIParent(value_column) 3644 UIFlags(UINodeFlag_Scroll) 3645 { 3646 UISignal signal = ui_label_button(bp->coherency_weighting ? 3647 str8("True###coherency_weighting") : 3648 str8("False###coherency_weighting")); 3649 if (signal.flags & (UISignalFlag_Pressed|UISignalFlag_Scrolled)) { 3650 bp->coherency_weighting = !bp->coherency_weighting; 3651 ui->flush_parameters = 1; 3652 } 3653 } 3654 3655 if (rebuild_transform) { 3656 if (ui_rebuild_das_transform(parameter_block, dimension, coordinates[0], coordinates[1])) 3657 ui->flush_parameters = 1; 3658 } 3659 } 3660 } 3661 } 3662 3663 function f32 3664 ui_slider_update_from_signal(f32 percent, UISignal signal) 3665 { 3666 f32 result = percent; 3667 result += 0.05f * signal.scroll.y; 3668 if ui_dragging(signal) 3669 result = rect_uv(ui_context->last_mouse, ui_node_rect(signal.node)).E[signal.node->parent->child_layout_axis]; 3670 result = Clamp01(result); 3671 return result; 3672 } 3673 3674 function void 3675 ui_build_live_imaging_controls(BeamformerUIPanel *panel) 3676 { 3677 BeamformerLiveImagingParameters *lip = &beamformer_context->shared_memory->live_imaging_parameters; 3678 3679 UIFontSize(30.f) 3680 UIScroll(Axis2_Count) 3681 { 3682 ui_top_parent()->child_layout_axis = Axis2_Y; 3683 ui_top_parent()->semantic_width = ui_px(4.f * UI_NODE_PAD + 200.f, 1.f); 3684 ui_top_parent()->parent->alignment[Axis2_X] = UIAlign_Center; 3685 3686 if (popcount_u64(lip->acquisition_kind_enabled_flags) > 1) 3687 UIPrefWidth(ui_children_sum(1.f)) 3688 UIPrefHeight(ui_children_sum(1.f)) 3689 UIChildLayoutAxis(Axis2_X) 3690 UIParent(ui_spacer(0)) 3691 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 3692 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3693 { 3694 u32 kind = lip->acquisition_kind; 3695 ui_label(str8("Acquisition: ")); 3696 str8 kind_string = kind < BeamformerAcquisitionKind_Count ? beamformer_acquisition_kind_strings[kind] 3697 : str8("Invalid"); 3698 3699 UISignal signal = ui_label_button(kind_string); 3700 if ui_pressed(signal) 3701 ui_context_menu_open(signal.node->key, panel); 3702 3703 if ui_context_menu(panel) { 3704 u64 enabled_kinds = atomic_load_u64(&lip->acquisition_kind_enabled_flags); 3705 if (enabled_kinds) 3706 UIFontSize(24.f) 3707 UIParent(ui_context_menu_default_equip(0, 0, 0)) 3708 for EachBit(enabled_kinds, kind) 3709 { 3710 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 3711 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3712 signal = ui_label_button(beamformer_acquisition_kind_strings[kind]); 3713 3714 if ui_pressed(signal) { 3715 ui_context_menu_close(); 3716 lip->acquisition_kind = kind; 3717 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3718 BeamformerLiveImagingDirtyFlags_AcquisitionKind); 3719 } 3720 } 3721 } 3722 } 3723 3724 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 3725 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3726 { 3727 UINode *spacer; 3728 UISignal signal; 3729 3730 f32 row_height = ui_label(str8("Power:")).node->computed_size[Axis2_Y]; 3731 3732 UIPrefWidth(ui_pct(1.f, 1.f)) 3733 UIPrefHeight(ui_px(row_height, 1.f)) 3734 UIChildLayoutAxis(Axis2_X) 3735 spacer = ui_spacer(0); 3736 UIParent(spacer) 3737 { 3738 ui_padw(2 * UI_NODE_PAD); 3739 v4 hsv_power_slider = {{0.35f * ease_in_out_cubic(1.0f - lip->transmit_power), 0.65f, 0.65f, 1}}; 3740 UIBGColour(hsv_to_rgb(hsv_power_slider)) 3741 UIPrefHeight(ui_px(row_height, 1.f)) 3742 UIPrefWidth(ui_pct(1.f, 0.f)) 3743 signal = ui_slider(lip->transmit_power, str8("###transmit_power")); 3744 if (signal.flags) { 3745 lip->transmit_power = ui_slider_update_from_signal(lip->transmit_power, signal); 3746 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3747 BeamformerLiveImagingDirtyFlags_TransmitPower); 3748 } 3749 ui_padw(2 * UI_NODE_PAD); 3750 } 3751 3752 row_height = ui_label(str8("TGC:")).node->computed_size[Axis2_Y]; 3753 for EachElement(lip->tgc_control_points, it) { 3754 UIPrefWidth(ui_pct(1.f, 1.f)) 3755 UIPrefHeight(ui_px(row_height, 1.f)) 3756 UIChildLayoutAxis(Axis2_X) 3757 spacer = ui_spacer(0); 3758 UIParent(spacer) 3759 { 3760 ui_padw(2 * UI_NODE_PAD); 3761 UIBGColour(g_colour_palette[1]) 3762 UIPrefHeight(ui_px(row_height, 1.f)) 3763 UIPrefWidth(ui_pct(1.f, 0.f)) 3764 signal = ui_sliderf(lip->tgc_control_points[it], "###tgc_%u", (u32)it); 3765 if (signal.flags) { 3766 lip->tgc_control_points[it] = ui_slider_update_from_signal(lip->tgc_control_points[it], signal); 3767 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3768 BeamformerLiveImagingDirtyFlags_TGCControlPoints); 3769 } 3770 ui_padw(2 * UI_NODE_PAD); 3771 } 3772 } 3773 3774 if (lip->save_enabled) { 3775 ui_label(str8("File Name Tag:")); 3776 str8 save_name = (str8){.data = (u8 *)lip->save_name_tag, 3777 .length = Clamp(lip->save_name_tag_length, 0, countof(lip->save_name_tag))}; 3778 3779 3780 v4 save_text_colour = FG_COLOUR; 3781 u64 text_input_flags = 0; 3782 if (lip->save_name_tag_length <= 0) { 3783 save_text_colour.a = 0.6f; 3784 save_name = str8("Insert Text..."); 3785 text_input_flags = UINodeFlag_TextInputClearOnStart; 3786 } 3787 3788 UIPrefWidth(ui_children_sum(1.f)) 3789 UIPrefHeight(ui_children_sum(1.f)) 3790 UIChildLayoutAxis(Axis2_X) 3791 spacer = ui_spacer(0); 3792 UIParent(spacer) 3793 { 3794 ui_padw(2 * UI_NODE_PAD); 3795 UITextColour(save_text_colour) 3796 UIFlags(text_input_flags) 3797 signal = ui_text_box(push_str8_from_parts(ui_build_arena(), str8(""), save_name, 3798 str8("###save_name_field"))); 3799 if (ui_node_key_equal(signal.node->key, ui_context->text_input_state.node_key)) 3800 signal.node->text_colour = FG_COLOUR; 3801 3802 if (signal.flags & UISignalFlag_TextCommit) { 3803 str8 string = signal.string; 3804 lip->save_name_tag_length = Min(string.length, countof(lip->save_name_tag)); 3805 memory_copy(lip->save_name_tag, string.data, lip->save_name_tag_length); 3806 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3807 BeamformerLiveImagingDirtyFlags_SaveNameTag); 3808 } 3809 } 3810 3811 ui_padh(UI_NODE_PAD); 3812 3813 UIPrefWidth(ui_pct(1.f, 1.f)) 3814 UIPrefHeight(ui_children_sum(1.f)) 3815 UIChildLayoutAxis(Axis2_X) 3816 UIAxisAlign(Axis2_X, Center) 3817 spacer = ui_spacer(0); 3818 3819 UIParent(spacer) 3820 UITextAlign(Center) 3821 UIBGColour((v4){0}) 3822 UIPrefWidth(ui_text_dim(1.3f, 1.f)) 3823 UIPrefHeight(ui_text_dim(1.3f, 1.f)) 3824 { 3825 b32 active = lip->save_active; 3826 str8 label = active ? str8("Saving...###save_button") : str8("Save Data###save_button"); 3827 f32 save_t = beamformer_ui_blinker_update(&panel->u.live_imaging_save_button_blinker, BLINK_SPEED); 3828 v4 border_colour = (v4){.a = 0.6f}; 3829 if (active) border_colour = v4_lerp(BORDER_COLOUR, FOCUSED_COLOUR, ease_in_out_cubic(save_t)); 3830 UIBorderColour(border_colour) 3831 signal = ui_button(label); 3832 if ui_pressed(signal) { 3833 lip->save_active = !active; 3834 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3835 BeamformerLiveImagingDirtyFlags_SaveData); 3836 } 3837 } 3838 } 3839 3840 ui_padh(UI_NODE_PAD); 3841 3842 UIAxisAlign(Axis2_X, Center) 3843 UIPrefWidth(ui_pct(1.f, 1.f)) 3844 UIPrefHeight(ui_children_sum(1.f)) 3845 spacer = ui_spacer(0); 3846 3847 UIParent(spacer) 3848 UITextAlign(Center) 3849 UIBGColour((v4){0}) 3850 UIPrefWidth(ui_text_dim(1.3f, 1.f)) 3851 UIPrefHeight(ui_text_dim(1.3f, 1.f)) 3852 { 3853 UIBorderColour((v4){.a = 0.6f}) 3854 signal = ui_button(str8("Stop Imaging")); 3855 if ui_pressed(signal) 3856 atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags, 3857 BeamformerLiveImagingDirtyFlags_StopImaging); 3858 } 3859 } 3860 } 3861 } 3862 3863 function UISignal 3864 ui_panel_label(BeamformerUIPanel *panel) 3865 { 3866 Stream sb = arena_stream(ui_build_arena()); 3867 switch (panel->kind) { 3868 InvalidDefaultCase; 3869 case BeamformerPanelKind_ComputeBarGraph:{stream_append_str8(&sb, str8("Compute Bar Graph"));}break; 3870 case BeamformerPanelKind_ComputeStats:{stream_append_str8(&sb, str8("Compute Stats"));}break; 3871 case BeamformerPanelKind_FrameViewLive:{stream_append_str8(&sb, str8("Frame View"));}break; 3872 case BeamformerPanelKind_FrameViewXPlane:{stream_append_str8(&sb, str8("X-Plane View"));}break; 3873 case BeamformerPanelKind_LiveImagingControls:{stream_append_str8(&sb, str8("Live Controls"));}break; 3874 case BeamformerPanelKind_FrameViewCopy:{ 3875 stream_append_str8(&sb, str8("Frame Copy [")); 3876 stream_append_hex_u64(&sb, panel->u.frame_view->frame.id); 3877 stream_append_str8(&sb, str8("]#")); 3878 }break; 3879 case BeamformerPanelKind_ParameterListing:{ 3880 stream_append_str8(&sb, str8("Parameter Listing [")); 3881 stream_append_u64(&sb, panel->u.parameter_listing.parameter_block); 3882 stream_append_str8(&sb, str8("]#")); 3883 }break; 3884 } 3885 stream_append_str8(&sb, str8("##")); 3886 stream_append_hex_u64(&sb, (u64)panel); 3887 str8 label = arena_stream_commit(ui_build_arena(), &sb); 3888 3889 UISignal result; 3890 UIPrefWidth(ui_text_dim(1.f, 1.f)) 3891 UIPrefHeight(ui_text_dim(1.4f, 1.f)) 3892 result = ui_label(label); 3893 3894 return result; 3895 } 3896 3897 function void 3898 ui_insert_drop_site_spacer_before(UINode *before, f32 pad_node_width) 3899 { 3900 UIParent(0) 3901 { 3902 UINode *spacer, *spacer_gap; 3903 UIPrefHeight(ui_pct(1.f, 0.5f)) 3904 UIPrefWidth(ui_px(24.f, 1.f)) 3905 UIBorderColour((v4){.a = 0.9f}) 3906 UIBGColour((v4){.a = 0.6f}) 3907 spacer = ui_spacer(UINodeFlag_DrawBackground|UINodeFlag_DrawBorder); 3908 3909 UIPrefWidth(ui_px(pad_node_width, 1.f)) 3910 spacer_gap = ui_spacer(0); 3911 3912 spacer->parent = before->parent; 3913 spacer_gap->parent = before->parent; 3914 spacer->parent->child_count += 2; 3915 3916 spacer->previous_sibling = before->previous_sibling; 3917 spacer->next_sibling = spacer_gap; 3918 before->previous_sibling->next_sibling = spacer; 3919 3920 spacer_gap->previous_sibling = spacer; 3921 spacer_gap->next_sibling = before; 3922 3923 before->previous_sibling = spacer_gap; 3924 } 3925 } 3926 3927 function UINode * 3928 ui_box_pad(UINode *container, UISize pad, str8 tag) 3929 { 3930 UINode *result; 3931 UIParent(container) 3932 UIAxisSize(Axis2_X, ui_pct(1.f, 0.5f)) 3933 { 3934 UIAxisSize(Axis2_Y, pad) ui_spacer(0); 3935 3936 UIAxisSize(Axis2_Y, ui_pct(1.f, 0.5f)) 3937 UIChildLayoutAxis(Axis2_X) 3938 UIParent(ui_spacer(0)) 3939 { 3940 UIAxisSize(Axis2_X, pad) ui_spacer(0); 3941 UIChildLayoutAxis(container->child_layout_axis) 3942 result = ui_node_from_string(0, tag); 3943 UIAxisSize(Axis2_X, pad) ui_spacer(0); 3944 } 3945 3946 UIAxisSize(Axis2_Y, pad) ui_spacer(0); 3947 } 3948 return result; 3949 } 3950 3951 function print_format(3, 4) UINode * 3952 ui_box_padf(UINode *container, UISize pad, const char *format, ...) 3953 { 3954 va_list args; 3955 va_start(args, format); 3956 UINode *result = ui_box_pad(container, pad, push_str8_fv(ui_build_arena(), format, args)); 3957 va_end(args); 3958 return result; 3959 } 3960 3961 function BeamformerUIPanel * 3962 ui_panel_group_equip(UINode *node, BeamformerUIPanel *group) 3963 { 3964 BeamformerUIPanel *result = group; 3965 3966 if (group->kind != BeamformerPanelKind_Split) 3967 UIPrefWidth(ui_children_sum(1.f)) 3968 UIPrefHeight(ui_children_sum(1.f)) 3969 { 3970 assert(group->kind == BeamformerPanelKind_TabGroup); 3971 BeamformerUIPanel *focus = result = group->u.tab_focus; 3972 3973 node->flags |= UINodeFlag_DropSite; 3974 3975 UINode *tab_bar_node, *tab_clip_node; 3976 UIParent(node) 3977 UIChildLayoutAxis(Axis2_X) 3978 UIPrefWidth(ui_pct(1.f, 1.f)) 3979 tab_bar_node = ui_node_from_string(UINodeFlag_Clip, str8("###tab_scroll")); 3980 3981 UIParent(tab_bar_node) 3982 UIAxisAlign(Axis2_Y, Center) 3983 UIChildLayoutAxis(Axis2_X) 3984 tab_clip_node = ui_node_from_string(UINodeFlag_ViewScrollX, str8("###tab_clip")); 3985 3986 b32 drop_site = ui_node_key_equal(node->key, ui_context->drop_target_key) && 3987 ui_context->drag_panel && 3988 !beamformer_registers()->split_left_tree && 3989 !beamformer_registers()->split_right_tree; 3990 b32 drop_site_handled = 0; 3991 u32 drop_site_index = 0; 3992 f32 tab_pad = 6.f; 3993 UIParent(tab_clip_node) 3994 UIFontSize(24.f) 3995 { 3996 for (BeamformerUIPanel *tab = group->first_child; tab; tab = tab->next_sibling) { 3997 ui_padw(tab_pad); 3998 3999 // NOTE(rnp): push tab 4000 UINode *tab_node; 4001 UIAxisAlign(Axis2_Y, Center) 4002 UIChildLayoutAxis(Axis2_X) 4003 // TODO(rnp): per edge border colour 4004 UIBorderColour((v4){.a = 0.9f}) 4005 UIBGColour(tab == focus ? BG_COLOUR : (v4){.a = 0.6f}) 4006 UIFlags(UINodeFlag_Clickable| 4007 UINodeFlag_DrawBackground| 4008 UINodeFlag_DrawBorder| 4009 UINodeFlag_DrawHotEffects| 4010 UINodeFlag_DrawActiveEffects) 4011 tab_node = ui_node_from_stringf(tab == focus ? UINodeFlag_FocusActive : 0, "###tab%p", tab); 4012 4013 if (drop_site && !drop_site_handled && 4014 ui_context->last_mouse.x < (tab_node->computed_position[Axis2_X] + 0.5f * tab_node->computed_size[Axis2_X])) 4015 { 4016 drop_site_handled = 1; 4017 if (ui_context->drag_panel != tab && ui_context->drag_panel != tab->previous_sibling) 4018 ui_insert_drop_site_spacer_before(tab_node, tab_pad); 4019 } 4020 4021 UISignal signal = {0}; 4022 UIParent(tab_node) 4023 UIPrefWidth(ui_text_dim(2.f, 1.f)) 4024 UIPrefHeight(ui_pct(1.f, 1.f)) 4025 UITextAlign(Center) 4026 { 4027 ui_padw(UI_BORDER_THICK + tab_pad); 4028 4029 v4 fg_colour = FG_COLOUR; 4030 if (tab != focus) fg_colour.a = 0.8f; 4031 UITextColour(fg_colour) 4032 ui_panel_label(tab); 4033 4034 b32 has_settings = (beamformer_panel_infos[tab->kind].flags & BeamformerPanelFlags_HasSettings) != 0; 4035 if (tab == focus && has_settings) 4036 UIFlags(UINodeFlag_IconText) 4037 { 4038 ui_padw(UI_NODE_PAD); 4039 4040 signal = ui_label_button(str8("+")); 4041 if ui_pressed(signal) 4042 ui_context_menu_open(signal.node->key, tab); 4043 } 4044 4045 ui_padw(0.5f * UI_NODE_PAD); 4046 4047 UIFlags(UINodeFlag_IconText) 4048 signal = ui_label_button(str8("x")); 4049 if (ui_pressed(signal) || signal.flags & UISignalFlag_MiddlePressed) 4050 beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)tab); 4051 4052 ui_padw(0.5f * UI_NODE_PAD); 4053 } 4054 4055 if (!drop_site_handled) drop_site_index++; 4056 4057 signal = ui_signal_from_node(tab_node); 4058 if ui_pressed(signal) 4059 beamformer_command(beamformer_command_infos[BeamformerCommandKind_FocusTab].string, .tree_node = (u64)tab); 4060 if (signal.flags & UISignalFlag_MiddlePressed) 4061 beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)tab); 4062 if (ui_dragging(signal) && !point_in_rect(ui_context->last_mouse, ui_node_rect(signal.node))) 4063 ui_drag_begin(tab); 4064 if ui_released(signal) 4065 ui_context->drag_end = 1; 4066 } 4067 4068 ui_padw(tab_pad); 4069 4070 // NOTE(rnp): context menu opener 4071 UISignal signal; 4072 UIPrefWidth(ui_text_dim(3.f, 1.f)) 4073 UIPrefHeight(ui_text_dim(3.f, 1.f)) 4074 UITextAlign(Center) 4075 UIFlags(UINodeFlag_IconText) 4076 signal = ui_label_button(str8("+")); 4077 if ui_pressed(signal) 4078 ui_context_menu_open(signal.node->key, group); 4079 4080 if ui_context_menu(group) { 4081 UIParent(ui_context_menu_default_equip(0, 0, 0)) 4082 for EachElement(beamformer_panel_infos, it) 4083 { 4084 const BeamformerPanelInfo *info = beamformer_panel_infos + it; 4085 b32 list = (info->flags & BeamformerPanelFlags_List) != 0; 4086 b32 needs_frame = (info->flags & BeamformerPanelFlags_NeedsFrame) != 0; 4087 if (list && (!needs_frame || beamformer_frame_valid(beamformer_registers()->frame))) { 4088 UIPrefHeight(ui_text_dim(1.1f, 1.f)) 4089 UIPrefWidth(ui_text_dim(1.f, 1.f)) 4090 signal = ui_label_button(info->display); 4091 4092 if ui_pressed(signal) { 4093 ui_context_menu_close(); 4094 beamformer_command(beamformer_command_infos[BeamformerCommandKind_OpenTab].string, 4095 .tree_node = (u64)group, 4096 .string = info->string); 4097 } 4098 } 4099 } 4100 } 4101 4102 if (drop_site && !drop_site_handled && ui_context->drag_panel != group->last_child) { 4103 drop_site_handled = 1; 4104 ui_insert_drop_site_spacer_before(signal.node, tab_pad); 4105 } 4106 4107 ui_padw(tab_pad); 4108 } 4109 4110 ui_signal_from_node(tab_clip_node); 4111 4112 if (drop_site || drop_site_handled) { 4113 beamformer_registers()->drop_target_tree = (u64)group; 4114 beamformer_registers()->drop_child_index = drop_site_handled ? drop_site_index : group->child_count; 4115 } 4116 } 4117 4118 // NOTE(rnp): close tabgroup button 4119 if (!result && group != ui_context->tree) 4120 UIParent(ui_box_pad(node, ui_pct(0.5f, 0.5f), str8(""))) 4121 { 4122 ui_top_parent()->semantic_size[Axis2_X] = ui_children_sum(1.f); 4123 4124 UISignal signal; 4125 UIPrefWidth(ui_text_dim(1.5f, 1.f)) 4126 UIPrefHeight(ui_text_dim(2.f, 1.f)) 4127 UIBGColour((v4){.a = 0.3f}) 4128 UIBorderColour((v4){.a = 0.6f}) 4129 UITextAlign(Center) 4130 signal = ui_button(str8("Close Panel")); 4131 if ui_pressed(signal) 4132 beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)group); 4133 } 4134 4135 ui_signal_from_node(node); 4136 4137 return result; 4138 } 4139 4140 function void 4141 ui_build_regions(UINode *root_node, BeamformerUIPanel *tree_root) 4142 { 4143 BeamformerUI *ui = ui_context; 4144 4145 struct tree_frame { 4146 BeamformerUIPanel *tree; 4147 UINode *node; 4148 } init[64]; 4149 4150 struct { 4151 struct tree_frame *data; 4152 da_count count; 4153 da_count capacity; 4154 } stack = {init, 0, countof(init)}; 4155 4156 *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4157 .node = ui_box_padf(root_node, ui_px(UI_NODE_PAD, 1.f), "%p_padded", root_node), 4158 .tree = tree_root, 4159 }; 4160 while (stack.count) { 4161 struct tree_frame *top = stack.data + --stack.count; 4162 4163 BeamformerUIPanel *panel = top->tree; 4164 UINode *top_node = top->node; 4165 4166 UIParent(top_node) 4167 switch (panel->kind) { 4168 4169 case BeamformerPanelKind_TabGroup:{ 4170 UINode *node; 4171 UIChildLayoutAxis(Axis2_Y) 4172 node = ui_node_from_stringf(UINodeFlag_Clip, "###%p_group", panel); 4173 BeamformerUIPanel *next = ui_panel_group_equip(node, panel); 4174 if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4175 .tree = next, 4176 .node = node, 4177 }; 4178 }break; 4179 4180 case BeamformerPanelKind_Split:{ 4181 assert(panel->child_count == 2); 4182 4183 Axis2 axis = panel->u.split.axis; 4184 top_node->child_layout_axis = axis; 4185 4186 UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f)) 4187 { 4188 f32 split_pct = panel->u.split.fraction; 4189 4190 UINode *left; 4191 UIAxisSize(axis, ui_pct(split_pct, 0.5f)) 4192 UIChildLayoutAxis(Axis2_Y) 4193 left = ui_node_from_stringf(UINodeFlag_Clip, "###%p_left", panel); 4194 4195 BeamformerUIPanel *next = ui_panel_group_equip(left, panel->first_child); 4196 if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4197 .tree = next, 4198 .node = left, 4199 }; 4200 4201 UIAxisSize(axis, ui_children_sum(1.f)) 4202 UIChildLayoutAxis(axis) 4203 UIParent(ui_node_from_stringf(UINodeFlag_Clickable, "###%p_split", panel)) 4204 { 4205 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4206 UIAxisSize(axis, ui_px(UI_SPLIT_HANDLE_THICK, 1.f)) 4207 UIBGColour((v4){.a = 0.6f}) 4208 { 4209 UINode *rn = ui_spacer(UINodeFlag_DrawBackground|UINodeFlag_DrawHotEffects|UINodeFlag_DrawActiveEffects); 4210 rn->hot_t = ui_top_parent()->hot_t; 4211 } 4212 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4213 4214 UISignal signal = ui_signal_from_node(ui_top_parent()); 4215 if ui_dragging(signal) { 4216 Rect nr = ui_node_rect(top_node); 4217 v2 uv = rect_uv(clamp_v2_rect(ui->last_mouse, nr), nr); 4218 panel->u.split.fraction = Clamp(uv.E[panel->u.split.axis], 0.03f, 0.97f); 4219 } 4220 } 4221 4222 UINode *right; 4223 UIAxisSize(axis, ui_pct(1.f - split_pct, 0.5f)) 4224 UIChildLayoutAxis(Axis2_Y) 4225 right = ui_node_from_stringf(UINodeFlag_Clip, "###%p_right", panel); 4226 4227 next = ui_panel_group_equip(right, panel->last_child); 4228 if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4229 .tree = next, 4230 .node = right, 4231 }; 4232 } 4233 }break; 4234 4235 case BeamformerPanelKind_ComputeBarGraph:{ 4236 UIFontSize(30.f) 4237 UIScroll(Axis2_Y) 4238 { 4239 ui_top_parent()->child_layout_axis = Axis2_X; 4240 4241 UINode *label_column, *bar_column; 4242 UIAxisAlign(Axis2_X, Left) 4243 UIChildLayoutAxis(Axis2_Y) 4244 UIPrefWidth(ui_children_sum(1.f)) 4245 UIPrefHeight(ui_children_sum(1.f)) 4246 { 4247 UIAxisAlign(Axis2_X, Right) 4248 label_column = ui_node_from_string(0, str8("###labels")); 4249 ui_padw(UI_NODE_PAD); 4250 f32 bar_width = ui_top_parent()->parent->computed_size[Axis2_X] 4251 - label_column->computed_size[Axis2_X] - 1.1f * UI_NODE_PAD; 4252 bar_column = ui_node_from_string(UINodeFlag_CustomDraw, str8("###bars")); 4253 bar_column->semantic_size[Axis2_X] = ui_px(bar_width, 0.f); 4254 bar_column->semantic_size[Axis2_Y] = ui_px(label_column->computed_size[Axis2_Y], 1.f); 4255 bar_column->custom_draw_function = beamformer_ui_custom_draw_compute_bar_graph; 4256 } 4257 UIParent(label_column) 4258 UIPrefHeight(ui_text_dim(1.3f, 1.f)) 4259 UIPrefWidth(ui_text_dim(1.f, 1.f)) 4260 for (i32 i = 0; i < 4; i++) 4261 ui_labelf("%d:", -i); 4262 } 4263 4264 }break; 4265 4266 case BeamformerPanelKind_ComputeStats:{ 4267 u32 selected_plan = ui->selected_parameter_block % BeamformerMaxParameterBlocks; 4268 BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[selected_plan]; 4269 if (!cp) cp = (BeamformerComputePlan *)&beamformer_nil_compute_plan; 4270 f32 t = beamformer_ui_blinker_update(&panel->u.compute_stats_broken_shader_blinker, BLINK_SPEED); 4271 ui_build_compute_stats(cp, t, panel); 4272 }break; 4273 4274 case BeamformerPanelKind_FrameViewXPlane: 4275 { 4276 BeamformerFrameView *view = panel->u.frame_view; 4277 b32 any_valid = 0; 4278 for EachElement(view->plane_active, plane) 4279 any_valid |= (view->plane_active[plane] && ui_context->latest_plane[plane].timeline_valid_value); 4280 if (any_valid) { 4281 UINode *container; 4282 UIChildLayoutAxis(Axis2_Y) 4283 UIPrefWidth(ui_pct(1.f, 0.5f)) 4284 UIPrefHeight(ui_pct(1.f, 0.5f)) 4285 UIAxisAlign(Axis2_X, Center) 4286 container = ui_node_from_string(0, str8("###frame_view_container")); 4287 ui_build_3d_xplane_frame_view(container, view); 4288 } 4289 if ui_context_menu(panel) ui_build_3d_xplane_context_menu(view); 4290 }break; 4291 4292 case BeamformerPanelKind_FrameViewCopy: 4293 case BeamformerPanelKind_FrameViewLive: 4294 { 4295 BeamformerFrameView *view = panel->u.frame_view; 4296 if (iv3_dimension(view->frame.points) != 0) { 4297 // TODO(rnp): cleanup, why do we need this extra container 4298 UINode *container; 4299 UIChildLayoutAxis(Axis2_Y) 4300 UIPrefWidth(ui_pct(1.f, 0.5f)) 4301 UIPrefHeight(ui_pct(1.f, 0.5f)) 4302 UIAxisAlign(Axis2_Y, Center) 4303 container = ui_node_from_string(0, str8("###frame_view_container")); 4304 ui_build_frame_view(container, view); 4305 } 4306 if ui_context_menu(panel) ui_build_frame_view_context_menu(panel, view); 4307 }break; 4308 4309 case BeamformerPanelKind_ParameterListing:{ ui_build_parameters_listing(panel); }break; 4310 4311 case BeamformerPanelKind_LiveImagingControls:{ ui_build_live_imaging_controls(panel); }break; 4312 4313 InvalidDefaultCase; 4314 } 4315 4316 ui_signal_from_node(top_node); 4317 } 4318 } 4319 4320 function UINode * 4321 ui_build_drag_hover_node(void) 4322 { 4323 BeamformerUI *ui = ui_context; 4324 4325 BeamformerUIPanel *tree = (BeamformerUIPanel *)beamformer_registers()->drop_target_tree; 4326 UINode *target = (UINode *)ui->drop_target_node; 4327 Axis2 axis = beamformer_registers()->split_axis; 4328 Axis2 flip = axis2_flip(axis); 4329 Rect nr = ui_node_rect(target); 4330 f32 pct = 4.0f; 4331 f32 off_pct = 0.95f; 4332 if (target == ui->root_node) 4333 pct = 0.1f; 4334 if (tree->kind == BeamformerPanelKind_TabGroup) { 4335 off_pct = 0.8f; 4336 pct = 0.3f; 4337 } 4338 if (beamformer_registers()->split_left_tree == beamformer_registers()->split_right_tree) 4339 off_pct = pct = 0.8f; 4340 4341 UINode *result = push_struct(ui_build_arena(), UINode); 4342 result->flags = UINodeFlag_DrawBackground; 4343 result->bg_colour = NODE_SPLIT_COLOUR; 4344 result->computed_size[flip] = off_pct * nr.size.E[flip]; 4345 result->computed_size[axis] = pct * nr.size.E[axis]; 4346 result->computed_position[axis] = nr.pos.E[axis]; 4347 result->computed_position[flip] = nr.pos.E[flip]; 4348 4349 if (target == ui->root_node) { 4350 result->computed_position[flip] += 0.5f * (nr.size.E[flip] - result->computed_size[flip]); 4351 if (beamformer_registers()->split_left_tree == (u64)ui->tree) 4352 result->computed_position[axis] = nr.pos.E[axis] + nr.size.E[axis] - result->computed_size[axis]; 4353 } else { 4354 result->computed_position[axis] += 0.5f * (nr.size.E[axis] - result->computed_size[axis]); 4355 result->computed_position[flip] += 0.5f * (nr.size.E[flip] - result->computed_size[flip]); 4356 4357 if (tree->kind == BeamformerPanelKind_TabGroup) { 4358 if (beamformer_registers()->split_left_tree == (u64)tree) 4359 result->computed_position[axis] += 0.45f * (nr.size.E[axis] - result->computed_size[axis]); 4360 if (beamformer_registers()->split_right_tree == (u64)tree) 4361 result->computed_position[axis] -= 0.45f * (nr.size.E[axis] - result->computed_size[axis]); 4362 } 4363 } 4364 4365 return result; 4366 } 4367 4368 function b32 4369 ui_build_drag_split_box(Axis2 axis, b32 two_way, i32 highlight_index, str8 tag) 4370 { 4371 UINode *split_box; 4372 UIAxisAlign(axis2_flip(axis), Center) 4373 UIAxisSize(axis2_flip(axis), ui_px(60.f, 1.f)) 4374 UIAxisSize(axis, ui_children_sum(1.f)) 4375 UIChildLayoutAxis(axis) 4376 UIBorderColour((v4){.a = 0.8f}) 4377 UIBGColour(BG_COLOUR) 4378 split_box = ui_node_from_string(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground, 4379 push_str8_from_parts(ui_build_arena(), str8(""), 4380 str8("drag_split_box_"), tag)); 4381 b32 result = point_in_rect(ui_context->last_mouse, ui_node_rect(split_box)); 4382 4383 UIParent(split_box) 4384 UIAxisSize(axis2_flip(axis), ui_pct(0.7f, 0.5f)) 4385 UIAxisSize(axis, ui_px(1.5f * UI_NODE_PAD, 1.f)) 4386 { 4387 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4388 4389 UIBorderColour((v4){.a = highlight_index <= 0 ? 0.0f : 0.4f}) 4390 UIBGColour(highlight_index <= 0 ? NODE_SPLIT_COLOUR : (v4){0}) 4391 ui_spacer(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground); 4392 4393 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4394 4395 if (two_way) { 4396 UIBorderColour((v4){.a = highlight_index != 0 ? 0.0f : 0.4f}) 4397 UIBGColour(highlight_index != 0 ? NODE_SPLIT_COLOUR : (v4){0}) 4398 ui_spacer(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground); 4399 4400 UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0); 4401 } 4402 } 4403 return result; 4404 } 4405 4406 function b32 4407 ui_build_drag_overlay_splitter(Axis2 axis, b32 two_way, i32 highlight_index, str8 tag) 4408 { 4409 b32 result = 0; 4410 4411 UINode *container; 4412 UIChildLayoutAxis(axis2_flip(axis)) 4413 UIAxisAlign(axis2_flip(axis), Center) 4414 UIAxisSize(axis, ui_children_sum(1.f)) 4415 UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f)) 4416 { 4417 container = ui_spacer(0); 4418 } 4419 4420 UIParent(container) 4421 result = ui_build_drag_split_box(axis, two_way, highlight_index, tag); 4422 return result; 4423 } 4424 4425 function void 4426 ui_build_drag_overlay(Rect window_rect) 4427 { 4428 BeamformerUI *ui = ui_context; 4429 4430 UIChildLayoutAxis(Axis2_Y) 4431 UIPrefWidth(ui_px(window_rect.size.x, 1.f)) 4432 UIPrefHeight(ui_px(window_rect.size.y, 1.f)) 4433 ui->drag_overlay_edges_root = ui_node_from_string(0, str8("drag_overlay_edges_root")); 4434 4435 UIParent(ui->drag_overlay_edges_root) 4436 { 4437 if (ui_build_drag_overlay_splitter(Axis2_Y, 0, 0, str8("top"))) 4438 { 4439 beamformer_registers()->split_axis = Axis2_Y; 4440 beamformer_registers()->split_left_tree = 0; 4441 beamformer_registers()->split_right_tree = (u64)ui->tree; 4442 beamformer_registers()->drop_target_tree = (u64)ui->tree; 4443 ui->drop_target_node = ui->root_node; 4444 } 4445 4446 UIPrefHeight(ui_pct(1.f, 0.5f)) 4447 UIParent(ui_spacer(0)) 4448 { 4449 if (ui_build_drag_overlay_splitter(Axis2_X, 0, 0, str8("left"))) 4450 { 4451 beamformer_registers()->split_axis = Axis2_X; 4452 beamformer_registers()->split_left_tree = 0; 4453 beamformer_registers()->split_right_tree = (u64)ui->tree; 4454 beamformer_registers()->drop_target_tree = (u64)ui->tree; 4455 ui->drop_target_node = ui->root_node; 4456 } 4457 4458 UIPrefWidth(ui_pct(1.f, 0.5f)) ui_spacer(0); 4459 4460 if (ui_build_drag_overlay_splitter(Axis2_X, 0, 0, str8("right"))) 4461 { 4462 beamformer_registers()->split_axis = Axis2_X; 4463 beamformer_registers()->split_left_tree = (u64)ui->tree; 4464 beamformer_registers()->split_right_tree = 0; 4465 beamformer_registers()->drop_target_tree = (u64)ui->tree; 4466 ui->drop_target_node = ui->root_node; 4467 } 4468 } 4469 4470 if (ui_build_drag_overlay_splitter(Axis2_Y, 0, 0, str8("bottom"))) 4471 { 4472 beamformer_registers()->split_axis = Axis2_Y; 4473 beamformer_registers()->split_left_tree = (u64)ui->tree; 4474 beamformer_registers()->split_right_tree = 0; 4475 beamformer_registers()->drop_target_tree = (u64)ui->tree; 4476 ui->drop_target_node = ui->root_node; 4477 } 4478 } 4479 4480 struct tree_frame { 4481 BeamformerUIPanel *tree; 4482 UINode *node; 4483 } init[64]; 4484 4485 struct { 4486 struct tree_frame *data; 4487 da_count count; 4488 da_count capacity; 4489 } stack = {init, 0, countof(init)}; 4490 4491 UIChildLayoutAxis(Axis2_Y) 4492 UIPrefWidth(ui_px(window_rect.size.x, 1.f)) 4493 UIPrefHeight(ui_px(window_rect.size.y, 1.f)) 4494 ui->drag_overlay_root = ui_node_from_string(0, str8("drag_overlay_root")); 4495 4496 *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4497 .node = ui->drag_overlay_root, 4498 .tree = ui->tree, 4499 }; 4500 while (stack.count) { 4501 struct tree_frame *top = stack.data + --stack.count; 4502 4503 BeamformerUIPanel *panel = top->tree; 4504 UINode *top_node = top->node; 4505 4506 UIParent(top_node) 4507 switch (panel->kind) { 4508 default:{}break; 4509 case BeamformerPanelKind_Split:{ 4510 Axis2 axis = panel->u.split.axis; 4511 top_node->child_layout_axis = axis; 4512 4513 UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f)) 4514 { 4515 f32 split_pct = panel->u.split.fraction; 4516 4517 UINode *spacer; 4518 UIAxisSize(axis, ui_pct(split_pct, 0.5f)) spacer = ui_spacer(0); 4519 4520 if (panel->first_child->kind == BeamformerPanelKind_Split) { 4521 *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4522 .tree = panel->first_child, 4523 .node = spacer, 4524 }; 4525 } 4526 4527 UIChildLayoutAxis(axis2_flip(axis)) 4528 UIAxisAlign(axis2_flip(axis), Center) 4529 UIAxisSize(axis, ui_children_sum(1.f)) 4530 UIParent(ui_spacer(0)) 4531 { 4532 // TODO(rnp): cleanup 4533 Stream sb = arena_stream(ui_build_arena()); 4534 stream_appendf(&sb, "###%p_split", panel); 4535 str8 tag = arena_stream_commit(ui_build_arena(), &sb); 4536 4537 if (ui_build_drag_split_box(axis, 1, -1, tag)) { 4538 beamformer_registers()->split_axis = axis; 4539 beamformer_registers()->split_left_tree = (u64)panel; 4540 beamformer_registers()->split_right_tree = (u64)ui->drag_panel; 4541 beamformer_registers()->drop_target_tree = (u64)panel; 4542 ui->drop_target_node = ui_top_parent(); 4543 } 4544 } 4545 4546 UIAxisSize(axis, ui_pct(1.f - split_pct, 0.5f)) spacer = ui_spacer(0); 4547 4548 if (panel->last_child->kind == BeamformerPanelKind_Split) { 4549 *da_push(ui_build_arena(), &stack) = (struct tree_frame){ 4550 .tree = panel->last_child, 4551 .node = spacer, 4552 }; 4553 } 4554 } 4555 }break; 4556 } 4557 } 4558 4559 ui->drag_overlay_tab_root = 0; 4560 if (beamformer_registers()->drop_target_tree && 4561 !beamformer_registers()->split_left_tree && 4562 !beamformer_registers()->split_right_tree) 4563 { 4564 BeamformerUIPanel *group = (BeamformerUIPanel *)beamformer_registers()->drop_target_tree; 4565 UINode *target = ui_node_from_key(ui->drop_target_key); 4566 4567 assert(!group->parent || (group->parent && group->parent->kind == BeamformerPanelKind_Split)); 4568 Axis2 parent_axis = group->parent ? group->parent->u.split.axis : Axis2_Count; 4569 4570 Rect tr = ui_node_rect(target); 4571 if (point_in_rect(ui->last_mouse, tr)) { 4572 UIPrefWidth(ui_px(tr.size.x, 1.f)) 4573 UIPrefHeight(ui_px(tr.size.h, 1.f)) 4574 UIAxisAlign(Axis2_X, Center) 4575 UIAxisAlign(Axis2_Y, Center) 4576 UIChildLayoutAxis(Axis2_Y) 4577 { 4578 ui->drag_overlay_tab_root = ui_node_from_string(0, str8("drag_overlay_tab_root")); 4579 } 4580 4581 ui->drag_overlay_tab_root->computed_position[Axis2_X] = tr.pos.x; 4582 ui->drag_overlay_tab_root->computed_position[Axis2_Y] = tr.pos.y; 4583 4584 UINode *inner; 4585 UIAxisAlign(Axis2_X, Center) 4586 UIChildLayoutAxis(Axis2_Y) 4587 UIPrefHeight(ui_children_sum(1.f)) 4588 UIPrefWidth(ui_children_sum(1.f)) 4589 UIParent(ui->drag_overlay_tab_root) 4590 inner = ui_spacer(0); 4591 4592 UIParent(inner) 4593 { 4594 if (parent_axis != Axis2_Y && ui_build_drag_split_box(Axis2_Y, 1, 0, str8("top"))) 4595 { 4596 beamformer_registers()->split_axis = Axis2_Y; 4597 beamformer_registers()->split_left_tree = (u64)ui->drag_panel; 4598 beamformer_registers()->split_right_tree = (u64)group; 4599 ui->drop_target_node = target; 4600 } 4601 4602 ui_padh(UI_NODE_PAD); 4603 4604 UIPrefHeight(ui_children_sum(1.f)) 4605 UIPrefWidth(ui_children_sum(1.f)) 4606 UIParent(ui_spacer(0)) 4607 { 4608 if (parent_axis != Axis2_X && ui_build_drag_split_box(Axis2_X, 1, 0, str8("left"))) 4609 { 4610 beamformer_registers()->split_axis = Axis2_X; 4611 beamformer_registers()->split_left_tree = (u64)ui->drag_panel; 4612 beamformer_registers()->split_right_tree = (u64)group; 4613 ui->drop_target_node = target; 4614 } 4615 4616 ui_padw(UI_NODE_PAD); 4617 4618 if (parent_axis != Axis2_Count && 4619 ui_build_drag_split_box(axis2_flip(parent_axis), 0, 0, str8("center"))) 4620 { 4621 beamformer_registers()->split_left_tree = (u64)group; 4622 beamformer_registers()->split_right_tree = (u64)group; 4623 beamformer_registers()->drop_target_tree = (u64)group; 4624 beamformer_registers()->drop_child_index = group->child_count; 4625 ui->drop_target_node = target; 4626 } 4627 4628 ui_padw(UI_NODE_PAD); 4629 4630 if (parent_axis != Axis2_X && ui_build_drag_split_box(Axis2_X, 1, 1, str8("right"))) 4631 { 4632 beamformer_registers()->split_axis = Axis2_X; 4633 beamformer_registers()->split_left_tree = (u64)group; 4634 beamformer_registers()->split_right_tree = (u64)ui->drag_panel; 4635 ui->drop_target_node = target; 4636 } 4637 } 4638 4639 ui_padh(UI_NODE_PAD); 4640 4641 if (parent_axis != Axis2_Y && ui_build_drag_split_box(Axis2_Y, 1, 1, str8("bottom"))) 4642 { 4643 beamformer_registers()->split_axis = Axis2_Y; 4644 beamformer_registers()->split_left_tree = (u64)group; 4645 beamformer_registers()->split_right_tree = (u64)ui->drag_panel; 4646 ui->drop_target_node = target; 4647 } 4648 } 4649 } 4650 } 4651 } 4652 4653 function void 4654 ui_layout_constrain(UINode *root) 4655 { 4656 assert(!ui_node_is_nil(root->first_child)); 4657 4658 // NOTE(rnp): for violations in non-layout axis all we can do is clamp 4659 { 4660 Axis2 axis = axis2_flip(root->child_layout_axis); 4661 if ((root->flags & (UINodeFlag_AllowOverflowX << axis)) == 0) { 4662 for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4663 child->computed_size[axis] = Min(child->computed_size[axis], root->computed_size[axis]); 4664 } 4665 } 4666 4667 Axis2 axis = root->child_layout_axis; 4668 if ((root->flags & (UINodeFlag_AllowOverflowX << axis)) == 0) { 4669 f32 allowed_size = root->computed_size[axis]; 4670 f32 total_size = 0; 4671 f32 total_weighted_size = 0; 4672 4673 for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4674 total_size += child->computed_size[axis]; 4675 total_weighted_size += child->computed_size[axis] * (1.0f - child->semantic_size[axis].strictness); 4676 } 4677 4678 f32 remaining_size = root->computed_size[axis]; 4679 f32 violation = total_size - allowed_size; 4680 if (violation > 0 && total_weighted_size > 0) { 4681 f32 fixup_fraction = Clamp01(violation / total_weighted_size); 4682 for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4683 f32 fixup = Max(0, child->computed_size[axis] * (1.0f - child->semantic_size[axis].strictness)); 4684 child->computed_size[axis] -= fixup * fixup_fraction; 4685 4686 if (child->semantic_size[axis].kind != UISizeKind_PercentOfParent) 4687 remaining_size -= child->computed_size[axis]; 4688 } 4689 } 4690 4691 // NOTE(rnp): fixup sizes dependant on parent 4692 for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4693 if (child->semantic_size[axis].kind == UISizeKind_PercentOfParent) 4694 child->computed_size[axis] = remaining_size * child->semantic_size[axis].value; 4695 } 4696 } 4697 4698 function void 4699 ui_layout_nodes(UINode *root) 4700 { 4701 struct node_frame { 4702 UINode *node; 4703 // NOTE(rnp): for post order traversal 4704 b32 visited; 4705 } init[64] = {0}; 4706 4707 struct { 4708 struct node_frame *data; 4709 da_count count; 4710 da_count capacity; 4711 } stack = {init, 0, countof(init)}; 4712 4713 /////////////////////// 4714 // NOTE(rnp): First Pass: non dependant sizes 4715 da_push(ui_build_arena(), &stack)->node = root; 4716 while (stack.count) { 4717 struct node_frame *top = stack.data + --stack.count; 4718 UINode *node = top->node; 4719 4720 if (node->flags & UINodeFlag_DrawText) { 4721 Font font = ui_font_for_node(node); 4722 str8 string = ui_draw_part_from_key_string(node->string); 4723 if (node->flags & UINodeFlag_IconText) 4724 node->text_size = measure_text_tight(font, string); 4725 else 4726 node->text_size = measure_text(font, string); 4727 } 4728 4729 for EachElement(node->semantic_size, it) { 4730 switch (node->semantic_size[it].kind) { 4731 case UISizeKind_Pixels:{node->computed_size[it] = node->semantic_size[it].value;}break; 4732 4733 case UISizeKind_TextContent:{ 4734 node->computed_size[it] = node->semantic_size[it].value * node->text_size.E[it]; 4735 }break; 4736 4737 default:{}break; 4738 } 4739 } 4740 4741 // NOTE(rnp): push children 4742 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4743 da_push(ui_build_arena(), &stack)->node = child; 4744 } 4745 4746 /////////////////////// 4747 // NOTE(rnp): Second Pass (Pre Order): parent dependant sizes 4748 da_push(ui_build_arena(), &stack)->node = root; 4749 while (stack.count) { 4750 struct node_frame *top = stack.data + --stack.count; 4751 UINode *node = top->node; 4752 4753 for EachElement(node->semantic_size, it) { 4754 if (node->semantic_size[it].kind == UISizeKind_PercentOfParent) { 4755 f32 parent_size = node->parent->computed_size[it]; 4756 node->computed_size[it] = node->semantic_size[it].value * parent_size; 4757 } 4758 } 4759 4760 // NOTE(rnp): push children 4761 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4762 da_push(ui_build_arena(), &stack)->node = child; 4763 } 4764 4765 /////////////////////// 4766 // NOTE(rnp): Third Pass (Post Order): child dependant sizes 4767 da_push(ui_build_arena(), &stack)->node = root; 4768 while (stack.count) { 4769 struct node_frame *top = stack.data + stack.count - 1; 4770 4771 UINode *node = top->node; 4772 if (!top->visited && node->child_count) { 4773 top->visited = 1; 4774 4775 // NOTE(rnp): push children 4776 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4777 da_push(ui_build_arena(), &stack)->node = child; 4778 } else { 4779 // NOTE(rnp): pop 4780 stack.count--; 4781 4782 for EachElement(node->semantic_size, it) { 4783 if (node->semantic_size[it].kind == UISizeKind_ChildrenSum) { 4784 f32 size_sum = 0; 4785 for (UINode *child = node->first_child; 4786 !ui_node_is_nil(child); 4787 child = child->next_sibling) 4788 { 4789 if (it == node->child_layout_axis) { 4790 size_sum += child->computed_size[it]; 4791 } else { 4792 size_sum = Max(size_sum, child->computed_size[it]); 4793 } 4794 } 4795 node->computed_size[it] = size_sum; 4796 } 4797 } 4798 } 4799 } 4800 4801 /////////////////////// 4802 // NOTE(rnp): Fourth Pass (Pre Order): solve violations 4803 da_push(ui_build_arena(), &stack)->node = root; 4804 while (stack.count) { 4805 struct node_frame *top = stack.data + --stack.count; 4806 4807 UINode *node = top->node; 4808 if (node->child_count) 4809 ui_layout_constrain(node); 4810 4811 // NOTE(rnp): push children 4812 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4813 da_push(ui_build_arena(), &stack)->node = child; 4814 } 4815 4816 /////////////////////// 4817 // NOTE(rnp): Final Pass (Pre Order): fill positions 4818 da_push(ui_build_arena(), &stack)->node = root; 4819 while (stack.count) { 4820 struct node_frame *top = stack.data + --stack.count; 4821 4822 UINode *node = top->node; 4823 Axis2 layout_axis = node->child_layout_axis; 4824 Axis2 flipped_axis = axis2_flip(layout_axis); 4825 f32 offset = 0; 4826 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4827 child->computed_position[flipped_axis] = node->computed_position[flipped_axis]; 4828 child->computed_position[layout_axis] = offset + node->computed_position[layout_axis]; 4829 offset += child->computed_size[layout_axis]; 4830 } 4831 4832 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4833 for EachElement(node->alignment, axis) { 4834 UIAlign align = node->alignment[axis]; 4835 f32 size_delta = node->computed_size[axis] - child->computed_size[axis]; 4836 if (size_delta < 0) align = UIAlign_Left; 4837 child->computed_position[axis] += ui_alignment_correction(align, size_delta); 4838 } 4839 } 4840 4841 // NOTE(rnp): push children 4842 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) 4843 if (child->child_count > 0) 4844 da_push(ui_build_arena(), &stack)->node = child; 4845 } 4846 } 4847 4848 function void 4849 ui_draw_nodes(UINode *root, Rect window_rect) 4850 { 4851 BeamformerUI *ui = ui_context; 4852 4853 struct node_frame { 4854 b32 visited; 4855 UINode *node; 4856 } init[64]; 4857 4858 struct { 4859 struct node_frame *data; 4860 da_count count; 4861 da_count capacity; 4862 } stack = {init, 0, countof(init)}; 4863 4864 u32 colour_index = 0; 4865 4866 da_push(ui_build_arena(), &stack)->node = root; 4867 while (stack.count) { 4868 struct node_frame *top = stack.data + stack.count - 1; 4869 4870 UINode *node = top->node; 4871 if (!top->visited) { 4872 top->visited = 1; 4873 4874 Rect r = ui_node_rect(node); 4875 if (node->flags & UINodeFlag_Clip) 4876 BeginScissorMode(r.pos.x, r.pos.y, r.size.w, r.size.h); 4877 4878 if (node->flags & UINodeFlag_ViewScroll) { 4879 v2 view_off = node->view_scroll_offset; 4880 rlPushMatrix(); 4881 rlTranslatef(-view_off.x, -view_off.y, 0); 4882 } 4883 4884 // NOTE(rnp): debug node bounds drawing 4885 if (0) { 4886 v4 colour = g_colour_palette[(colour_index++) % countof(g_colour_palette)]; 4887 DrawRectangleLinesEx(rl_rect(r), 4.0f, colour_from_normalized(colour)); 4888 } 4889 4890 v4 bg_colour = node->bg_colour; 4891 if (node->flags & UINodeFlag_DrawHotEffects) 4892 bg_colour = v4_lerp(bg_colour, HOVERED_COLOUR, node->hot_t); 4893 4894 if (node->flags & UINodeFlag_DrawBackground) 4895 DrawRectangleRec(rl_rect(r), colour_from_normalized(bg_colour)); 4896 4897 if (node->flags & UINodeFlag_DrawBorder) { 4898 v4 colour = node->border_colour; 4899 u64 masked = node->flags & (UINodeFlag_DrawBackground|UINodeFlag_DrawHotEffects); 4900 if (masked == UINodeFlag_DrawHotEffects) 4901 colour = v4_lerp(colour, HOVERED_COLOUR, node->hot_t); 4902 4903 DrawRectangleLinesEx(rl_rect(r), node->border_thickness, colour_from_normalized(colour)); 4904 } 4905 4906 if (node->flags & UINodeFlag_CustomDraw) { 4907 node->custom_draw_function(node, r); 4908 } else { 4909 if (node->flags & UINodeFlag_DrawText) { 4910 Font font = ui_font_for_node(node); 4911 4912 TextSpec text_spec = { 4913 .font = &font, 4914 .flags = TF_LIMITED, 4915 .colour = node->text_colour, 4916 .outline_colour = node->text_outline_colour, 4917 .outline_thick = node->text_outline_thickness, 4918 .limits.size = r.size, 4919 }; 4920 if (node->text_outline_thickness > 0) 4921 text_spec.flags |= TF_OUTLINED; 4922 4923 v2 pos = ui_node_text_position(node); 4924 4925 UITextInputState *tis = &ui_context->text_input_state; 4926 b32 input = ui_node_key_equal(node->key, tis->node_key); 4927 // TODO(rnp): cleanup: visible part 4928 str8 string = ui_draw_part_from_key_string(node->string); 4929 if (!input && node->flags & UINodeFlag_DrawHotEffects && (node->flags & UINodeFlag_DrawBackground) == 0) 4930 text_spec.colour = v4_lerp(text_spec.colour, HOVERED_COLOUR, node->hot_t); 4931 4932 if (node->flags & UINodeFlag_IconText) 4933 draw_text_tight(*text_spec.font, string, pos, colour_from_normalized(text_spec.colour)); 4934 else 4935 draw_text(string, pos, &text_spec); 4936 4937 if (input) { 4938 iv2 range = ui_text_input_cursor_range(); 4939 str8 parts[2]; 4940 parts[0] = (str8){.data = string.data, .length = range.x}; 4941 parts[1] = (str8){.data = string.data + range.x, .length = range.y - range.x}; 4942 4943 Rect cursor = {.pos = pos}; 4944 cursor.pos.x += measure_text(font, parts[0]).x; 4945 4946 v4 cursor_colour = FOCUSED_COLOUR; 4947 if (parts[1].length > 0) { 4948 cursor_colour = SELECTION_COLOUR; 4949 cursor.size = measure_text(font, parts[1]); 4950 4951 if (range.x == 0) { 4952 cursor.pos.x -= 2.f; 4953 cursor.size.x += 2.f; 4954 } 4955 4956 if (range.y == tis->count) 4957 cursor.size.x += 2.f; 4958 } else { 4959 cursor_colour.a = ease_in_out_cubic(ui->text_input_state.blinker.t); 4960 cursor.size.x = string.length - range.y > 0 ? 4.0f : 0.55f * (f32)font.baseSize; 4961 cursor.size.y = font.baseSize; 4962 } 4963 4964 if (cursor.size.x > 0) 4965 DrawRectanglePro(rl_rect(cursor), (Vector2){0}, 0, colour_from_normalized(cursor_colour)); 4966 } 4967 } 4968 } 4969 4970 // NOTE(rnp): push children 4971 for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) { 4972 Rect cr = ui_node_rect(child); 4973 if ((cr.size.x > 0 && cr.size.y > 0) || ui_node_key_equal(child->key, ui->text_input_state.node_key)) 4974 da_push(ui_build_arena(), &stack)->node = child; 4975 } 4976 } else { 4977 // NOTE(rnp): pop 4978 stack.count--; 4979 4980 if (node->flags & UINodeFlag_ViewScroll) { 4981 rlPopMatrix(); 4982 } 4983 4984 if (node->flags & UINodeFlag_Clip) 4985 EndScissorMode(); 4986 } 4987 } 4988 4989 // TODO(rnp): can we make the mouse latency not shit? 4990 //if (ui->current_mouse.x > 0) DrawCircle(ui->current_mouse.x, ui->current_mouse.y, 6, GREEN); 4991 } 4992 4993 function void 4994 beamformer_ui_panel_unlink(BeamformerUIPanel *node) 4995 { 4996 BeamformerUIPanel *parent = node->parent; 4997 if (parent->kind == BeamformerPanelKind_TabGroup && parent->u.tab_focus == node) 4998 parent->u.tab_focus = node->previous_sibling ? node->previous_sibling : node->next_sibling; 4999 DLLRemove(0, parent->first_child, parent->last_child, node, next_sibling, previous_sibling); 5000 parent->child_count--; 5001 } 5002 5003 function void 5004 ui_kill_panel(BeamformerUIPanel *node) 5005 { 5006 BeamformerUI *ui = ui_context; 5007 BeamformerUIPanel *parent = node->parent; 5008 5009 if (node->kind == BeamformerPanelKind_FrameViewLive || 5010 node->kind == BeamformerPanelKind_FrameViewCopy || 5011 node->kind == BeamformerPanelKind_FrameViewXPlane) 5012 { 5013 BeamformerFrameView *bv = node->u.frame_view; 5014 beamformer_ui_frame_view_release_subresources(bv, bv->kind); 5015 DLLRemove(0, ui->view_first, ui->view_last, bv, next, prev); 5016 SLLStackPush(ui->view_freelist, bv, next); 5017 } 5018 5019 if (node->kind == BeamformerPanelKind_LiveImagingControls && (u64)node == beamformer_context->base_registers.v.live_controls) { 5020 beamformer_context->base_registers.v.live_controls = 0; 5021 // TODO(rnp): find first live control panel and update this with it 5022 } 5023 5024 if (node->kind == BeamformerPanelKind_LiveImagingControls && node == beamformer_context->auto_live_control_panel) 5025 beamformer_context->auto_live_control_panel = 0; 5026 5027 beamformer_ui_panel_unlink(node); 5028 5029 if (node->kind == BeamformerPanelKind_TabGroup) { 5030 assert(parent->kind == BeamformerPanelKind_Split); 5031 5032 BeamformerUIPanel *old_child = parent->first_child; 5033 parent->kind = old_child->kind; 5034 parent->first_child = old_child->first_child; 5035 parent->last_child = old_child->last_child; 5036 parent->child_count = old_child->child_count; 5037 memory_copy(&parent->u, &old_child->u, sizeof(parent->u)); 5038 5039 for (BeamformerUIPanel *child = parent->first_child; child; child = child->next_sibling) 5040 child->parent = parent; 5041 5042 SLLStackPush(ui->tree_node_freelist, old_child, next_sibling); 5043 } 5044 5045 SLLStackPush(ui->tree_node_freelist, node, next_sibling); 5046 } 5047 5048 function BeamformerUIPanel * 5049 beamformer_ui_push_panel_node(BeamformerUIPanel *parent) 5050 { 5051 BeamformerUI *ui = ui_context; 5052 BeamformerUIPanel *result = ui->tree_node_freelist; 5053 if (result) SLLStackPop(ui->tree_node_freelist, next_sibling); 5054 else result = push_struct_no_zero(ui->arena, BeamformerUIPanel); 5055 zero_struct(result); 5056 5057 result->parent = parent; 5058 if (parent) { 5059 DLLInsertLast(0, parent->first_child, parent->last_child, result, next_sibling, previous_sibling); 5060 parent->child_count++; 5061 } 5062 5063 return result; 5064 } 5065 5066 function BeamformerUIPanel * 5067 beamformer_ui_push_panel(BeamformerUIPanel *parent, BeamformerPanelKind kind) 5068 { 5069 BeamformerUIPanel *result = beamformer_ui_push_panel_node(parent); 5070 result->kind = kind; 5071 if (parent && parent->kind == BeamformerPanelKind_TabGroup) 5072 parent->u.tab_focus = result; 5073 5074 if (kind == BeamformerPanelKind_FrameViewLive || 5075 kind == BeamformerPanelKind_FrameViewCopy || 5076 kind == BeamformerPanelKind_FrameViewXPlane) 5077 { 5078 BeamformerFrameViewKind view_kind = BeamformerFrameViewKind_Latest; 5079 if (kind == BeamformerPanelKind_FrameViewCopy) 5080 view_kind = BeamformerFrameViewKind_Copy; 5081 if (kind == BeamformerPanelKind_FrameViewXPlane) 5082 view_kind = BeamformerFrameViewKind_3DXPlane; 5083 result->u.frame_view = beamformer_ui_frame_view_new(view_kind); 5084 } 5085 5086 if (kind == BeamformerPanelKind_LiveImagingControls) 5087 beamformer_context->base_registers.v.live_controls = (u64)result; 5088 5089 return result; 5090 } 5091 5092 /* NOTE(rnp): this only exists to make asan less annoying. do not waste 5093 * people's time by freeing, closing, etc... */ 5094 DEBUG_EXPORT BEAMFORMER_DEBUG_UI_DEINIT_FN(beamformer_debug_ui_deinit) 5095 { 5096 #if ASAN_ACTIVE 5097 BeamformerUI *ui = ctx->ui; 5098 UnloadFont(ui->font); 5099 UnloadFont(ui->small_font); 5100 CloseWindow(); 5101 #endif 5102 } 5103 5104 function void 5105 ui_init(BeamformerCtx *ctx, Arena *store) 5106 { 5107 BeamformerUI *ui = ui_context = ctx->ui; 5108 if (!ui) { 5109 ui = ui_context = ctx->ui = push_struct(store, typeof(*ui)); 5110 ui->arena = store; 5111 5112 for EachElement(ui->build_arenas, it) 5113 ui->build_arenas[it] = arena_create(); 5114 ui->node_freelist = (UINode *)&ui_node_nil; 5115 5116 /* TODO(rnp): better font, this one is jank at small sizes */ 5117 ui->font = LoadFontFromMemory(".ttf", beamformer_base_font, sizeof(beamformer_base_font), 28, 0, 0); 5118 ui->small_font = LoadFontFromMemory(".ttf", beamformer_base_font, sizeof(beamformer_base_font), 20, 0, 0); 5119 5120 // NOTE(rnp): push default UI layout 5121 // TODO(rnp): load last layout from file and only load default if not present 5122 { 5123 BeamformerUIPanel *node = ui->tree = beamformer_ui_push_panel(0, BeamformerPanelKind_Split); 5124 node->u.split.fraction = 0.35f; 5125 node->u.split.axis = Axis2_X; 5126 5127 DeferLoop(node = beamformer_ui_push_panel(node, BeamformerPanelKind_Split), node = node->parent) 5128 { 5129 node->u.split.fraction = 0.65f; 5130 node->u.split.axis = Axis2_Y; 5131 5132 BeamformerUIPanel *left = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup); 5133 BeamformerUIPanel *right = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup); 5134 beamformer_ui_push_panel(left, BeamformerPanelKind_ParameterListing); 5135 beamformer_ui_push_panel(right, BeamformerPanelKind_ComputeBarGraph); 5136 beamformer_ui_push_panel(right, BeamformerPanelKind_ComputeStats); 5137 } 5138 5139 DeferLoop(node = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup), node = node->parent) 5140 { 5141 beamformer_ui_push_panel(node, BeamformerPanelKind_FrameViewLive); 5142 } 5143 } 5144 5145 u32 samples = gpu_info()->max_msaa_samples; 5146 vk_image_allocate(&ui->render_3d_image, FRAME_VIEW_RENDER_TARGET_SIZE, 1, samples, VulkanImageUsage_Colour, 0, 0, str8("Render Target Colour")); 5147 vk_image_allocate(&ui->render_3d_depth_image, FRAME_VIEW_RENDER_TARGET_SIZE, 1, samples, VulkanImageUsage_DepthStencil, 0, 0, str8("Render Target Depth")); 5148 5149 glGenSemaphoresEXT(countof(ui->render_semaphores_gl), ui->render_semaphores_gl); 5150 for EachElement(ui->render_semaphores, it) 5151 ui->render_semaphores[it] = gpu_semaphore_create(ui->render_semaphores_export + it); 5152 5153 if (OS_WINDOWS) { 5154 glImportSemaphoreWin32HandleEXT(ui->render_semaphores_gl[0], GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, (void *)ui->render_semaphores_export[0].value[0]); 5155 glImportSemaphoreWin32HandleEXT(ui->render_semaphores_gl[1], GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, (void *)ui->render_semaphores_export[1].value[0]); 5156 } else { 5157 glImportSemaphoreFdEXT(ui->render_semaphores_gl[0], GL_HANDLE_TYPE_OPAQUE_FD_EXT, ui->render_semaphores_export[0].value[0]); 5158 glImportSemaphoreFdEXT(ui->render_semaphores_gl[1], GL_HANDLE_TYPE_OPAQUE_FD_EXT, ui->render_semaphores_export[1].value[0]); 5159 ui->render_semaphores_export[0].value[0] = OSInvalidHandleValue; 5160 ui->render_semaphores_export[1].value[0] = OSInvalidHandleValue; 5161 } 5162 5163 if (!BakeShaders) 5164 { 5165 for EachElement(beamformer_reloadable_render_shader_info_indices, it) { 5166 i32 index = beamformer_reloadable_render_shader_info_indices[it]; 5167 for (u32 i = 0; i < 2; i++) { 5168 BeamformerFileReloadContext *frc = push_struct(ui->arena, typeof(*frc)); 5169 frc->kind = BeamformerFileReloadKind_RenderShader; 5170 frc->shader_reload.shader = beamformer_reloadable_shader_kinds[index]; 5171 frc->shader_reload.pipeline = ui->pipelines + it; 5172 5173 Temp scratch = temp_begin(ui->arena); 5174 str8 file = push_str8_from_parts(ui->arena, os_path_separator(), str8("shaders"), 5175 beamformer_reloadable_shader_files[index][i]); 5176 5177 os_add_file_watch((char *)file.data, file.length, frc); 5178 temp_end(scratch); 5179 } 5180 } 5181 } 5182 5183 f32 unit_cube_vertices[] = { 5184 0.5f, 0.5f, -0.5f, 0.0f, 5185 0.5f, 0.5f, -0.5f, 0.0f, 5186 0.5f, 0.5f, -0.5f, 0.0f, 5187 0.5f, -0.5f, -0.5f, 0.0f, 5188 0.5f, -0.5f, -0.5f, 0.0f, 5189 0.5f, -0.5f, -0.5f, 0.0f, 5190 0.5f, 0.5f, 0.5f, 0.0f, 5191 0.5f, 0.5f, 0.5f, 0.0f, 5192 0.5f, 0.5f, 0.5f, 0.0f, 5193 0.5f, -0.5f, 0.5f, 0.0f, 5194 0.5f, -0.5f, 0.5f, 0.0f, 5195 0.5f, -0.5f, 0.5f, 0.0f, 5196 -0.5f, 0.5f, -0.5f, 0.0f, 5197 -0.5f, 0.5f, -0.5f, 0.0f, 5198 -0.5f, 0.5f, -0.5f, 0.0f, 5199 -0.5f, -0.5f, -0.5f, 0.0f, 5200 -0.5f, -0.5f, -0.5f, 0.0f, 5201 -0.5f, -0.5f, -0.5f, 0.0f, 5202 -0.5f, 0.5f, 0.5f, 0.0f, 5203 -0.5f, 0.5f, 0.5f, 0.0f, 5204 -0.5f, 0.5f, 0.5f, 0.0f, 5205 -0.5f, -0.5f, 0.5f, 0.0f, 5206 -0.5f, -0.5f, 0.5f, 0.0f, 5207 -0.5f, -0.5f, 0.5f, 0.0f, 5208 }; 5209 f32 unit_cube_normals[] = { 5210 0.0f, 0.0f, -1.0f, 0.0f, 5211 0.0f, 1.0f, 0.0f, 0.0f, 5212 1.0f, 0.0f, 0.0f, 0.0f, 5213 0.0f, 0.0f, -1.0f, 0.0f, 5214 0.0f, -1.0f, 0.0f, 0.0f, 5215 1.0f, 0.0f, 0.0f, 0.0f, 5216 0.0f, 0.0f, 1.0f, 0.0f, 5217 0.0f, 1.0f, 0.0f, 0.0f, 5218 1.0f, 0.0f, 0.0f, 0.0f, 5219 0.0f, 0.0f, 1.0f, 0.0f, 5220 0.0f, -1.0f, 0.0f, 0.0f, 5221 1.0f, 0.0f, 0.0f, 0.0f, 5222 0.0f, 0.0f, -1.0f, 0.0f, 5223 0.0f, 1.0f, 0.0f, 0.0f, 5224 -1.0f, 0.0f, 0.0f, 0.0f, 5225 0.0f, 0.0f, -1.0f, 0.0f, 5226 0.0f, -1.0f, 0.0f, 0.0f, 5227 -1.0f, 0.0f, 0.0f, 0.0f, 5228 0.0f, 0.0f, 1.0f, 0.0f, 5229 0.0f, 1.0f, 0.0f, 0.0f, 5230 -1.0f, 0.0f, 0.0f, 0.0f, 5231 0.0f, 0.0f, 1.0f, 0.0f, 5232 0.0f, -1.0f, 0.0f, 0.0f, 5233 -1.0f, 0.0f, 0.0f, 0.0f, 5234 }; 5235 u16 unit_cube_indices[] = { 5236 1, 13, 19, 5237 1, 19, 7, 5238 9, 6, 18, 5239 9, 18, 21, 5240 23, 20, 14, 5241 23, 14, 17, 5242 16, 4, 10, 5243 16, 10, 22, 5244 5, 2, 8, 5245 5, 8, 11, 5246 15, 12, 0, 5247 15, 0, 3 5248 }; 5249 5250 static_assert(countof(unit_cube_normals) == countof(unit_cube_vertices), ""); 5251 5252 RenderModel *rm = &ui->unit_cube_model; 5253 rm->vertex_count = countof(unit_cube_vertices) / 4; 5254 rm->normals_offset = round_up_to(sizeof(unit_cube_vertices), 16); 5255 5256 u64 model_size = 2 * round_up_to(sizeof(unit_cube_vertices), 16); 5257 vk_render_model_allocate(&rm->model, unit_cube_indices, countof(unit_cube_indices), model_size, str8("unit_cube_model")); 5258 vk_render_model_range_upload(&rm->model, unit_cube_vertices, 0, sizeof(unit_cube_vertices), 0); 5259 vk_render_model_range_upload(&rm->model, unit_cube_normals, rm->normals_offset, sizeof(unit_cube_normals), 0); 5260 } 5261 5262 for EachElement(beamformer_reloadable_render_shader_info_indices, it) { 5263 i32 index = beamformer_reloadable_render_shader_info_indices[it]; 5264 BeamformerShaderKind shader = beamformer_reloadable_shader_kinds[index]; 5265 beamformer_reload_render_pipeline(ui->pipelines + it, shader, ui->arena); 5266 } 5267 } 5268 5269 function void 5270 beamformer_ui_frame(void) 5271 { 5272 BeamformerUI *ui = ui_context = beamformer_context->ui; 5273 5274 { 5275 BeamformerFrame *frame = beamformer_frame_from_index(beamformer_registers()->frame); 5276 memory_copy(ui->latest_plane + frame->view_plane_tag, frame, sizeof(*frame)); 5277 } 5278 5279 BeamformerInput *input = beamformer_input; 5280 for EachIndex(input->event_count, it) { 5281 if (input->event_queue[it].kind == BeamformerInputEventKind_WindowResize) { 5282 // TODO(rnp): match window against window list 5283 beamformer_context->window_size.w = input->event_queue[it].window_resize.width; 5284 beamformer_context->window_size.h = input->event_queue[it].window_resize.height; 5285 } 5286 } 5287 5288 asan_poison_region(ui->arena.beg, ui->arena.end - ui->arena.beg); 5289 5290 u32 selected_block = ui->selected_parameter_block % BeamformerMaxParameterBlocks; 5291 u32 selected_mask = 1 << selected_block; 5292 if (beamformer_context->ui_dirty_parameter_blocks & selected_mask) { 5293 BeamformerParameterBlock *pb = beamformer_parameter_block_lock(beamformer_context->shared_memory, selected_block, 0); 5294 if (pb) { 5295 ui->flush_parameters = 0; 5296 5297 m4 das_transform; 5298 memory_copy(&ui->parameters, &pb->parameters_ui, sizeof(ui->parameters)); 5299 memory_copy(das_transform.E, pb->parameters.das_voxel_transform.E, sizeof(das_transform)); 5300 5301 atomic_and_u32(&beamformer_context->ui_dirty_parameter_blocks, ~selected_mask); 5302 beamformer_parameter_block_unlock(beamformer_context->shared_memory, selected_block); 5303 5304 BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[selected_block]; 5305 m4 identity = m4_identity(); 5306 b32 recompute = !m4_equal(identity, cp->ui_voxel_transform); 5307 memory_copy(cp->ui_voxel_transform.E, identity.E, sizeof(identity)); 5308 5309 if (recompute) { 5310 beamformed_shared_memory_dirty_flag(beamformer_context->shared_memory, selected_block, 5311 BeamformerParameterDirtyFlag_Parameters); 5312 beamformer_queue_compute(beamformer_context, 5313 beamformer_frame_from_index(beamformer_registers()->frame), 5314 selected_block); 5315 } 5316 5317 ui->off_axis_position = plane_offset_from_transform(das_transform); 5318 ui->beamform_plane = 0; 5319 } 5320 } 5321 5322 /* NOTE: process interactions first because the user interacted with 5323 * the ui that was presented last frame */ 5324 Rect window_rect = {.size = {{(f32)beamformer_context->window_size.w, (f32)beamformer_context->window_size.h}}}; 5325 5326 ui->last_mouse = ui->current_mouse; 5327 ui->current_mouse.x = input->mouse_x; 5328 ui->current_mouse.y = input->mouse_y; 5329 for EachElement(ui->input_consumed, it) 5330 ui->input_consumed[it] = 0; 5331 5332 if (ui->flush_parameters && beamformer_frame_valid(beamformer_registers()->frame)) { 5333 BeamformerParameterBlock *pb = beamformer_parameter_block_lock(beamformer_context->shared_memory, selected_block, 0); 5334 if (pb) { 5335 ui->flush_parameters = 0; 5336 memory_copy(&pb->parameters_ui, &ui->parameters, sizeof(ui->parameters)); 5337 beamformed_shared_memory_dirty_flag(beamformer_context->shared_memory, selected_block, 5338 BeamformerParameterDirtyFlag_Parameters); 5339 beamformer_parameter_block_unlock(beamformer_context->shared_memory, selected_block); 5340 beamformer_queue_compute(beamformer_context, 5341 beamformer_frame_from_index(beamformer_registers()->frame), 5342 selected_block); 5343 } 5344 } 5345 5346 /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */ 5347 update_frame_views(ui, window_rect); 5348 5349 //////////////////////////// 5350 // NOTE(rnp): Text Input 5351 { 5352 UITextInputState *tis = &ui->text_input_state; 5353 // NOTE(rnp): transition to new node 5354 tis->last_node_key = ui_node_key_zero(); 5355 tis->last_count = 0; 5356 if (tis->changed) { 5357 tis->changed = 0; 5358 ui_text_input_end(); 5359 if (!ui_node_key_nil(tis->next_node_key)) { 5360 tis->node_key = tis->next_node_key; 5361 tis->next_node_key = ui_node_key_zero(); 5362 if (point_in_rect(ui->current_mouse, ui_text_input_rect())) 5363 tis->cursor = tis->mark = ui_text_input_index_from_point(ui->last_mouse.x); 5364 tis->blinker.t = 1.0f; 5365 } 5366 } 5367 5368 if (!ui_node_key_nil(tis->node_key)) { 5369 beamformer_ui_blinker_update(&tis->blinker, BLINK_SPEED); 5370 5371 UISignal signal = ui_signal_from_node(ui_node_from_key(tis->node_key)); 5372 5373 if (signal.flags & UISignalFlag_LeftPressed) { 5374 if (point_in_rect(ui->current_mouse, ui_text_input_rect())) 5375 tis->cursor = tis->mark = ui_text_input_index_from_point(ui->last_mouse.x); 5376 tis->blinker.t = 1.0f; 5377 } 5378 5379 if (signal.flags & UISignalFlag_LeftDragging) 5380 tis->mark = ui_text_input_index_from_point(ui->last_mouse.x); 5381 5382 if (signal.flags & UISignalFlag_DoubleClicked) { 5383 // TODO(rnp): select word 5384 } 5385 5386 if (signal.flags & UISignalFlag_TripleClicked) { 5387 tis->cursor = 0; 5388 tis->mark = tis->count; 5389 } 5390 5391 if (ui_text_input_update(input)) 5392 ui_text_input_end(); 5393 } 5394 } 5395 5396 if (ui->context_menu_state_changed) { 5397 ui->context_menu_state_changed = 0; 5398 ui->context_menu_anchor_key = ui->context_menu_next_anchor_key; 5399 ui->context_menu_panel = ui->context_menu_next_panel; 5400 } 5401 5402 { 5403 //////////////////////////// 5404 // NOTE(rnp): Build Pass 5405 arena_clear(ui_build_arena()); 5406 // NOTE(rnp): reset last frame's build stacks 5407 { 5408 #define X(type, name, ...) \ 5409 ui->name##_node_stack.top = (type *)&ui_##name##_node_nil; \ 5410 ui->name##_node_stack.free = 0; \ 5411 ui->name##_node_stack.count = 0; 5412 UI_STACK_LIST 5413 #undef X 5414 5415 UIPrefWidth(ui_px(window_rect.size.x, 1.f)) 5416 UIPrefHeight(ui_px(window_rect.size.y, 1.f)) 5417 UIChildLayoutAxis(Axis2_Y) 5418 ui->root_node = ui_node_from_string(0, str8("UI Root Node")); 5419 ui_push_semantic_width(ui_pct(1.f, 0.5f)); 5420 ui_push_semantic_height(ui_pct(1.f, 0.5f)); 5421 } 5422 5423 ui->drag_root = 0; 5424 ui->drop_target_node = 0; 5425 ui->drag_overlay_root = 0; 5426 ui->drag_overlay_edges_root = 0; 5427 ui->drag_overlay_tab_root = 0; 5428 5429 beamformer_registers()->split_left_tree = 0; 5430 beamformer_registers()->split_right_tree = 0; 5431 beamformer_registers()->drop_child_index = 0; 5432 5433 // NOTE(rnp): check for active nodes 5434 { 5435 b32 active = 0; 5436 for EachEnumValue(UIMouseButtonKind, k) 5437 active |= !ui_node_key_equal(ui->active_node_key[k], ui_node_key_zero()); 5438 // NOTE(rnp): clear hot node if there are no active nodes 5439 if (!active) ui->hot_node_key = ui_node_key_zero(); 5440 } 5441 5442 // NOTE(rnp): context menu 5443 if (!ui_node_key_nil(ui->context_menu_anchor_key)) { 5444 // TODO(rnp): context_menu_open_t 5445 UIPrefWidth(ui_children_sum(1.f)) 5446 UIPrefHeight(ui_children_sum(1.f)) 5447 UIChildLayoutAxis(Axis2_Y) 5448 UIBGColour((v4){.a = 0.8f}) 5449 { 5450 // TODO(rnp): this should be tied to the window state 5451 ui->context_menu_root = ui_node_from_string(UINodeFlag_DrawBackground, str8("context_menu_root")); 5452 } 5453 5454 UIParent(ui->context_menu_root) UIAxisSize(Axis2_X, ui_px(0.f, 0.5f)) ui_padh(0.8f * UI_NODE_PAD); 5455 } 5456 5457 // NOTE(rnp): drag panel 5458 if (ui->drag_panel) { 5459 ui_build_drag_overlay(window_rect); 5460 5461 UIPrefWidth(ui_px(640.f, 1.f)) 5462 UIPrefHeight(ui_px(480.f, 1.f)) 5463 UIChildLayoutAxis(Axis2_Y) 5464 UIBGColour((v4){.a = 0.8f}) 5465 { 5466 ui->drag_root = ui_node_from_string(UINodeFlag_DrawBackground, str8("drag_panel_root")); 5467 } 5468 5469 UIParent(ui->drag_root) 5470 { 5471 UIChildLayoutAxis(Axis2_X) 5472 UIPrefHeight(ui_children_sum(1.f)) 5473 UIPrefWidth(ui_children_sum(1.f)) 5474 UIParent(ui_spacer(0)) 5475 { 5476 ui_padw(UI_NODE_PAD); 5477 ui_panel_label(ui->drag_panel); 5478 } 5479 } 5480 5481 ui_build_regions(ui->drag_root, ui->drag_panel); 5482 } 5483 5484 ui_build_regions(ui->root_node, ui->tree); 5485 5486 //////////////////////////// 5487 // NOTE(rnp): Prune Dead UI Nodes 5488 for EachElement(ui->node_hash_table, it) { 5489 UINodeHashBucket *hb = ui->node_hash_table + it; 5490 UINode *next = hb->first; 5491 for (UINode *b = next; !ui_node_is_nil(b); b = next) { 5492 next = b == b->hash_next ? 0 : b->hash_next; 5493 if (b->last_frame_active_index != ui->current_frame_index) { 5494 for EachEnumValue(UIMouseButtonKind, k) 5495 if (ui_node_key_equal(ui->active_node_key[k], b->key)) 5496 ui->active_node_key[k] = ui_node_key_zero(); 5497 5498 DLLRemove(&ui_node_nil, hb->first, hb->last, b, hash_next, hash_prev); 5499 SLLStackPush(ui->node_freelist, b, next_sibling); 5500 } 5501 } 5502 } 5503 5504 for (BeamformerInputEvent *event = ui_event_next(input, 0); 5505 event; 5506 event = ui_event_next(input, event)) 5507 { 5508 if (event->kind == BeamformerInputEventKind_ButtonPress) { 5509 if (event->button_id == BeamformerButtonID_Escape) 5510 beamformer_context->state = BeamformerState_ShouldClose; 5511 5512 if (!Between(event->button_id, BeamformerButtonID_ModifierFirst, BeamformerButtonID_ModifierLast)) { 5513 ui_context_menu_close(); 5514 ui->text_input_state.changed = 1; 5515 ui->text_input_state.next_node_key = ui_node_key_zero(); 5516 } 5517 } 5518 } 5519 5520 //////////////////////////// 5521 // NOTE(rnp): Layout Pass 5522 if (ui->drag_root) { 5523 ui->drag_root->computed_position[Axis2_X] = ui->last_mouse.x; 5524 ui->drag_root->computed_position[Axis2_Y] = ui->last_mouse.y; 5525 ui_layout_nodes(ui->drag_root); 5526 ui_layout_nodes(ui->drag_overlay_edges_root); 5527 if (ui->drag_overlay_tab_root) 5528 ui_layout_nodes(ui->drag_overlay_tab_root); 5529 ui_layout_nodes(ui->drag_overlay_root); 5530 } 5531 5532 ui_layout_nodes(ui->root_node); 5533 5534 b32 context_menu_ready = 0; 5535 if (!ui_node_key_nil(ui->context_menu_anchor_key)) { 5536 UIParent(ui->context_menu_root) UIAxisSize(Axis2_X, ui_px(0.f, 0.5f)) ui_padh(0.8f * UI_NODE_PAD); 5537 5538 UINode *anchor = ui_node_from_key(ui->context_menu_anchor_key); 5539 v2 anchor_p = ui_node_final_position(anchor); 5540 ui->context_menu_root->computed_position[Axis2_X] = anchor_p.x; 5541 ui->context_menu_root->computed_position[Axis2_Y] = anchor_p.y + anchor->computed_size[Axis2_Y]; 5542 Rect nr = ui_node_rect(ui->context_menu_root); 5543 if (nr.pos.y + nr.size.y > window_rect.size.y) 5544 ui->context_menu_root->computed_position[Axis2_Y] += (window_rect.size.y - (nr.pos.y + nr.size.y)); 5545 5546 ui_layout_nodes(ui->context_menu_root); 5547 5548 nr = ui_node_rect(ui->context_menu_root); 5549 context_menu_ready = (nr.pos.y + nr.size.y <= window_rect.size.y); 5550 } 5551 5552 BeginDrawing(); 5553 glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_COLOUR.E); 5554 glClearNamedFramebufferfv(0, GL_DEPTH, 0, (f32 []){1}); 5555 ui_draw_nodes(ui->root_node, window_rect); 5556 5557 if (!ui_node_key_nil(ui->context_menu_anchor_key) && context_menu_ready) 5558 ui_draw_nodes(ui->context_menu_root, window_rect); 5559 5560 if (ui->drag_root) { 5561 if (beamformer_registers()->split_left_tree || beamformer_registers()->split_right_tree) 5562 ui_draw_nodes(ui_build_drag_hover_node(), window_rect); 5563 ui_draw_nodes(ui->drag_overlay_root, window_rect); 5564 ui_draw_nodes(ui->drag_overlay_edges_root, window_rect); 5565 if (ui->drag_overlay_tab_root) 5566 ui_draw_nodes(ui->drag_overlay_tab_root, window_rect); 5567 ui_draw_nodes(ui->drag_root, window_rect); 5568 } 5569 5570 // TODO(rnp): hack: until raylib is removed this happens in ui since raylib will cause 5571 // glfw to call the input callbacks during EndDrawing() 5572 input->event_count = 0; 5573 EndDrawing(); 5574 5575 if (ui->drag_end) 5576 ui_drag_end(); 5577 5578 ui->current_frame_index++; 5579 } 5580 }