ogl_beamforming

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

ui.c (188816B)


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