ogl_beamforming

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

ui.c (188473B)


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