ogl_beamforming

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

ui.c (188854B)


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