ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

ui.c (186972B)


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