Commit: 05efc8efd5bac3faec0b00e33377007c1b8d296b
Parent: 9b2aa15a0e5a21afefe185cb6513a621a2493312
Author: Randy Palamar
Date: Tue, 14 Jul 2026 19:47:12 -0700
ui: fully immediate mode rewrite
Diffstat:
| M | beamformer.c | | | 5 | +++++ |
| M | beamformer.h | | | 140 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | beamformer.meta | | | 153 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
| M | beamformer_core.c | | | 279 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | beamformer_internal.h | | | 151 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | beamformer_parameters.h | | | 15 | --------------- |
| M | build.c | | | 1 | + |
| M | generated/beamformer.meta.c | | | 156 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
| M | intrinsics.c | | | 8 | ++++++++ |
| M | main_linux.c | | | 20 | ++++++++++++++++++++ |
| M | main_w32.c | | | 20 | ++++++++++++++++++++ |
| M | math.c | | | 94 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- |
| M | ui.c | | | 7577 | +++++++++++++++++++++++++++++++++++++++++++++---------------------------------- |
| M | util.c | | | 82 | ------------------------------------------------------------------------------- |
| M | util.h | | | 10 | +++++++--- |
| M | util_os_ui.c | | | 201 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- |
16 files changed, 5453 insertions(+), 3459 deletions(-)
diff --git a/beamformer.c b/beamformer.c
@@ -175,6 +175,11 @@ beamformer_init(BeamformerInput *input)
BeamformerCtx *ctx = push_struct(&memory, BeamformerCtx);
+ for EachElement(ctx->frame_arenas, it) {
+ ctx->frame_arenas[it] = sub_arena(&memory, KB(64), KB(4));
+ ctx->frame_arena_savepoints[it] = begin_temp_arena(ctx->frame_arenas + it);
+ }
+
str8 window_title = str8("VK Beamformer");
ctx->main_window = os_window_create(window_title.data, window_title.length, 1280, 840);
ctx->window_size = (iv2){{1280, 840}};
diff --git a/beamformer.h b/beamformer.h
@@ -109,6 +109,9 @@ BEAMFORMER_IMPORT OSWindow os_window_create(uint8_t *title, int64_t title_
//BEAMFORMER_IMPORT void os_window_title(OSWindow window, uint8_t *title, int64_t title_length);
//BEAMFORMER_IMPORT void os_window_destroy(OSWindow window);
+BEAMFORMER_IMPORT uint8_t * os_get_clipboard_text(int64_t *length);
+BEAMFORMER_IMPORT void os_set_clipboard_text(uint8_t *data, int64_t length);
+
// NOTE(rnp): eventually logging will just be done internally
BEAMFORMER_IMPORT void os_console_log(uint8_t *data, int64_t length);
BEAMFORMER_IMPORT void os_fatal(uint8_t *data, int64_t length);
@@ -138,11 +141,135 @@ typedef enum {
BeamformerInputEventKind_FileEvent,
} BeamformerInputEventKind;
-// TODO: not yet used
typedef enum {
+ BeamformerButtonID_Space = ' ',
+ BeamformerButtonID_Apostrophe = '\'',
+ BeamformerButtonID_Comma = ',',
+ BeamformerButtonID_Minus = '-',
+ BeamformerButtonID_Period = '.',
+ BeamformerButtonID_Slash = '/',
+ BeamformerButtonID_0 = '0',
+ BeamformerButtonID_1 = '1',
+ BeamformerButtonID_2 = '2',
+ BeamformerButtonID_3 = '3',
+ BeamformerButtonID_4 = '4',
+ BeamformerButtonID_5 = '5',
+ BeamformerButtonID_6 = '6',
+ BeamformerButtonID_7 = '7',
+ BeamformerButtonID_8 = '8',
+ BeamformerButtonID_9 = '9',
+ BeamformerButtonID_Semicolon = ';',
+ BeamformerButtonID_Equal = '=',
+ BeamformerButtonID_A = 'A',
+ BeamformerButtonID_B = 'B',
+ BeamformerButtonID_C = 'C',
+ BeamformerButtonID_D = 'D',
+ BeamformerButtonID_E = 'E',
+ BeamformerButtonID_F = 'F',
+ BeamformerButtonID_G = 'G',
+ BeamformerButtonID_H = 'H',
+ BeamformerButtonID_I = 'I',
+ BeamformerButtonID_J = 'J',
+ BeamformerButtonID_K = 'K',
+ BeamformerButtonID_L = 'L',
+ BeamformerButtonID_M = 'M',
+ BeamformerButtonID_N = 'N',
+ BeamformerButtonID_O = 'O',
+ BeamformerButtonID_P = 'P',
+ BeamformerButtonID_Q = 'Q',
+ BeamformerButtonID_R = 'R',
+ BeamformerButtonID_S = 'S',
+ BeamformerButtonID_T = 'T',
+ BeamformerButtonID_U = 'U',
+ BeamformerButtonID_V = 'V',
+ BeamformerButtonID_W = 'W',
+ BeamformerButtonID_X = 'X',
+ BeamformerButtonID_Y = 'Y',
+ BeamformerButtonID_Z = 'Z',
+ BeamformerButtonID_LeftBracket = '[',
+ BeamformerButtonID_Backslash = '\\',
+ BeamformerButtonID_RightBracket = ']',
+ BeamformerButtonID_Grave = '`',
+
+ BeamformerButtonID_Escape,
+ BeamformerButtonID_Enter,
+ BeamformerButtonID_Tab,
+ BeamformerButtonID_Backspace,
+ BeamformerButtonID_Insert,
+ BeamformerButtonID_Delete,
+ BeamformerButtonID_Right,
+ BeamformerButtonID_Left,
+ BeamformerButtonID_Down,
+ BeamformerButtonID_Up,
+ BeamformerButtonID_PageUp,
+ BeamformerButtonID_PageDown,
+ BeamformerButtonID_Home,
+ BeamformerButtonID_End,
+ BeamformerButtonID_CapsLock,
+ BeamformerButtonID_ScrollLock,
+ BeamformerButtonID_NumLock,
+ BeamformerButtonID_PrintScreen,
+ BeamformerButtonID_Pause,
+ BeamformerButtonID_F1,
+ BeamformerButtonID_F2,
+ BeamformerButtonID_F3,
+ BeamformerButtonID_F4,
+ BeamformerButtonID_F5,
+ BeamformerButtonID_F6,
+ BeamformerButtonID_F7,
+ BeamformerButtonID_F8,
+ BeamformerButtonID_F9,
+ BeamformerButtonID_F10,
+ BeamformerButtonID_F11,
+ BeamformerButtonID_F12,
+ BeamformerButtonID_F13,
+ BeamformerButtonID_F14,
+ BeamformerButtonID_F15,
+ BeamformerButtonID_F16,
+ BeamformerButtonID_F17,
+ BeamformerButtonID_F18,
+ BeamformerButtonID_F19,
+ BeamformerButtonID_F20,
+ BeamformerButtonID_F21,
+ BeamformerButtonID_F22,
+ BeamformerButtonID_F23,
+ BeamformerButtonID_F24,
+ BeamformerButtonID_F25,
+ BeamformerButtonID_KP0,
+ BeamformerButtonID_KP1,
+ BeamformerButtonID_KP2,
+ BeamformerButtonID_KP3,
+ BeamformerButtonID_KP4,
+ BeamformerButtonID_KP5,
+ BeamformerButtonID_KP6,
+ BeamformerButtonID_KP7,
+ BeamformerButtonID_KP8,
+ BeamformerButtonID_KP9,
+ BeamformerButtonID_KPDecimal,
+ BeamformerButtonID_KPDivide,
+ BeamformerButtonID_KPMultiply,
+ BeamformerButtonID_KPSubtract,
+ BeamformerButtonID_KPAdd,
+ BeamformerButtonID_KPEnter,
+ BeamformerButtonID_KPEqual,
+
+ BeamformerButtonID_LeftShift,
+ BeamformerButtonID_LeftControl,
+ BeamformerButtonID_LeftAlt,
+ BeamformerButtonID_LeftSuper,
+ BeamformerButtonID_RightShift,
+ BeamformerButtonID_RightControl,
+ BeamformerButtonID_RightAlt,
+ BeamformerButtonID_RightSuper,
+ BeamformerButtonID_ModifierFirst = BeamformerButtonID_LeftShift,
+ BeamformerButtonID_ModifierLast = BeamformerButtonID_RightSuper,
+
+ BeamformerButtonID_Menu,
+
BeamformerButtonID_MouseLeft,
BeamformerButtonID_MouseRight,
BeamformerButtonID_MouseMiddle,
+
BeamformerButtonID_Count,
} BeamformerButtonID;
@@ -171,8 +298,14 @@ typedef enum {
typedef struct {
BeamformerInputEventKind kind;
+ BeamformerInputModifiers modifiers;
union {
- BeamformerButtonID button_id;
+ struct {
+ BeamformerButtonID button_id;
+ // NOTE(rnp): if the button is not also an input key codepoint should be 0
+ uint32_t codepoint;
+ };
+ struct {float x, y;} scroll;
struct {
uint32_t width, height;
@@ -206,8 +339,7 @@ typedef struct {
uint32_t event_count;
- BeamformerInputModifiers input_modifiers;
- BeamformerInputEvent event_queue[256];
+ BeamformerInputEvent event_queue[256];
/* NOTE(rnp): the beamformer is not allowed to dynamically load libraries
* itself. Besides Vulkan, which is required, libraries are optional and
diff --git a/beamformer.meta b/beamformer.meta
@@ -68,6 +68,32 @@
Cubic
}
+@Enumeration ViewPlaneTag
+{
+ XZ
+ YZ
+ XY
+ Arbitrary
+}
+
+@Table([name pretty_name fixed_transmits]) AcquisitionKindTable
+{
+ [FORCES FORCES 1]
+ [UFORCES UFORCES 0]
+ [HERCULES HERCULES 1]
+ [RCA_VLS VLS 0]
+ [RCA_TPW TPW 0]
+ [UHERCULES UHERCULES 0]
+ [RACES RACES 1]
+ [EPIC_FORCES EPIC-FORCES 1]
+ [EPIC_UFORCES EPIC-UFORCES 0]
+ [EPIC_UHERCULES EPIC-UHERCULES 0]
+ [Flash Flash 0]
+ [HERO_PA HERO-PA 0]
+ [ULM ULM 0]
+}
+@Expand(AcquisitionKindTable) @Enumeration(`$(name)`) AcquisitionKind
+
@Struct SineParameters
{
[cycles F32]
@@ -142,24 +168,6 @@
@Library @Struct FilterParameters
@MATLAB(Filter) @Struct FilterParameters
-@Table([name pretty_name fixed_transmits]) AcquisitionKindTable
-{
- [FORCES FORCES 1]
- [UFORCES UFORCES 0]
- [HERCULES HERCULES 1]
- [RCA_VLS VLS 0]
- [RCA_TPW TPW 0]
- [UHERCULES UHERCULES 0]
- [RACES RACES 1]
- [EPIC_FORCES EPIC-FORCES 1]
- [EPIC_UFORCES EPIC-UFORCES 0]
- [EPIC_UHERCULES EPIC-UHERCULES 0]
- [Flash Flash 0]
- [HERO_PA HERO-PA 0]
- [ULM ULM 0]
-}
-@Expand(AcquisitionKindTable) @Enumeration(`$(name)`) AcquisitionKind
-
@Struct ParametersHead
{
[das_voxel_transform M4 ]
@@ -264,12 +272,16 @@
@Expand(ContrastModeTable) ` str8_comp("$(name)"),`
`};`
``
+ `read_only global str8 beamformer_view_plane_tag_strings[] = {`
+ @Expand(ViewPlaneTag) ` str8_comp("$(name)"),`
+ `};`
+ ``
`read_only global u8 beamformer_acquisition_kind_has_fixed_transmits[] = {`
@Expand(AcquisitionKindTable) ` $(fixed_transmits),`
`};`
``
- `read_only global s8 beamformer_acquisition_kind_strings[] = {`
- @Expand(AcquisitionKindTable) ` s8_comp("$(pretty_name)"),`
+ `read_only global str8 beamformer_acquisition_kind_strings[] = {`
+ @Expand(AcquisitionKindTable) ` str8_comp("$(pretty_name)"),`
`};`
``
`read_only global s8 beamformer_filter_kind_strings[] = {`
@@ -283,10 +295,6 @@
`read_only global s8 beamformer_shader_resource_kind_strings[] = {`
@Expand(ShaderResourceKind) ` s8_comp("$(name)"),`
`};`
- ``
- `read_only global s8 game_shader_buffer_slot_strings[] = {`
- @Expand(ShaderBufferSlot) ` s8_comp("$(name)"),`
- `};`
}
@ShaderGroup Compute
@@ -490,3 +498,100 @@
}
}
}
+
+////////////////////
+// NOTE: Core Data
+
+@Flags PanelFlags
+{
+ List
+ NeedsFrame
+ HasSettings
+}
+
+@Struct PanelInfo
+{
+ [display STR]
+ [string STR]
+ [description STR]
+ [flags PanelFlags]
+}
+
+@Table([name display string list needs_frame has_settings description]) PanelTable
+{
+ [Nil `` nil 0 0 0 ``]
+ [Split `` split 0 0 0 ``]
+ [TabGroup `` group 0 0 0 ``]
+
+ [ComputeBarGraph `Compute Bar Graph` compute_bar_graph 1 0 0 `Bar graph showing portions of compute occupied by each stage.`]
+ [ComputeStats `Compute Stats` compute_stats 1 0 0 `Average stats about beamforming computations.`]
+ [FrameViewLive `Frame View` frame_view_live 1 0 1 `Latest frame with selected tag.`]
+ [FrameViewCopy `Frame View (Copy)` frame_view_copy 0 1 1 `Copy of an old frame. Useful for comparisons.`]
+ [FrameViewXPlane `3D X-Plane` frame_view_xplane 1 0 1 `3D Cross Plane View.`]
+ [LiveImagingControls `Live Controls` live_controls 1 0 0 `Imaging system controls.`]
+ [ParameterListing `Parameter Listing` parameters_listing 1 0 1 `Editable parameter set used for beamforming.`]
+}
+@Expand(PanelTable) @Enumeration(`$(name)`) PanelKind
+@Emit
+{
+ `read_only global BeamformerPanelInfo beamformer_panel_infos[] = {`
+ @Expand(PanelTable) ` {str8_comp("$(display)"), str8_comp("$(string)"), str8_comp("$(description)"), ($(list)*BeamformerPanelFlags_List)|($(needs_frame)*BeamformerPanelFlags_NeedsFrame)|($(has_settings)*BeamformerPanelFlags_HasSettings)},`
+ `};`
+}
+
+@Table ([type name upper]) RegistersTable
+{
+ [STR string String ]
+ [U64 tree_node TreeNode ]
+ [U64 window Window ]
+ [U64 frame Frame ]
+
+ [U64 frame_view FrameView ]
+
+ // NOTE(rnp): Panel Movement and Splitting
+ [U64 split_axis SplitAxis ]
+ [U64 split_left_tree SplitLeftTree ]
+ [U64 split_right_tree SplitRightTree]
+
+ [U64 drop_target_tree DropTargetTree]
+ [U64 drop_child_index DropChildIndex]
+}
+@Expand(RegistersTable) @Struct([`$(name)` `$(type)`]) Registers
+@Expand(RegistersTable) @Enumeration(`$(upper)`) RegisterSlot
+
+@Emit
+{
+ `#define beamformer_registers_init_literal \`
+ @Expand(RegistersTable) ` .$(name)$(|)= beamformer_registers()->$(name), \`
+}
+
+@Struct CommandInfo
+{
+ [string STR]
+ [display STR]
+ [description STR]
+}
+
+@Table([kind string display_string description]) CommandTable
+{
+ // NOTE(rnp): tab commands
+ [CloseTab `close_tab` `Close Tab` `Closes currently active tab.`]
+ [FocusTab `focus_tab` `Focus Tab` `Focus the currently selected tab in its group.`]
+ [MoveTab `move_tab` `Move Tab` `Moves an exisiting tab to a different group/split.`]
+ [OpenTab `open_tab` `Open Tab` `Opens a new tab with a specified view.`]
+ [SplitTree `split_tree` `Split Tree` `Splits the UI into two new splits.`]
+}
+
+@Enumeration CommandKind
+{
+ Nil
+ @Expand(CommandTable) `$(kind)`
+}
+
+@Emit
+{
+ `read_only global BeamformerCommandInfo beamformer_command_infos[] = {`
+ ` {0},`
+ @Expand(CommandTable) ` {str8_comp("$(string)"), str8_comp("$(display_string)"), str8_comp("$(description)")},`
+ `};`
+}
diff --git a/beamformer_core.c b/beamformer_core.c
@@ -1,5 +1,6 @@
/* See LICENSE for license details. */
/* TODO(rnp):
+ * [ ]: bug? beamforming non XZ tag from non-0 parameter slot doesn't work?
* [ ]: backtrace dumping on SIGSEGV
* [ ]: bug? HERCULES might be broken, we may need to to chunk on transmits instead of channels
* [ ]: refactor: do_compute should build its own "command graph" which tracks
@@ -24,8 +25,6 @@
#include "beamformer_internal.h"
-global f32 dt_for_frame;
-
typedef struct BeamformerComputeGraphNode BeamformerComputeGraphNode;
struct BeamformerComputeGraphNode {
// NOTE(rnp): will be BeamformerShaderKind_Count for root node
@@ -65,6 +64,102 @@ read_only global u32 beamformer_compute_array_parameter_offsets[] = {
#undef X
};
+read_only global BeamformerFrame beamformer_nil_frame;
+read_only global BeamformerComputePlan beamformer_nil_compute_plan;
+
+global BeamformerCtx *beamformer_context;
+global BeamformerInput *beamformer_input;
+global f32 dt_for_frame;
+
+#define beamformer_frame_arena() (beamformer_context->frame_arenas + beamformer_context->frame_index % countof(beamformer_context->frame_arenas))
+#define beamformer_registers() (&beamformer_context->registers->v)
+#define beamformer_push_registers(...) beamformer_push_registers_(&(BeamformerRegisters){beamformer_registers_init_literal __VA_ARGS__})
+#define BeamformerRegistersScope(...) DeferLoop(beamformer_push_registers(__VA_ARGS__), beamformer_pop_registers())
+#define beamformer_command(name, ...) beamformer_push_command(name, &(BeamformerRegisters){beamformer_registers_init_literal __VA_ARGS__})
+
+function BeamformerRegisters *
+beamformer_pop_registers(void)
+{
+ BeamformerRegisters *result = &beamformer_context->registers->v;
+ SLLStackPop(beamformer_context->registers, next);
+ if (beamformer_context->registers == 0)
+ beamformer_context->registers = &beamformer_context->base_registers;
+ return result;
+}
+
+function BeamformerRegisters *
+beamformer_push_registers_(BeamformerRegisters *registers)
+{
+ BeamformerRegistersNode *node = push_struct(beamformer_frame_arena(), BeamformerRegistersNode);
+ BeamformerRegisters *result = &node->v;
+ memory_copy(result, registers, sizeof(node->v));
+ SLLStackPush(beamformer_context->registers, node, next);
+ return result;
+}
+
+function void
+beamformer_command_list_push_new(Arena *arena, BeamformerCommandList *commands, str8 name, BeamformerRegisters *registers)
+{
+ BeamformerCommandNode *node = push_struct(arena, BeamformerCommandNode);
+ node->command.registers = push_struct_no_zero(arena, BeamformerRegisters);
+ node->command.name = push_str8(arena, name);
+ memory_copy(node->command.registers, registers, sizeof(*registers));
+ DLLInsertLast(0, commands->first, commands->last, node, next, prev);
+ commands->count += 1;
+}
+
+function void
+beamformer_push_command(str8 name, BeamformerRegisters *registers)
+{
+ beamformer_command_list_push_new(beamformer_frame_arena(), beamformer_context->command_queues + 0,
+ name, registers);
+}
+
+function BeamformerCommandKind
+beamformer_command_kind_from_string(str8 s)
+{
+ BeamformerCommandKind result = BeamformerCommandKind_Nil;
+ for EachElement(beamformer_command_infos, it) {
+ if (str8_equal(beamformer_command_infos[it].string, s)) {
+ result = (BeamformerCommandKind)it;
+ break;
+ }
+ }
+ return result;
+}
+
+function BeamformerPanelKind
+beamformer_panel_kind_from_string(str8 s)
+{
+ BeamformerPanelKind result = BeamformerPanelKind_Nil;
+ for EachElement(beamformer_panel_infos, it) {
+ if (str8_equal(beamformer_panel_infos[it].string, s)) {
+ result = (BeamformerPanelKind)it;
+ break;
+ }
+ }
+ return result;
+}
+
+function BeamformerFrame *
+beamformer_frame_from_index(u64 index)
+{
+ BeamformerFrame *result = &beamformer_nil_frame;
+ if (index < countof(beamformer_context->compute_context.backlog.frames)) {
+ BeamformerFrame *frame = beamformer_context->compute_context.backlog.frames + index;
+ if (frame->timeline_valid_value != 0)
+ result = frame;
+ }
+ return result;
+}
+
+function b32
+beamformer_frame_valid(u64 index)
+{
+ b32 result = beamformer_frame_from_index(index) != &beamformer_nil_frame;
+ return result;
+}
+
function void
beamformer_compute_plan_release(BeamformerComputeContext *cc, u32 block)
{
@@ -641,9 +736,11 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb, A
cp->voxel_transform = m4_mul(cp->ui_voxel_transform, pb->parameters.das_voxel_transform);
cp->xdc_element_pitch = pb->parameters.xdc_element_pitch;
+ memory_copy(cp->das_voxel_transform.E, cp->voxel_transform.E, sizeof(cp->voxel_transform));
+
u32 id = pb->parameters.acquisition_kind;
if (id == BeamformerAcquisitionKind_UFORCES || id == BeamformerAcquisitionKind_FORCES)
- cp->voxel_transform = m4_mul(cp->xdc_transform, cp->voxel_transform);
+ cp->das_voxel_transform = m4_mul(cp->xdc_transform, cp->das_voxel_transform);
db->sparse = id == BeamformerAcquisitionKind_UFORCES || id == BeamformerAcquisitionKind_UHERCULES;
db->single_focus = pb->parameters.single_focus;
@@ -1124,8 +1221,8 @@ do_compute_shader(BeamformerCtx *ctx, VulkanHandle cmd, BeamformerComputePlan *c
.channel_offset = channel_offset,
.array_parameters = cp->array_parameters.gpu_pointer + offsetof(BeamformerComputeArrayParameters, FocalVectors),
};
- mem_copy(pc.voxel_transform.E, cp->voxel_transform.E, sizeof(pc.voxel_transform));
- mem_copy(pc.xdc_transform.E, cp->xdc_transform.E, sizeof(pc.xdc_transform));
+ memory_copy(pc.voxel_transform.E, cp->das_voxel_transform.E, sizeof(pc.voxel_transform));
+ memory_copy(pc.xdc_transform.E, cp->xdc_transform.E, sizeof(pc.xdc_transform));
b32 coherent = cp->shader_descriptors[shader_slot].bake.DAS.coherency_weighting;
@@ -1395,6 +1492,7 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena *arena)
frame->acquisition_kind = cp->acquisition_kind;
frame->contrast_mode = cp->contrast_mode;
frame->compound_count = cp->acquisition_count;
+ frame->parameter_block = work->compute_context.parameter_block;
frame->view_plane_tag = work->compute_context.view_plane;
mem_copy(frame->voxel_transform.E, cp->voxel_transform.E, sizeof(cp->voxel_transform));
@@ -1706,28 +1804,191 @@ beamformer_process_input_events(BeamformerCtx *ctx, BeamformerInput *input,
}
}
+function void
+beamformer_panel_group_insert_at(BeamformerUIPanel *group, BeamformerUIPanel *tab, u64 new_child_index)
+{
+ if (tab->parent) beamformer_ui_panel_unlink(tab);
+ new_child_index = Min(new_child_index, group->child_count);
+
+ tab->parent = group;
+ group->child_count++;
+ if (group->kind == BeamformerPanelKind_TabGroup) group->u.tab_focus = tab;
+
+ BeamformerUIPanel *previous_sibling = new_child_index == 0 ? 0 : group->first_child;
+ for (u64 child_index = 1; child_index < new_child_index; child_index++)
+ previous_sibling = previous_sibling->next_sibling;
+
+ if (previous_sibling) {
+ tab->previous_sibling = previous_sibling;
+ tab->next_sibling = previous_sibling->next_sibling;
+ if (tab->next_sibling) tab->next_sibling->previous_sibling = tab;
+ previous_sibling->next_sibling = tab;
+ if (previous_sibling == group->last_child) group->last_child = tab;
+ } else {
+ DLLInsertFirst(0, group->first_child, group->last_child, tab, next_sibling, previous_sibling);
+ }
+}
+
BEAMFORMER_EXPORT void
beamformer_frame_step(BeamformerInput *input)
{
- BeamformerCtx *ctx = BeamformerContextMemory(input->memory);
+ BeamformerCtx *ctx = beamformer_context = BeamformerContextMemory(input->memory);
+ beamformer_input = input;
u64 current_time = os_timer_count();
dt_for_frame = (f64)(current_time - ctx->frame_timestamp) / os_system_info()->timer_frequency;
ctx->frame_timestamp = current_time;
+ ctx->frame_index++;
coalesce_timing_table(ctx->compute_timing_table, ctx->compute_shader_stats);
+ // NOTE(rnp): reset frame state
+ {
+ ctx->registers = &ctx->base_registers;
+ swap(ctx->command_queues[0], ctx->command_queues[1]);
+ zero_struct(ctx->command_queues + 0);
+ //zero_struct(ctx->registers);
+ end_temp_arena(ctx->frame_arena_savepoints[ctx->frame_index % countof(ctx->frame_arenas)]);
+ }
+
beamformer_process_input_events(ctx, input, input->event_queue, input->event_count);
BeamformerSharedMemory *sm = ctx->shared_memory;
+ u32 live_imaging_active = atomic_load_u32(&sm->live_imaging_parameters.active);
+ if (live_imaging_active != ctx->live_imaging_active) {
+ if (ctx->live_imaging_active) {
+ BeamformerUIPanel *parent = ctx->auto_live_control_panel->parent;
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)ctx->auto_live_control_panel);
+ if (parent->child_count == 1)
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)parent);
+ ctx->auto_live_control_panel = 0;
+ } else {
+ ctx->live_imaging_active_frame = ctx->frame_index;
+ ctx->auto_live_control_panel = beamformer_ui_push_panel(0, BeamformerPanelKind_LiveImagingControls);
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_SplitTree].string,
+ .tree_node = (u64)ctx->auto_live_control_panel,
+ .split_axis = Axis2_X,
+ .split_left_tree = (u64)ui_context->tree,
+ .split_right_tree = 0,
+ .drop_target_tree = (u64)ui_context->tree);
+ }
+ ctx->live_imaging_active = live_imaging_active;
+ }
+
if (atomic_load_u32(sm->locks + BeamformerSharedMemoryLockKind_UploadRF))
os_wake_all_waiters(&ctx->upload_worker.sync_variable);
if (atomic_load_u32(sm->locks + BeamformerSharedMemoryLockKind_DispatchCompute))
os_wake_all_waiters(&ctx->compute_worker.sync_variable);
- BeamformerFrame *frame = ctx->latest_frame;
- BeamformerViewPlaneTag tag = frame? frame->view_plane_tag : 0;
- draw_ui(ctx, input, frame, tag);
+ beamformer_registers()->frame = (u64)(ctx->latest_frame - ctx->compute_context.backlog.frames);
+
+ beamformer_ui_frame();
+
+ // NOTE(rnp): execute commands
+ for (BeamformerCommandNode *node = ctx->command_queues[0].first;
+ node;
+ node = node == node->next ? 0 : node->next)
+ {
+ BeamformerRegistersScope()
+ {
+ memory_copy(beamformer_registers(), node->command.registers, sizeof(*node->command.registers));
+ BeamformerCommandKind kind = beamformer_command_kind_from_string(node->command.name);
+ switch (kind) {
+ InvalidDefaultCase;
+ case BeamformerCommandKind_CloseTab:{
+ BeamformerUIPanel *tab = (BeamformerUIPanel *)beamformer_registers()->tree_node;
+ ui_kill_panel(tab);
+ }break;
+
+ case BeamformerCommandKind_FocusTab:{
+ BeamformerUIPanel *tab = (BeamformerUIPanel *)beamformer_registers()->tree_node;
+ assert(tab->parent->kind == BeamformerPanelKind_TabGroup);
+ tab->parent->u.tab_focus = tab;
+ }break;
+
+ case BeamformerCommandKind_MoveTab:{
+ BeamformerUIPanel *move = (BeamformerUIPanel *)beamformer_registers()->tree_node;
+ BeamformerUIPanel *group = (BeamformerUIPanel *)beamformer_registers()->drop_target_tree;
+ u64 new_child_index = beamformer_registers()->drop_child_index;
+ beamformer_panel_group_insert_at(group, move, new_child_index);
+ }break;
+
+ case BeamformerCommandKind_OpenTab:{
+ BeamformerUIPanel *panel = (BeamformerUIPanel *)beamformer_registers()->tree_node;
+ assert(panel->kind == BeamformerPanelKind_TabGroup);
+
+ BeamformerPanelKind new_panel_kind = beamformer_panel_kind_from_string(beamformer_registers()->string);
+ beamformer_ui_push_panel(panel, new_panel_kind);
+ }break;
+
+ case BeamformerCommandKind_SplitTree:{
+ BeamformerUIPanel *drag = (BeamformerUIPanel *)beamformer_registers()->tree_node;
+ BeamformerUIPanel *left = (BeamformerUIPanel *)beamformer_registers()->split_left_tree;
+ BeamformerUIPanel *right = (BeamformerUIPanel *)beamformer_registers()->split_right_tree;
+ Axis2 axis = beamformer_registers()->split_axis;
+
+ BeamformerUIPanel *new_split = beamformer_ui_push_panel(0, BeamformerPanelKind_Split);
+ BeamformerUIPanel *new_tab_group = beamformer_ui_push_panel(0, BeamformerPanelKind_TabGroup);
+ beamformer_panel_group_insert_at(new_tab_group, drag, 0);
+
+ BeamformerUIPanel *target = 0;
+ u32 target_child_index = 0;
+ f32 new_split_pct = 0.5f;
+
+ if (left == 0 || right == 0) {
+ // NOTE(rnp): split on edge of window
+ target = left ? left : right;
+ target_child_index = left ? 0 : 1;
+
+ if (target->kind == BeamformerPanelKind_TabGroup) {
+ new_split->kind = BeamformerPanelKind_TabGroup;
+ new_split->u.tab_focus = target->u.tab_focus;
+ }
+
+ for (BeamformerUIPanel *child = target->last_child, *next; child; child = next) {
+ next = child->previous_sibling;
+ beamformer_panel_group_insert_at(new_split, child, 0);
+ }
+
+ beamformer_panel_group_insert_at(target, new_tab_group, 0);
+ } else if (((drag == left) && right->kind == BeamformerPanelKind_Split) ||
+ ((drag == right) && left->kind == BeamformerPanelKind_Split))
+ {
+ // NOTE(rnp): split on internal split
+ target = left == drag ? right : left;
+ target_child_index = 1;
+ new_split_pct = 1.f / 3.f;
+ beamformer_panel_group_insert_at(new_split, new_tab_group, 0);
+ beamformer_panel_group_insert_at(new_split, target->last_child, 1);
+ } else {
+ // NOTE(rnp): TabGroup Split
+ target = left == drag ? right : left;
+ target_child_index = left == drag ? 1 : 0;
+ assert(target->kind == BeamformerPanelKind_TabGroup);
+
+ new_split->kind = BeamformerPanelKind_TabGroup;
+ new_split->u.tab_focus = target->u.tab_focus;
+ for (BeamformerUIPanel *child = target->last_child, *next; child; child = next) {
+ next = child->previous_sibling;
+ beamformer_panel_group_insert_at(new_split, child, 0);
+ }
+
+ beamformer_panel_group_insert_at(target, new_tab_group, 0);
+ }
+
+ beamformer_panel_group_insert_at(target, new_split, target_child_index);
+ if (target->kind == BeamformerPanelKind_Split) {
+ new_split->u.split.axis = target->u.split.axis;
+ new_split->u.split.fraction = target->u.split.fraction;
+ }
+ target->kind = BeamformerPanelKind_Split;
+ target->u.split.axis = axis;
+ target->u.split.fraction = new_split_pct;
+ }break;
+
+ }
+ }
+ }
ctx->render_shader_updated = 0;
}
diff --git a/beamformer_internal.h b/beamformer_internal.h
@@ -308,8 +308,11 @@ struct BeamformerComputePlan {
i32 hadamard_order;
b32 iq_pipeline;
- m4 voxel_transform;
m4 ui_voxel_transform;
+ // NOTE(rnp): final voxel transform determining frame dimensions
+ m4 voxel_transform;
+ // NOTE(rnp): final voxel transform used for beamforming
+ m4 das_voxel_transform;
iv3 output_points;
i32 average_frames;
@@ -317,8 +320,6 @@ struct BeamformerComputePlan {
// TODO(rnp): specialization constants
v2 xdc_element_pitch;
m4 xdc_transform;
- // TODO(rnp): probably just compute this everytime
- m4 das_voxel_transform;
GPUBuffer array_parameters;
@@ -406,6 +407,7 @@ typedef struct {
u32 id;
u32 compound_count;
+ u32 parameter_block;
BeamformerDataKind data_kind;
BeamformerAcquisitionKind acquisition_kind;
BeamformerContrastMode contrast_mode;
@@ -456,6 +458,30 @@ typedef struct {
b32 awake;
} GLWorkerThreadContext;
+typedef struct {
+ str8 name;
+ BeamformerRegisters *registers;
+} BeamformerCommand;
+
+typedef struct BeamformerRegistersNode BeamformerRegistersNode;
+struct BeamformerRegistersNode {
+ BeamformerRegistersNode *next;
+ BeamformerRegisters v;
+};
+
+typedef struct BeamformerCommandNode BeamformerCommandNode;
+struct BeamformerCommandNode {
+ BeamformerCommandNode *next;
+ BeamformerCommandNode *prev;
+ BeamformerCommand command;
+};
+
+typedef struct {
+ BeamformerCommandNode *first;
+ BeamformerCommandNode *last;
+ u64 count;
+} BeamformerCommandList;
+
typedef enum {
BeamformerState_Uninitialized = 0,
BeamformerState_Running,
@@ -463,6 +489,111 @@ typedef enum {
BeamformerState_Terminated,
} BeamformerState;
+typedef enum {
+ RulerState_None,
+ RulerState_Start,
+ RulerState_Hold,
+ RulerState_Count,
+} RulerState;
+
+typedef struct {
+ v3 start;
+ v3 end;
+ RulerState state;
+} Ruler;
+
+typedef struct {
+ f32 t;
+ f32 scale;
+} BeamformerUIBlinker;
+
+#define BEAMFORMER_FRAME_VIEW_KIND_LIST \
+ X(Latest, "Latest") \
+ X(3DXPlane, "3D X-Plane") \
+ X(Copy, "Copy") \
+
+typedef enum {
+ #define X(kind, ...) BeamformerFrameViewKind_##kind,
+ BEAMFORMER_FRAME_VIEW_KIND_LIST
+ #undef X
+ BeamformerFrameViewKind_Count,
+} BeamformerFrameViewKind;
+
+typedef struct BeamformerFrameView BeamformerFrameView;
+struct BeamformerFrameView {
+ BeamformerFrameViewKind kind;
+ b32 dirty;
+
+ // NOTE(rnp): for FrameViewKindCopy
+ GPUBuffer copy_buffer;
+
+ GPUImage colour_image;
+ // NOTE(rnp): temporary, on w32 we must hold onto this when importing vulkan data to OpenGL
+ OSHandle export_handle;
+ u32 memory_object;
+ u32 texture;
+
+ b32 log_scale;
+ f32 threshold;
+ f32 dynamic_range;
+ f32 gamma;
+
+ union {
+ /* BeamformerFrameViewKind_Latest/BeamformerFrameViewKind_Copy */
+ struct {
+ b32 scale_bar_active[2];
+ Ruler ruler;
+ BeamformerViewPlaneTag view_plane;
+ BeamformerFrame frame;
+ };
+
+ /* BeamformerFrameViewKind_3DXPlane */
+ struct {
+ f32 hot_t[BeamformerViewPlaneTag_Count];
+ //b32 plane_active[BeamformerViewPlaneTag_Count];
+ b32 plane_active[BeamformerViewPlaneTag_Count - 2];
+ i32 plane_drag_index;
+ f32 rotation;
+ b32 demo;
+ v3 hit_start_point;
+ v3 hit_test_point;
+ };
+ };
+
+ BeamformerFrameView *prev, *next;
+};
+
+typedef struct BeamformerUIPanel BeamformerUIPanel;
+struct BeamformerUIPanel {
+ BeamformerUIPanel *parent;
+ BeamformerUIPanel *last_child;
+ BeamformerUIPanel *first_child;
+ BeamformerUIPanel *previous_sibling;
+ BeamformerUIPanel *next_sibling;
+
+ u64 child_count;
+
+ BeamformerPanelKind kind;
+ union {
+ BeamformerFrameView *frame_view;
+
+ BeamformerUIPanel *tab_focus;
+
+ BeamformerUIBlinker compute_stats_broken_shader_blinker;
+ BeamformerUIBlinker live_imaging_save_button_blinker;
+
+ struct {
+ u32 parameter_block;
+ b16 expand_coordinate[2];
+ } parameter_listing;
+
+ struct {
+ Axis2 axis;
+ f32 fraction;
+ } split;
+ } u;
+};
+
typedef struct {
BeamformerState state;
@@ -473,7 +604,19 @@ typedef struct {
void *ui;
u32 ui_dirty_parameter_blocks;
- u64 frame_timestamp;
+ u64 frame_timestamp;
+ u64 frame_index;
+ Arena frame_arenas[2];
+ TempArena frame_arena_savepoints[2];
+
+ BeamformerUIPanel *auto_live_control_panel;
+ u64 live_imaging_active_frame;
+ b32 live_imaging_active;
+
+ BeamformerRegistersNode base_registers;
+ BeamformerRegistersNode *registers;
+
+ BeamformerCommandList command_queues[2];
Stream error_stream;
diff --git a/beamformer_parameters.h b/beamformer_parameters.h
@@ -1,7 +1,6 @@
/* See LICENSE for license details. */
/* TODO(rnp):
- * [ ]: shader kinds have ballooned; shader stats table needs to be compressed
* [ ]: Upload previously exported data for display. maybe this is a UI thing but doing it
* programatically would be nice.
* [ ]: Add interface for multi frame upload. RF upload already uses an offset into SM so
@@ -18,20 +17,6 @@ typedef struct {
float rf_time_deltas[32];
} BeamformerComputeStatsTable;
-/* X(type, id, pretty name) */
-#define BEAMFORMER_VIEW_PLANE_TAG_LIST \
- X(XZ, 0, "XZ") \
- X(YZ, 1, "YZ") \
- X(XY, 2, "XY") \
- X(Arbitrary, 3, "Arbitrary")
-
-typedef enum {
- #define X(type, id, pretty) BeamformerViewPlaneTag_##type = id,
- BEAMFORMER_VIEW_PLANE_TAG_LIST
- #undef X
- BeamformerViewPlaneTag_Count,
-} BeamformerViewPlaneTag;
-
#define BEAMFORMER_LIVE_IMAGING_DIRTY_FLAG_LIST \
X(ImagePlaneOffsets, 0) \
X(TransmitPower, 1) \
diff --git a/build.c b/build.c
@@ -21,6 +21,7 @@
#include <stdio.h>
#define BeamformerMaxComputeShaderStages 1
+#define BeamformerViewPlaneTag_Count 1
#include "beamformer_parameters.h"
global char *g_argv0;
diff --git a/generated/beamformer.meta.c b/generated/beamformer.meta.c
@@ -74,10 +74,12 @@ typedef enum {
} BeamformerInterpolationMode;
typedef enum {
- BeamformerFilterKind_Kaiser = 0,
- BeamformerFilterKind_MatchedChirp = 1,
- BeamformerFilterKind_Count,
-} BeamformerFilterKind;
+ BeamformerViewPlaneTag_XZ = 0,
+ BeamformerViewPlaneTag_YZ = 1,
+ BeamformerViewPlaneTag_XY = 2,
+ BeamformerViewPlaneTag_Arbitrary = 3,
+ BeamformerViewPlaneTag_Count,
+} BeamformerViewPlaneTag;
typedef enum {
BeamformerAcquisitionKind_FORCES = 0,
@@ -97,6 +99,56 @@ typedef enum {
} BeamformerAcquisitionKind;
typedef enum {
+ BeamformerFilterKind_Kaiser = 0,
+ BeamformerFilterKind_MatchedChirp = 1,
+ BeamformerFilterKind_Count,
+} BeamformerFilterKind;
+
+typedef enum {
+ BeamformerPanelKind_Nil = 0,
+ BeamformerPanelKind_Split = 1,
+ BeamformerPanelKind_TabGroup = 2,
+ BeamformerPanelKind_ComputeBarGraph = 3,
+ BeamformerPanelKind_ComputeStats = 4,
+ BeamformerPanelKind_FrameViewLive = 5,
+ BeamformerPanelKind_FrameViewCopy = 6,
+ BeamformerPanelKind_FrameViewXPlane = 7,
+ BeamformerPanelKind_LiveImagingControls = 8,
+ BeamformerPanelKind_ParameterListing = 9,
+ BeamformerPanelKind_Count,
+} BeamformerPanelKind;
+
+typedef enum {
+ BeamformerRegisterSlot_String = 0,
+ BeamformerRegisterSlot_TreeNode = 1,
+ BeamformerRegisterSlot_Window = 2,
+ BeamformerRegisterSlot_Frame = 3,
+ BeamformerRegisterSlot_FrameView = 4,
+ BeamformerRegisterSlot_SplitAxis = 5,
+ BeamformerRegisterSlot_SplitLeftTree = 6,
+ BeamformerRegisterSlot_SplitRightTree = 7,
+ BeamformerRegisterSlot_DropTargetTree = 8,
+ BeamformerRegisterSlot_DropChildIndex = 9,
+ BeamformerRegisterSlot_Count,
+} BeamformerRegisterSlot;
+
+typedef enum {
+ BeamformerCommandKind_Nil = 0,
+ BeamformerCommandKind_CloseTab = 1,
+ BeamformerCommandKind_FocusTab = 2,
+ BeamformerCommandKind_MoveTab = 3,
+ BeamformerCommandKind_OpenTab = 4,
+ BeamformerCommandKind_SplitTree = 5,
+ BeamformerCommandKind_Count,
+} BeamformerCommandKind;
+
+typedef enum {
+ BeamformerPanelFlags_List = 1 << 0,
+ BeamformerPanelFlags_NeedsFrame = 1 << 1,
+ BeamformerPanelFlags_HasSettings = 1 << 2,
+} BeamformerPanelFlags;
+
+typedef enum {
BeamformerShaderKind_Decode = 0,
BeamformerShaderKind_Filter = 1,
BeamformerShaderKind_Demodulate = 2,
@@ -398,6 +450,32 @@ typedef struct {
u16 transmit_receive_orientations[BeamformerMaxChannelCount];
} BeamformerDASArrayParameters;
+typedef struct {
+ str8 display;
+ str8 string;
+ str8 description;
+ BeamformerPanelFlags flags;
+} BeamformerPanelInfo;
+
+typedef struct {
+ str8 string;
+ u64 tree_node;
+ u64 window;
+ u64 frame;
+ u64 frame_view;
+ u64 split_axis;
+ u64 split_left_tree;
+ u64 split_right_tree;
+ u64 drop_target_tree;
+ u64 drop_child_index;
+} BeamformerRegisters;
+
+typedef struct {
+ str8 string;
+ str8 display;
+ str8 description;
+} BeamformerCommandInfo;
+
typedef union {
BeamformerDecodeBakeParameters Decode;
BeamformerFilterBakeParameters Filter;
@@ -469,6 +547,13 @@ read_only global str8 beamformer_contrast_mode_strings[] = {
str8_comp("A1S2"),
};
+read_only global str8 beamformer_view_plane_tag_strings[] = {
+ str8_comp("XZ"),
+ str8_comp("YZ"),
+ str8_comp("XY"),
+ str8_comp("Arbitrary"),
+};
+
read_only global u8 beamformer_acquisition_kind_has_fixed_transmits[] = {
1,
0,
@@ -485,20 +570,20 @@ read_only global u8 beamformer_acquisition_kind_has_fixed_transmits[] = {
0,
};
-read_only global s8 beamformer_acquisition_kind_strings[] = {
- s8_comp("FORCES"),
- s8_comp("UFORCES"),
- s8_comp("HERCULES"),
- s8_comp("VLS"),
- s8_comp("TPW"),
- s8_comp("UHERCULES"),
- s8_comp("RACES"),
- s8_comp("EPIC-FORCES"),
- s8_comp("EPIC-UFORCES"),
- s8_comp("EPIC-UHERCULES"),
- s8_comp("Flash"),
- s8_comp("HERO-PA"),
- s8_comp("ULM"),
+read_only global str8 beamformer_acquisition_kind_strings[] = {
+ str8_comp("FORCES"),
+ str8_comp("UFORCES"),
+ str8_comp("HERCULES"),
+ str8_comp("VLS"),
+ str8_comp("TPW"),
+ str8_comp("UHERCULES"),
+ str8_comp("RACES"),
+ str8_comp("EPIC-FORCES"),
+ str8_comp("EPIC-UFORCES"),
+ str8_comp("EPIC-UHERCULES"),
+ str8_comp("Flash"),
+ str8_comp("HERO-PA"),
+ str8_comp("ULM"),
};
read_only global s8 beamformer_filter_kind_strings[] = {
@@ -516,9 +601,38 @@ read_only global s8 beamformer_shader_resource_kind_strings[] = {
s8_comp("Buffer"),
};
-read_only global s8 game_shader_buffer_slot_strings[] = {
- s8_comp("BeamformedData"),
- s8_comp("PingPong"),
+read_only global BeamformerPanelInfo beamformer_panel_infos[] = {
+ {str8_comp(""), str8_comp("nil"), str8_comp(""), (0*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(0*BeamformerPanelFlags_HasSettings)},
+ {str8_comp(""), str8_comp("split"), str8_comp(""), (0*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(0*BeamformerPanelFlags_HasSettings)},
+ {str8_comp(""), str8_comp("group"), str8_comp(""), (0*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(0*BeamformerPanelFlags_HasSettings)},
+ {str8_comp("Compute Bar Graph"), str8_comp("compute_bar_graph"), str8_comp("Bar graph showing portions of compute occupied by each stage."), (1*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(0*BeamformerPanelFlags_HasSettings)},
+ {str8_comp("Compute Stats"), str8_comp("compute_stats"), str8_comp("Average stats about beamforming computations."), (1*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(0*BeamformerPanelFlags_HasSettings)},
+ {str8_comp("Frame View"), str8_comp("frame_view_live"), str8_comp("Latest frame with selected tag."), (1*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(1*BeamformerPanelFlags_HasSettings)},
+ {str8_comp("Frame View (Copy)"), str8_comp("frame_view_copy"), str8_comp("Copy of an old frame. Useful for comparisons."), (0*BeamformerPanelFlags_List)|(1*BeamformerPanelFlags_NeedsFrame)|(1*BeamformerPanelFlags_HasSettings)},
+ {str8_comp("3D X-Plane"), str8_comp("frame_view_xplane"), str8_comp("3D Cross Plane View."), (1*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(1*BeamformerPanelFlags_HasSettings)},
+ {str8_comp("Live Controls"), str8_comp("live_controls"), str8_comp("Imaging system controls."), (1*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(0*BeamformerPanelFlags_HasSettings)},
+ {str8_comp("Parameter Listing"), str8_comp("parameters_listing"), str8_comp("Editable parameter set used for beamforming."), (1*BeamformerPanelFlags_List)|(0*BeamformerPanelFlags_NeedsFrame)|(1*BeamformerPanelFlags_HasSettings)},
+};
+
+#define beamformer_registers_init_literal \
+ .string = beamformer_registers()->string, \
+ .tree_node = beamformer_registers()->tree_node, \
+ .window = beamformer_registers()->window, \
+ .frame = beamformer_registers()->frame, \
+ .frame_view = beamformer_registers()->frame_view, \
+ .split_axis = beamformer_registers()->split_axis, \
+ .split_left_tree = beamformer_registers()->split_left_tree, \
+ .split_right_tree = beamformer_registers()->split_right_tree, \
+ .drop_target_tree = beamformer_registers()->drop_target_tree, \
+ .drop_child_index = beamformer_registers()->drop_child_index, \
+
+read_only global BeamformerCommandInfo beamformer_command_infos[] = {
+ {0},
+ {str8_comp("close_tab"), str8_comp("Close Tab"), str8_comp("Closes currently active tab.")},
+ {str8_comp("focus_tab"), str8_comp("Focus Tab"), str8_comp("Focus the currently selected tab in its group.")},
+ {str8_comp("move_tab"), str8_comp("Move Tab"), str8_comp("Moves an exisiting tab to a different group/split.")},
+ {str8_comp("open_tab"), str8_comp("Open Tab"), str8_comp("Opens a new tab with a specified view.")},
+ {str8_comp("split_tree"), str8_comp("Split Tree"), str8_comp("Splits the UI into two new splits.")},
};
read_only global s8 beamformer_shader_names[] = {
diff --git a/intrinsics.c b/intrinsics.c
@@ -220,3 +220,11 @@ typedef __m128i u32x4;
#define store_fence _mm_sfence
#endif
+
+function force_inline f32
+inf32(void)
+{
+ union {u32 u; f32 f;} result;
+ result.u = 0x7F800000u;
+ return result.f;
+}
diff --git a/main_linux.c b/main_linux.c
@@ -303,6 +303,26 @@ os_window_create(u8 *title, i64 title_length, i32 width, i32 height)
return result;
}
+BEAMFORMER_IMPORT u8 *
+os_get_clipboard_text(i64 *length)
+{
+ str8 result = str8_from_c_str((char *)GetClipboardText());
+ if (length) *length = result.length;
+ return result.data;
+}
+
+BEAMFORMER_IMPORT void
+os_set_clipboard_text(u8 *data, i64 length)
+{
+ DeferLoop(take_lock(&os_linux_context.arena_lock, -1), release_lock(&os_linux_context.arena_lock))
+ {
+ Arena scratch = os_linux_context.arena;
+ str8 string = {.data = data, .length = Min(length, arena_capacity(&scratch, u8) - 1)};
+ str8 safe_string = push_str8(&scratch, string);
+ SetClipboardText((char *)safe_string.data);
+ }
+}
+
function OSLibrary
load_library(char *name, char *temp_name, u32 flags)
{
diff --git a/main_w32.c b/main_w32.c
@@ -343,6 +343,26 @@ os_window_create(u8 *title, i64 title_length, i32 width, i32 height)
return result;
}
+BEAMFORMER_IMPORT u8 *
+os_get_clipboard_text(i64 *length)
+{
+ str8 result = str8_from_c_str((char *)GetClipboardText());
+ if (length) *length = result.length;
+ return result.data;
+}
+
+BEAMFORMER_IMPORT void
+os_set_clipboard_text(u8 *data, i64 length)
+{
+ DeferLoop(take_lock(&os_w32_context.arena_lock, -1), release_lock(&os_w32_context.arena_lock))
+ {
+ Arena scratch = os_w32_context.arena;
+ str8 string = {.data = data, .length = Min(length, arena_capacity(&scratch, u8) - 1)};
+ str8 safe_string = push_str8(&scratch, string);
+ SetClipboardText((char *)safe_string.data);
+ }
+}
+
#if BEAMFORMER_RENDERDOC_HOOKS
function OSLibrary
get_module(char *name)
diff --git a/math.c b/math.c
@@ -424,16 +424,13 @@ m4_equal(m4 a, m4 b)
return result;
}
-function m4
-m4_identity(void)
-{
- m4 result;
- result.c[0] = (v4){{1, 0, 0, 0}};
- result.c[1] = (v4){{0, 1, 0, 0}};
- result.c[2] = (v4){{0, 0, 1, 0}};
- result.c[3] = (v4){{0, 0, 0, 1}};
- return result;
-}
+#define m4_identity() \
+ (m4){.E = { \
+ 1, 0, 0, 0, \
+ 0, 1, 0, 0, \
+ 0, 0, 1, 0, \
+ 0, 0, 0, 1, \
+ }}
function v4
m4_row(m4 a, u32 row)
@@ -567,6 +564,54 @@ m4_mul_v3(m4 a, v3 v)
return result;
}
+function v2
+rect_uv(v2 p, Rect r)
+{
+ v2 result = v2_div(v2_sub(p, r.pos), r.size);
+ return result;
+}
+
+function v2
+rect_uv_ndc(v2 p, Rect r)
+{
+ v2 uv = rect_uv(p, r);
+ v2 result = v2_sub(v2_scale(uv, 2.f), (v2){{1.f, 1.f}});
+ return result;
+}
+
+function Rect
+rect_intersect(Rect a, Rect b)
+{
+ v2 ae = v2_add(a.pos, a.size);
+ v2 be = v2_add(b.pos, b.size);
+
+ Rect result = {0};
+ result.pos.x = Max(a.pos.x, b.pos.x);
+ result.pos.y = Max(a.pos.y, b.pos.y);
+ result.size.x = Min(ae.x, be.x) - result.pos.x;
+ result.size.y = Min(ae.y, be.y) - result.pos.y;
+ return result;
+}
+
+function Rect
+rect_squish_centered(Rect a, v2 pct)
+{
+ v2 delta_size = v2_mul(a.size, pct);
+ Rect result;
+ result.pos = v2_add(a.pos, v2_scale(delta_size, 0.5f));
+ result.size = v2_add(a.size, v2_scale(delta_size, -1.f));
+ return result;
+}
+
+function Rect
+rect_shrink_centered(Rect a, v2 px)
+{
+ Rect result;
+ result.pos = v2_add(a.pos, v2_scale(px, 0.5f));
+ result.size = v2_add(a.size, v2_scale(px, -1.f));
+ return result;
+}
+
function m4
orthographic_projection(f32 n, f32 f, f32 t, f32 r)
{
@@ -871,6 +916,35 @@ das_transform(v3 min_coordinate, v3 max_coordinate, iv3 *points)
return result;
}
+function v3
+plane_normal_from_transform(m4 transform)
+{
+ v3 U = v3_normalize(transform.c[0].xyz);
+ v3 V = v3_normalize(transform.c[1].xyz);
+ v3 result = cross(V, U);
+ return result;
+}
+
+function f32
+plane_offset_from_transform(m4 transform)
+{
+ f32 result = v3_dot(plane_normal_from_transform(transform), transform.c[3].xyz);
+ return result;
+}
+
+function void
+plane_corners_from_transform(m4 transform, v2 *min, v2 *max)
+{
+ v3 U = v3_normalize(transform.c[0].xyz);
+ v3 V = v3_normalize(transform.c[1].xyz);
+
+ v3 min_3d = m4_mul_v3(transform, (v3){{0.f, 0.f, 0.f}});
+ v3 max_3d = m4_mul_v3(transform, (v3){{1.f, 1.f, 1.f}});
+
+ if (min) *min = (v2){{v3_dot(U, min_3d), v3_dot(V, min_3d)}};
+ if (max) *max = (v2){{v3_dot(U, max_3d), v3_dot(V, max_3d)}};
+}
+
function v2
plane_uv(v3 point, v3 U, v3 V)
{
diff --git a/ui.c b/ui.c
@@ -1,67 +1,44 @@
/* See LICENSE for license details. */
/* TODO(rnp):
- * [ ]: bug: draw_view_ruler() needs to use a ray intersection instead of clamping
- * [ ]: bug: plane rotation and offset position only work if plane is Z aligned
- * [ ]: refactor: ui and views need to store current uv coordinates for expected transform
- * [ ]: refactor: there shouldn't need to be if 1d checks all over
- * [ ]: refactor: ui kind of needs to be mostly thrown away
- * - want all drawing to be immediate mode
- * - only layout information should be retained
- * - leaf nodes of layout have a kind associated which
- * instructs the builder code on how to build the view
- * - ui items (currently called Variables) are stored in a hash
- * table and are looked up for state information at frame building time
- * - removed/recycled when last building frame index is less than drawing frame index
- * - building:
- * - loop over tiled layout tree and floating layout tree as is currently done
- * - this will build current frame ui draw tree
- * - for each view use a stack structure with grouping, similar to how tables are made
- * - more general though: sub groups contain a draw axis (x or y)
- * - each ui item gets looked up in the hash table for previous frame drawing info
- * - this can then be used to construct a "ui comm" which contains relevant info
- * about how that ui item is being interacted with
- * - drawing:
- * - must be separated into layout constraint solving and rendering
- * - layout constraint solving handles sizing, clipping, etc.
- * - this will need multiple passes per subgroup to allow for autosizing
- * - pay attention to the fixed size points in the hierarchy. fixed size
- * items are complete once their children are complete.
- * - rendering simply uses the rect/clipping regions produced by layout
- * to send draw commands
- * [ ]: bug: resizing live view causes texture to jump around
- * [ ]: bug: group at end of parameter listing
- * [ ]: refactor: ui should be in its own thread and that thread should only be concerned with the ui
- * [ ]: refactor: remove all the excessive measure_texts (cell drawing, hover_interaction in params table)
- * [ ]: refactor: move remaining fragment shader stuff into ui
- * [ ]: refactor: scale table to rect
- * [ ]: scroll bar for views that don't have enough space
- * [ ]: allow views to collapse to just their title bar
- * - title bar struct with expanded. Check when pushing onto draw stack; if expanded
- * do normal behaviour else make size title bar size and ignore the splits fraction.
- * [ ]: enforce a minimum region size or allow regions themselves to scroll
- * [ ]: refactor: add_variable_no_link()
- * [ ]: refactor: draw_text_limited should clamp to rect and measure text itself
- * [ ]: draw the ui with a post-order traversal instead of pre-order traversal
- * [ ]: consider V_HOVER_GROUP and use that to implement submenus
- * [ ]: menu's need to support nested groups
- * [ ]: don't redraw on every refresh; instead redraw on mouse movement/event or when a new frame
- * arrives. For animations the ui can have a list of "timers" which while active will
- * do a redraw on every refresh until completed.
- * [ ]: show full non-truncated string on hover
- * [ ]: refactor: hovered element type and show hovered element in full even when truncated
- * [ ]: bug: cross-plane view with different dimensions for each plane
- * [ ]: refactor: make table_skip_rows useful
- * [ ]: refactor: better method of grouping variables for views such as FrameView/ComputeStatsView
+ * [ ]: bug: context_menu_close on live parameters acquisition jumps to left corner of beamformer
+ * [ ]: bug: flickering x-scroll bar on switch from ComputeBarGraph to other
+ * [ ]: word scan for text input
+ * [ ]: animation state
+ * [ ]: tooltips
+ * [ ]: extra copy view settings
+ * [ ]: refactor: all drag overlay floating elements can be children of the drag_root.
+ * as long as we layout before chaining them on there won't be an issue.
+ * [ ]: refactor: move context menu building code next to normal ui building.
+ * keeping this localized makes it easier to add new panels.
+ * [ ]: refactor: can the scroll container just use the ViewScroll flags like the tab bar?
+ * [ ]: refactor: it would be nice to have some table building helpers
+ *
+ * [ ]: refactor: cross plane view for non XZ/YZ planes. math needs to be cleaned up
+ * to support this.
+ * - model transform needs to first rotate so that Z is normal, then scale, the rotate from Z to Y.
+ * - ideally the hardcoded +0.25f rotation for YZ should just be a consequence of the math
+ * [ ]: command window
+ * [ ]: 3D data view
+ * - add extra view controls, change view without recompute
+ * - to start just have starting plane/normal, plane uvs, rotation, and offset
+ * - confirmation on recompute
+ * [ ]: rich highlighting for parameters -> X-Plane link
+ *
+ * [ ]: multi-os windows
+ * [ ]: ui color configuration at runtime
*/
#include "assets/generated/assets.c"
+#define NIL_COLOUR (v4){{0.76f, 0.00f, 0.65f, 1.0f}}
#define BG_COLOUR (v4){{0.15f, 0.12f, 0.13f, 1.0f}}
#define FG_COLOUR (v4){{0.92f, 0.88f, 0.78f, 1.0f}}
#define FOCUSED_COLOUR (v4){{0.86f, 0.28f, 0.21f, 1.0f}}
#define HOVERED_COLOUR (v4){{0.11f, 0.50f, 0.59f, 1.0f}}
+#define SELECTION_COLOUR (v4){{0.07f, 0.37f, 0.90f, 0.5f}}
#define RULER_COLOUR (v4){{1.00f, 0.70f, 0.00f, 1.0f}}
#define BORDER_COLOUR v4_lerp(FG_COLOUR, BG_COLOUR, 0.85f)
+#define NODE_SPLIT_COLOUR (v4){{0.6f, 0.6f, 0.6f, 0.5f}}
#define FRAME_VIEW_BB_COLOUR (v4){{0.92f, 0.88f, 0.78f, 1.0f}}
#define FRAME_VIEW_BB_FRACTION 0.007f
@@ -70,6 +47,11 @@
#define MENU_PLUS_COLOUR (v4){{0.33f, 0.42f, 1.00f, 1.00f}}
#define MENU_CLOSE_COLOUR FOCUSED_COLOUR
+#define UI_NODE_PAD 8.f
+#define UI_BORDER_THICK 4.f
+
+#define UI_HASH_TABLE_COUNT 4096
+
read_only global v4 g_colour_palette[] = {
{{0.32f, 0.20f, 0.50f, 1.00f}},
{{0.14f, 0.39f, 0.61f, 1.00f}},
@@ -85,10 +67,10 @@ read_only global v4 g_colour_palette[] = {
#define TABLE_CELL_PAD_HEIGHT 2.0f
#define TABLE_CELL_PAD_WIDTH 8.0f
-#define RULER_TEXT_PAD 10.0f
+#define RULER_TEXT_PAD 6.0f
#define RULER_TICK_LENGTH 20.0f
-#define UI_SPLIT_HANDLE_THICK 8.0f
+#define UI_SPLIT_HANDLE_THICK 5.0f
#define UI_REGION_PAD 32.0f
/* TODO(rnp) smooth scroll */
@@ -97,346 +79,290 @@ read_only global v4 g_colour_palette[] = {
#define LISTING_LINE_PAD 6.0f
#define TITLE_BAR_PAD 6.0f
-typedef struct v2_sll {
- struct v2_sll *next;
- v2 v;
-} v2_sll;
-
-typedef struct {
- f32 t;
- f32 scale;
-} UIBlinker;
+typedef enum {
+ UINodeFlag_MouseClickable = 1ull << 0,
+ UINodeFlag_KeyboardClickable = 1ull << 1,
+ UINodeFlag_DropSite = 1ull << 2,
+ UINodeFlag_ClickToFocus = 1ull << 3,
+ UINodeFlag_Scroll = 1ull << 4,
+ UINodeFlag_FocusHot = 1ull << 5,
+ UINodeFlag_FocusActive = 1ull << 6,
+ UINodeFlag_FocusHotDisabled = 1ull << 7,
+ UINodeFlag_FocusActiveDisabled = 1ull << 8,
+ UINodeFlag_Disabled = 1ull << 9,
+
+ UINodeFlag_FloatingX = 1ull << 10,
+ UINodeFlag_FloatingY = 1ull << 11,
+ UINodeFlag_FixedWidth = 1ull << 12,
+ UINodeFlag_FixedHeight = 1ull << 13,
+ UINodeFlag_AllowOverflowX = 1ull << 14,
+ UINodeFlag_AllowOverflowY = 1ull << 15,
+
+ // NOTE(rnp): for scrollable containers
+ UINodeFlag_ViewScrollX = 1ull << 16,
+ UINodeFlag_ViewScrollY = 1ull << 17,
+
+ UINodeFlag_DrawDropShadow = 1ull << 18,
+ UINodeFlag_DrawBackgroundBlur = 1ull << 19,
+ UINodeFlag_DrawBackground = 1ull << 20,
+ UINodeFlag_DrawBorder = 1ull << 21,
+ UINodeFlag_DrawText = 1ull << 22,
+ UINodeFlag_DrawHotEffects = 1ull << 23,
+ UINodeFlag_DrawActiveEffects = 1ull << 24,
+ UINodeFlag_DrawOverlay = 1ull << 25,
+ UINodeFlag_Clip = 1ull << 26,
+ UINodeFlag_DisableTextTrunc = 1ull << 27,
+ UINodeFlag_DisableFocusBorder = 1ull << 28,
+ UINodeFlag_DisableFocusOverlay = 1ull << 29,
+
+ UINodeFlag_TextInput = 1ull << 30,
+ UINodeFlag_TextInputNumeric = 1ull << 31,
+ UINodeFlag_TextInputClearOnStart = 1ull << 32,
+
+ UINodeFlag_CustomDraw = 1ull << 33,
+
+ // TODO(rnp): hack: when text is not drawn with raylib do something smarter
+ UINodeFlag_IconText = 1ull << 34,
+
+ UINodeFlag_Clickable = UINodeFlag_MouseClickable|UINodeFlag_KeyboardClickable,
+ UINodeFlag_Floating = UINodeFlag_FloatingX|UINodeFlag_FloatingY,
+ UINodeFlag_FixedSize = UINodeFlag_FixedWidth|UINodeFlag_FixedHeight,
+ UINodeFlag_AllowOverflow = UINodeFlag_AllowOverflowX|UINodeFlag_AllowOverflowY,
+ UINodeFlag_DisableFocusEffects = UINodeFlag_DisableFocusBorder|UINodeFlag_DisableFocusOverlay,
+ UINodeFlag_ViewScroll = UINodeFlag_ViewScrollX|UINodeFlag_ViewScrollY,
+} UINodeFlags;
+
+typedef struct UINodeFlagsNode UINodeFlagsNode;
+struct UINodeFlagsNode {UINodeFlagsNode *next; UINodeFlags v;};
+
+typedef struct Axis2Node Axis2Node;
+struct Axis2Node {Axis2Node *next; Axis2 v;};
-typedef struct BeamformerUI BeamformerUI;
-typedef struct Variable Variable;
+typedef enum {
+ UISizeKind_Nil,
+ UISizeKind_Pixels,
+ UISizeKind_TextContent,
+ UISizeKind_PercentOfParent,
+ UISizeKind_ChildrenSum,
+} UISizeKind;
typedef struct {
- u8 buf[128];
- i32 count;
- i32 cursor;
- b32 numeric;
- UIBlinker cursor_blink;
- Font *font, *hot_font;
- Variable *container;
-} InputState;
+ UISizeKind kind;
+ f32 value;
+ f32 strictness;
+} UISize;
+
+typedef struct UISizeNode UISizeNode;
+struct UISizeNode {UISizeNode *next; UISize v;};
typedef enum {
- RulerState_None,
- RulerState_Start,
- RulerState_Hold,
-} RulerState;
+ UIAlign_Left,
+ UIAlign_Right,
+ UIAlign_Center,
+ UIAlign_Count,
+} UIAlign;
-typedef struct {
- v3 start;
- v3 end;
- RulerState state;
-} Ruler;
+typedef struct UIAlignNode UIAlignNode;
+struct UIAlignNode {UIAlignNode *next; UIAlign v;};
-typedef enum {
- SB_LATERAL,
- SB_AXIAL,
-} ScaleBarDirection;
+typedef struct {u64 value;} UINodeKey;
-typedef struct {
- f32 *min_value, *max_value;
- v2_sll *savepoint_stack;
- v2 scroll_scale;
- f32 zoom_starting_coord;
- ScaleBarDirection direction;
-} ScaleBar;
+typedef struct UINode UINode;
-typedef struct { f32 val, scale; } scaled_f32;
+#define UI_CUSTOM_DRAW_FUNCTION(name) void name(UINode *node, Rect node_rect)
+typedef UI_CUSTOM_DRAW_FUNCTION(UICustomDrawFunction);
-typedef enum {
- RSD_VERTICAL,
- RSD_HORIZONTAL,
-} RegionSplitDirection;
+struct UINode {
+ UINode *parent;
+ UINode *first_child;
+ UINode *last_child;
+ UINode *previous_sibling;
+ UINode *next_sibling;
-typedef struct {
- Variable *left;
- Variable *right;
- f32 fraction;
- RegionSplitDirection direction;
-} RegionSplit;
-
-#define COMPUTE_STATS_VIEW_LIST \
- X(Average, "Average") \
- X(Bar, "Bar")
-
-#define X(kind, ...) ComputeStatsViewKind_ ##kind,
-typedef enum {COMPUTE_STATS_VIEW_LIST ComputeStatsViewKind_Count} ComputeStatsViewKind;
-#undef X
+ u32 child_count;
-typedef struct {
- ComputeShaderStats *compute_shader_stats;
- Variable *cycler;
- ComputeStatsViewKind kind;
- UIBlinker blink;
-} ComputeStatsView;
+ UINodeFlags flags;
+ str8 string;
+ // NOTE(rnp): desired sizing info from build step
+ union {
+ struct {
+ UISize semantic_width;
+ UISize semantic_height;
+ };
+ UISize semantic_size[Axis2_Count];
+ };
-typedef struct {
- b32 *processing;
- f32 *progress;
- f32 display_t;
- f32 display_t_velocity;
-} ComputeProgressBar;
+ union {
+ struct {
+ UIAlign alignment_x;
+ UIAlign alignment_y;
+ };
+ UIAlign alignment[Axis2_Count];
+ };
-typedef enum {
- VT_NULL,
- VT_B32,
- VT_F32,
- VT_I32,
- VT_U32,
- VT_GROUP,
- VT_CYCLER,
- VT_SCALED_F32,
- VT_BEAMFORMER_VARIABLE,
- VT_BEAMFORMER_FRAME_VIEW,
- VT_COMPUTE_STATS_VIEW,
- VT_COMPUTE_PROGRESS_BAR,
- VT_LIVE_CONTROLS_VIEW,
- VT_LIVE_CONTROLS_STRING,
- VT_SCALE_BAR,
- VT_UI_BUTTON,
- VT_UI_MENU,
- VT_UI_REGION_SPLIT,
- VT_UI_TEXT_BOX,
- VT_UI_VIEW,
- VT_X_PLANE_SHIFT,
-} VariableType;
+ UIAlign text_alignment;
-typedef enum {
- VariableGroupKind_List,
- /* NOTE(rnp): special group for vectors with components
- * stored in separate memory locations */
- VariableGroupKind_Vector,
-} VariableGroupKind;
+ Axis2 child_layout_axis;
+ f32 font_size;
-typedef struct {
- VariableGroupKind kind;
- b32 expanded;
- Variable *first;
- Variable *last;
- Variable *container;
-} VariableGroup;
+ u64 last_frame_active_index;
+ UINodeKey key;
+ UINode *hash_prev;
+ UINode *hash_next;
-typedef enum {
- UIViewFlag_CustomText = 1 << 0,
- UIViewFlag_Floating = 1 << 1,
-} UIViewFlags;
+ // NOTE(rnp): recomputed every frame before drawing. also
+ // used on next frame for mouse collision detection.
+ f32 computed_position[Axis2_Count];
+ f32 computed_size[Axis2_Count];
-typedef struct {
- Variable *child;
- Variable *close;
- Variable *menu;
- Rect rect;
- UIViewFlags flags;
-} UIView;
-
-/* X(id, text) */
-#define FRAME_VIEW_BUTTONS \
- X(FV_COPY_HORIZONTAL, "Copy Horizontal") \
- X(FV_COPY_VERTICAL, "Copy Vertical")
-
-#define GLOBAL_MENU_BUTTONS \
- X(GM_OPEN_VIEW_RIGHT, "Open View Right") \
- X(GM_OPEN_VIEW_BELOW, "Open View Below")
-
-#define X(id, text) UI_BID_ ##id,
-typedef enum {
- UI_BID_VIEW_CLOSE,
- GLOBAL_MENU_BUTTONS
- FRAME_VIEW_BUTTONS
- UI_BID_ACQUISITION_KIND_FIRST,
- UI_BID_ACQUISITION_KIND_LAST = UI_BID_ACQUISITION_KIND_FIRST + BeamformerAcquisitionKind_Count - 1,
-} UIButtonID;
-#undef X
+ v2 text_size;
-typedef struct {
- s8 *labels;
- u32 *state;
- u32 cycle_length;
-} VariableCycler;
+ // NOTE(rnp): persistent data
+ f32 active_t;
+ f32 hot_t;
-typedef struct {
- s8 suffix;
- f32 display_scale;
- f32 scroll_scale;
- v2 limits;
- f32 *store;
-} BeamformerVariable;
+ v2 view_scroll_offset;
-typedef struct {
- v3 start_point;
- v3 end_point;
-} XPlaneShift;
+ v4 bg_colour;
-typedef enum {
- V_INPUT = 1 << 0,
- V_TEXT = 1 << 1,
- V_RADIO_BUTTON = 1 << 2,
- V_EXTRA_ACTION = 1 << 3,
- V_HIDES_CURSOR = 1 << 4,
- V_LIVE_CONTROL = 1 << 28,
- V_CAUSES_COMPUTE = 1 << 29,
- V_UPDATE_VIEW = 1 << 30,
-} VariableFlags;
-
-struct Variable {
- s8 name;
- union {
- void *generic;
- BeamformerVariable beamformer_variable;
- ComputeProgressBar compute_progress_bar;
- ComputeStatsView compute_stats_view;
- RegionSplit region_split;
- ScaleBar scale_bar;
- UIButtonID button;
- UIView view;
- VariableCycler cycler;
- VariableGroup group;
- XPlaneShift x_plane_shift;
- scaled_f32 scaled_real32;
- b32 bool32;
- i32 signed32;
- u32 unsigned32;
- f32 real32;
- };
- Variable *next;
- Variable *parent;
- VariableFlags flags;
- VariableType type;
+ v4 text_colour;
+ v4 text_outline_colour;
+ f32 text_outline_thickness;
- f32 hover_t;
- f32 name_width;
+ v4 border_colour;
+ f32 border_thickness;
+
+ UICustomDrawFunction *custom_draw_function;
+ void *custom_draw_context;
};
-#define BEAMFORMER_FRAME_VIEW_KIND_LIST \
- X(Latest, "Latest") \
- X(3DXPlane, "3D X-Plane") \
- X(Copy, "Copy") \
+typedef struct {UINode *first, *last;} UINodeHashBucket;
+
+typedef struct UIParentNode UIParentNode;
+struct UIParentNode {UIParentNode *next; UINode *v;};
typedef enum {
- #define X(kind, ...) BeamformerFrameViewKind_##kind,
- BEAMFORMER_FRAME_VIEW_KIND_LIST
- #undef X
- BeamformerFrameViewKind_Count,
-} BeamformerFrameViewKind;
+ UIMouseButtonKind_Left,
+ UIMouseButtonKind_Middle,
+ UIMouseButtonKind_Right,
+ UIMouseButtonKind_Count,
+} UIMouseButtonKind;
-typedef struct BeamformerFrameView BeamformerFrameView;
-struct BeamformerFrameView {
- BeamformerFrameViewKind kind;
- b32 dirty;
- BeamformerFrameView *prev, *next;
+typedef enum {
+ UISignalFlag_LeftPressed = (1 << 0),
+ UISignalFlag_MiddlePressed = (1 << 1),
+ UISignalFlag_RightPressed = (1 << 2),
- // NOTE(rnp): for FrameViewKindCopy
- GPUBuffer copy_buffer;
+ UISignalFlag_LeftDragging = (1 << 3),
+ UISignalFlag_MiddleDragging = (1 << 4),
+ UISignalFlag_RightDragging = (1 << 5),
- GPUImage colour_image;
- // NOTE(rnp): temporary, on w32 we must hold onto this when importing vulkan data to OpenGL
- OSHandle export_handle;
- u32 memory_object;
- u32 texture;
+ UISignalFlag_LeftDoubleDragging = (1 << 6),
+ UISignalFlag_MiddleDoubleDragging = (1 << 7),
+ UISignalFlag_RightDoubleDragging = (1 << 8),
- /* NOTE(rnp): any pointers to variables are added to the menu and will
- * be put onto the freelist if the view is closed. */
+ UISignalFlag_LeftTripleDragging = (1 << 9),
+ UISignalFlag_MiddleTripleDragging = (1 << 10),
+ UISignalFlag_RightTripleDragging = (1 << 11),
- Variable *kind_cycler;
- Variable *log_scale;
- Variable threshold;
- Variable dynamic_range;
- Variable gamma;
+ UISignalFlag_LeftReleased = (1 << 12),
+ UISignalFlag_MiddleReleased = (1 << 13),
+ UISignalFlag_RightReleased = (1 << 14),
- union {
- /* BeamformerFrameViewKind_Latest/BeamformerFrameViewKind_Copy */
- struct {
- Variable lateral_scale_bar;
- Variable axial_scale_bar;
- Variable *lateral_scale_bar_active;
- Variable *axial_scale_bar_active;
- /* NOTE(rnp): selects which plane to use */
- Variable *cycler;
- u32 cycler_state;
+ UISignalFlag_LeftClicked = (1 << 15),
+ UISignalFlag_MiddleClicked = (1 << 16),
+ UISignalFlag_RightClicked = (1 << 17),
- Ruler ruler;
+ UISignalFlag_LeftDoubleClicked = (1 << 18),
+ UISignalFlag_MiddleDoubleClicked = (1 << 19),
+ UISignalFlag_RightDoubleClicked = (1 << 20),
- v3 min_coordinate;
- v3 max_coordinate;
+ UISignalFlag_LeftTripleClicked = (1 << 21),
+ UISignalFlag_MiddleTripleClicked = (1 << 22),
+ UISignalFlag_RightTripleClicked = (1 << 23),
- BeamformerFrame frame;
- };
+ UISignalFlag_ScrolledX = (1 << 24),
+ UISignalFlag_ScrolledY = (1 << 25),
- /* BeamformerFrameViewKind_3DXPlane */
- struct {
- Variable x_plane_shifts[2];
- Variable *demo;
- f32 rotation;
- v3 hit_test_point;
- };
- };
-};
+ UISignalFlag_KeyboardPressed = (1 << 26),
-typedef struct BeamformerLiveControlsView BeamformerLiveControlsView;
-struct BeamformerLiveControlsView {
- Variable acquisition_menu;
- Variable transmit_power;
- Variable tgc_control_points[countof(((BeamformerLiveImagingParameters *)0)->tgc_control_points)];
- Variable save_button;
- Variable stop_button;
- Variable save_text;
- UIBlinker save_button_blink;
- u32 hot_field_flag;
- u32 active_field_flag;
-};
+ UISignalFlag_Hovering = (1 << 27),
-typedef enum {
- InteractionKind_None,
- InteractionKind_Nop,
- InteractionKind_Auto,
- InteractionKind_Button,
- InteractionKind_Drag,
- InteractionKind_AcquisitionMenu,
- InteractionKind_Menu,
- InteractionKind_Ruler,
- InteractionKind_Scroll,
- InteractionKind_Set,
- InteractionKind_Text,
-} InteractionKind;
+ UISignalFlag_TextCommit = (1 << 28),
+
+ UISignalFlag_Scrolled = UISignalFlag_ScrolledX|UISignalFlag_ScrolledY,
+ UISignalFlag_Pressed = UISignalFlag_LeftPressed|UISignalFlag_KeyboardPressed,
+ UISignalFlag_Released = UISignalFlag_LeftReleased,
+ UISignalFlag_Clicked = UISignalFlag_LeftClicked|UISignalFlag_KeyboardPressed,
+ UISignalFlag_DoubleClicked = UISignalFlag_LeftDoubleClicked,
+ UISignalFlag_TripleClicked = UISignalFlag_LeftTripleClicked,
+ UISignalFlag_Dragging = UISignalFlag_LeftDragging,
+} UISignalFlags;
typedef struct {
- InteractionKind kind;
- union {
- void *generic;
- Variable *var;
- };
- Rect rect;
-} Interaction;
+ UINode *node;
+ v2 scroll;
+ str8 string;
+ UISignalFlags flags;
+} UISignal;
+
+typedef struct {
+ UINodeKey node_key;
+ UINodeKey next_node_key;
+ UINodeKey last_node_key;
+
+ i16 cursor;
+ i16 mark;
+ i16 count;
+ i16 last_count;
+ b32 numeric;
+ b32 changed;
+ // TODO(rnp): animation key
+ BeamformerUIBlinker blinker;
+ u8 buffer[256];
+ u8 last_buffer[256];
+} UITextInputState;
+
+typedef struct F32Node F32Node;
+struct F32Node {F32Node *next; f32 v;};
+
+typedef struct V4Node V4Node;
+struct V4Node {V4Node *next; v4 v;};
+
+#define UI_STACK_LIST \
+ X(Axis2Node, child_layout_axis, Axis2, 0) \
+ X(F32Node, font_size, f32, 0) \
+ X(F32Node, border_thickness, f32, UI_BORDER_THICK) \
+ X(F32Node, text_outline_thickness, f32, 0) \
+ X(UINodeFlagsNode, flags, UINodeFlags, 0) \
+ X(UIParentNode, parent, UINode *, (&ui_node_nil)) \
+ X(UISizeNode, semantic_height, UISize, {0}) \
+ X(UISizeNode, semantic_width, UISize, {0}) \
+ X(UIAlignNode, alignment_y, UIAlign, 0) \
+ X(UIAlignNode, alignment_x, UIAlign, 0) \
+ X(UIAlignNode, text_alignment, UIAlign, UIAlign_Left) \
+ X(V4Node, text_colour, v4, FG_COLOUR) \
+ X(V4Node, text_outline_colour, v4, NIL_COLOUR) \
+ X(V4Node, border_colour, v4, NIL_COLOUR) \
+ X(V4Node, bg_colour, v4, NIL_COLOUR) \
-#define auto_interaction(r, v) (Interaction){.kind = InteractionKind_Auto, .var = v, .rect = r}
-struct BeamformerUI {
+typedef struct {
+ u64 current_frame_index;
Arena arena;
+ v2 current_mouse;
+ v2 last_mouse;
+ u64 input_consumed[countof(((BeamformerInput *)0)->event_queue) / 64];
+ static_assert(countof(((BeamformerInput *)0)->event_queue) % 64 == 0, "");
+
Font font;
Font small_font;
- v2 last_mouse;
-
- Variable *regions;
- Variable *variable_freelist;
-
- Variable floating_widget_sentinal;
-
BeamformerFrameView *view_first;
BeamformerFrameView *view_last;
BeamformerFrameView *view_freelist;
- Interaction interaction;
- Interaction hot_interaction;
- Interaction next_interaction;
-
- InputState text_input_state;
-
VulkanHandle pipelines[BeamformerShaderKind_RenderCount];
OSHandle render_semaphores_export[2];
@@ -447,23 +373,58 @@ struct BeamformerUI {
GPUImage render_3d_depth_image;
RenderModel unit_cube_model;
- v2_sll *scale_bar_savepoint_freelist;
+ BeamformerFrame latest_plane[BeamformerViewPlaneTag_Count];
- BeamformerFrame latest_plane[BeamformerViewPlaneTag_Count + 1];
- b32 latest_plane_valid[BeamformerViewPlaneTag_Count + 1];
-
- BeamformerUIParameters params;
- b32 flush_params;
+ BeamformerUIParameters parameters;
+ b32 flush_parameters;
u32 selected_parameter_block;
- v2 min_coordinate;
- v2 max_coordinate;
+ // TODO(rnp): this should be per parameter block
f32 off_axis_position;
f32 beamform_plane;
- BeamformerSharedMemory * shared_memory;
- BeamformerCtx * beamformer_context;
-};
+ BeamformerUIPanel *tree;
+ BeamformerUIPanel *tree_node_freelist;
+
+ // NOTE(rnp): context menu
+ UINode *context_menu_root;
+ UINodeKey context_menu_anchor_key;
+ UINodeKey context_menu_next_anchor_key;
+ BeamformerUIPanel *context_menu_panel;
+ BeamformerUIPanel *context_menu_next_panel;
+ f32 context_menu_open_t;
+ b32 context_menu_state_changed;
+
+ // NOTE(rnp): drag info
+ UINodeKey drop_target_key; // alway a stable node
+ UINode *drop_target_node; // may point to a transient node
+ UINode *drag_root;
+ UINode *drag_overlay_root;
+ UINode *drag_overlay_edges_root;
+ UINode *drag_overlay_tab_root;
+ BeamformerUIPanel *drag_panel;
+ f32 drag_open_t;
+ b32 drag_end;
+
+ // NOTE(rnp): User Interaction
+ UINodeKey hot_node_key;
+ UINodeKey active_node_key[UIMouseButtonKind_Count];
+ // TODO(rnp): click timestamp history (double/triple press)
+
+ // NOTE(rnp): Builder State
+ UINode *node_freelist;
+ UINode *root_node;
+ Arena build_arenas[2];
+ TempArena build_arena_savepoints[2];
+ // NOTE(rnp): Builder Stacks
+ #define X(type, name, ...) struct {type *top; type *free; u64 count;} name##_node_stack;
+ UI_STACK_LIST
+ #undef X
+
+ UINodeHashBucket node_hash_table[UI_HASH_TABLE_COUNT];
+
+ UITextInputState text_input_state;
+} BeamformerUI;
typedef enum {
TF_NONE = 0,
@@ -489,486 +450,338 @@ typedef struct {
TextFlags flags;
} TextSpec;
-typedef enum {
- TRK_CELLS,
- TRK_TABLE,
-} TableRowKind;
-
-typedef enum {
- TableCellKind_None,
- TableCellKind_Variable,
- TableCellKind_VariableGroup,
-} TableCellKind;
-
-typedef struct {
- s8 text;
- union {
- i64 integer;
- Variable *var;
- void *generic;
- };
- TableCellKind kind;
- f32 width;
-} TableCell;
-
-typedef struct {
- void *data;
- TableRowKind kind;
-} TableRow;
-
-typedef struct Table {
- TableRow *data;
- da_count count;
- da_count capacity;
-
- /* NOTE(rnp): counted by columns */
- TextAlignment *alignment;
- f32 *widths;
-
- v4 border_colour;
- f32 column_border_thick;
- f32 row_border_thick;
- v2 size;
- v2 cell_pad;
-
- /* NOTE(rnp): row count including nested tables */
- i32 rows;
- i32 columns;
-
- struct Table *parent;
-} Table;
-
-typedef struct {
- Table *table;
- i32 row_index;
-} TableStackFrame;
+global BeamformerUI *ui_context;
+global BeamformerInput *beamformer_input;
-typedef struct {
- TableStackFrame *data;
- da_count count;
- da_count capacity;
-} TableStack;
+read_only global UINode ui_node_nil = {
+ .parent = &ui_node_nil,
+ .first_child = &ui_node_nil,
+ .last_child = &ui_node_nil,
+ .previous_sibling = &ui_node_nil,
+ .next_sibling = &ui_node_nil,
+};
-typedef enum {
- TIK_ROWS,
- TIK_CELLS,
-} TableIteratorKind;
+#define X(type, name, _t, impl) read_only global type ui_##name##_node_nil = {.v = impl};
+UI_STACK_LIST
+#undef X
-typedef struct {
- TableStack stack;
- TableStackFrame frame;
+#define ui_node_is_nil(n) ((n) == 0 || (n) == &ui_node_nil)
+#define ui_build_arena() (ui_context->build_arenas + (ui_context->current_frame_index % countof(ui_context->build_arenas)))
+
+#define UIStackPushBody(name_upper, name_lower, type, new_value) \
+ name_upper *node = SLLPop(ui_context->name_lower##_node_stack.free, next); \
+ if (!node) node = push_struct_no_zero(ui_build_arena(), name_upper); \
+ node->v = new_value; \
+ type result = ui_context->name_lower##_node_stack.top->v; \
+ SLLStackPush(ui_context->name_lower##_node_stack.top, node, next); \
+ ui_context->name_lower##_node_stack.count++; \
+ return result
+
+#define UIStackPopBody(name_upper, name_lower, type) \
+ name_upper *node = ui_context->name_lower##_node_stack.top; \
+ type result = node->v; \
+ if (node != &ui_##name_lower##_node_nil) { \
+ node = SLLPop(ui_context->name_lower##_node_stack.top, next); \
+ SLLStackPush(ui_context->name_lower##_node_stack.free, node, next); \
+ } \
+ return result
+
+#define UIAlign(v) DeferLoop(ui_push_alignment(UIAlign_##v), ui_pop_alignment())
+#define UIAxisAlign(axis, v) DeferLoop(ui_push_axis_alignment(axis, UIAlign_##v), ui_pop_axis_alignment(axis))
+#define UIAxisSize(axis, v) DeferLoop(ui_push_axis_size(axis, v), ui_pop_axis_size(axis))
+#define UIBorderColour(v) DeferLoop(ui_push_border_colour(v), ui_pop_border_colour())
+#define UIBorderThickness(v) DeferLoop(ui_push_border_thickness(v), ui_pop_border_thickness())
+#define UIBGColour(v) DeferLoop(ui_push_bg_colour(v), ui_pop_bg_colour())
+#define UIChildLayoutAxis(v) DeferLoop(ui_push_child_layout_axis(v), ui_pop_child_layout_axis())
+#define UIFlags(v) DeferLoop(ui_push_flags(v), ui_pop_flags())
+#define UIFontSize(v) DeferLoop(ui_push_font_size(v), ui_pop_font_size())
+#define UIParent(v) DeferLoop(ui_push_parent(v), ui_pop_parent())
+#define UIPrefHeight(v) DeferLoop(ui_push_semantic_height(v), ui_pop_semantic_height())
+#define UIPrefWidth(v) DeferLoop(ui_push_semantic_width(v), ui_pop_semantic_width())
+#define UISize(v) DeferLoop(ui_push_size(v), ui_pop_size())
+#define UITextAlign(v) DeferLoop(ui_push_text_alignment(UIAlign_##v), ui_pop_text_alignment())
+#define UITextOutlineColour(v) DeferLoop(ui_push_text_outline_colour(v), ui_pop_text_outline_colour())
+#define UITextOutlineThickness(v) DeferLoop(ui_push_text_outline_thickness(v), ui_pop_text_outline_thickness())
+#define UITextColour(v) DeferLoop(ui_push_text_colour(v), ui_pop_text_colour())
+
+#define UIScroll(axis) DeferLoop(ui_scroll_begin(axis), ui_scroll_end())
+
+#define X(type, name, value_type, ...) \
+ function value_type ui_push_##name(value_type v) {UIStackPushBody(type, name, value_type, v);} \
+ function value_type ui_pop_##name(void) {UIStackPopBody(type, name, value_type);} \
+ function value_type ui_top_##name(void) {return ui_context->name##_node_stack.top->v;}
+UI_STACK_LIST
+#undef X
- TableRow *row;
- i16 column;
- i16 sub_table_depth;
+#define ui_size(k, v, s) (UISize){.kind = UISizeKind_##k, .value = (v), .strictness = (s)}
+#define ui_em(value, strictness) ui_size(Pixels, (value) * ui_top_font_size(), (strictness))
+#define ui_px(value, strictness) ui_size(Pixels, (value), (strictness))
+#define ui_pct(value, strictness) ui_size(PercentOfParent, (value), (strictness))
+#define ui_children_sum(strictness) ui_size(ChildrenSum, 0.f, (strictness))
+#define ui_text_dim(padding, strictness) ui_size(TextContent, (padding), (strictness))
- TableIteratorKind kind;
+#define ui_node_key_zero() (UINodeKey){0}
- f32 start_x;
- TextAlignment alignment;
- Rect cell_rect;
-} TableIterator;
+#define ui_spacer(flags) ui_build_node_from_key(flags, ui_node_key_zero())
+#define ui_padw(v) UIPrefWidth(ui_px(v, 1.f)) ui_spacer(0)
+#define ui_padh(v) UIPrefHeight(ui_px(v, 1.f)) ui_spacer(0)
+#define ui_pads(v) UISize(ui_px(v, 1.f)) ui_spacer(0)
-function Vector2
-rl_v2(v2 a)
-{
- Vector2 result = {a.x, a.y};
- return result;
-}
+#define ui_dragging(s) (!!(s.flags & UISignalFlag_Dragging))
+#define ui_released(s) (!!(s.flags & UISignalFlag_Released))
+#define ui_pressed(s) (!!(s.flags & UISignalFlag_Pressed))
+#define ui_scrolled(s) (!!(s.flags & UISignalFlag_Scrolled))
-function Rectangle
-rl_rect(Rect a)
+function UIAlign
+ui_push_axis_alignment(Axis2 axis, UIAlign v)
{
- Rectangle result = {a.pos.x, a.pos.y, a.size.w, a.size.h};
+ UIAlign result = 0;
+ switch (axis) {
+ case Axis2_X:{result = ui_push_alignment_x(v);}break;
+ case Axis2_Y:{result = ui_push_alignment_y(v);}break;
+ InvalidDefaultCase;
+ }
return result;
}
-function BeamformerViewPlaneTag
-ui_plane_layout_from_normal(v3 normal)
+function UIAlign
+ui_pop_axis_alignment(Axis2 axis)
{
- BeamformerViewPlaneTag result = BeamformerViewPlaneTag_Arbitrary;
- b32 has_x = !f32_equal(normal.x, 0.0f);
- b32 has_y = !f32_equal(normal.y, 0.0f);
- b32 has_z = !f32_equal(normal.z, 0.0f);
- if ((has_x + has_y + has_z) == 1) {
- if (has_x) result = BeamformerViewPlaneTag_YZ;
- if (has_y) result = BeamformerViewPlaneTag_XZ;
- if (has_z) result = BeamformerViewPlaneTag_XY;
- assert(result != BeamformerViewPlaneTag_Arbitrary);
+ UIAlign result = 0;
+ switch (axis) {
+ case Axis2_X:{result = ui_pop_alignment_x();}break;
+ case Axis2_Y:{result = ui_pop_alignment_y();}break;
+ InvalidDefaultCase;
}
return result;
}
-function f32
-ui_blinker_update(UIBlinker *b, f32 scale)
+function UIAlign
+ui_push_alignment(UIAlign v)
{
- b->t += b->scale * dt_for_frame;
- if (b->t >= 1.0f) b->scale = -scale;
- if (b->t <= 0.0f) b->scale = scale;
- f32 result = b->t;
+ UIAlign result = ui_push_axis_alignment(ui_top_child_layout_axis(), v);
return result;
}
-function v2
-measure_glyph(Font font, u32 glyph)
+function UIAlign
+ui_pop_alignment(void)
{
- assert(glyph >= 0x20);
- v2 result = {.y = (f32)font.baseSize};
- /* NOTE: assumes font glyphs are ordered ASCII */
- result.x = (f32)font.glyphs[glyph - 0x20].advanceX;
- if (result.x == 0)
- result.x = (font.recs[glyph - 0x20].width + (f32)font.glyphs[glyph - 0x20].offsetX);
+ UIAlign result = ui_pop_axis_alignment(ui_top_child_layout_axis());
return result;
}
-function v2
-measure_text(Font font, s8 text)
+function UISize
+ui_push_axis_size(Axis2 axis, UISize v)
{
- v2 result = {.y = (f32)font.baseSize};
- for (iz i = 0; i < text.len; i++)
- result.x += measure_glyph(font, text.data[i]).x;
+ UISize result = {0};
+ switch (axis) {
+ case Axis2_X:{result = ui_push_semantic_width(v); }break;
+ case Axis2_Y:{result = ui_push_semantic_height(v);}break;
+ InvalidDefaultCase;
+ }
return result;
}
-function s8
-clamp_text_to_width(Font font, s8 text, f32 limit)
+function UISize
+ui_pop_axis_size(Axis2 axis)
{
- s8 result = text;
- f32 width = 0;
- for (iz i = 0; i < text.len; i++) {
- f32 next = measure_glyph(font, text.data[i]).w;
- if (width + next > limit) {
- result.len = i;
- break;
- }
- width += next;
+ UISize result = {0};
+ switch (axis) {
+ case Axis2_X:{result = ui_pop_semantic_width(); }break;
+ case Axis2_Y:{result = ui_pop_semantic_height();}break;
+ InvalidDefaultCase;
}
return result;
}
-function v2
-align_text_in_rect(s8 text, Rect r, Font font)
+function UISize
+ui_push_size(UISize v)
{
- v2 size = measure_text(font, text);
- v2 pos = v2_add(r.pos, v2_scale(v2_sub(r.size, size), 0.5));
- v2 result = clamp_v2_rect(pos, r);
+ UISize result = ui_push_axis_size(ui_top_child_layout_axis(), v);
return result;
}
-function Texture
-make_raylib_texture(BeamformerFrameView *v)
+function UISize
+ui_pop_size(void)
{
- Texture result;
- result.id = v->texture;
- result.width = v->colour_image.width;
- result.height = v->colour_image.height;
- result.mipmaps = v->colour_image.mip_map_levels;
- result.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
+ UISize result = ui_pop_axis_size(ui_top_child_layout_axis());
return result;
}
-function void
-stream_append_variable(Stream *s, Variable *var)
+#define ui_node_key_nil(k) (ui_node_key_equal((k), ui_node_key_zero()))
+#define ui_node_hot(n) (ui_node_key_equal((n)->key, ui_context->hot_node_key))
+function b32
+ui_node_key_equal(UINodeKey a, UINodeKey b)
{
- switch (var->type) {
- case VT_UI_BUTTON:
- case VT_GROUP:{ stream_append_s8(s, var->name); }break;
- case VT_F32:{ stream_append_f64(s, var->real32, 100); }break;
- case VT_B32:{ stream_append_s8(s, var->bool32 ? s8("True") : s8("False")); }break;
- case VT_SCALED_F32:{ stream_append_f64(s, var->scaled_real32.val, 100); }break;
- case VT_BEAMFORMER_VARIABLE:{
- BeamformerVariable *bv = &var->beamformer_variable;
- stream_append_f64(s, *bv->store * bv->display_scale, 100);
- }break;
- case VT_CYCLER:{
- u32 index = *var->cycler.state;
- if (var->cycler.labels) stream_append_s8(s, var->cycler.labels[index]);
- else stream_append_u64(s, index);
- }break;
- case VT_LIVE_CONTROLS_STRING:{
- BeamformerLiveImagingParameters *lip = var->generic;
- stream_append_s8(s, (s8){.data = (u8 *)lip->save_name_tag, .len = lip->save_name_tag_length});
- if (lip->save_name_tag_length <= 0) stream_append_s8(s, s8("Tag..."));
- }break;
- InvalidDefaultCase;
- }
+ b32 result = a.value == b.value;
+ return result;
}
-function void
-stream_append_variable_group(Stream *s, Variable *var)
+function UINodeKey
+ui_node_ancestor_key(void)
{
- switch (var->type) {
- case VT_GROUP:{
- switch (var->group.kind) {
- case VariableGroupKind_Vector:{
- Variable *v = var->group.first;
- stream_append_s8(s, s8("{"));
- while (v) {
- stream_append_variable(s, v);
- v = v->next;
- if (v) stream_append_s8(s, s8(", "));
- }
- stream_append_s8(s, s8("}"));
- }break;
- InvalidDefaultCase;
- }
- }break;
- InvalidDefaultCase;
- }
+ UINode *node = ui_top_parent();
+ while (!ui_node_is_nil(node) && ui_node_key_equal(node->key, ui_node_key_zero()))
+ node = node->parent;
+ UINodeKey result = node->key;
+ return result;
}
-function s8
-push_acquisition_kind(Stream *s, BeamformerAcquisitionKind kind, u32 transmit_count, BeamformerContrastMode contrast_mode)
+function Rect
+ui_node_rect(UINode *node)
{
- s8 name = beamformer_acquisition_kind_strings[kind];
- b32 fixed_transmits = beamformer_acquisition_kind_has_fixed_transmits[kind];
- if (kind >= BeamformerAcquisitionKind_Count || kind < 0) {
- fixed_transmits = 0;
- name = s8("Invalid");
- }
-
- stream_append_s8(s, name);
- if (!fixed_transmits) {
- stream_append_byte(s, '-');
- stream_append_u64(s, transmit_count);
- }
-
- if (contrast_mode != BeamformerContrastMode_None)
- stream_append_s8s(s, s8(" ("), s8_from_str8(beamformer_contrast_mode_strings[contrast_mode]), s8(")"));
-
- return stream_to_s8(s);
+ Rect result = {0};
+ result.size = (v2){{node->computed_size[0], node->computed_size[1]}};
+ result.pos = (v2){{node->computed_position[0], node->computed_position[1]}};
+ return result;
}
-function s8
-push_custom_view_title(Stream *s, Variable *var)
+function f32
+ui_alignment_correction(UIAlign alignment, f32 delta)
{
- switch (var->type) {
- case VT_COMPUTE_STATS_VIEW:{
- stream_append_s8(s, s8("Compute Stats: "));
- stream_append_variable(s, var->compute_stats_view.cycler);
- }break;
- case VT_COMPUTE_PROGRESS_BAR:{
- stream_append_s8(s, s8("Compute Progress: "));
- stream_append_f64(s, 100 * *var->compute_progress_bar.progress, 100);
- stream_append_byte(s, '%');
- } break;
- case VT_BEAMFORMER_FRAME_VIEW:{
- BeamformerFrameView *bv = var->generic;
- stream_append_s8(s, s8("Frame View"));
- switch (bv->kind) {
- case BeamformerFrameViewKind_Copy:{ stream_append_s8(s, s8(": Copy [")); }break;
- case BeamformerFrameViewKind_Latest:{
- #define X(plane, id, pretty) s8_comp(": " pretty " ["),
- read_only local_persist s8 labels[BeamformerViewPlaneTag_Count + 1] = {
- BEAMFORMER_VIEW_PLANE_TAG_LIST
- s8_comp(": Live [")
- };
- #undef X
- stream_append_s8(s, labels[*bv->cycler->cycler.state % (BeamformerViewPlaneTag_Count + 1)]);
- }break;
- case BeamformerFrameViewKind_3DXPlane:{ stream_append_s8(s, s8(": 3D X-Plane")); }break;
- InvalidDefaultCase;
- }
- if (bv->kind != BeamformerFrameViewKind_3DXPlane) {
- stream_append_hex_u64(s, bv->frame.id);
- stream_append_byte(s, ']');
- }
- }break;
+ f32 result = 0;
+ switch (alignment) {
InvalidDefaultCase;
+ case UIAlign_Left:{ result = 0; }break;
+ case UIAlign_Center:{result = 0.5f * delta;}break;
+ case UIAlign_Right:{ result = delta; }break;
}
- return stream_to_s8(s);
+ return result;
}
-#define table_new(a, init, ...) table_new_(a, init, arg_list(TextAlignment, ##__VA_ARGS__))
-function Table *
-table_new_(Arena *a, i32 initial_capacity, TextAlignment *alignment, i32 columns)
+function v2
+ui_node_text_position(UINode *node)
{
- Table *result = push_struct(a, Table);
- da_reserve(a, result, initial_capacity);
- result->columns = columns;
- result->alignment = push_array(a, TextAlignment, columns);
- result->widths = push_array(a, f32, columns);
- result->cell_pad = (v2){{TABLE_CELL_PAD_WIDTH, TABLE_CELL_PAD_HEIGHT}};
- mem_copy(result->alignment, alignment, sizeof(*alignment) * (u32)columns);
+ Rect r = ui_node_rect(node);
+ v2 result = r.pos;
+ result.x += ui_alignment_correction(node->text_alignment, r.size.x - node->text_size.x);
+ result.y += (r.size.y - node->text_size.y) / 2.f;
return result;
}
-function i32
-table_skip_rows(Table *t, f32 draw_height, f32 text_height)
+function void
+ui_disable_cursor(void)
{
- i32 max_rows = (i32)(draw_height / (text_height + t->cell_pad.h));
- i32 result = t->rows - MIN(t->rows, max_rows);
- return result;
+ HideCursor();
+ DisableCursor();
+ /* wtf raylib */
+ SetMousePosition((i32)ui_context->current_mouse.x, (i32)ui_context->current_mouse.y);
}
-function TableIterator *
-table_iterator_new(Table *table, TableIteratorKind kind, Arena *a, i32 starting_row, v2 at, Font *font)
+function void
+ui_enable_cursor(void)
{
- TableIterator *result = push_struct(a, TableIterator);
- result->kind = kind;
- result->frame.table = table;
- result->frame.row_index = starting_row;
- result->start_x = at.x;
- result->cell_rect.size.h = (f32)font->baseSize;
- result->cell_rect.pos = v2_add(at, v2_scale(table->cell_pad, 0.5f));
- result->cell_rect.pos.y += (f32)(starting_row - 1) * (result->cell_rect.size.h + table->cell_pad.h + table->row_border_thick);
- da_reserve(a, &result->stack, 4);
- return result;
+ EnableCursor();
}
-function void *
-table_iterator_next(TableIterator *it, Arena *a)
+function Vector2
+rl_v2(v2 a)
{
- void *result = 0;
-
- if (!it->row || it->kind == TIK_ROWS) {
- for (;;) {
- TableRow *row = it->frame.table->data + it->frame.row_index++;
- if (it->frame.row_index <= it->frame.table->count) {
- if (row->kind == TRK_TABLE) {
- *da_push(a, &it->stack) = it->frame;
- it->frame = (TableStackFrame){.table = row->data};
- it->sub_table_depth++;
- } else {
- result = row;
- break;
- }
- } else if (it->stack.count) {
- it->frame = it->stack.data[--it->stack.count];
- it->sub_table_depth--;
- } else {
- break;
- }
- }
- Table *t = it->frame.table;
- it->row = result;
- it->column = 0;
- it->cell_rect.pos.x = it->start_x + t->cell_pad.w / 2 +
- it->cell_rect.size.h * it->sub_table_depth;
- it->cell_rect.pos.y += it->cell_rect.size.h + t->row_border_thick + t->cell_pad.h;
- }
-
- if (it->row && it->kind == TIK_CELLS) {
- Table *t = it->frame.table;
- i32 column = it->column++;
- it->cell_rect.pos.x += column > 0 ? it->cell_rect.size.w + t->cell_pad.w : 0;
- it->cell_rect.size.w = t->widths[column];
- it->alignment = t->alignment[column];
- result = (TableCell *)it->row->data + column;
-
- if (it->column == t->columns)
- it->row = 0;
- }
+ Vector2 result = {a.x, a.y};
+ return result;
+}
+function Rectangle
+rl_rect(Rect a)
+{
+ Rectangle result = {a.pos.x, a.pos.y, a.size.w, a.size.h};
return result;
}
function f32
-table_width(Table *t)
+beamformer_ui_blinker_update(BeamformerUIBlinker *b, f32 scale)
{
- f32 result = 0;
- i32 valid = 0;
- for (i32 i = 0; i < t->columns; i++) {
- result += t->widths[i];
- if (t->widths[i] > 0) valid++;
- }
- result += t->cell_pad.w * (f32)valid;
- result += MAX(0, ((f32)valid - 1)) * t->column_border_thick;
+ b->t += b->scale * dt_for_frame;
+ if (b->t >= 1.0f) b->scale = -scale;
+ if (b->t <= 0.0f) b->scale = scale;
+ f32 result = b->t;
return result;
}
function v2
-table_extent(Table *t, Arena arena, Font *font)
+measure_glyph(Font font, u32 glyph)
{
- TableIterator *it = table_iterator_new(t, TIK_ROWS, &arena, 0, (v2){0}, font);
- for (TableRow *row = table_iterator_next(it, &arena);
- row;
- row = table_iterator_next(it, &arena))
- {
- for (i32 i = 0; i < it->frame.table->columns; i++) {
- TableCell *cell = (TableCell *)row->data + i;
- if (!cell->text.len && cell->var && cell->var->flags & V_RADIO_BUTTON) {
- cell->width = (f32)font->baseSize;
- } else {
- cell->width = measure_text(*font, cell->text).w;
- }
- it->frame.table->widths[i] = MAX(cell->width, it->frame.table->widths[i]);
- }
- }
-
- t->size = (v2){.x = table_width(t), .y = it->cell_rect.pos.y - t->cell_pad.h / 2};
- v2 result = t->size;
+ assert(glyph >= 0x20);
+ v2 result = {.y = (f32)font.baseSize};
+ /* NOTE: assumes font glyphs are ordered ASCII */
+ result.x = (f32)font.glyphs[glyph - 0x20].advanceX;
+ if (result.x == 0)
+ result.x = (font.recs[glyph - 0x20].width + (f32)font.glyphs[glyph - 0x20].offsetX);
return result;
}
function v2
-table_cell_align(TableCell *cell, TextAlignment align, Rect r)
+measure_text_tight(Font font, str8 text)
{
- v2 result = r.pos;
- if (r.size.w >= cell->width) {
- switch (align) {
- case TextAlignment_Left:{}break;
- case TextAlignment_Right:{ result.x += (r.size.w - cell->width); }break;
- case TextAlignment_Center:{ result.x += (r.size.w - cell->width) / 2; }break;
- }
+ v2 result = {0};
+ for (i64 i = 0; i < text.length; i++) {
+ assert(text.data[i] >= 0x20);
+ u8 glyph = text.data[i] - 0x20;
+ result.x += font.recs[glyph].width;
+ result.y = Max(font.recs[glyph].height, result.y);
}
return result;
}
-function TableCell
-table_variable_cell(Arena *a, Variable *var)
+function v2
+measure_text(Font font, str8 text)
{
- TableCell result = {.var = var, .kind = TableCellKind_Variable};
- if ((var->flags & V_RADIO_BUTTON) == 0) {
- Stream text = arena_stream(*a);
- stream_append_variable(&text, var);
- result.text = arena_stream_commit(a, &text);
- }
+ v2 result = {.y = (f32)font.baseSize};
+ for (i64 i = 0; i < text.length; i++)
+ result.x += measure_glyph(font, text.data[i]).x;
return result;
}
-function TableRow *
-table_push_row(Table *t, Arena *a, TableRowKind kind)
+function str8
+clamp_text_to_width(Font font, str8 text, f32 limit)
{
- TableRow *result = da_push(a, t);
- if (kind == TRK_CELLS) {
- result->data = push_array(a, TableCell, t->columns);
- /* NOTE(rnp): do not increase rows for an empty subtable */
- t->rows++;
+ str8 result = text;
+ f32 width = 0;
+ for (i64 i = 0; i < text.length; i++) {
+ f32 next = measure_glyph(font, text.data[i]).w;
+ if (width + next > limit) {
+ result.length = i;
+ break;
+ }
+ width += next;
}
- result->kind = kind;
return result;
}
-function TableRow *
-table_push_parameter_row(Table *t, Arena *a, s8 label, Variable *var, s8 suffix)
+function Texture
+make_raylib_texture(BeamformerFrameView *v)
{
- assert(t->columns >= 3);
- TableRow *result = table_push_row(t, a, TRK_CELLS);
- TableCell *cells = result->data;
-
- cells[0].text = label;
- cells[1] = table_variable_cell(a, var);
- cells[2].text = suffix;
-
+ Texture result;
+ result.id = v->texture;
+ result.width = v->colour_image.width;
+ result.height = v->colour_image.height;
+ result.mipmaps = v->colour_image.mip_map_levels;
+ result.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
return result;
}
-#define table_begin_subtable(t, a, ...) table_begin_subtable_(t, a, arg_list(TextAlignment, ##__VA_ARGS__))
-function Table *
-table_begin_subtable_(Table *table, Arena *a, TextAlignment *alignment, i32 columns)
+function str8
+push_acquisition_kind(Arena *arena, BeamformerAcquisitionKind kind, u32 transmit_count, BeamformerContrastMode contrast_mode)
{
- TableRow *row = table_push_row(table, a, TRK_TABLE);
- Table *result = row->data = table_new_(a, 0, alignment, columns);
- result->parent = table;
- return result;
-}
+ str8 name = str8("Invalid");
+ b32 fixed_transmits = 0;
+ if Between(kind, 0, BeamformerAcquisitionKind_Count - 1) {
+ name = beamformer_acquisition_kind_strings[kind];
+ fixed_transmits = beamformer_acquisition_kind_has_fixed_transmits[kind];
+ }
-function Table *
-table_end_subtable(Table *table)
-{
- Table *result = table->parent ? table->parent : table;
- return result;
+ Stream sb = arena_stream(*arena);
+ stream_append_s8(&sb, s8_from_str8(name));
+ if (!fixed_transmits) {
+ stream_append_byte(&sb, '-');
+ stream_append_u64(&sb, transmit_count);
+ }
+
+ if (contrast_mode != BeamformerContrastMode_None)
+ stream_append_s8s(&sb, s8(" ("), s8_from_str8(beamformer_contrast_mode_strings[contrast_mode]), s8(")"));
+
+ s8 result = arena_stream_commit(arena, &sb);
+ return str8_from_s8(result);
}
function void
@@ -1017,600 +830,97 @@ resize_frame_view(BeamformerFrameView *view, uv2 dim)
}
function void
-ui_beamformer_frame_view_release_subresources(BeamformerUI *ui, BeamformerFrameView *bv, BeamformerFrameViewKind kind)
+beamformer_ui_frame_view_release_subresources(BeamformerFrameView *bv, BeamformerFrameViewKind kind)
{
if (kind == BeamformerFrameViewKind_Copy)
vk_buffer_release(&bv->copy_buffer);
-
- if (kind != BeamformerFrameViewKind_3DXPlane) {
- if (bv->axial_scale_bar.scale_bar.savepoint_stack)
- SLLPushFreelist(bv->axial_scale_bar.scale_bar.savepoint_stack, ui->scale_bar_savepoint_freelist);
- if (bv->lateral_scale_bar.scale_bar.savepoint_stack)
- SLLPushFreelist(bv->lateral_scale_bar.scale_bar.savepoint_stack, ui->scale_bar_savepoint_freelist);
- }
}
function void
-ui_variable_free(BeamformerUI *ui, Variable *var)
+beamformer_ui_frame_view_copy_frame(BeamformerFrameView *new, BeamformerFrameView *old)
{
- if (var) {
- var->parent = 0;
- while (var) {
- if (var->type == VT_GROUP) {
- var = var->group.first;
- } else {
- if (var->type == VT_BEAMFORMER_FRAME_VIEW) {
- /* TODO(rnp): instead there should be a way of linking these up */
- BeamformerFrameView *bv = var->generic;
- ui_beamformer_frame_view_release_subresources(ui, bv, bv->kind);
- DLLRemove(0, ui->view_first, ui->view_last, bv, next, prev);
- SLLPushFreelist(bv, ui->view_freelist);
- }
+ memory_copy(&new->frame, &old->frame, sizeof(old->frame));
- Variable *dead = var;
- if (var->next) {
- var = var->next;
- } else {
- var = var->parent;
- /* NOTE(rnp): when we assign parent here we have already
- * released the children. Assign type so we don't loop */
- if (var) var->type = VT_NULL;
- }
- SLLPushFreelist(dead, ui->variable_freelist);
- }
- }
- }
-}
-
-function void
-ui_variable_free_group_items(BeamformerUI *ui, Variable *group)
-{
- assert(group->type == VT_GROUP);
- /* NOTE(rnp): prevent traversal back to us */
- group->group.last->parent = 0;
- ui_variable_free(ui, group->group.first);
- group->group.first = group->group.last = 0;
-}
+ iv3 points = new->frame.points;
+ i64 frame_size = points.x * points.y * points.z * beamformer_data_kind_byte_size[new->frame.data_kind];
-function void
-ui_view_free(BeamformerUI *ui, Variable *view)
-{
- assert(view->type == VT_UI_VIEW);
- ui_variable_free(ui, view->view.child);
- ui_variable_free(ui, view->view.close);
- ui_variable_free(ui, view->view.menu);
- ui_variable_free(ui, view);
-}
+ Stream sb = arena_stream(ui_context->arena);
+ stream_append_s8(&sb, s8("Frame Copy ["));
+ stream_append_hex_u64(&sb, new->frame.id);
+ stream_append_s8(&sb, s8("]"));
+ stream_append_byte(&sb, 0);
-function Variable *
-fill_variable(Variable *var, Variable *group, s8 name, u32 flags, VariableType type, Font font)
-{
- var->flags = flags;
- var->type = type;
- var->name = name;
- var->parent = group;
- var->name_width = measure_text(font, name).x;
-
- if (group && group->type == VT_GROUP) {
- if (group->group.last) group->group.last = group->group.last->next = var;
- else group->group.last = group->group.first = var;
- }
+ GPUBufferAllocateInfo allocate_info = {
+ .size = frame_size,
+ .flags = VulkanUsageFlag_TransferDestination,
+ .label = stream_to_str8(&sb),
+ };
+ vk_buffer_allocate(&new->copy_buffer, &allocate_info);
- return var;
+ GPUBuffer *backlog = beamformer_context->compute_context.backlog.buffer;
+ VulkanHandle cmd = vk_command_begin(VulkanTimeline_Compute);
+ vk_command_wait_timeline(cmd, VulkanTimeline_Compute, old->frame.timeline_valid_value);
+ vk_command_copy_buffer(cmd, &new->copy_buffer, backlog, old->frame.buffer_offset, frame_size);
+ new->frame.timeline_valid_value = vk_command_end(cmd, (VulkanHandle){0}, (VulkanHandle){0});
}
-function Variable *
-add_variable(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, u32 flags,
- VariableType type, Font font)
+function BeamformerFrameView *
+beamformer_ui_frame_view_new(BeamformerFrameViewKind kind)
{
- Variable *result = SLLPopFreelist(ui->variable_freelist);
- if (!result) result = push_struct_no_zero(arena, Variable);
+ BeamformerFrameView *old = (BeamformerFrameView *)beamformer_registers()->frame_view;
+ BeamformerFrameView *result = SLLPopFreelist(ui_context->view_freelist);
+ if (!result) result = push_struct_no_zero(&ui_context->arena, typeof(*result));
zero_struct(result);
- return fill_variable(result, group, name, flags, type, font);
-}
-
-function Variable *
-add_variable_group(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, VariableGroupKind kind, Font font)
-{
- Variable *result = add_variable(ui, group, arena, name, V_INPUT, VT_GROUP, font);
- result->group.kind = kind;
- return result;
-}
-
-function Variable *
-end_variable_group(Variable *group)
-{
- assert(group->type == VT_GROUP);
- return group->parent;
-}
-
-function void
-fill_variable_cycler(Variable *cycler, u32 *store, s8 *labels, u32 cycle_count)
-{
- cycler->cycler.cycle_length = cycle_count;
- cycler->cycler.state = store;
- cycler->cycler.labels = labels;
-}
-
-function Variable *
-add_variable_cycler(BeamformerUI *ui, Variable *group, Arena *arena, u32 flags, Font font, s8 name,
- u32 *store, s8 *labels, u32 cycle_count)
-{
- Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_CYCLER, font);
- fill_variable_cycler(result, store, labels, cycle_count);
- return result;
-}
-
-function Variable *
-add_button(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, UIButtonID id,
- u32 flags, Font font)
-{
- Variable *result = add_variable(ui, group, arena, name, V_INPUT|flags, VT_UI_BUTTON, font);
- result->button = id;
- return result;
-}
-
-function Variable *
-add_ui_split(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, f32 fraction,
- RegionSplitDirection direction, Font font)
-{
- Variable *result = add_variable(ui, parent, arena, name, V_HIDES_CURSOR, VT_UI_REGION_SPLIT, font);
- result->region_split.direction = direction;
- result->region_split.fraction = fraction;
- return result;
-}
-
-function Variable *
-add_global_menu_to_group(BeamformerUI *ui, Arena *arena, Variable *group)
-{
- #define X(id, text) add_button(ui, group, arena, s8(text), UI_BID_ ##id, 0, ui->small_font);
- GLOBAL_MENU_BUTTONS
- #undef X
- return group;
-}
-
-function Variable *
-add_global_menu(BeamformerUI *ui, Arena *arena, Variable *parent)
-{
- Variable *result = add_variable_group(ui, 0, arena, s8(""), VariableGroupKind_List, ui->small_font);
- result->parent = parent;
- return add_global_menu_to_group(ui, arena, result);
-}
-
-function Variable *
-add_ui_view(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, u32 view_flags, b32 menu, b32 closable)
-{
- Variable *result = add_variable(ui, parent, arena, name, 0, VT_UI_VIEW, ui->small_font);
- UIView *view = &result->view;
- view->flags = view_flags;
- if (menu) view->menu = add_global_menu(ui, arena, result);
- if (closable) {
- view->close = add_button(ui, 0, arena, s8(""), UI_BID_VIEW_CLOSE, 0, ui->small_font);
- /* NOTE(rnp): we do this explicitly so that close doesn't end up in the view group */
- view->close->parent = result;
- }
- return result;
-}
-
-function Variable *
-add_floating_view(BeamformerUI *ui, Arena *arena, VariableType type, v2 at, Variable *child, b32 closable)
-{
- Variable *result = add_ui_view(ui, 0, arena, s8(""), UIViewFlag_Floating, 0, closable);
- result->type = type;
- result->view.rect.pos = at;
- result->view.child = child;
-
- result->parent = &ui->floating_widget_sentinal;
- result->next = ui->floating_widget_sentinal.next;
- result->next->parent = result;
- ui->floating_widget_sentinal.next = result;
- return result;
-}
-
-function void
-fill_beamformer_variable(Variable *var, s8 suffix, f32 *store, v2 limits, f32 display_scale, f32 scroll_scale)
-{
- BeamformerVariable *bv = &var->beamformer_variable;
- bv->suffix = suffix;
- bv->store = store;
- bv->display_scale = display_scale;
- bv->scroll_scale = scroll_scale;
- bv->limits = limits;
-}
-
-function void
-add_beamformer_variable(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, s8 suffix, f32 *store,
- v2 limits, f32 display_scale, f32 scroll_scale, u32 flags, Font font)
-{
- Variable *var = add_variable(ui, group, arena, name, flags, VT_BEAMFORMER_VARIABLE, font);
- fill_beamformer_variable(var, suffix, store, limits, display_scale, scroll_scale);
-}
-
-function Variable *
-add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx)
-{
- BeamformerUI *ui = ctx->ui;
- BeamformerUIParameters *bp = &ui->params;
-
- v2 v2_inf = {.x = -F32_INFINITY, .y = F32_INFINITY};
-
- /* TODO(rnp): this can be closable once we have a way of opening new views */
- Variable *result = add_ui_view(ui, parent, &ui->arena, s8("Parameters"), 0, 1, 0);
- Variable *group = result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0,
- VT_GROUP, ui->font);
-
- add_beamformer_variable(ui, group, &ui->arena, s8("Sampling Frequency:"), s8("[MHz]"),
- &bp->sampling_frequency, (v2){0}, 1e-6f, 0, 0, ui->font);
-
- add_beamformer_variable(ui, group, &ui->arena, s8("Demodulation Frequency:"), s8("[MHz]"),
- &bp->demodulation_frequency, (v2){.y = 100e6f}, 1e-6f, 0, 0, ui->font);
-
- add_beamformer_variable(ui, group, &ui->arena, s8("Speed of Sound:"), s8("[m/s]"),
- &bp->speed_of_sound, (v2){.y = 1e6f}, 1.0f, 10.0f,
- V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font);
-
- group = add_variable_group(ui, group, &ui->arena, s8("Lateral Extent:"),
- VariableGroupKind_Vector, ui->font);
- {
- add_beamformer_variable(ui, group, &ui->arena, s8("Min:"), s8("[mm]"),
- &ui->min_coordinate.x, v2_inf, 1e3f, 0.5e-3f,
- V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font);
-
- add_beamformer_variable(ui, group, &ui->arena, s8("Max:"), s8("[mm]"),
- &ui->max_coordinate.x, v2_inf, 1e3f, 0.5e-3f,
- V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font);
- }
- group = end_variable_group(group);
-
- group = add_variable_group(ui, group, &ui->arena, s8("Axial Extent:"),
- VariableGroupKind_Vector, ui->font);
- {
- add_beamformer_variable(ui, group, &ui->arena, s8("Min:"), s8("[mm]"),
- &ui->min_coordinate.y, v2_inf, 1e3f, 0.5e-3f,
- V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font);
-
- add_beamformer_variable(ui, group, &ui->arena, s8("Max:"), s8("[mm]"),
- &ui->max_coordinate.y, v2_inf, 1e3f, 0.5e-3f,
- V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font);
- }
- group = end_variable_group(group);
-
- add_beamformer_variable(ui, group, &ui->arena, s8("Off Axis Position:"), s8("[mm]"),
- &ui->off_axis_position, v2_inf, 1e3f, 0.5e-3f,
- V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font);
-
- add_beamformer_variable(ui, group, &ui->arena, s8("Beamform Plane:"), s8(""),
- &ui->beamform_plane, (v2){{0, 1.0f}}, 1.0f, 0.025f,
- V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font);
-
- add_beamformer_variable(ui, group, &ui->arena, s8("F#:"), s8(""), &bp->f_number, (v2){.y = 1e3f},
- 1, 0.1f, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font);
-
- add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Interpolation:"),
- &bp->interpolation_mode, beamformer_interpolation_mode_strings,
- countof(beamformer_interpolation_mode_strings));
+ DLLInsertLast(0, ui_context->view_first, ui_context->view_last, result, next, prev);
- read_only local_persist s8 true_false_labels[] = {s8_comp("False"), s8_comp("True")};
- add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Coherency Weighting:"),
- &bp->coherency_weighting, true_false_labels, countof(true_false_labels));
+ result->export_handle.value[0] = OSInvalidHandleValue;
- return result;
-}
+ result->kind = kind;
+ result->dirty = 1;
-function void
-ui_beamformer_frame_view_convert(BeamformerUI *ui, Arena *arena, Variable *view, Variable *menu,
- BeamformerFrameViewKind kind, BeamformerFrameView *old, b32 log_scale)
-{
- assert(menu->group.first == menu->group.last && menu->group.first == 0);
- assert(view->type == VT_BEAMFORMER_FRAME_VIEW);
-
- BeamformerFrameView *bv = view->generic;
- bv->kind = kind;
- bv->dirty = 1;
-
- fill_variable(&bv->dynamic_range, view, s8("Dynamic Range:"), V_INPUT|V_TEXT|V_UPDATE_VIEW,
- VT_F32, ui->small_font);
- fill_variable(&bv->threshold, view, s8("Threshold:"), V_INPUT|V_TEXT|V_UPDATE_VIEW,
- VT_F32, ui->small_font);
- fill_variable(&bv->gamma, view, s8("Gamma:"), V_INPUT|V_TEXT|V_UPDATE_VIEW,
- VT_SCALED_F32, ui->small_font);
-
- bv->dynamic_range.real32 = old? old->dynamic_range.real32 : 50.0f;
- bv->threshold.real32 = old? old->threshold.real32 : 55.0f;
- bv->gamma.scaled_real32.val = old? old->gamma.scaled_real32.val : 1.0f;
- bv->gamma.scaled_real32.scale = old? old->gamma.scaled_real32.scale : 0.05f;
- bv->min_coordinate = old ? m4_mul_v4(old->frame.voxel_transform, (v4){{0.0f, 0.0f, 0.0f, 1.0f}}).xyz
- : (v3){0};
- bv->max_coordinate = old ? m4_mul_v4(old->frame.voxel_transform, (v4){{1.0f, 1.0f, 1.0f, 1.0f}}).xyz
- : (v3){0};
-
- #define X(_t, pretty) s8_comp(pretty),
- read_only local_persist s8 kind_labels[] = {BEAMFORMER_FRAME_VIEW_KIND_LIST};
- #undef X
- bv->kind_cycler = add_variable_cycler(ui, menu, arena, V_EXTRA_ACTION, ui->small_font,
- s8("Kind:"), (u32 *)&bv->kind, kind_labels, countof(kind_labels));
+ result->log_scale = old? old->log_scale : 0;
+ result->dynamic_range = old? old->dynamic_range : 50.0f;
+ result->threshold = old? old->threshold : 55.0f;
+ result->gamma = old? old->gamma : 1.0f;
/* TODO(rnp): this is quite dumb. what we actually want is to render directly
* into the view region with the appropriate size for that region (scissor) */
- resize_frame_view(bv, (uv2){{FRAME_VIEW_RENDER_TARGET_SIZE}});
+ resize_frame_view(result, (uv2){{FRAME_VIEW_RENDER_TARGET_SIZE}});
switch (kind) {
- case BeamformerFrameViewKind_3DXPlane:{
- view->flags |= V_HIDES_CURSOR;
- glTextureParameteri(bv->texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTextureParameteri(bv->texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
- fill_variable(bv->x_plane_shifts + 0, view, s8("XZ Shift"), V_INPUT|V_HIDES_CURSOR,
- VT_X_PLANE_SHIFT, ui->small_font);
- fill_variable(bv->x_plane_shifts + 1, view, s8("YZ Shift"), V_INPUT|V_HIDES_CURSOR,
- VT_X_PLANE_SHIFT, ui->small_font);
- bv->demo = add_variable(ui, menu, arena, s8("Demo Mode"), V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font);
- }break;
default:{
- view->flags &= ~(u32)V_HIDES_CURSOR;
- fill_variable(&bv->lateral_scale_bar, view, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font);
- fill_variable(&bv->axial_scale_bar, view, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font);
- ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar;
- ScaleBar *axial = &bv->axial_scale_bar.scale_bar;
- lateral->direction = SB_LATERAL;
- axial->direction = SB_AXIAL;
- lateral->scroll_scale = (v2){{-0.5e-3f, 0.5e-3f}};
- axial->scroll_scale = (v2){{ 0, 1.0e-3f}};
- lateral->zoom_starting_coord = F32_INFINITY;
- axial->zoom_starting_coord = F32_INFINITY;
-
b32 copy = kind == BeamformerFrameViewKind_Copy;
- v3 N = (v3){.y = 1.0f};
- if (old) N = cross(old->frame.voxel_transform.c[0].xyz, old->frame.voxel_transform.c[1].xyz);
-
- BeamformerViewPlaneTag plane = ui_plane_layout_from_normal(v3_normalize(N));
- switch (plane) {
- case BeamformerViewPlaneTag_XY:{
- lateral->min_value = copy ? &bv->min_coordinate.x : &ui->min_coordinate.x;
- lateral->max_value = copy ? &bv->max_coordinate.x : &ui->max_coordinate.x;
- axial->min_value = copy ? &bv->min_coordinate.y : &ui->min_coordinate.y;
- axial->max_value = copy ? &bv->max_coordinate.y : &ui->max_coordinate.y;
- }break;
-
- case BeamformerViewPlaneTag_XZ:{
- lateral->min_value = copy ? &bv->min_coordinate.x : &ui->min_coordinate.x;
- lateral->max_value = copy ? &bv->max_coordinate.x : &ui->max_coordinate.x;
- axial->min_value = copy ? &bv->min_coordinate.z : &ui->min_coordinate.y;
- axial->max_value = copy ? &bv->max_coordinate.z : &ui->max_coordinate.y;
- }break;
-
- case BeamformerViewPlaneTag_YZ:{
- lateral->min_value = copy ? &bv->min_coordinate.y : &ui->min_coordinate.x;
- lateral->max_value = copy ? &bv->max_coordinate.y : &ui->max_coordinate.x;
- axial->min_value = copy ? &bv->min_coordinate.z : &ui->min_coordinate.y;
- axial->max_value = copy ? &bv->max_coordinate.z : &ui->max_coordinate.y;
- }break;
-
- default:{
- lateral->min_value = copy ? &bv->min_coordinate.x : &ui->min_coordinate.x;
- lateral->max_value = copy ? &bv->max_coordinate.x : &ui->max_coordinate.x;
- axial->min_value = copy ? &bv->min_coordinate.z : &ui->min_coordinate.y;
- axial->max_value = copy ? &bv->max_coordinate.z : &ui->max_coordinate.y;
- }break;
- }
-
- #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, 0, ui->small_font);
- FRAME_VIEW_BUTTONS
- #undef X
-
- bv->axial_scale_bar_active = add_variable(ui, menu, arena, s8("Axial Scale Bar"),
- V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font);
- bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"),
- V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font);
-
- if (kind == BeamformerFrameViewKind_Latest) {
- bv->axial_scale_bar_active->bool32 = 1;
- bv->lateral_scale_bar_active->bool32 = 1;
- bv->axial_scale_bar.flags |= V_CAUSES_COMPUTE;
- bv->lateral_scale_bar.flags |= V_CAUSES_COMPUTE;
- }
+ result->scale_bar_active[0] = copy ? old->scale_bar_active[0] : 1;
+ result->scale_bar_active[1] = copy ? old->scale_bar_active[1] : 1;
}break;
- }
-
- bv->log_scale = add_variable(ui, menu, arena, s8("Log Scale"),
- V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON, VT_B32, ui->small_font);
- bv->log_scale->bool32 = log_scale;
-
- switch (kind) {
- case BeamformerFrameViewKind_Latest:{
- #define X(_type, _id, pretty) s8_comp(pretty),
- read_only local_persist s8 labels[] = {BEAMFORMER_VIEW_PLANE_TAG_LIST s8_comp("Any")};
- #undef X
- bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Live:"),
- &bv->cycler_state, labels, countof(labels));
- bv->cycler_state = BeamformerViewPlaneTag_Count;
+ case BeamformerFrameViewKind_3DXPlane:{
+ glTextureParameteri(result->texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTextureParameteri(result->texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ result->demo = 1;
+ result->plane_drag_index = -1;
+ result->plane_active[BeamformerViewPlaneTag_XZ] = 1;
+ result->plane_active[BeamformerViewPlaneTag_YZ] = 1;
}break;
- default:{}break;
}
- add_global_menu_to_group(ui, arena, menu);
-}
-
-function BeamformerFrameView *
-ui_beamformer_frame_view_new(BeamformerUI *ui, Arena *arena)
-{
- BeamformerFrameView *result = SLLPopFreelist(ui->view_freelist);
- if (!result) result = push_struct_no_zero(arena, typeof(*result));
- zero_struct(result);
- result->export_handle.value[0] = OSInvalidHandleValue;
- DLLInsertLast(0, ui->view_first, ui->view_last, result, next, prev);
- return result;
-}
-
-function Variable *
-add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena,
- BeamformerFrameViewKind kind, b32 closable, BeamformerFrameView *old)
-{
- /* TODO(rnp): this can be always closable once we have a way of opening new views */
- Variable *result = add_ui_view(ui, parent, arena, s8(""), UIViewFlag_CustomText, 1, closable);
- Variable *var = result->view.child = add_variable(ui, result, arena, s8(""), 0,
- VT_BEAMFORMER_FRAME_VIEW, ui->small_font);
- Variable *menu = result->view.menu = add_variable_group(ui, 0, arena, s8(""),
- VariableGroupKind_List, ui->small_font);
- menu->parent = result;
- var->generic = ui_beamformer_frame_view_new(ui, arena);
- ui_beamformer_frame_view_convert(ui, arena, var, menu, kind, old, old? old->log_scale->bool32 : 0);
- return result;
-}
-
-function Variable *
-add_compute_progress_bar(Variable *parent, BeamformerCtx *ctx)
-{
- BeamformerUI *ui = ctx->ui;
- /* TODO(rnp): this can be closable once we have a way of opening new views */
- Variable *result = add_ui_view(ui, parent, &ui->arena, s8(""), UIViewFlag_CustomText, 1, 0);
- result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0,
- VT_COMPUTE_PROGRESS_BAR, ui->small_font);
- ComputeProgressBar *bar = &result->view.child->compute_progress_bar;
- bar->progress = &ctx->compute_context.processing_progress;
- bar->processing = &ctx->compute_context.processing_compute;
-
- return result;
-}
-
-function Variable *
-add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, BeamformerCtx *ctx)
-{
- /* TODO(rnp): this can be closable once we have a way of opening new views */
- Variable *result = add_ui_view(ui, parent, arena, s8(""), UIViewFlag_CustomText, 0, 0);
- result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0,
- VT_COMPUTE_STATS_VIEW, ui->small_font);
-
- Variable *menu = result->view.menu = add_variable_group(ui, 0, arena, s8(""),
- VariableGroupKind_List, ui->small_font);
- menu->parent = result;
-
- #define X(_k, label) s8_comp(label),
- read_only local_persist s8 labels[] = {COMPUTE_STATS_VIEW_LIST};
- #undef X
-
- ComputeStatsView *csv = &result->view.child->compute_stats_view;
- csv->compute_shader_stats = ctx->compute_shader_stats;
- csv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Stats View:"),
- (u32 *)&csv->kind, labels, countof(labels));
- add_global_menu_to_group(ui, arena, menu);
- return result;
-}
-
-function Variable *
-add_live_controls_view(BeamformerUI *ui, Variable *parent, Arena *arena)
-{
- BeamformerLiveImagingParameters *lip = &ui->shared_memory->live_imaging_parameters;
- /* TODO(rnp): this can be closable once we have a way of opening new views */
- Variable *result = add_ui_view(ui, parent, &ui->arena, s8("Live Controls"), 0, 1, 0);
- result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0,
- VT_LIVE_CONTROLS_VIEW, ui->small_font);
- Variable *view = result->view.child;
- BeamformerLiveControlsView *lv = view->generic = push_struct(arena, typeof(*lv));
-
- Variable *amenu = &lv->acquisition_menu;
- fill_variable(amenu, view, s8(""), V_INPUT, VT_GROUP, ui->small_font);
- amenu->group.kind = VariableGroupKind_List;
-
- fill_variable(&lv->transmit_power, view, s8(""), V_INPUT|V_LIVE_CONTROL,
- VT_BEAMFORMER_VARIABLE, ui->small_font);
- fill_beamformer_variable(&lv->transmit_power, s8(""), &lip->transmit_power, (v2){{0, 1.0f}}, 100.0f, 0.05f);
-
- for (u32 i = 0; i < countof(lv->tgc_control_points); i++) {
- Variable *v = lv->tgc_control_points + i;
- fill_variable(v, view, s8(""), V_INPUT|V_LIVE_CONTROL, VT_BEAMFORMER_VARIABLE, ui->small_font);
- fill_beamformer_variable(v, s8(""), lip->tgc_control_points + i, (v2){{0, 1.0f}}, 0, 0.05f);
+ if (kind == BeamformerFrameViewKind_Copy) {
+ assert(old != 0);
+ beamformer_ui_frame_view_copy_frame(result, old);
}
- fill_variable(&lv->stop_button, view, s8("Stop Imaging"), V_INPUT|V_LIVE_CONTROL,
- VT_B32, ui->small_font);
-
- read_only local_persist s8 save_labels[] = {s8_comp("Save Data"), s8_comp("Saving...")};
- fill_variable(&lv->save_button, view, s8("Save Data"), V_INPUT|V_LIVE_CONTROL,
- VT_CYCLER, ui->small_font);
- fill_variable_cycler(&lv->save_button, &lip->save_active, save_labels, countof(save_labels));
-
- fill_variable(&lv->save_text, view, s8(""), V_INPUT|V_TEXT|V_LIVE_CONTROL,
- VT_LIVE_CONTROLS_STRING, ui->small_font);
- lv->save_text.generic = lip;
+ if (kind == BeamformerFrameViewKind_Latest)
+ result->view_plane = BeamformerViewPlaneTag_Count;
return result;
}
-function Variable *
-ui_split_region(BeamformerUI *ui, Variable *region, Variable *split_side, RegionSplitDirection direction)
-{
- Variable *result = add_ui_split(ui, region, &ui->arena, s8(""), 0.5, direction, ui->small_font);
- if (split_side == region->region_split.left) {
- region->region_split.left = result;
- } else {
- region->region_split.right = result;
- }
- split_side->parent = result;
- result->region_split.left = split_side;
- return result;
-}
-
-function void
-ui_add_live_frame_view(BeamformerUI *ui, Variable *view, RegionSplitDirection direction,
- BeamformerFrameViewKind kind)
-{
- Variable *region = view->parent;
- assert(region->type == VT_UI_REGION_SPLIT);
- assert(view->type == VT_UI_VIEW);
- Variable *new_region = ui_split_region(ui, region, view, direction);
- new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, kind, 1, 0);
-}
-
-function void
-ui_beamformer_frame_view_copy_frame(BeamformerUI *ui, BeamformerFrameView *new, BeamformerFrameView *old)
-{
- mem_copy(&new->frame, &old->frame, sizeof(old->frame));
-
- iv3 points = new->frame.points;
- i64 frame_size = points.x * points.y * points.z * beamformer_data_kind_byte_size[new->frame.data_kind];
-
- Stream sb = arena_stream(ui->arena);
- stream_append_s8(&sb, s8("Frame Copy ["));
- stream_append_hex_u64(&sb, new->frame.id);
- stream_append_s8(&sb, s8("]"));
- stream_append_byte(&sb, 0);
-
- GPUBufferAllocateInfo allocate_info = {
- .size = frame_size,
- .flags = VulkanUsageFlag_TransferDestination,
- .label = stream_to_str8(&sb),
- };
- vk_buffer_allocate(&new->copy_buffer, &allocate_info);
-
- GPUBuffer *backlog = ui->beamformer_context->compute_context.backlog.buffer;
- VulkanHandle cmd = vk_command_begin(VulkanTimeline_Compute);
- vk_command_wait_timeline(cmd, VulkanTimeline_Compute, old->frame.timeline_valid_value);
- vk_command_copy_buffer(cmd, &new->copy_buffer, backlog, old->frame.buffer_offset, frame_size);
- new->frame.timeline_valid_value = vk_command_end(cmd, (VulkanHandle){0}, (VulkanHandle){0});
-}
-
-function void
-ui_copy_frame(BeamformerUI *ui, Variable *view, RegionSplitDirection direction)
-{
- Variable *region = view->parent;
- assert(region->type == VT_UI_REGION_SPLIT);
- assert(view->type == VT_UI_VIEW);
-
- BeamformerFrameView *old = view->view.child->generic;
- Variable *new_region = ui_split_region(ui, region, view, direction);
- new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena,
- BeamformerFrameViewKind_Copy, 1, old);
-
- BeamformerFrameView *bv = new_region->region_split.right->view.child->generic;
- ui_beamformer_frame_view_copy_frame(ui, bv, old);
-}
-
function v3
-beamformer_frame_view_plane_size(BeamformerUI *ui, BeamformerFrameView *view)
+x_plane_display_size(BeamformerFrame *frame)
{
- assert(view->kind == BeamformerFrameViewKind_3DXPlane);
v3 result = {0};
- result.xy = v2_sub(ui->max_coordinate, ui->min_coordinate);
+ v2 min_2d, max_2d;
+ plane_corners_from_transform(frame->voxel_transform, &min_2d, &max_2d);
+ result.xy = v2_sub(max_2d, min_2d);
result.x = Max(1e-3f, result.x);
result.y = Max(1e-3f, result.y);
result.z = Max(1e-3f, result.z);
@@ -1626,72 +936,62 @@ x_plane_rotation_for_view_plane(BeamformerFrameView *view, BeamformerViewPlaneTa
return result;
}
-function v2
-normalized_p_in_rect(Rect r, v2 p, b32 invert_y)
-{
- v2 result = v2_div(v2_scale(v2_sub(p, r.pos), 2.0f), r.size);
- if (invert_y) result = (v2){{result.x - 1.0f, 1.0f - result.y}};
- else result = v2_sub(result, (v2){{1.0f, 1.0f}});
- return result;
-}
-
function v3
-x_plane_position(BeamformerUI *ui)
+x_plane_position(BeamformerFrame *frame)
{
- f32 y_min = ui->min_coordinate.y;
- f32 y_max = ui->max_coordinate.y;
+ v2 min_2d, max_2d;
+ plane_corners_from_transform(frame->voxel_transform, &min_2d, &max_2d);
+ f32 y_min = min_2d.y;
+ f32 y_max = max_2d.y;
v3 result = {.y = y_min + (y_max - y_min) / 2};
return result;
}
function v3
-offset_x_plane_position(BeamformerUI *ui, BeamformerFrameView *view, BeamformerViewPlaneTag tag)
+x_plane_offset_position(BeamformerFrameView *view, BeamformerFrame *frame, BeamformerViewPlaneTag tag)
{
- BeamformerLiveImagingParameters *li = &ui->shared_memory->live_imaging_parameters;
+ BeamformerLiveImagingParameters *li = &beamformer_context->shared_memory->live_imaging_parameters;
m4 x_rotation = m4_rotation_about_y(x_plane_rotation_for_view_plane(view, tag));
v3 Z = x_rotation.c[2].xyz;
v3 offset = v3_scale(Z, li->image_plane_offsets[tag]);
- v3 result = v3_add(x_plane_position(ui), offset);
+ v3 result = v3_add(x_plane_position(frame), offset);
return result;
}
function v3
-camera_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view)
+x_plane_camera(BeamformerFrame *frame)
{
- v3 size = beamformer_frame_view_plane_size(ui, view);
- v3 target = x_plane_position(ui);
- f32 dist = v2_magnitude(XY(size));
+ v3 size = x_plane_display_size(frame);
+ v3 target = x_plane_position(frame);
+ f32 dist = v2_magnitude(size.xy);
v3 result = v3_add(target, (v3){{dist, -0.5f * size.y * tan_f32(50.0f * PI / 180.0f), dist}});
return result;
}
function m4
-view_matrix_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view, v3 camera)
+x_plane_view_matrix(BeamformerFrame *frame, v3 camera)
{
- assert(view->kind == BeamformerFrameViewKind_3DXPlane);
- m4 result = camera_look_at(camera, x_plane_position(ui));
+ m4 result = camera_look_at(camera, x_plane_position(frame));
return result;
}
function m4
-projection_matrix_for_x_plane_view(BeamformerFrameView *view)
+x_plane_projection_matrix(f32 aspect)
{
- assert(view->kind == BeamformerFrameViewKind_3DXPlane);
- f32 aspect = (f32)view->colour_image.width / (f32)view->colour_image.height;
m4 result = perspective_projection(10e-3f, 500e-3f, 45.0f * PI / 180.0f, aspect);
return result;
}
function ray
-ray_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view, v2 uv)
+x_plane_raycast(BeamformerFrameView *view, BeamformerFrame *frame, v2 uv)
{
assert(view->kind == BeamformerFrameViewKind_3DXPlane);
- ray result = {.origin = camera_for_x_plane_view(ui, view)};
+ ray result = {.origin = x_plane_camera(frame)};
v4 ray_clip = {{uv.x, uv.y, -1.0f, 1.0f}};
/* TODO(rnp): combine these so we only do one matrix inversion */
- m4 proj_m = projection_matrix_for_x_plane_view(view);
- m4 view_m = view_matrix_for_x_plane_view(ui, view, result.origin);
+ m4 proj_m = x_plane_projection_matrix((f32)view->colour_image.width / (f32)view->colour_image.height);
+ m4 view_m = x_plane_view_matrix(frame, result.origin);
m4 proj_inv = m4_inverse(proj_m);
m4 view_inv = m4_inverse(view_m);
@@ -1703,130 +1003,111 @@ ray_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view, v2 uv)
return result;
}
-function BeamformerViewPlaneTag
-view_plane_tag_from_x_plane_shift(BeamformerFrameView *view, Variable *x_plane_shift)
-{
- assert(BETWEEN(x_plane_shift, view->x_plane_shifts + 0, view->x_plane_shifts + 1));
- BeamformerViewPlaneTag result = BeamformerViewPlaneTag_XZ;
- if (x_plane_shift == view->x_plane_shifts + 1)
- result = BeamformerViewPlaneTag_YZ;
- return result;
-}
-
function void
-render_single_xplane(BeamformerUI *ui, BeamformerFrameView *view, Variable *x_plane_shift,
- f32 rotation_turns, v3 translate, BeamformerViewPlaneTag tag,
- VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc)
-{
- GPUBuffer *beamformed_buffer = ui->beamformer_context->compute_context.backlog.buffer;
- pc->input_data = 0;
- if (ui->latest_plane_valid[tag]) {
- BeamformerFrame *f = ui->latest_plane + tag;
- pc->input_data = beamformed_buffer->gpu_pointer + f->buffer_offset;
- pc->input_size_x = f->points.x;
- pc->input_size_y = f->points.y;
- pc->input_size_z = f->points.z;
- pc->data_kind = f->data_kind;
- vk_command_wait_timeline(command, VulkanTimeline_Compute, f->timeline_valid_value);
- }
-
- v3 camera = camera_for_x_plane_view(ui, view);
- v3 scale = beamformer_frame_view_plane_size(ui, view);
-
- m4 model_transform = y_aligned_volume_transform(scale, translate, rotation_turns);
- m4 view_m = view_matrix_for_x_plane_view(ui, view, camera);
- m4 projection = projection_matrix_for_x_plane_view(view);
-
- //pc->mvp_matrix = m4_mul(m4_mul(model_transform, view_m), projection);
- pc->mvp_matrix = m4_mul(projection, m4_mul(view_m, model_transform));
- pc->bounding_box_colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, x_plane_shift->hover_t);
- pc->bounding_box_fraction = FRAME_VIEW_BB_FRACTION;
-
+render_single_xplane(BeamformerFrameView *view, BeamformerFrame *frame, v3 translate, f32 rotation_turns,
+ VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc, m4 vp_m, b32 drag_plane)
+{
+ GPUBuffer *beamformed_buffer = beamformer_context->compute_context.backlog.buffer;
+ pc->input_data = frame->timeline_valid_value ? beamformed_buffer->gpu_pointer + frame->buffer_offset : 0;
+ pc->input_size_x = frame->points.x;
+ pc->input_size_y = frame->points.y;
+ pc->input_size_z = frame->points.z;
+ pc->data_kind = frame->data_kind;
+ pc->mvp_matrix = m4_mul(vp_m, y_aligned_volume_transform(x_plane_display_size(frame), translate, rotation_turns));
+
+ vk_command_wait_timeline(command, VulkanTimeline_Compute, frame->timeline_valid_value);
vk_command_push_constants(command, 0, sizeof(*pc), pc);
- vk_command_draw(command, &ui->unit_cube_model.model);
+ vk_command_draw(command, &ui_context->unit_cube_model.model);
- XPlaneShift *xp = &x_plane_shift->x_plane_shift;
- v3 xp_delta = v3_sub(xp->end_point, xp->start_point);
- if (!f32_equal(v3_magnitude(xp_delta), 0)) {
+ v3 xp_delta = v3_sub(view->hit_test_point, view->hit_start_point);
+ if (drag_plane && !f32_equal(v3_magnitude_squared(xp_delta), 0)) {
m4 x_rotation = m4_rotation_about_y(rotation_turns);
v3 Z = x_rotation.c[2].xyz;
- v3 f = v3_scale(Z, v3_dot(Z, v3_sub(xp->end_point, xp->start_point)));
-
- /* TODO(rnp): there is no reason to compute the rotation matrix again */
- model_transform = y_aligned_volume_transform(scale, v3_add(f, translate), rotation_turns);
+ v3 f = v3_scale(Z, v3_dot(Z, xp_delta));
- pc->mvp_matrix = m4_mul(projection, m4_mul(view_m, model_transform));
+ pc->mvp_matrix = m4_mul(vp_m, y_aligned_volume_transform(x_plane_display_size(frame), v3_add(f, translate), rotation_turns));
pc->bounding_box_colour = HOVERED_COLOUR;
pc->bounding_box_fraction = 1.0f;
pc->input_data = 0;
vk_command_push_constants(command, 0, sizeof(*pc), pc);
- vk_command_draw(command, &ui->unit_cube_model.model);
+ vk_command_draw(command, &ui_context->unit_cube_model.model);
}
}
function void
-render_3D_xplane(BeamformerUI *ui, BeamformerFrameView *view, VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc)
+render_3d_xplane(BeamformerFrameView *view, VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc)
{
- if (view->demo->bool32) {
+ if (view->demo) {
view->rotation += dt_for_frame * 0.125f;
- if (view->rotation > 1.0f) view->rotation -= 1.0f;
+ if (view->rotation > 1.f) view->rotation -= 1.f;
}
- v3 model_translate = offset_x_plane_position(ui, view, BeamformerViewPlaneTag_XZ);
- render_single_xplane(ui, view, view->x_plane_shifts + 0,
- x_plane_rotation_for_view_plane(view, BeamformerViewPlaneTag_XZ),
- model_translate, BeamformerViewPlaneTag_XZ, command, pc);
- model_translate = offset_x_plane_position(ui, view, BeamformerViewPlaneTag_YZ);
- model_translate.y -= 0.0001f;
- render_single_xplane(ui, view, view->x_plane_shifts + 1,
- x_plane_rotation_for_view_plane(view, BeamformerViewPlaneTag_YZ),
- model_translate, BeamformerViewPlaneTag_YZ, command, pc);
+ u32 largest_plane_index = 0;
+ f32 largest_magnitude = 0.f;
+ for EachElement(view->plane_active, plane) if (view->plane_active[plane]) {
+ BeamformerFrame *frame = ui_context->latest_plane + plane;
+ f32 m = v3_magnitude_squared(x_plane_display_size(frame));
+ if (largest_magnitude < m) {
+ largest_magnitude = m;
+ largest_plane_index = plane;
+ }
+ }
+
+ BeamformerFrame *frame = ui_context->latest_plane + largest_plane_index;
+ m4 projection = x_plane_projection_matrix((f32)view->colour_image.width / (f32)view->colour_image.height);
+ m4 view_m = camera_look_at(x_plane_camera(frame), x_plane_position(frame));
+ m4 vp_m = m4_mul(projection, view_m);
+
+ for EachElement(view->plane_active, plane) {
+ frame = ui_context->latest_plane + plane;
+ if (view->plane_active[plane] && frame->timeline_valid_value) {
+ pc->bounding_box_fraction = FRAME_VIEW_BB_FRACTION;
+ pc->bounding_box_colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, view->hot_t[plane]);
+ f32 rotation = x_plane_rotation_for_view_plane(view, plane);
+ v3 translate = x_plane_offset_position(view, frame, plane);
+ render_single_xplane(view, frame, translate, rotation, command, pc, vp_m, (i32)plane == view->plane_drag_index);
+ }
+ }
}
function void
-render_2D_plane(BeamformerUI *ui, BeamformerFrameView *view, VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc)
+render_2d_plane(BeamformerFrameView *view, VulkanHandle command, BeamformerRenderBeamformedPushConstants *pc)
{
m4 view_m = m4_identity();
m4 model = m4_scale((v3){{2.0f, 2.0f, 0.0f}});
m4 projection = orthographic_projection(0, 1, 1, 1);
- GPUBuffer *beamformed_buffer = ui->beamformer_context->compute_context.backlog.buffer;
+ GPUBuffer *beamformed_buffer = beamformer_context->compute_context.backlog.buffer;
pc->mvp_matrix = m4_mul(m4_mul(model, view_m), projection);
- pc->input_data = beamformed_buffer->gpu_pointer + view->frame.buffer_offset,
- pc->input_size_x = view->frame.points.x,
- pc->input_size_y = view->frame.points.y,
- pc->input_size_z = view->frame.points.z,
- pc->data_kind = view->frame.data_kind,
+ pc->input_data = beamformed_buffer->gpu_pointer + view->frame.buffer_offset;
+ pc->input_size_x = view->frame.points.x;
+ pc->input_size_y = view->frame.points.y;
+ pc->input_size_z = view->frame.points.z;
+ pc->data_kind = view->frame.data_kind;
vk_command_wait_timeline(command, VulkanTimeline_Compute, view->frame.timeline_valid_value);
vk_command_push_constants(command, 0, sizeof(*pc), pc);
- vk_command_draw(command, &ui->unit_cube_model.model);
-}
-
-function b32
-frame_view_ready_to_present(BeamformerUI *ui, BeamformerFrameView *view)
-{
- b32 result = view->colour_image.width > 0 || view->colour_image.height > 0;
- return result;
+ vk_command_draw(command, &ui_context->unit_cube_model.model);
}
function b32
view_update(BeamformerUI *ui, BeamformerFrameView *view)
{
if (view->kind == BeamformerFrameViewKind_Latest) {
- u32 index = *view->cycler->cycler.state;
- view->dirty |= view->frame.timeline_valid_value != ui->latest_plane[index].timeline_valid_value;
- mem_copy(&view->frame, ui->latest_plane + index, sizeof(view->frame));
- if (view->dirty) {
- view->min_coordinate = m4_mul_v4(view->frame.voxel_transform, (v4){{0.0f, 0.0f, 0.0f, 1.0f}}).xyz;
- view->max_coordinate = m4_mul_v4(view->frame.voxel_transform, (v4){{1.0f, 1.0f, 1.0f, 1.0f}}).xyz;
- }
+ BeamformerFrame *frame;
+ if (view->view_plane == BeamformerViewPlaneTag_Count)
+ frame = beamformer_frame_from_index(beamformer_registers()->frame);
+ else
+ frame = ui->latest_plane + view->view_plane;
+
+ view->dirty |= view->frame.timeline_valid_value != frame->timeline_valid_value;
+ memory_copy(&view->frame, frame, sizeof(view->frame));
}
/* TODO(rnp): x-z or y-z */
// TODO(rnp): how to track this now? use pipeline handle value?
- view->dirty |= ui->beamformer_context->render_shader_updated;
+ view->dirty |= beamformer_context->render_shader_updated;
view->dirty |= view->kind == BeamformerFrameViewKind_3DXPlane;
b32 result = view->dirty;
@@ -1840,9 +1121,9 @@ update_frame_views(BeamformerUI *ui, Rect window)
if (view_update(ui, view)) {
BeamformerRenderBeamformedPushConstants pc = {
.bounding_box_colour = FRAME_VIEW_BB_COLOUR,
- .db_cutoff = view->log_scale->bool32 ? view->dynamic_range.real32 : 0,
- .threshold = view->threshold.real32,
- .gamma = view->gamma.scaled_real32.val,
+ .db_cutoff = view->log_scale ? view->dynamic_range : 0,
+ .threshold = view->threshold,
+ .gamma = view->gamma,
.positions = ui->unit_cube_model.model.gpu_pointer,
.normals = ui->unit_cube_model.model.gpu_pointer + ui->unit_cube_model.normals_offset,
};
@@ -1857,9 +1138,9 @@ update_frame_views(BeamformerUI *ui, Rect window)
vk_command_viewport(cmd, view->colour_image.width, view->colour_image.height, 0, 0, 0.0f, 1.0f);
vk_command_scissor(cmd, view->colour_image.width, view->colour_image.height, 0, 0);
if (view->kind == BeamformerFrameViewKind_3DXPlane) {
- render_3D_xplane(ui, view, cmd, &pc);
+ render_3d_xplane(view, cmd, &pc);
} else {
- render_2D_plane(ui, view, cmd, &pc);
+ render_2d_plane(view, cmd, &pc);
}
vk_command_end_rendering(cmd);
vk_command_end(cmd, ui->render_semaphores[0], ui->render_semaphores[1]);
@@ -1867,7 +1148,6 @@ update_frame_views(BeamformerUI *ui, Rect window)
glWaitSemaphoreEXT(ui->render_semaphores_gl[1], 0, 0, 1, &view->texture, (GLenum[]){GL_LAYOUT_COLOR_ATTACHMENT_EXT});
//end_renderdoc_capture();
-
view->dirty = 0;
}
}
@@ -1881,19 +1161,36 @@ colour_from_normalized(v4 rgba)
return result;
}
-function Color
-fade(Color a, f32 visibility)
+function void
+draw_text_tight(Font font, str8 text, v2 pos, Color colour)
{
- a.a = (u8)((f32)a.a * visibility);
- return a;
+ v2 off = v2_floor(pos);
+ for (i64 i = 0; i < text.length; i++) {
+ /* NOTE: assumes font glyphs are ordered ASCII */
+ i32 idx = text.data[i] - 0x20;
+ Rectangle dst = {
+ off.x, off.y,
+ font.recs[idx].width,
+ font.recs[idx].height,
+ };
+ Rectangle src = {
+ font.recs[idx].x,
+ font.recs[idx].y,
+ font.recs[idx].width,
+ font.recs[idx].height,
+ };
+ DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour);
+
+ off.x += (f32)font.recs[idx].width;
+ }
}
function v2
-draw_text_base(Font font, s8 text, v2 pos, Color colour)
+draw_text_base(Font font, str8 text, v2 pos, Color colour)
{
v2 off = v2_floor(pos);
f32 glyph_pad = (f32)font.glyphPadding;
- for (iz i = 0; i < text.len; i++) {
+ for (i64 i = 0; i < text.length; i++) {
/* NOTE: assumes font glyphs are ordered ASCII */
i32 idx = text.data[i] - 0x20;
Rectangle dst = {
@@ -1920,7 +1217,7 @@ draw_text_base(Font font, s8 text, v2 pos, Color colour)
/* NOTE(rnp): expensive but of the available options in raylib this gives the best results */
function v2
-draw_outlined_text(s8 text, v2 pos, TextSpec *ts)
+draw_outlined_text(str8 text, v2 pos, TextSpec *ts)
{
f32 ow = ts->outline_thick;
Color outline = colour_from_normalized(ts->outline_colour);
@@ -1936,7 +1233,7 @@ draw_outlined_text(s8 text, v2 pos, TextSpec *ts)
}
function v2
-draw_text(s8 text, v2 pos, TextSpec *ts)
+draw_text(str8 text, v2 pos, TextSpec *ts)
{
if (ts->flags & TF_ROTATED) {
rlPushMatrix();
@@ -1947,15 +1244,15 @@ draw_text(s8 text, v2 pos, TextSpec *ts)
v2 result = measure_text(*ts->font, text);
/* TODO(rnp): the size of this should be stored for each font */
- s8 ellipsis = s8("...");
+ str8 ellipsis = str8("...");
b32 clamped = ts->flags & TF_LIMITED && result.w > ts->limits.size.w;
if (clamped) {
f32 ellipsis_width = measure_text(*ts->font, ellipsis).x;
if (ellipsis_width < ts->limits.size.w) {
text = clamp_text_to_width(*ts->font, text, ts->limits.size.w - ellipsis_width);
} else {
- text.len = 0;
- ellipsis.len = 0;
+ text.length = 0;
+ ellipsis.length = 0;
}
}
@@ -1975,2116 +1272,3691 @@ draw_text(s8 text, v2 pos, TextSpec *ts)
return result;
}
-function Rect
-extend_rect_centered(Rect r, v2 delta)
+function b32
+point_in_rect(v2 p, Rect r)
{
- r.size.w += delta.x;
- r.size.h += delta.y;
- r.pos.x -= delta.x / 2;
- r.pos.y -= delta.y / 2;
- return r;
+ v2 end = v2_add(r.pos, r.size);
+ b32 result = Between(p.x, r.pos.x, end.x) & Between(p.y, r.pos.y, end.y);
+ return result;
}
-function Rect
-shrink_rect_centered(Rect r, v2 delta)
+function v3
+world_point_from_plane_uv(m4 world, v2 uv)
{
- delta.x = MIN(delta.x, r.size.w);
- delta.y = MIN(delta.y, r.size.h);
- r.size.w -= delta.x;
- r.size.h -= delta.y;
- r.pos.x += delta.x / 2;
- r.pos.y += delta.y / 2;
- return r;
+ v3 U = world.c[0].xyz;
+ v3 V = world.c[1].xyz;
+ v3 min = world.c[3].xyz;
+ v3 result = v3_add(v3_add(v3_scale(U, uv.x), v3_scale(V, uv.y)), min);
+ return result;
}
-function Rect
-scale_rect_centered(Rect r, v2 scale)
+function v2
+screen_point_to_world_2d(v2 p, v2 screen_min, v2 screen_max, v2 world_min, v2 world_max)
{
- Rect or = r;
- r.size.w *= scale.x;
- r.size.h *= scale.y;
- r.pos.x += (or.size.w - r.size.w) / 2;
- r.pos.y += (or.size.h - r.size.h) / 2;
- return r;
-}
+ v2 pixels_to_m = v2_div(v2_sub(world_max, world_min), v2_sub(screen_max, screen_min));
+ v2 result = v2_add(v2_mul(v2_sub(p, screen_min), pixels_to_m), world_min);
+ return result;
+}
-function b32
-interactions_equal(Interaction a, Interaction b)
+function v2
+world_point_to_screen_2d(v2 p, v2 world_min, v2 world_max, v2 screen_min, v2 screen_max)
{
- b32 result = (a.kind == b.kind) && (a.generic == b.generic);
+ v2 m_to_pixels = v2_div(v2_sub(screen_max, screen_min), v2_sub(world_max, world_min));
+ v2 result = v2_add(v2_mul(v2_sub(p, world_min), m_to_pixels), screen_min);
return result;
}
-function b32
-interaction_is_sticky(Interaction a)
+function void
+draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts)
{
- b32 result = a.kind == InteractionKind_Text || a.kind == InteractionKind_Ruler;
- return result;
+ // TODO(rnp): merge this into draw function, tons of duplicate code
+ v2 vr_max_p = v2_add(view_rect.pos, view_rect.size);
+
+ v3 U = view->frame.voxel_transform.c[0].xyz;
+ v3 V = view->frame.voxel_transform.c[1].xyz;
+ v3 min = view->frame.voxel_transform.c[3].xyz;
+
+ v3 end = view->ruler.end;
+ if (view->ruler.state != RulerState_Hold)
+ end = world_point_from_plane_uv(view->frame.voxel_transform, rect_uv(ui_context->current_mouse, view_rect));
+
+ v2 start_uv = plane_uv(v3_sub(view->ruler.start, min), U, V);
+ v2 end_uv = plane_uv(v3_sub(end, min), U, V);
+
+ v2 start_p = v2_add(view_rect.pos, v2_mul(start_uv, view_rect.size));
+ v2 end_p = v2_add(view_rect.pos, v2_mul(end_uv, view_rect.size));
+
+ b32 start_in_bounds = point_in_rect(start_p, view_rect);
+ b32 end_in_bounds = point_in_rect(end_p, view_rect);
+
+ // TODO(rnp): this should be a ray intersection not a clamp
+ start_p = clamp_v2_rect(start_p, view_rect);
+ end_p = clamp_v2_rect(end_p, view_rect);
+
+ Color rl_colour = colour_from_normalized(ts.colour);
+ DrawLineEx(rl_v2(end_p), rl_v2(start_p), 2, rl_colour);
+ if (start_in_bounds) DrawCircleV(rl_v2(start_p), 3, rl_colour);
+ if (end_in_bounds) DrawCircleV(rl_v2(end_p), 3, rl_colour);
+
+ Stream buf = arena_stream(a);
+ stream_append_f64(&buf, 1e3 * v3_magnitude(v3_sub(end, view->ruler.start)), 100);
+ stream_append_s8(&buf, s8(" mm"));
+
+ s8 s = stream_to_s8(&buf);
+ v2 txt_p = start_p;
+ v2 txt_s = measure_text(*ts.font, str8_from_s8(s));
+ v2 pixel_delta = v2_sub(start_p, end_p);
+ if (pixel_delta.y < 0) txt_p.y -= txt_s.y;
+ if (pixel_delta.x < 0) txt_p.x -= txt_s.x;
+ if (txt_p.x < view_rect.pos.x) txt_p.x = view_rect.pos.x;
+ if (txt_p.x + txt_s.x > vr_max_p.x) txt_p.x -= (txt_p.x + txt_s.x) - vr_max_p.x;
+
+ draw_text(str8_from_s8(s), txt_p, &ts);
}
-function b32
-interaction_is_hot(BeamformerUI *ui, Interaction a)
+function void
+ui_event_consume(BeamformerInput *input, BeamformerInputEvent *current)
+{
+ BeamformerUI *ui = ui_context;
+ BeamformerInputEvent *last = input->event_queue + input->event_count - 1;
+ if Between(current, input->event_queue, last) {
+ u64 index = current - input->event_queue;
+ u64 bin = index / (sizeof(ui->input_consumed[0]) * 8);
+ u64 bit = index % (sizeof(ui->input_consumed[0]) * 8);
+ ui->input_consumed[bin] |= (1 << bit);
+ }
+}
+
+function BeamformerInputEvent *
+ui_event_next(BeamformerInput *input, BeamformerInputEvent *current)
{
- b32 result = interactions_equal(ui->hot_interaction, a);
+ BeamformerUI *ui = ui_context;
+ BeamformerInputEvent *result = 0, *last = input->event_queue + input->event_count - 1;
+
+ current++;
+ current = Max(current, input->event_queue);
+
+ for (; !result && Between(current, input->event_queue, last); current++) {
+ u64 index = current - input->event_queue;
+ u64 bin = index / (sizeof(ui->input_consumed[0]) * 8);
+ u64 bit = index % (sizeof(ui->input_consumed[0]) * 8);
+
+ if (!(ui->input_consumed[bin] & (1 << bit)) &&
+ (current->kind == BeamformerInputEventKind_ButtonPress ||
+ current->kind == BeamformerInputEventKind_ButtonRelease ||
+ current->kind == BeamformerInputEventKind_MouseScroll))
+ {
+ result = current;
+ }
+ }
return result;
}
-function b32
-point_in_rect(v2 p, Rect r)
+function UINode *
+ui_node_from_key(UINodeKey key)
{
- v2 end = v2_add(r.pos, r.size);
- b32 result = Between(p.x, r.pos.x, end.x) & Between(p.y, r.pos.y, end.y);
+ UINodeHashBucket *hb = ui_context->node_hash_table + (key.value % UI_HASH_TABLE_COUNT);
+ UINode *result = &ui_node_nil;
+
+ for (UINode *b = hb->first; !ui_node_is_nil(b); b = b->hash_next) {
+ if (ui_node_key_equal(b->key, key)) {
+ result = b;
+ break;
+ }
+ }
+
return result;
}
-function v2
-rect_uv(v2 p, Rect r)
+function str8
+ui_draw_part_from_key_string(str8 string)
{
- v2 result = v2_div(v2_sub(p, r.pos), r.size);
+ str8 result = string;
+ i64 index = str8_find_needle(string, str8("##"), 0);
+ if (index < string.length)
+ result.length = index;
return result;
}
-function v3
-world_point_from_plane_uv(m4 world, v2 uv)
+function str8
+ui_hash_part_from_key_string(str8 string)
{
- v3 U = world.c[0].xyz;
- v3 V = world.c[1].xyz;
- v3 min = world.c[3].xyz;
- v3 result = v3_add(v3_add(v3_scale(U, uv.x), v3_scale(V, uv.y)), min);
+ str8 result = string;
+ // NOTE(rnp): for xxx###yyy only use the ###yyy otherwise the whole string is hashed
+ i64 index = str8_find_needle(string, str8("###"), 0);
+ if (index < string.length)
+ result = str8_skip(string, index);
return result;
}
-function v2
-screen_point_to_world_2d(v2 p, v2 screen_min, v2 screen_max, v2 world_min, v2 world_max)
+function UINodeKey
+ui_key_from_string(str8 string, UINodeKey seed)
{
- v2 pixels_to_m = v2_div(v2_sub(world_max, world_min), v2_sub(screen_max, screen_min));
- v2 result = v2_add(v2_mul(v2_sub(p, screen_min), pixels_to_m), world_min);
+ UINodeKey result = {0};
+ if (string.length > 0) {
+ str8 hash_string = ui_hash_part_from_key_string(string);
+ result.value = u64_hash_from_str8_seed(hash_string, seed.value);
+ }
return result;
}
-function v2
-world_point_to_screen_2d(v2 p, v2 world_min, v2 world_max, v2 screen_min, v2 screen_max)
+function Font
+ui_font_for_node(UINode *node)
{
- v2 m_to_pixels = v2_div(v2_sub(screen_max, screen_min), v2_sub(world_max, world_min));
- v2 result = v2_add(v2_mul(v2_sub(p, world_min), m_to_pixels), screen_min);
+ Font result = node->font_size > 28.0f ? ui_context->font : ui_context->small_font;
return result;
}
function b32
-hover_interaction(BeamformerUI *ui, v2 mouse, Interaction interaction)
+ui_number_conversion_f64(str8 s, f64 *out_value)
+{
+ b32 result = 0;
+ NumberConversion number = number_from_str8(s);
+ if (number.result == NumberConversionResult_Success) {
+ result = 1;
+ if (number.kind == NumberConversionKind_Float)
+ *out_value = number.F64;
+ else
+ *out_value = (f64)number.S64;
+ }
+ return result;
+}
+
+function iv2
+ui_text_input_cursor_range(void)
+{
+ UITextInputState *tis = &ui_context->text_input_state;
+ iv2 range;
+ range.x = Min(tis->cursor, tis->mark);
+ range.y = Max(tis->cursor, tis->mark);
+ return range;
+}
+
+function str8
+ui_text_input_string(void)
{
- Variable *var = interaction.var;
- b32 result = point_in_rect(mouse, interaction.rect);
- if (result) ui->next_interaction = interaction;
- if (interaction_is_hot(ui, interaction)) var->hover_t += HOVER_SPEED * dt_for_frame;
- else var->hover_t -= HOVER_SPEED * dt_for_frame;
- var->hover_t = CLAMP01(var->hover_t);
+ UITextInputState *tis = &ui_context->text_input_state;
+ str8 result = {.data = tis->buffer, .length = tis->count};
return result;
}
-function void
-draw_close_button(BeamformerUI *ui, Variable *close, v2 mouse, Rect r, v2 x_scale)
+function str8
+ui_text_input_last_string(void)
{
- assert(close->type == VT_UI_BUTTON);
- hover_interaction(ui, mouse, auto_interaction(r, close));
-
- Color colour = colour_from_normalized(v4_lerp(MENU_CLOSE_COLOUR, FG_COLOUR, close->hover_t));
- r = scale_rect_centered(r, x_scale);
- DrawLineEx(rl_v2(r.pos), rl_v2(v2_add(r.pos, r.size)), 4, colour);
- DrawLineEx(rl_v2(v2_add(r.pos, (v2){.x = r.size.w})),
- rl_v2(v2_add(r.pos, (v2){.y = r.size.h})), 4, colour);
+ UITextInputState *tis = &ui_context->text_input_state;
+ str8 result = {.data = tis->last_buffer, .length = tis->last_count};
+ return result;
}
function Rect
-draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mouse)
+ui_text_input_rect(void)
{
- assert(ui_view->type == VT_UI_VIEW);
- UIView *view = &ui_view->view;
-
- s8 title = ui_view->name;
- if (view->flags & UIViewFlag_CustomText) {
- Stream buf = arena_stream(arena);
- push_custom_view_title(&buf, ui_view->view.child);
- title = arena_stream_commit(&arena, &buf);
- }
-
- Rect result, title_rect;
- cut_rect_vertical(r, (f32)ui->small_font.baseSize + TITLE_BAR_PAD, &title_rect, &result);
- cut_rect_vertical(result, LISTING_LINE_PAD, 0, &result);
+ Rect result = ui_node_rect(ui_node_from_key(ui_context->text_input_state.node_key));
+ f32 text_box_slop = 4.0f;
+ result.pos.x -= text_box_slop;
+ result.size.x += 2 * text_box_slop;
+ return result;
+}
- DrawRectangleRec(rl_rect(title_rect), BLACK);
+function i32
+ui_text_input_index_from_point(f32 point)
+{
+ i32 result = 0;
- title_rect = shrink_rect_centered(title_rect, (v2){.x = 1.5f * TITLE_BAR_PAD});
- DrawRectangleRounded(rl_rect(title_rect), 0.5f, 0, fade(colour_from_normalized(BG_COLOUR), 0.55f));
- title_rect = shrink_rect_centered(title_rect, (v2){.x = 3.0f * TITLE_BAR_PAD});
+ // TODO(rnp): visible range, extended virtual rect which exactly fits the visible text
+ UITextInputState *tis = &ui_context->text_input_state;
+ Rect r = ui_text_input_rect();
- if (view->close) {
- Rect close;
- cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &close);
- draw_close_button(ui, view->close, mouse, close, (v2){{0.4f, 0.4f}});
- }
+ Font font = ui_font_for_node(ui_node_from_key(tis->node_key));
- if (view->menu) {
- Rect menu;
- cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &menu);
- Interaction interaction = {.kind = InteractionKind_Menu, .var = view->menu, .rect = menu};
- hover_interaction(ui, mouse, interaction);
-
- Color colour = colour_from_normalized(v4_lerp(MENU_PLUS_COLOUR, FG_COLOUR, view->menu->hover_t));
- menu = shrink_rect_centered(menu, (v2){{14.0f, 14.0f}});
- DrawLineEx(rl_v2(v2_add(menu.pos, (v2){.x = menu.size.w / 2})),
- rl_v2(v2_add(menu.pos, (v2){.x = menu.size.w / 2, .y = menu.size.h})), 4, colour);
- DrawLineEx(rl_v2(v2_add(menu.pos, (v2){.y = menu.size.h / 2})),
- rl_v2(v2_add(menu.pos, (v2){.x = menu.size.w, .y = menu.size.h / 2})), 4, colour);
+ /* NOTE: extra offset to help with putting a cursor at idx 0 */
+ f32 pct = Clamp01((point - r.pos.x) / r.size.w);
+ f32 x_off = 10.0f, x_bounds = r.size.w * pct;
+ for (; result < tis->count && x_off < x_bounds; result++) {
+ /* NOTE: assumes font glyphs are ordered ASCII */
+ i32 idx = tis->buffer[result] - 0x20;
+ x_off += (f32)font.glyphs[idx].advanceX;
+ if (font.glyphs[idx].advanceX == 0)
+ x_off += font.recs[idx].width;
}
- v2 title_pos = title_rect.pos;
- title_pos.y += 0.5f * TITLE_BAR_PAD;
- TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED, .colour = FG_COLOUR,
- .limits.size = title_rect.size};
- draw_text(title, title_pos, &text_spec);
-
return result;
}
-/* TODO(rnp): once this has more callers decide if it would be better for this to take
- * an orientation rather than force CCW/right-handed */
function void
-draw_ruler(BeamformerUI *ui, Arena arena, v2 start_point, v2 end_point,
- f32 start_value, f32 end_value, f32 *markers, u32 marker_count,
- u32 segments, s8 suffix, v4 marker_colour, v4 txt_colour)
+ui_text_input_end(void)
{
- b32 draw_plus = SIGN(start_value) != SIGN(end_value);
-
- end_point = v2_sub(end_point, start_point);
- f32 rotation = atan2_f32(end_point.y, end_point.x) * 180 / PI;
-
- rlPushMatrix();
- rlTranslatef(start_point.x, start_point.y, 0);
- rlRotatef(rotation, 0, 0, 1);
-
- f32 inc = v2_magnitude(end_point) / (f32)segments;
- f32 value_inc = (end_value - start_value) / (f32)segments;
- f32 value = start_value;
-
- Stream buf = arena_stream(arena);
- v2 sp = {0}, ep = {.y = RULER_TICK_LENGTH};
- v2 tp = {{(f32)ui->small_font.baseSize / 2.0f, ep.y + RULER_TEXT_PAD}};
- TextSpec text_spec = {.font = &ui->small_font, .rotation = 90.0f, .colour = txt_colour, .flags = TF_ROTATED};
- Color rl_txt_colour = colour_from_normalized(txt_colour);
-
- for (u32 j = 0; j <= segments; j++) {
- DrawLineEx(rl_v2(sp), rl_v2(ep), 3, rl_txt_colour);
+ UITextInputState *tis = &ui_context->text_input_state;
- stream_reset(&buf, 0);
- if (draw_plus && value > 0) stream_append_byte(&buf, '+');
- stream_append_f64(&buf, value, Abs(value_inc) < 1 ? 100 : 10);
- stream_append_s8(&buf, suffix);
- draw_text(stream_to_s8(&buf), tp, &text_spec);
-
- value += value_inc;
- sp.x += inc;
- ep.x += inc;
- tp.x += inc;
- }
+ UINode *next_node = ui_node_from_key(tis->next_node_key);
+ str8 new_input_string = str8("");
+ if ((next_node->flags & UINodeFlag_TextInputClearOnStart) == 0)
+ new_input_string = ui_draw_part_from_key_string(next_node->string);
- Color rl_marker_colour = colour_from_normalized(marker_colour);
- ep.y += RULER_TICK_LENGTH;
- for (u32 i = 0; i < marker_count; i++) {
- if (markers[i] < F32_INFINITY) {
- ep.x = sp.x = markers[i];
- DrawLineEx(rl_v2(sp), rl_v2(ep), 3, rl_marker_colour);
- DrawCircleV(rl_v2(ep), 3, rl_marker_colour);
- }
- }
+ tis->cursor = tis->mark = 0;
+ tis->last_count = tis->count;
+ tis->count = Min(new_input_string.length, countof(tis->buffer));
+ tis->numeric = (next_node->flags & UINodeFlag_TextInputNumeric) != 0;
+ memory_copy(tis->last_buffer, tis->buffer, tis->last_count);
+ memory_copy(tis->buffer, new_input_string.data, tis->count);
- rlPopMatrix();
+ tis->last_node_key = tis->node_key;
+ tis->node_key = ui_node_key_zero();
}
function void
-do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect draw_rect,
- f32 start_value, f32 end_value, s8 suffix)
+ui_text_input_insert(str8 text)
{
- assert(scale_bar->type == VT_SCALE_BAR);
- ScaleBar *sb = &scale_bar->scale_bar;
-
- v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm"));
-
- Rect tick_rect = draw_rect;
- v2 start_pos = tick_rect.pos;
- v2 end_pos = tick_rect.pos;
- v2 relative_mouse = v2_sub(mouse, tick_rect.pos);
-
- f32 markers[2];
- u32 marker_count = 1;
-
- v2 world_zoom_point = {{sb->zoom_starting_coord, sb->zoom_starting_coord}};
- v2 screen_zoom_point = world_point_to_screen_2d(world_zoom_point,
- (v2){{*sb->min_value, *sb->min_value}},
- (v2){{*sb->max_value, *sb->max_value}},
- (v2){0}, tick_rect.size);
- u32 tick_count;
- if (sb->direction == SB_AXIAL) {
- tick_rect.size.x = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x;
- tick_count = (u32)(tick_rect.size.y / (1.5f * (f32)ui->small_font.baseSize));
- start_pos.y += tick_rect.size.y;
- markers[0] = tick_rect.size.y - screen_zoom_point.y;
- markers[1] = tick_rect.size.y - relative_mouse.y;
- } else {
- tick_rect.size.y = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x;
- tick_count = (u32)(tick_rect.size.x / (1.5f * (f32)ui->small_font.baseSize));
- end_pos.x += tick_rect.size.x;
- markers[0] = screen_zoom_point.x;
- markers[1] = relative_mouse.x;
- }
+ UITextInputState *tis = &ui_context->text_input_state;
+ iv2 cursor_range = ui_text_input_cursor_range();
+ i64 bytes_after_cursor = tis->count - cursor_range.y;
+ i64 remaining_length = ((i32)countof(tis->buffer) - cursor_range.x) - bytes_after_cursor;
+ i64 truncated_length = Min(remaining_length, text.length);
- if (hover_interaction(ui, mouse, auto_interaction(tick_rect, scale_bar)))
- marker_count = 2;
+ memory_move(tis->buffer + cursor_range.x + truncated_length,
+ tis->buffer + cursor_range.y, bytes_after_cursor);
+ memory_copy(tis->buffer + cursor_range.x, text.data, truncated_length);
- draw_ruler(ui, arena, start_pos, end_pos, start_value, end_value, markers, marker_count,
- tick_count, suffix, RULER_COLOUR, v4_lerp(FG_COLOUR, HOVERED_COLOUR, scale_bar->hover_t));
+ tis->count -= cursor_range.y - cursor_range.x;
+ tis->count += truncated_length;
+ tis->cursor = tis->mark = cursor_range.x + truncated_length;
}
-function v2
-draw_radio_button(BeamformerUI *ui, Variable *var, v2 at, v2 mouse, v4 base_colour, f32 size)
+function b32
+ui_text_input_update(BeamformerInput *input)
{
- assert(var->type == VT_B32);
- b32 value = var->bool32;
+ UITextInputState *tis = &ui_context->text_input_state;
- v2 result = (v2){.x = size, .y = size};
- Rect hover_rect = {.pos = at, .size = result};
- hover_rect.pos.y += 1;
- hover_interaction(ui, mouse, auto_interaction(hover_rect, var));
+ Arena scratch = *ui_build_arena();
+ Stream sb = arena_stream(scratch);
- hover_rect = shrink_rect_centered(hover_rect, (v2){{8.0f, 8.0f}});
- Rect inner = shrink_rect_centered(hover_rect, (v2){{4.0f, 4.0f}});
- v4 fill = v4_lerp(value? base_colour : (v4){0}, HOVERED_COLOUR, var->hover_t);
- DrawRectangleRoundedLinesEx(rl_rect(hover_rect), 0.2f, 0, 2, colour_from_normalized(base_colour));
- DrawRectangleRec(rl_rect(inner), colour_from_normalized(fill));
+ enum {
+ DeltaPicksSide = (1 << 0),
+ WordScan = (1 << 1),
+ Delete = (1 << 2),
+ KeepMark = (1 << 3),
+ Copy = (1 << 4),
+ Paste = (1 << 5),
+ };
- return result;
-}
+ i32 delta = 0;
+ u32 flags = 0;
-function f32
-draw_variable_slider(BeamformerUI *ui, Variable *var, Rect r, f32 fill, v4 fill_colour, v2 mouse)
-{
- f32 border_thick = 3.0f;
- f32 bar_height_frac = 0.8f;
- v2 bar_size = {{6.0f, bar_height_frac * r.size.y}};
+ b32 result = 0;
- Rect inner = shrink_rect_centered(r, (v2){{2.0f * border_thick, // NOTE(rnp): raylib jank
- MAX(0, 2.0f * (r.size.y - bar_size.y))}});
- Rect filled = inner;
- filled.size.w *= fill;
+ // NOTE(rnp): first pass, non uniform inputs
+ for (BeamformerInputEvent *event = ui_event_next(input, 0);
+ event;
+ event = ui_event_next(input, event))
+ {
+ b32 taken = 0;
- Rect bar;
- bar.pos = v2_add(r.pos, (v2){{fill * (r.size.w - bar_size.w), (1 - bar_height_frac) * 0.5f * r.size.y}});
- bar.size = bar_size;
- v4 bar_colour = v4_lerp(FG_COLOUR, FOCUSED_COLOUR, var->hover_t);
+ BeamformerInputModifiers mods = event->modifiers;
+ if (event->kind == BeamformerInputEventKind_ButtonPress) {
+ if (mods & BeamformerInputModifier_Control)
+ flags |= WordScan;
- hover_interaction(ui, mouse, auto_interaction(inner, var));
+ if (mods & BeamformerInputModifier_Shift)
+ flags |= KeepMark;
- DrawRectangleRec(rl_rect(filled), colour_from_normalized(fill_colour));
- DrawRectangleRoundedLinesEx(rl_rect(inner), 0.2f, 0, border_thick, BLACK);
- DrawRectangleRounded(rl_rect(bar), 0.6f, 1, colour_from_normalized(bar_colour));
+ switch (event->button_id) {
+ default:{}break;
+ case BeamformerButtonID_Escape:
+ case BeamformerButtonID_Enter:
+ {
+ taken = 1;
+ result = 1;
+ }break;
- return r.size.y;
-}
+ case BeamformerButtonID_A: if (mods & BeamformerInputModifier_Control) {
+ tis->cursor = 0;
+ tis->mark = tis->count;
+ taken = 1;
+ }break;
-function v2
-draw_fancy_button(BeamformerUI *ui, Variable *var, s8 label, Rect r, v4 border_colour, v2 mouse, TextSpec ts)
-{
- assert((f32)ts.font->baseSize <= r.size.h * 0.8f);
- f32 pad = 0.1f * r.size.h;
+ case BeamformerButtonID_C: if (mods & BeamformerInputModifier_Control) {
+ flags |= Copy;
+ taken = 1;
+ }break;
- v2 shadow_off = {{2.5f, 3.0f}};
- f32 border_thick = 3.0f;
- v2 border_size = v2_add((v2){{pad + 2.0f * border_thick, pad}}, shadow_off);
+ case BeamformerButtonID_V: if (mods & BeamformerInputModifier_Control) {
+ flags |= Paste;
+ taken = 1;
+ }break;
- Rect border = shrink_rect_centered(r, border_size);
- Rect inner = shrink_rect_centered(border, (v2){{pad, pad}});
+ case BeamformerButtonID_X: if (mods & BeamformerInputModifier_Control) {
+ flags |= Copy|Delete|KeepMark;
+ taken = 1;
+ }break;
- ts.limits.size = inner.size;
- hover_interaction(ui, mouse, auto_interaction(inner, var));
+ case BeamformerButtonID_Backspace:{
+ delta -= 1;
+ flags |= Delete|KeepMark;
+ taken = 1;
+ }break;
- border.pos = v2_add(border.pos, shadow_off);
+ case BeamformerButtonID_Delete:{
+ delta += 1;
+ flags |= Delete|KeepMark;
+ taken = 1;
+ }break;
- DrawRectangleRoundedLinesEx(rl_rect(border), 0.6f, 0, border_thick, fade(BLACK, 0.8f));
- border.pos = v2_sub(border.pos, shadow_off);
- DrawRectangleRounded(rl_rect(border), 0.6f, 1, colour_from_normalized(BG_COLOUR));
- DrawRectangleRoundedLinesEx(rl_rect(border), 0.6f, 0, border_thick, colour_from_normalized(border_colour));
+ case BeamformerButtonID_Left:{
+ delta -= 1;
+ flags |= DeltaPicksSide;
+ taken = 1;
+ }break;
- /* TODO(rnp): teach draw_text() about alignment */
- v2 at = align_text_in_rect(label, inner, *ts.font);
- at = v2_add(at, (v2){{3.0f, 3.0f}});
- v4 base_colour = ts.colour;
- ts.colour = (v4){{0, 0, 0, 0.8f}};
- draw_text(label, at, &ts);
+ case BeamformerButtonID_Right:{
+ delta += 1;
+ flags |= DeltaPicksSide;
+ taken = 1;
+ }break;
- at = v2_sub(at, (v2){{3.0f, 3.0f}});
- ts.colour = v4_lerp(base_colour, HOVERED_COLOUR, var->hover_t);
- draw_text(label, at, &ts);
+ }
- v2 result = v2_add(r.size, border_size);
- return result;
-}
+ if (!taken && event->codepoint) {
+ u32 cp = event->codepoint;
+ taken = !tis->numeric || (Between(cp, '0', '9') || (cp == '.') || (cp == '-' && tis->cursor == 0));
+ if (taken) stream_append_codepoint(&sb, event->codepoint);
+ }
+ }
-function v2
-draw_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, v4 base_colour, TextSpec text_spec)
-{
- v2 result;
- if (var->flags & V_RADIO_BUTTON) {
- result = draw_radio_button(ui, var, at, mouse, base_colour, (f32)text_spec.font->baseSize);
- } else {
- Stream buf = arena_stream(arena);
- stream_append_variable(&buf, var);
- s8 text = arena_stream_commit(&arena, &buf);
- result = measure_text(*text_spec.font, text);
-
- if (var->flags & V_INPUT) {
- Rect text_rect = {.pos = at, .size = result};
- text_rect = extend_rect_centered(text_rect, (v2){.x = 8});
- if (hover_interaction(ui, mouse, auto_interaction(text_rect, var)) && (var->flags & V_TEXT))
- ui->text_input_state.hot_font = text_spec.font;
- text_spec.colour = v4_lerp(base_colour, HOVERED_COLOUR, var->hover_t);
+ if (taken) ui_event_consume(input, event);
+ }
+
+ if (flags & Paste) {
+ str8 string;
+ string.data = os_get_clipboard_text(&string.length);
+ for (i64 it = 0; it < string.length; it++) {
+ u8 cp = string.data[it];
+ if (!tis->numeric || (Between(cp, '0', '9') || (cp == '.') || (cp == '-' && tis->cursor == 0)))
+ stream_append_byte(&sb, cp);
}
+ }
+
+ if (flags & Copy) {
+ str8 string = ui_text_input_string();
+ os_set_clipboard_text(string.data, string.length);
+ }
- draw_text(text, at, &text_spec);
+ if ((flags & Delete) && tis->mark != tis->cursor)
+ delta = 0;
+
+ // TODO(rnp): word selection
+ tis->mark += delta;
+ tis->mark = Clamp(tis->mark, 0, tis->count);
+
+ if (!(flags & KeepMark) && delta) {
+ i32 new_cursor = tis->mark;
+ if (flags & DeltaPicksSide) {
+ if (delta < 0) new_cursor = Min(tis->mark, tis->cursor);
+ if (delta > 0) new_cursor = Max(tis->mark, tis->cursor);
+ }
+ tis->mark = tis->cursor = new_cursor;
}
+
+ if ((flags & Delete) || sb.widx)
+ ui_text_input_insert(stream_to_str8(&sb));
+
+ if (flags || delta || sb.widx)
+ tis->blinker.t = 1.0;
+
return result;
}
function void
-draw_table_cell(BeamformerUI *ui, Arena arena, TableCell *cell, Rect cell_rect,
- TextAlignment alignment, TextSpec ts, v2 mouse)
+ui_context_menu_close(void)
{
- f32 x_off = cell_rect.pos.x;
- v2 cell_at = table_cell_align(cell, alignment, cell_rect);
- ts.limits.size.w -= (cell_at.x - x_off);
- cell_rect.size.w = MIN(ts.limits.size.w, cell_rect.size.w);
-
- /* TODO(rnp): push truncated text for hovering */
- switch (cell->kind) {
- case TableCellKind_None:{ draw_text(cell->text, cell_at, &ts); }break;
- case TableCellKind_Variable:{
- if (cell->var->flags & V_INPUT) {
- draw_variable(ui, arena, cell->var, cell_at, mouse, ts.colour, ts);
- } else if (cell->text.len) {
- draw_text(cell->text, cell_at, &ts);
- }
- }break;
- case TableCellKind_VariableGroup:{
- Variable *v = cell->var->group.first;
- f32 dw = draw_text(s8("{"), cell_at, &ts).x;
- while (v) {
- cell_at.x += dw;
- ts.limits.size.w -= dw;
- dw = draw_variable(ui, arena, v, cell_at, mouse, ts.colour, ts).x;
-
- v = v->next;
- if (v) {
- cell_at.x += dw;
- ts.limits.size.w -= dw;
- dw = draw_text(s8(", "), cell_at, &ts).x;
- }
- }
- cell_at.x += dw;
- ts.limits.size.w -= dw;
- draw_text(s8("}"), cell_at, &ts);
- }break;
+ ui_context->context_menu_next_anchor_key = ui_node_key_zero();
+ ui_context->context_menu_state_changed = 1;
+}
+
+function void
+ui_context_menu_open(UINodeKey anchor_node_key, BeamformerUIPanel *panel)
+{
+ if (ui_node_key_equal(ui_context->context_menu_anchor_key, anchor_node_key)) {
+ ui_context_menu_close();
+ } else {
+ ui_context->context_menu_next_anchor_key = anchor_node_key;
+ ui_context->context_menu_next_panel = panel;
+ ui_context->context_menu_state_changed = 1;
+ ui_context->context_menu_open_t = 0;
}
}
function void
-draw_table_borders(Table *t, Rect r, f32 line_height)
+ui_drag_end(void)
{
- if (t->column_border_thick > 0) {
- v2 start = {.x = r.pos.x, .y = r.pos.y + t->cell_pad.h / 2};
- v2 end = start;
- end.y += t->size.y - t->cell_pad.y;
- for (i32 i = 0; i < t->columns - 1; i++) {
- f32 dx = t->widths[i] + t->cell_pad.w + t->column_border_thick;
- start.x += dx;
- end.x += dx;
- if (t->widths[i + 1] > 0)
- DrawLineEx(rl_v2(start), rl_v2(end), t->column_border_thick, fade(BLACK, 0.8f));
- }
+ if ((beamformer_registers()->split_left_tree != beamformer_registers()->split_right_tree) &&
+ ui_context->drag_panel)
+ {
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_SplitTree].string,
+ .tree_node = (u64)ui_context->drag_panel);
+ } else if (beamformer_registers()->drop_target_tree && ui_context->drag_panel) {
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_MoveTab].string,
+ .tree_node = (u64)ui_context->drag_panel);
}
+ ui_context->drag_panel = 0;
+ ui_context->drag_end = 0;
+}
- if (t->row_border_thick > 0) {
- v2 start = {.x = r.pos.x + t->cell_pad.w / 2, .y = r.pos.y};
- v2 end = start;
- end.x += t->size.x - t->cell_pad.x;
- for (i32 i = 0; i < t->rows - 1; i++) {
- f32 dy = line_height + t->cell_pad.y + t->row_border_thick;
- start.y += dy;
- end.y += dy;
- DrawLineEx(rl_v2(start), rl_v2(end), t->row_border_thick, fade(BLACK, 0.8f));
- }
+function void
+ui_drag_begin(BeamformerUIPanel *panel)
+{
+ if (!ui_context->drag_panel) {
+ ui_context->drag_panel = panel;
+ ui_context->drag_open_t = 0;
+ ui_context->drop_target_key = ui_node_key_zero();
+ beamformer_registers()->drop_target_tree = 0;
}
}
function v2
-draw_table(BeamformerUI *ui, Arena arena, Table *table, Rect draw_rect, TextSpec ts, v2 mouse, b32 skip_rows)
+ui_node_final_position(UINode *node)
{
- ts.flags |= TF_LIMITED;
-
- v2 result = {.x = table_width(table)};
- i32 row_index = skip_rows? table_skip_rows(table, draw_rect.size.h, (f32)ts.font->baseSize) : 0;
- TableIterator *it = table_iterator_new(table, TIK_CELLS, &arena, row_index, draw_rect.pos, ts.font);
- for (TableCell *cell = table_iterator_next(it, &arena);
- cell;
- cell = table_iterator_next(it, &arena))
- {
- ts.limits.size.w = draw_rect.size.w - (it->cell_rect.pos.x - it->start_x);
- draw_table_cell(ui, arena, cell, it->cell_rect, it->alignment, ts, mouse);
- }
- draw_table_borders(table, draw_rect, (f32)ts.font->baseSize);
- result.y = it->cell_rect.pos.y - draw_rect.pos.y - table->cell_pad.h / 2.0f;
+ v2 result = ui_node_rect(node).pos;
+ for (UINode *p = node->parent; !ui_node_is_nil(p); p = p->parent)
+ if (p->flags & UINodeFlag_ViewScroll)
+ result = v2_sub(result, p->view_scroll_offset);
return result;
}
-function void
-draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts)
+function UISignal
+ui_signal_from_node(UINode *node)
{
- v2 vr_max_p = v2_add(view_rect.pos, view_rect.size);
-
- v3 U = view->frame.voxel_transform.c[0].xyz;
- v3 V = view->frame.voxel_transform.c[1].xyz;
- v3 min = view->frame.voxel_transform.c[3].xyz;
+ BeamformerUI *ui = ui_context;
+ BeamformerInput *input = beamformer_input;
- v2 start_uv = plane_uv(v3_sub(view->ruler.start, min), U, V);
- v2 end_uv = plane_uv(v3_sub(view->ruler.end, min), U, V);
+ UISignal result = {.node = node};
+ Rect nr = ui_node_rect(node);
- v2 start_p = v2_add(view_rect.pos, v2_mul(start_uv, view_rect.size));
- v2 end_p = v2_add(view_rect.pos, v2_mul(end_uv, view_rect.size));
+ // NOTE(rnp): use the last mouse as this matches what the user saw when they positioned
+ v2 mouse = ui->last_mouse;
- b32 start_in_bounds = point_in_rect(start_p, view_rect);
- b32 end_in_bounds = point_in_rect(end_p, view_rect);
+ // NOTE(rnp): apply offset
+ nr.pos = ui_node_final_position(node);
- // TODO(rnp): this should be a ray intersection not a clamp
- start_p = clamp_v2_rect(start_p, view_rect);
- end_p = clamp_v2_rect(end_p, view_rect);
+ // NOTE(rnp): apply clipping
+ for (UINode *p = node->parent; !ui_node_is_nil(p); p = p->parent)
+ if (p->flags & UINodeFlag_Clip)
+ nr = rect_intersect(nr, ui_node_rect(p));
- Color rl_colour = colour_from_normalized(ts.colour);
- DrawLineEx(rl_v2(end_p), rl_v2(start_p), 2, rl_colour);
- if (start_in_bounds) DrawCircleV(rl_v2(start_p), 3, rl_colour);
- if (end_in_bounds) DrawCircleV(rl_v2(end_p), 3, rl_colour);
+ // NOTE(rnp): filter when node is under context menu
+ b32 context_menu_descendent = 0;
+ for (UINode *p = node->parent; !ui_node_is_nil(p); p = p->parent)
+ if (p == ui->context_menu_root)
+ context_menu_descendent = 1;
- Stream buf = arena_stream(a);
- stream_append_f64(&buf, 1e3 * v3_magnitude(v3_sub(view->ruler.end, view->ruler.start)), 100);
- stream_append_s8(&buf, s8(" mm"));
+ Rect filter_rect = {0};
+ if (!context_menu_descendent && !ui_node_key_nil(ui->context_menu_anchor_key))
+ filter_rect = ui_node_rect(ui->context_menu_root);
- v2 txt_p = start_p;
- v2 txt_s = measure_text(*ts.font, stream_to_s8(&buf));
- v2 pixel_delta = v2_sub(start_p, end_p);
- if (pixel_delta.y < 0) txt_p.y -= txt_s.y;
- if (pixel_delta.x < 0) txt_p.x -= txt_s.x;
- if (txt_p.x < view_rect.pos.x) txt_p.x = view_rect.pos.x;
- if (txt_p.x + txt_s.x > vr_max_p.x) txt_p.x -= (txt_p.x + txt_s.x) - vr_max_p.x;
+ b32 disabled = (node->flags & UINodeFlag_Disabled) != 0;
+ b32 collides = point_in_rect(mouse, nr) && !point_in_rect(mouse, filter_rect);
- draw_text(stream_to_s8(&buf), txt_p, &ts);
-}
+ result.flags |= collides * UISignalFlag_Hovering;
-function v2
-draw_frame_view_controls(BeamformerUI *ui, Arena arena, BeamformerFrameView *view, Rect vr, v2 mouse)
-{
- TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED|TF_OUTLINED,
- .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1,
- .limits.size.x = vr.size.w};
-
- Table *table = table_new(&arena, 3, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left);
- table_push_parameter_row(table, &arena, view->gamma.name, &view->gamma, s8(""));
- table_push_parameter_row(table, &arena, view->threshold.name, &view->threshold, s8(""));
- if (view->log_scale->bool32)
- table_push_parameter_row(table, &arena, view->dynamic_range.name, &view->dynamic_range, s8("[dB]"));
-
- Rect table_rect = vr;
- f32 height = table_extent(table, arena, text_spec.font).y;
- height = MIN(height, vr.size.h);
- table_rect.pos.w += 8;
- table_rect.pos.y += vr.size.h - height - 8;
- table_rect.size.h = height;
- table_rect.size.w = vr.size.w - 16;
-
- return draw_table(ui, arena, table, table_rect, text_spec, mouse, 0);
-}
+ if (!disabled)
+ for (BeamformerInputEvent *event = ui_event_next(input, 0);
+ event;
+ event = ui_event_next(input, event))
+ {
+ b32 taken = 0;
+ b32 press = event->kind == BeamformerInputEventKind_ButtonPress;
+ b32 release = event->kind == BeamformerInputEventKind_ButtonRelease;
+ b32 event_is_mouse = (press || release) && (
+ event->button_id == BeamformerButtonID_MouseLeft ||
+ event->button_id == BeamformerButtonID_MouseRight ||
+ event->button_id == BeamformerButtonID_MouseMiddle ||
+ (0));
+ UIMouseButtonKind mouse_button = (event->button_id == BeamformerButtonID_MouseLeft ? UIMouseButtonKind_Left :
+ event->button_id == BeamformerButtonID_MouseRight ? UIMouseButtonKind_Right :
+ event->button_id == BeamformerButtonID_MouseMiddle ? UIMouseButtonKind_Middle :
+ UIMouseButtonKind_Left);
+
+ if ((node->flags & UINodeFlag_MouseClickable) && event_is_mouse && press && collides) {
+ ui->hot_node_key = node->key;
+ ui->active_node_key[mouse_button] = node->key;
+
+ // TODO(rnp): store timestamp
+ // TODO(rnp): check with timestamp for double/triple click
+
+ result.flags |= UISignalFlag_LeftPressed << mouse_button;
+
+ taken = 1;
+ }
-function void
-draw_3D_xplane_frame_view(BeamformerUI *ui, Arena arena, Variable *var, Rect display_rect, v2 mouse)
-{
- assert(var->type == VT_BEAMFORMER_FRAME_VIEW);
- BeamformerFrameView *view = var->generic;
+ // NOTE(rnp): release, applies whenever this node is active regardless of in bounds or not.
+ if ((node->flags & UINodeFlag_MouseClickable) && event_is_mouse && release &&
+ ui_node_key_equal(ui->active_node_key[mouse_button], node->key))
+ {
+ ui->hot_node_key = ui_node_key_zero();
+ ui->active_node_key[mouse_button] = ui_node_key_zero();
+ result.flags |= UISignalFlag_LeftReleased << mouse_button;
- f32 aspect = (f32)view->colour_image.width / (f32)view->colour_image.height;
- Rect vr = display_rect;
- if (aspect > 1.0f) vr.size.w = vr.size.h;
- else vr.size.h = vr.size.w;
+ taken = 1;
+ }
- if (vr.size.w > display_rect.size.w) {
- vr.size.w -= (vr.size.w - display_rect.size.w);
- vr.size.h = vr.size.w / aspect;
- } else if (vr.size.h > display_rect.size.h) {
- vr.size.h -= (vr.size.h - display_rect.size.h);
- vr.size.w = vr.size.h * aspect;
- }
- vr.pos = v2_add(vr.pos, v2_scale(v2_sub(display_rect.size, vr.size), 0.5));
+ // NOTE(rnp): custom scroll handling
+ if (node->flags & UINodeFlag_Scroll && event->kind == BeamformerInputEventKind_MouseScroll && collides) {
+ v2 delta = {{event->scroll.x, event->scroll.y}};
+ // TODO(rnp): glfw doesn't pass these through
+ if (event->modifiers & BeamformerInputModifier_Shift)
+ swap(delta.x, delta.y);
+ result.scroll = v2_add(result.scroll, delta);
- i32 id = -1;
- if (hover_interaction(ui, mouse, auto_interaction(vr, var))) {
- ray mouse_ray = ray_for_x_plane_view(ui, view, normalized_p_in_rect(vr, mouse, 0));
- v3 x_size = v3_scale(beamformer_frame_view_plane_size(ui, view), 0.5f);
+ taken = 1;
+ }
- f32 rotation = x_plane_rotation_for_view_plane(view, BeamformerViewPlaneTag_XZ);
- m4 x_rotation = m4_rotation_about_y(rotation);
- v3 x_position = offset_x_plane_position(ui, view, BeamformerViewPlaneTag_XZ);
+ // NOTE(rnp): scrollable container handling
+ if (node->flags & UINodeFlag_ViewScroll && collides) {
+ v2 delta = {{event->scroll.x, event->scroll.y}};
+ // TODO(rnp): glfw doesn't pass these through
+ if (event->modifiers & BeamformerInputModifier_Shift)
+ swap(delta.x, delta.y);
- f32 test[2] = {0};
- test[0] = obb_raycast(x_rotation, x_size, x_position, mouse_ray);
+ // NOTE(rnp): if the view only has scroll in one direction we ignore the delta's direction
- x_position = offset_x_plane_position(ui, view, BeamformerViewPlaneTag_YZ);
- rotation = x_plane_rotation_for_view_plane(view, BeamformerViewPlaneTag_YZ);
- x_rotation = m4_rotation_about_y(rotation);
- test[1] = obb_raycast(x_rotation, x_size, x_position, mouse_ray);
+ if ((node->flags & UINodeFlag_ViewScrollX) == 0) {
+ if f32_equal(delta.y, 0)
+ delta.y = delta.x;
+ delta.x = 0;
+ }
- if (test[0] >= 0 && test[1] >= 0) id = test[1] < test[0]? 1 : 0;
- else if (test[0] >= 0) id = 0;
- else if (test[1] >= 0) id = 1;
+ if ((node->flags & UINodeFlag_ViewScrollY) == 0) {
+ if f32_equal(delta.x, 0)
+ delta.x = delta.y;
+ delta.y = 0;
+ }
- if (id != -1) {
- view->hit_test_point = v3_add(mouse_ray.origin, v3_scale(mouse_ray.direction, test[id]));
+ node->view_scroll_offset = v2_add(node->view_scroll_offset, v2_scale(delta, -10.f));
+ taken = 1;
}
+
+ if (taken) ui_event_consume(input, event);
}
- for (i32 i = 0; i < countof(view->x_plane_shifts); i++) {
- Variable *it = view->x_plane_shifts + i;
- Interaction interaction = auto_interaction(vr, it);
- if (id == i) ui->next_interaction = interaction;
- if (interaction_is_hot(ui, interaction)) it->hover_t += HOVER_SPEED * dt_for_frame;
- else it->hover_t -= HOVER_SPEED * dt_for_frame;
- it->hover_t = CLAMP01(it->hover_t);
+ // NOTE(rnp): single click dragging
+ if (node->flags & UINodeFlag_MouseClickable) {
+ for EachEnumValue(UIMouseButtonKind, k) {
+ if (ui_node_key_equal(ui->active_node_key[k], node->key) ||
+ result.flags & (UISignalFlag_LeftPressed << k))
+ {
+ result.flags |= (UISignalFlag_LeftDragging << k);
+ }
+ }
}
- Rectangle tex_r = {0, 0, (f32)view->colour_image.width, (f32)view->colour_image.height};
- NPatchInfo tex_np = {tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH};
- DrawTextureNPatch(make_raylib_texture(view), tex_np, rl_rect(vr), (Vector2){0}, 0, WHITE);
+ // NOTE(rnp): drop handling
+ if (node->flags & UINodeFlag_DropSite && collides
+ && ui_node_key_equal(ui->drop_target_key, ui_node_key_zero()))
+ {
+ ui->drop_target_key = node->key;
+ }
- draw_frame_view_controls(ui, arena, view, vr, mouse);
-}
+ if (node->flags & UINodeFlag_DropSite && !collides
+ && ui_node_key_equal(ui->drop_target_key, node->key))
+ {
+ ui->drop_target_key = ui_node_key_zero();
+ }
-function void
-draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect display_rect, v2 mouse)
-{
- assert(var->type == VT_BEAMFORMER_FRAME_VIEW);
- BeamformerFrameView *view = var->generic;
- BeamformerFrame *frame = &view->frame;
+ // TODO(rnp): double click dragging
- b32 is_1d = iv3_dimension(frame->points) == 1;
+ // TODO(rnp): triple click dragging
- f32 txt_w = measure_text(ui->small_font, s8("-288.8 mm")).w;
- f32 scale_bar_size = 1.2f * txt_w + RULER_TICK_LENGTH;
+ result.flags |= (!f32_equal(0, result.scroll.x) * UISignalFlag_ScrolledX);
+ result.flags |= (!f32_equal(0, result.scroll.y) * UISignalFlag_ScrolledY);
- v3 U = frame->voxel_transform.c[0].xyz;
- v3 V = frame->voxel_transform.c[1].xyz;
-
- v2 min_uv = plane_uv(view->min_coordinate, U, V);
- v2 max_uv = plane_uv(view->max_coordinate, U, V);
-
- v2 output_dim;
- output_dim.x = v3_magnitude(U);
- output_dim.y = v3_magnitude(V);
-
- // NOTE(rnp): may be different from UV if recompute in progress or Copy View
- v2 requested_dim;
- requested_dim.x = v3_magnitude(v3_sub(v3_scale(U, max_uv.x), v3_scale(U, min_uv.x)));
- requested_dim.y = v3_magnitude(v3_sub(v3_scale(V, max_uv.y), v3_scale(V, min_uv.y)));
-
- f32 aspect = is_1d ? 1.0f : output_dim.w / output_dim.h;
-
- Rect vr = display_rect;
- v2 scale_bar_area = {0};
- if (view->axial_scale_bar_active->bool32) {
- vr.pos.y += 0.5f * (f32)ui->small_font.baseSize;
- scale_bar_area.x += scale_bar_size;
- scale_bar_area.y += (f32)ui->small_font.baseSize;
+ if (node->flags & UINodeFlag_MouseClickable && collides &&
+ (ui_node_key_nil(ui->hot_node_key) || ui_node_key_equal(ui->hot_node_key, node->key)) &&
+ (ui_node_key_nil(ui->active_node_key[UIMouseButtonKind_Left]) || ui_node_key_equal(ui->active_node_key[UIMouseButtonKind_Left], node->key)) &&
+ (ui_node_key_nil(ui->active_node_key[UIMouseButtonKind_Middle]) || ui_node_key_equal(ui->active_node_key[UIMouseButtonKind_Middle], node->key)) &&
+ (ui_node_key_nil(ui->active_node_key[UIMouseButtonKind_Right]) || ui_node_key_equal(ui->active_node_key[UIMouseButtonKind_Right], node->key)))
+ {
+ ui->hot_node_key = node->key;
}
- if (view->lateral_scale_bar_active->bool32) {
- vr.pos.x += 0.5f * (f32)ui->small_font.baseSize;
- scale_bar_area.x += (f32)ui->small_font.baseSize;
- scale_bar_area.y += scale_bar_size;
+ if (node->flags & UINodeFlag_ViewScroll) {
+ v2 offset = node->view_scroll_offset;
+ f32 clamp_x = Max(0, node->computed_size[Axis2_X] - node->parent->computed_size[Axis2_X]);
+ f32 clamp_y = Max(0, node->computed_size[Axis2_Y] - node->parent->computed_size[Axis2_Y]);
+ node->view_scroll_offset.x = Max(0, Sign(offset.x) * Min(Abs(offset.x), clamp_x));
+ node->view_scroll_offset.y = Max(0, Sign(offset.y) * Min(Abs(offset.y), clamp_y));
}
- vr.size = v2_sub(vr.size, scale_bar_area);
- if (aspect > 1) vr.size.h = vr.size.w / aspect;
- else vr.size.w = vr.size.h * aspect;
-
- v2 occupied = v2_add(vr.size, scale_bar_area);
- if (occupied.w > display_rect.size.w) {
- vr.size.w -= (occupied.w - display_rect.size.w);
- vr.size.h = vr.size.w / aspect;
- } else if (occupied.h > display_rect.size.h) {
- vr.size.h -= (occupied.h - display_rect.size.h);
- vr.size.w = vr.size.h * aspect;
+ // NOTE(rnp): activate text input
+ if (ui_pressed(result) && !ui_node_key_equal(ui->text_input_state.node_key, node->key)) {
+ ui->text_input_state.changed = 1;
+ ui->text_input_state.next_node_key = node->flags & UINodeFlag_TextInput ? node->key : ui_node_key_zero();
}
- occupied = v2_add(vr.size, scale_bar_area);
- vr.pos = v2_add(vr.pos, v2_scale(v2_sub(display_rect.size, occupied), 0.5));
- Rectangle tex_r;
- if (is_1d) {
- tex_r = (Rectangle){0, 0, view->colour_image.width, -view->colour_image.height};
- } else {
- v2 pixels_per_meter = {
- .w = (f32)view->colour_image.width / output_dim.w,
- .h = (f32)view->colour_image.height / output_dim.h,
- };
+ // NOTE(rnp): signal ended text input
+ if (node->flags & UINodeFlag_TextInput &&
+ ui_node_key_equal(ui->text_input_state.last_node_key, node->key))
+ {
+ result.flags |= UISignalFlag_TextCommit;
+ result.string = (str8){.length = ui->text_input_state.last_count,
+ .data = ui->text_input_state.last_buffer};
+ }
- /* NOTE(rnp): math to resize the texture without stretching when the view changes
- * but the texture hasn't been (or cannot be) rebeamformed */
- v2 texture_points = v2_mul(pixels_per_meter, requested_dim);
- v2 texture_start = {
- .x = pixels_per_meter.x * 0.5f * (output_dim.x - requested_dim.x),
- .y = pixels_per_meter.y * (output_dim.y - requested_dim.y),
- };
+ if (ui_pressed(result) && !context_menu_descendent)
+ ui_context_menu_close();
- tex_r = (Rectangle){texture_start.x, texture_start.y, texture_points.x, texture_points.y};
+ if (!disabled) {
+ b32 hot = ui_node_key_equal(ui->hot_node_key, node->key);
+ if (hot) node->hot_t += HOVER_SPEED * dt_for_frame;
+ else node->hot_t -= HOVER_SPEED * dt_for_frame;
+ node->hot_t = Clamp01(node->hot_t);
}
- NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH };
- DrawTextureNPatch(make_raylib_texture(view), tex_np, rl_rect(vr), (Vector2){0}, 0, WHITE);
+ return result;
+}
- v2 start_pos = vr.pos;
- start_pos.y += vr.size.y;
+function UINode *
+ui_build_node_from_key(UINodeFlags flags, UINodeKey key)
+{
+ UINode *result = ui_node_from_key(key);
- if (vr.size.w > 0 && view->lateral_scale_bar_active->bool32) {
- do_scale_bar(ui, a, &view->lateral_scale_bar, mouse,
- (Rect){.pos = start_pos, .size = vr.size},
- *view->lateral_scale_bar.scale_bar.min_value * 1e3f,
- *view->lateral_scale_bar.scale_bar.max_value * 1e3f, s8(" mm"));
- }
+ b32 first_frame = ui_node_is_nil(result);
+ b32 transient = ui_node_key_equal(key, ui_node_key_zero());
- start_pos = vr.pos;
- start_pos.x += vr.size.x;
+ assert(first_frame || result->last_frame_active_index != ui_context->current_frame_index);
- if (vr.size.h > 0 && view->axial_scale_bar_active->bool32) {
- if (is_1d) {
- v2 end_pos = start_pos;
- u32 tick_count = (u32)(vr.size.y / (1.5f * (f32)ui->small_font.baseSize));
- start_pos.y += vr.size.y;
- draw_ruler(ui, a, start_pos, end_pos, 0.0f, 1.0f, 0, 0, tick_count, s8(""), RULER_COLOUR, FG_COLOUR);
+ if (first_frame) {
+ result = transient ? 0 : ui_context->node_freelist;
+ if (!ui_node_is_nil(result)) {
+ SLLStackPop(ui_context->node_freelist, next_sibling);
} else {
- do_scale_bar(ui, a, &view->axial_scale_bar, mouse, (Rect){.pos = start_pos, .size = vr.size},
- *view->axial_scale_bar.scale_bar.max_value * 1e3f,
- *view->axial_scale_bar.scale_bar.min_value * 1e3f, s8(" mm"));
+ result = push_struct_no_zero(transient ? ui_build_arena() : &ui_context->arena, UINode);
}
+ zero_struct(result);
}
- TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED|TF_OUTLINED,
- .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1,
- .limits.size.x = vr.size.w};
-
- f32 draw_table_width = vr.size.w;
- /* NOTE: avoid hover_t modification */
- Interaction viewer = auto_interaction(vr, var);
- if (point_in_rect(mouse, viewer.rect)) {
- ui->next_interaction = viewer;
-
- v2 world = screen_point_to_world_2d(mouse, vr.pos, v2_add(vr.pos, vr.size),
- XZ(view->min_coordinate),
- XZ(view->max_coordinate));
- world = v2_scale(world, 1e3f);
-
- if (is_1d) world.y = ((vr.pos.y + vr.size.y) - mouse.y) / vr.size.y;
-
- Stream buf = arena_stream(a);
- stream_append_s8(&buf, s8("{"));
- stream_append_f64(&buf, world.x, 100);
- if (is_1d) stream_append_s8(&buf, s8(" mm"));
- stream_append_s8(&buf, s8(", "));
- stream_append_f64(&buf, world.y, 100);
- stream_append_s8(&buf, s8("}"));
-
- text_spec.limits.size.w -= 4.0f;
- v2 txt_s = measure_text(*text_spec.font, stream_to_s8(&buf));
- v2 txt_p = {
- .x = vr.pos.x + vr.size.w - txt_s.w - 4.0f,
- .y = vr.pos.y + vr.size.h - txt_s.h - 4.0f,
- };
- txt_p.x = Max(vr.pos.x, txt_p.x);
- draw_table_width -= draw_text(stream_to_s8(&buf), txt_p, &text_spec).w;
- text_spec.limits.size.w += 4.0f;
+ // NOTE(rnp): reassigned per frame
+ {
+ result->parent = result->first_child = result->last_child = &ui_node_nil;
+ result->next_sibling = result->previous_sibling = &ui_node_nil;
+ result->child_count = 0;
}
- {
- Stream buf = arena_stream(a);
- s8 shader = push_acquisition_kind(&buf, frame->acquisition_kind, frame->compound_count, frame->contrast_mode);
- text_spec.font = &ui->font;
- text_spec.limits.size.w -= 16;
- v2 txt_s = measure_text(*text_spec.font, shader);
- v2 txt_p = {
- .x = vr.pos.x + vr.size.w - txt_s.w - 16,
- .y = vr.pos.y + 4,
- };
- txt_p.x = Max(vr.pos.x, txt_p.x);
- draw_text(stream_to_s8(&buf), txt_p, &text_spec);
- text_spec.font = &ui->small_font;
- text_spec.limits.size.w += 16;
+ if (first_frame && !transient) {
+ UINodeHashBucket *hb = ui_context->node_hash_table + (key.value % UI_HASH_TABLE_COUNT);
+ DLLInsert(&ui_node_nil, hb->first, hb->last, result, hash_next, hash_prev);
}
- if (view->ruler.state != RulerState_None) draw_view_ruler(view, a, vr, text_spec);
+ #define X(type, name, value_type, ...) result->name = ui_top_##name();
+ UI_STACK_LIST
+ #undef X
- vr.size.w = draw_table_width;
- draw_frame_view_controls(ui, a, view, vr, mouse);
-}
+ result->last_frame_active_index = ui_context->current_frame_index;
+ result->key = key;
+ result->flags |= flags;
-function v2
-draw_compute_progress_bar(BeamformerUI *ui, ComputeProgressBar *state, Rect r)
-{
- if (*state->processing) state->display_t_velocity += 65.0f * dt_for_frame;
- else state->display_t_velocity -= 45.0f * dt_for_frame;
-
- state->display_t_velocity = CLAMP(state->display_t_velocity, -10.0f, 10.0f);
- state->display_t += state->display_t_velocity * dt_for_frame;
- state->display_t = CLAMP01(state->display_t);
-
- if (state->display_t > (1.0f / 255.0f)) {
- Rect outline = {.pos = r.pos, .size = {{r.size.w, (f32)ui->font.baseSize}}};
- outline = scale_rect_centered(outline, (v2){{0.96f, 0.7f}});
- Rect filled = outline;
- filled.size.w *= *state->progress;
- DrawRectangleRounded(rl_rect(filled), 2.0f, 0, fade(colour_from_normalized(HOVERED_COLOUR), state->display_t));
- DrawRectangleRoundedLinesEx(rl_rect(outline), 2.0f, 0, 3, fade(BLACK, state->display_t));
+ if (!ui_node_is_nil(result->parent)) {
+ DLLInsertLast(&ui_node_nil, result->parent->first_child, result->parent->last_child,
+ result, next_sibling, previous_sibling);
+ result->parent->child_count++;
}
- v2 result = {{r.size.w, (f32)ui->font.baseSize}};
return result;
}
-function s8
-push_compute_time(Arena *arena, s8 prefix, f32 time)
+function UINode *
+ui_node_from_string(UINodeFlags flags, str8 string)
{
- Stream sb = arena_stream(*arena);
- stream_append_s8(&sb, prefix);
- stream_append_f64_e(&sb, time);
- return arena_stream_commit(arena, &sb);
+ UINode *result = ui_build_node_from_key(flags, ui_key_from_string(string, ui_node_ancestor_key()));
+ if (flags & UINodeFlag_DrawText) {
+ if (ui_node_key_equal(ui_context->text_input_state.node_key, result->key))
+ result->string = ui_text_input_string();
+ else if (ui_node_key_equal(ui_context->text_input_state.last_node_key, result->key))
+ result->string = ui_text_input_last_string();
+ else
+ result->string = string;
+ }
+ return result;
}
-function v2
-draw_compute_stats_bar_view(BeamformerUI *ui, Arena arena, ComputeShaderStats *stats,
- f32 compute_time_sum, TextSpec ts, Rect r, v2 mouse)
+function print_format(2, 3) UINode *
+ui_node_from_stringf(UINodeFlags flags, const char *format, ...)
{
- read_only local_persist s8 frame_labels[] = {s8_comp("0:"), s8_comp("-1:"), s8_comp("-2:"), s8_comp("-3:")};
- f32 total_times[countof(frame_labels)] = {0};
+ va_list args;
+ va_start(args, format);
+ str8 string = push_str8_fv(ui_build_arena(), format, args);
+ va_end(args);
+ UINode *result = ui_node_from_string(flags, string);
+ return result;
+}
- u32 stages = stats->table.shader_count;
- Table *table = table_new(&arena, countof(frame_labels), TextAlignment_Right, TextAlignment_Left);
- for (u32 i = 0; i < countof(frame_labels); i++) {
- TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data;
- cells[0].text = frame_labels[i];
- u32 frame_index = (stats->latest_frame_index - i - 1) % countof(stats->table.times);
- for (u32 j = 0; j < stages; j++)
- total_times[i] += stats->table.times[frame_index][j];
- }
+typedef struct {
+ f32 percent;
+} UIDrawSliderData;
- v2 result = table_extent(table, arena, ts.font);
+function UI_CUSTOM_DRAW_FUNCTION(ui_custom_draw_slider)
+{
+ UIDrawSliderData *data = node->custom_draw_context;
- f32 remaining_width = r.size.w - result.w - table->cell_pad.w;
- f32 average_width = 0.8f * remaining_width;
+ f32 pct = data->percent;
+ f32 border_thick = 3.0f;
+ f32 bar_height_frac = 0.8f;
+ v2 bar_size = {{6.0f, bar_height_frac * node_rect.size.y}};
- s8 mouse_text = s8("");
- v2 text_pos;
+ Rect inner = rect_shrink_centered(node_rect, (v2){{2.0f * border_thick, // NOTE(rnp): raylib jank
+ Max(0, 2.0f * (node_rect.size.y - bar_size.y))}});
+ Rect filled = inner;
+ filled.size.w *= pct;
- u32 row_index = 0;
- TableIterator *it = table_iterator_new(table, TIK_ROWS, &arena, 0, r.pos, ts.font);
- for (TableRow *row = table_iterator_next(it, &arena);
- row;
- row = table_iterator_next(it, &arena))
- {
- Rect cr = it->cell_rect;
- cr.size.w = table->widths[0];
- ts.limits.size.w = cr.size.w;
- draw_table_cell(ui, arena, (TableCell *)row->data, cr, table->alignment[0], ts, mouse);
+ Rect bar;
- u32 frame_index = (stats->latest_frame_index - row_index - 1) % countof(stats->table.times);
- f32 total_width = average_width * total_times[row_index] / compute_time_sum;
- Rect rect;
- rect.pos = v2_add(cr.pos, (v2){{cr.size.w + table->cell_pad.w , cr.size.h * 0.15f}});
- rect.size = (v2){.y = 0.7f * cr.size.h};
- for (u32 i = 0; i < stages; i++) {
- rect.size.w = total_width * stats->table.times[frame_index][i] / total_times[row_index];
- Color color = colour_from_normalized(g_colour_palette[i % countof(g_colour_palette)]);
- DrawRectangleRec(rl_rect(rect), color);
- if (point_in_rect(mouse, rect)) {
- text_pos = v2_add(rect.pos, (v2){.x = table->cell_pad.w});
- s8 name = push_s8_from_parts(&arena, s8(""), beamformer_shader_names[stats->table.shader_ids[i]], s8(": "));
- mouse_text = push_compute_time(&arena, name, stats->table.times[frame_index][i]);
- }
- rect.pos.x += rect.size.w;
- }
- row_index++;
- }
+ bar.pos = v2_add(node_rect.pos, (v2){{pct * (node_rect.size.w - bar_size.w),
+ (1 - bar_height_frac) * 0.5f * node_rect.size.y}});
+ bar.size = bar_size;
+ v4 bar_colour = v4_lerp(FG_COLOUR, FOCUSED_COLOUR, node->hot_t);
- v2 start = v2_add(r.pos, (v2){.x = table->widths[0] + average_width + table->cell_pad.w});
- v2 end = v2_add(start, (v2){.y = result.y});
- DrawLineEx(rl_v2(start), rl_v2(end), 4, colour_from_normalized(FG_COLOUR));
+ DrawRectangleRec(rl_rect(filled), colour_from_normalized(node->bg_colour));
+ DrawRectangleRoundedLinesEx(rl_rect(inner), 0.2f, 0, border_thick, BLACK);
+ DrawRectangleRounded(rl_rect(bar), 0.6f, 1, colour_from_normalized(bar_colour));
+}
- if (mouse_text.len) {
- ts.font = &ui->small_font;
- ts.flags &= ~(u32)TF_LIMITED;
- ts.flags |= (u32)TF_OUTLINED;
- ts.outline_colour = (v4){.a = 1};
- ts.outline_thick = 1;
- draw_text(mouse_text, text_pos, &ts);
- }
+function UISignal
+ui_slider(f32 percent, str8 tag)
+{
+ UINode *slider = ui_node_from_string(UINodeFlag_Clickable|
+ UINodeFlag_Scroll|
+ UINodeFlag_CustomDraw, tag);
+ // TODO(rnp): don't need custom draw for this when individual borders can be specified
+ slider->custom_draw_function = ui_custom_draw_slider;
+ slider->custom_draw_context = push_struct(ui_build_arena(), UIDrawSliderData);
+ UIDrawSliderData *data = slider->custom_draw_context;
+ data->percent = percent;
+ UISignal result = ui_signal_from_node(slider);
return result;
}
-function void
-push_table_time_row(Table *table, Arena *arena, s8 label, f32 time)
+function print_format(2, 3) UISignal
+ui_sliderf(f32 percent, const char *format, ...)
{
- assert(table->columns == 3);
- TableCell *cells = table_push_row(table, arena, TRK_CELLS)->data;
- cells[0].text = push_s8_from_parts(arena, s8(""), label, s8(":"));
- cells[1].text = push_compute_time(arena, s8(""), time);
- cells[2].text = s8("[s]");
+ va_list args;
+ va_start(args, format);
+ str8 string = push_str8_fv(ui_build_arena(), format, args);
+ va_end(args);
+ UISignal result = ui_slider(percent, string);
+ return result;
}
-function void
-push_table_time_row_with_fps(Table *table, Arena *arena, s8 label, f32 time)
+function UISignal
+ui_button(str8 string)
{
- assert(table->columns == 3);
- TableCell *cells = table_push_row(table, arena, TRK_CELLS)->data;
-
- Stream sb = arena_stream(*arena);
- stream_append_f64_e(&sb, time);
- stream_append_s8(&sb, s8(" ("));
- stream_append_f64(&sb, time > 0 ? 1.0f / time : 0, 100);
- stream_append_s8(&sb, s8(")"));
-
- cells[0].text = label;
- cells[1].text = arena_stream_commit(arena, &sb);
- cells[2].text = s8("[s] (FPS)");
+ UINode *node = ui_node_from_string(UINodeFlag_Clickable|
+ UINodeFlag_DrawBackground|
+ UINodeFlag_DrawBorder|
+ UINodeFlag_DrawText|
+ UINodeFlag_DrawHotEffects|
+ UINodeFlag_DrawActiveEffects,
+ string);
+ UISignal result = ui_signal_from_node(node);
+ return result;
}
-function void
-push_table_memory_size_row(Table *table, Arena *arena, s8 label, u64 memory_size)
+function print_format(1, 2) UISignal
+ui_buttonf(const char *format, ...)
{
- TableCell *cells = table_push_row(table, arena, TRK_CELLS)->data;
- Stream sb = arena_stream(*arena);
- stream_append_u64(&sb, memory_size);
- cells[0].text = label;
- cells[1].text = arena_stream_commit(arena, &sb);
- cells[2].text = s8("[B/F]");
+ va_list args;
+ va_start(args, format);
+ str8 string = push_str8_fv(ui_build_arena(), format, args);
+ va_end(args);
+ UISignal result = ui_button(string);
+ return result;
}
-function v2
-draw_compute_stats_view(BeamformerUI *ui, Arena arena, Variable *view, Rect r, v2 mouse)
+function UISignal
+ui_toggle_button(b32 state, str8 string)
{
- assert(view->type == VT_COMPUTE_STATS_VIEW);
+ UINode *node, *outer;
- read_only local_persist BeamformerComputePlan dummy_plan = {0};
- u32 selected_plan = ui->selected_parameter_block % BeamformerMaxParameterBlocks;
- BeamformerComputePlan *cp = ui->beamformer_context->compute_context.compute_plans[selected_plan];
- if (!cp) cp = &dummy_plan;
+ UIAxisAlign(Axis2_Y, Center)
+ UIAxisAlign(Axis2_X, Center)
+ UIParent(ui_spacer(0))
+ {
+ UIPrefHeight(ui_pct(0.75f, 1.f))
+ UIPrefWidth(ui_pct(0.75f, 1.f))
+ UIBorderThickness(2.f)
+ UIBorderColour(FG_COLOUR)
+ outer = ui_node_from_string(UINodeFlag_Clickable|UINodeFlag_DrawBorder,
+ push_str8_from_parts(ui_build_arena(), str8(""), string, str8("_outer")));
+
+ UIParent(outer)
+ UIPrefHeight(ui_pct(0.46f, 1.f))
+ UIPrefWidth(ui_pct(0.46f, 1.f))
+ UIBGColour(state ? FG_COLOUR : (v4){0})
+ {
+ node = ui_node_from_string(UINodeFlag_DrawBackground|
+ UINodeFlag_DrawHotEffects|
+ UINodeFlag_DrawActiveEffects,
+ string);
+ node->hot_t = outer->hot_t;
+ }
+ }
- ComputeStatsView *csv = &view->compute_stats_view;
- ComputeShaderStats *stats = csv->compute_shader_stats;
- f32 compute_time_sum = 0;
- u32 stages = stats->table.shader_count;
- TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED};
+ UISignal result = ui_signal_from_node(outer);
+ return result;
+}
- ui_blinker_update(&csv->blink, BLINK_SPEED);
+function print_format(2, 3) UISignal
+ui_toggle_buttonf(b32 state, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ str8 string = push_str8_fv(ui_build_arena(), format, args);
+ va_end(args);
+ UISignal result = ui_toggle_button(state, string);
+ return result;
+}
- for (u32 index = 0; index < stages; index++)
- compute_time_sum += stats->average_times[index];
+function UISignal
+ui_label(str8 string)
+{
+ UINode *node = ui_node_from_string(UINodeFlag_DrawText, string);
+ UISignal result = ui_signal_from_node(node);
+ return result;
+}
- v2 result = {0};
+function print_format(1, 2) UISignal
+ui_labelf(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ str8 string = push_str8_fv(ui_build_arena(), format, args);
+ va_end(args);
+ UISignal result = ui_label(string);
+ return result;
+}
- Table *table = table_new(&arena, 2, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left);
- switch (csv->kind) {
- case ComputeStatsViewKind_Average:{
- da_reserve(&arena, table, stages);
- for (u32 i = 0; i < stages; i++) {
- push_table_time_row(table, &arena, beamformer_shader_names[stats->table.shader_ids[i]],
- stats->average_times[i]);
- }
- }break;
- case ComputeStatsViewKind_Bar:{
- result = draw_compute_stats_bar_view(ui, arena, stats, compute_time_sum, text_spec, r, mouse);
- r.pos = v2_add(r.pos, (v2){.y = result.y});
- }break;
- InvalidDefaultCase;
- }
+function UISignal
+ui_label_button(str8 string)
+{
+ UINode *node = ui_node_from_string(UINodeFlag_DrawText|
+ UINodeFlag_Clickable|
+ UINodeFlag_DrawHotEffects|
+ UINodeFlag_DrawActiveEffects,
+ string);
+ UISignal result = ui_signal_from_node(node);
+ return result;
+}
+
+function print_format(1, 2) UISignal
+ui_label_buttonf(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ str8 string = push_str8_fv(ui_build_arena(), format, args);
+ va_end(args);
+ UISignal result = ui_label_button(string);
+ return result;
+}
- u32 rf_size = ui->beamformer_context->compute_context.rf_buffer.active_rf_size;
- push_table_time_row_with_fps(table, &arena, s8("Compute Total:"), compute_time_sum);
- push_table_time_row_with_fps(table, &arena, s8("RF Upload Delta:"), stats->rf_time_delta_average);
- push_table_memory_size_row(table, &arena, s8("Input RF Size:"), rf_size);
- if (rf_size != cp->rf_size)
- push_table_memory_size_row(table, &arena, s8("DAS RF Size:"), cp->rf_size);
+function UISignal
+ui_text_box(str8 string)
+{
+ UINode *node = ui_node_from_string(UINodeFlag_TextInput|
+ UINodeFlag_DrawText|
+ UINodeFlag_Clickable|
+ UINodeFlag_DrawHotEffects|
+ UINodeFlag_DrawActiveEffects,
+ string);
+ UISignal result = ui_signal_from_node(node);
+ return result;
+}
- result = v2_add(result, table_extent(table, arena, text_spec.font));
+function print_format(1, 2) UISignal
+ui_text_boxf(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ str8 string = push_str8_fv(ui_build_arena(), format, args);
+ va_end(args);
+ UISignal result = ui_text_box(string);
+ return result;
+}
- u32 row_index = 0;
- TableIterator *it = table_iterator_new(table, TIK_ROWS, &arena, 0, r.pos, text_spec.font);
- for (TableRow *row = table_iterator_next(it, &arena);
- row;
- row = table_iterator_next(it, &arena), row_index++)
- {
- Table *t = it->frame.table;
- Rect cell_rect = it->cell_rect;
- for (i32 column = 0; column < t->columns; column++) {
- TableCell *cell = (TableCell *)it->row->data + column;
- cell_rect.size.w = t->widths[column];
- text_spec.limits.size.w = r.size.w - (cell_rect.pos.x - it->start_x);
-
- if (column == 0 && row_index < stages &&
- vk_pipeline_valid(cp->vulkan_pipelines[row_index]) == 0 &&
- stats->table.shader_ids[row_index] != BeamformerShaderKind_Hilbert)
- {
- text_spec.colour = v4_lerp(FG_COLOUR, FOCUSED_COLOUR, ease_in_out_quartic(csv->blink.t));
- } else {
- text_spec.colour = FG_COLOUR;
- }
+function b32
+ui_tweak_f32_compute_variable(UISignal signal, f32 *value, f32 text_scale, f32 scroll_scale, v2 limits)
+{
+ b32 result = 0;
+ if (signal.flags) {
+ f64 new_value = *value;
+ if (signal.flags & UISignalFlag_TextCommit && ui_number_conversion_f64(signal.string, &new_value))
+ new_value *= text_scale;
- draw_table_cell(ui, arena, cell, cell_rect, t->alignment[column], text_spec, mouse);
+ if (signal.flags & UISignalFlag_ScrolledY)
+ new_value += scroll_scale * signal.scroll.y;
- cell_rect.pos.x += cell_rect.size.w + t->cell_pad.w;
- }
- }
+ new_value = Clamp(new_value, limits.x, limits.y);
+ result = !f32_equal(*value, (f32)new_value);
+ *value = (f32)new_value;
+ }
return result;
}
-function v2
-draw_live_controls_view(BeamformerUI *ui, Variable *var, Rect r, v2 mouse, Arena arena)
-{
- BeamformerLiveImagingParameters *lip = &ui->shared_memory->live_imaging_parameters;
- BeamformerLiveControlsView *lv = var->generic;
+typedef struct {
+ v2 uv_start;
+ v2 uv_end;
+ BeamformerFrameView *view;
+} BeamformerCustomDrawFrameViewData;
+
+function UI_CUSTOM_DRAW_FUNCTION(beamformer_custom_draw_frame_view)
+{
+ // TODO(rnp): we should always just draw inline, requires no raylib
+ BeamformerCustomDrawFrameViewData *data = node->custom_draw_context;
+ BeamformerFrameView *view = data->view;
+ Rectangle tex_r = {
+ data->uv_start.x * view->colour_image.width,
+ data->uv_start.y * view->colour_image.height,
+ data->uv_end.x * view->colour_image.width,
+ data->uv_end.y * view->colour_image.height,
+ };
+ NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH };
+ DrawTextureNPatch(make_raylib_texture(view), tex_np, rl_rect(node_rect), (Vector2){0}, 0, WHITE);
- TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED};
+ TextSpec text_spec = {.font = &ui_context->small_font, .flags = TF_LIMITED|TF_OUTLINED,
+ .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1,
+ .limits.size.x = node_rect.size.w};
+ if (view->kind != BeamformerFrameViewKind_3DXPlane && view->ruler.state != RulerState_None)
+ draw_view_ruler(view, *ui_build_arena(), node_rect, text_spec);
+}
- v2 slider_size = {{MIN(140.0f, r.size.w), (f32)ui->font.baseSize}};
- v2 button_size = {{MIN(r.size.w, slider_size.x + (f32)ui->font.baseSize), (f32)ui->font.baseSize * 1.5f}};
+function b32
+ui_rebuild_das_transform(u32 parameter_block, i32 dimension, v3 min, v3 max)
+{
+ BeamformerUI *ui = ui_context;
- f32 text_off = r.pos.x + 0.5f * MAX(0, (r.size.w - slider_size.w - (f32)ui->font.baseSize));
- f32 slider_off = r.pos.x + 0.5f * (r.size.w - slider_size.w);
- f32 button_off = r.pos.x + 0.5f * (r.size.w - button_size.w);
+ b32 result = 0;
+ m4 new_transform = m4_identity();
- text_spec.limits.size.w = r.size.w - (text_off - r.pos.x);
+ BeamformerParameterBlock *pb = beamformer_parameter_block(beamformer_context->shared_memory, parameter_block);
- v2 at = {{text_off, r.pos.y}};
+ m4 das_transform = pb->parameters.das_voxel_transform;
- if (popcount_u64(lip->acquisition_kind_enabled_flags) > 1) {
- u32 kind = lip->acquisition_kind;
- s8 kind_string = kind < BeamformerAcquisitionKind_Count ? beamformer_acquisition_kind_strings[kind]
- : s8("Invalid");
- v2 label_size = draw_text(s8("Acquisition:"), at, &text_spec);
+ switch (dimension) {
+ case 1:{new_transform = das_transform_1d(min, max);}break;
+ case 3:{new_transform = das_transform_3d(min, max);}break;
+ case 2:{
+ v3 U = v3_normalize(das_transform.c[0].xyz);
+ v3 V = v3_normalize(das_transform.c[1].xyz);
+ v3 N = cross(V, U);
- text_spec.colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, lv->acquisition_menu.hover_t);
- text_spec.limits.size.w -= label_size.x + (f32)ui->small_font.baseSize / 2;
+ v2 min_2d = {{min.E[0], min.E[1]}};
+ v2 max_2d = {{max.E[0], max.E[1]}};
- Rect menu = {.pos = at};
- menu.pos.x += label_size.x + (f32)ui->small_font.baseSize / 2;
- menu.size.h = (f32)ui->small_font.baseSize + TITLE_BAR_PAD;
- menu.size.w = draw_text(kind_string, menu.pos, &text_spec).x;
+ new_transform = das_transform_2d_with_normal(N, min_2d, max_2d, 0);
- text_spec.colour = FG_COLOUR;
- text_spec.limits.size.w = r.size.w - (text_off - r.pos.x);
+ v3 rotation_axis = cross(v3_normalize(new_transform.c[0].xyz), N);
- Interaction interaction = {.kind = InteractionKind_AcquisitionMenu,
- .var = &lv->acquisition_menu,
- .rect = menu};
- hover_interaction(ui, mouse, interaction);
+ m4 R = m4_rotation_about_axis(rotation_axis, ui->beamform_plane);
+ m4 T = m4_translation(v3_scale(m4_mul_v3(R, N), ui->off_axis_position));
- at.y += label_size.y;
+ new_transform = m4_mul(T, m4_mul(R, new_transform));
+ }break;
}
- v4 hsv_power_slider = {{0.35f * ease_in_out_cubic(1.0f - lip->transmit_power), 0.65f, 0.65f, 1}};
- at.y += draw_text(s8("Power:"), at, &text_spec).y;
- at.x = slider_off;
- at.y += draw_variable_slider(ui, &lv->transmit_power, (Rect){.pos = at, .size = slider_size},
- lip->transmit_power, hsv_to_rgb(hsv_power_slider), mouse);
-
- at.x = text_off;
- at.y += draw_text(s8("TGC:"), at, &text_spec).y;
- at.x = slider_off;
- for (u32 i = 0; i < countof(lip->tgc_control_points); i++) {
- Variable *v = lv->tgc_control_points + i;
- at.y += draw_variable_slider(ui, v, (Rect){.pos = at, .size = slider_size},
- lip->tgc_control_points[i], g_colour_palette[1], mouse);
-
- if (interaction_is_hot(ui, auto_interaction(r, v)))
- lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_TGCControlPoints;
- }
+ new_transform = m4_mul(new_transform, m4_inverse(das_transform));
- at.x = button_off;
- at.y += (f32)ui->font.baseSize * 0.5f;
- at.y += draw_fancy_button(ui, &lv->stop_button, lv->stop_button.name,
- (Rect){.pos = at, .size = button_size},
- BORDER_COLOUR, mouse, text_spec).y;
-
- if (lip->save_enabled) {
- b32 active = lip->save_active;
- s8 label = lv->save_button.cycler.labels[active % lv->save_button.cycler.cycle_length];
-
- f32 save_t = ui_blinker_update(&lv->save_button_blink, BLINK_SPEED);
- v4 border_colour = BORDER_COLOUR;
- if (active) border_colour = v4_lerp(BORDER_COLOUR, FOCUSED_COLOUR, ease_in_out_cubic(save_t));
-
- at.x = text_off;
- at.y += draw_text(s8("File Tag:"), at, &text_spec).y;
- at.x += (f32)text_spec.font->baseSize / 2;
- text_spec.limits.size.w -= (f32)text_spec.font->baseSize;
-
- v4 save_text_colour = FG_COLOUR;
- if (lip->save_name_tag_length <= 0)
- save_text_colour.a = 0.6f;
- at.y += draw_variable(ui, arena, &lv->save_text, at, mouse, save_text_colour, text_spec).y;
- text_spec.limits.size.w += (f32)text_spec.font->baseSize;
-
- at.x = button_off;
- at.y += (f32)ui->font.baseSize * 0.25f;
- at.y += draw_fancy_button(ui, &lv->save_button, label, (Rect){.pos = at, .size = button_size},
- border_colour, mouse, text_spec).y;
-
- if (interaction_is_hot(ui, auto_interaction(r, &lv->save_text)))
- lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_SaveNameTag;
- if (interaction_is_hot(ui, auto_interaction(r, &lv->save_button)))
- lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_SaveData;
+ BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[parameter_block];
+ if (cp) {
+ result |= !m4_equal(new_transform, cp->ui_voxel_transform);
+ memory_copy(cp->ui_voxel_transform.E, new_transform.E, sizeof(new_transform));
}
- if (interaction_is_hot(ui, auto_interaction(r, &lv->transmit_power)))
- lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_TransmitPower;
- if (interaction_is_hot(ui, auto_interaction(r, &lv->stop_button)))
- lv->hot_field_flag = BeamformerLiveImagingDirtyFlags_StopImaging;
+ if (result) {
+ mark_parameter_block_region_dirty(beamformer_context->shared_memory, parameter_block,
+ BeamformerParameterBlockRegion_Parameters);
+ }
- v2 result = {{r.size.w, at.y - r.pos.y}};
return result;
}
-struct variable_iterator { Variable *current; };
-function i32
-variable_iterator_next(struct variable_iterator *it)
+function void
+ui_scroll_begin(Axis2 scroll_axis)
{
- i32 result = 0;
+ UINode *outer, *inner, *clip, *child;
- if (it->current->type == VT_GROUP && it->current->group.expanded) {
- it->current = it->current->group.first;
- result++;
- } else {
- while (it->current) {
- if (it->current->next) {
- it->current = it->current->next;
- break;
- }
- it->current = it->current->parent;
- result--;
- }
+ UIChildLayoutAxis(Axis2_Y)
+ UIParent(ui_spacer(0))
+ {
+ UIChildLayoutAxis(Axis2_X)
+ UIFontSize(30.f)
+ outer = ui_node_from_string(UINodeFlag_Scroll, str8("###scroll_box"));
+
+ ui_padh(UI_NODE_PAD);
}
- return result;
-}
+ UIParent(outer)
+ {
+ ui_padw(UI_NODE_PAD);
+ UIChildLayoutAxis(Axis2_Y)
+ inner = ui_node_from_string(0, str8("###scroll_inner"));
+ }
-function v2
-draw_ui_view_menu(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 mouse, TextSpec text_spec)
-{
- assert(group->type == VT_GROUP);
- Table *table = table_new(&arena, 0, TextAlignment_Left, TextAlignment_Right);
- table->row_border_thick = 2.0f;
- table->cell_pad = (v2){{16.0f, 8.0f}};
-
- i32 nesting = 0;
- for (struct variable_iterator it = {group->group.first};
- it.current;
- nesting = variable_iterator_next(&it))
+ UINodeFlags axis_flags;
+ switch (scroll_axis) {
+ InvalidDefaultCase;
+ case Axis2_Count:{axis_flags = UINodeFlag_ViewScroll; }break;
+ case Axis2_X:{ axis_flags = UINodeFlag_ViewScrollX;}break;
+ case Axis2_Y:{ axis_flags = UINodeFlag_ViewScrollY;}break;
+ }
+ UIParent(inner)
+ clip = ui_node_from_string(axis_flags|
+ UINodeFlag_Clip|
+ UINodeFlag_AllowOverflow|
+ 0, str8("###scroll_clip"));
+
+ UIParent(clip)
{
- (void)nesting;
- assert(nesting == 0);
- Variable *var = it.current;
- TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data;
- switch (var->type) {
- case VT_B32:
- case VT_CYCLER:
- {
- cells[0] = (TableCell){.text = var->name};
- cells[1] = table_variable_cell(&arena, var);
- }break;
- case VT_UI_BUTTON:{
- cells[0] = (TableCell){.text = var->name, .kind = TableCellKind_Variable, .var = var};
- }break;
- InvalidDefaultCase;
- }
+ UIPrefWidth(ui_children_sum(1.f))
+ UIPrefHeight(ui_children_sum(1.f))
+ child = ui_node_from_string(0, str8("###scroll_child"));
}
- r.size = table_extent(table, arena, text_spec.font);
- return draw_table(ui, arena, table, r, text_spec, mouse, 0);
+ ui_push_parent(child);
}
-function v2
-draw_ui_view_listing(BeamformerUI *ui, Variable *group, Arena arena, Rect r, v2 mouse, TextSpec text_spec)
+function void
+ui_scroll_end(void)
{
- assert(group->type == VT_GROUP);
- Table *table = table_new(&arena, 0, TextAlignment_Left, TextAlignment_Left, TextAlignment_Right);
+ BeamformerUI *ui = ui_context;
+ UINode *child = ui_pop_parent();
+ UINode *clip = child->parent;
+ UINode *inner = clip->parent;
+ UINode *outer = inner->parent;
+
+ v2 scroll_offset = clip->view_scroll_offset;
+
+ str8 labels[2][2] = {
+ [Axis2_X] = {str8_comp("<"), str8_comp(">")},
+ [Axis2_Y] = {str8_comp("^"), str8_comp("v")},
+ };
- i32 nesting = 0;
- for (struct variable_iterator it = {group->group.first};
- it.current;
- nesting = variable_iterator_next(&it))
+ f32 btn_size = (f32)ui_font_for_node(outer).baseSize;
+
+ UINode *axis_parents[] = {[Axis2_X] = inner, [Axis2_Y] = outer};
+ for EachElement(axis_parents, axis)
+ if (clip->flags & (UINodeFlag_ViewScrollX << axis))
+ UIParent(axis_parents[axis])
{
- while (nesting > 0) {
- table = table_begin_subtable(table, &arena, TextAlignment_Left,
- TextAlignment_Center, TextAlignment_Right);
- nesting--;
+ b32 build_scrollbar = 2.f * btn_size < clip->computed_size[axis] &&
+ child->computed_size[axis] > clip->computed_size[axis];
+
+ // NOTE(rnp): vertical scroll bar shares padding on bottom with horizontal
+ // scroll bar so padding was already pushed, if we aren't drawing the horizontal
+ // scroll bar we need to avoid a double pad
+ if (axis == Axis2_Y || build_scrollbar) {
+ UIChildLayoutAxis(axis2_flip(axis))
+ ui_pads(UI_NODE_PAD);
}
- while (nesting < 0) { table = table_end_subtable(table); nesting++; }
- Variable *var = it.current;
- switch (var->type) {
- case VT_CYCLER:
- case VT_BEAMFORMER_VARIABLE:
+ if (build_scrollbar)
+ UIAxisSize(axis2_flip(axis), ui_px(12.f, 1.f))
+ UIChildLayoutAxis(axis)
+ UIParent(axis_parents[axis])
{
- s8 suffix = s8("");
- if (var->type == VT_BEAMFORMER_VARIABLE)
- suffix = var->beamformer_variable.suffix;
- table_push_parameter_row(table, &arena, var->name, var, suffix);
- }break;
- case VT_GROUP:{
- VariableGroup *g = &var->group;
-
- TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data;
- cells[0] = (TableCell){.text = var->name, .kind = TableCellKind_Variable, .var = var};
-
- if (!g->expanded) {
- Stream sb = arena_stream(arena);
- stream_append_variable_group(&sb, var);
- cells[1].kind = TableCellKind_VariableGroup;
- cells[1].text = arena_stream_commit(&arena, &sb);
- cells[1].var = var;
-
- Variable *v = g->first;
- assert(!v || v->type == VT_BEAMFORMER_VARIABLE);
- /* NOTE(rnp): assume the suffix is the same for all elements */
- if (v) cells[2].text = v->beamformer_variable.suffix;
- }
- }break;
- InvalidDefaultCase;
- }
- }
-
- v2 result = table_extent(table, arena, text_spec.font);
- draw_table(ui, arena, table, r, text_spec, mouse, 0);
- return result;
-}
+ UINode *parent = axis_parents[axis];
+ f32 d_size = child->computed_size[axis] - clip->computed_size[axis];
+ f32 used_pct = clip->computed_size[axis] / child->computed_size[axis];
+ f32 rem_pct = 1.f - used_pct;
+ f32 before_pct = rem_pct - (d_size - scroll_offset.E[axis]) / child->computed_size[axis];
+ f32 after_pct = rem_pct - before_pct;
+
+ UINode *scroll_container;
+ UIAxisAlign(axis2_flip(axis), Center)
+ UIAxisSize(axis, ui_px(parent->computed_size[axis], 1.f))
+ scroll_container = ui_spacer(0);
+
+ UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f))
+ UIFontSize(outer->font_size)
+ UIParent(scroll_container)
+ {
+ UISignal signal;
+ // TODO(rnp): icons
+ UIFlags(UINodeFlag_IconText)
+ UIAxisSize(axis2_flip(axis), ui_text_dim(1.f, 1.f))
+ UIAxisSize(axis, ui_text_dim(1.f, 1.f))
+ signal = ui_label_button(labels[axis][0]);
+ if (signal.flags & UISignalFlag_LeftPressed) {
+ // TODO(rnp): handle repeat
+ scroll_offset.E[axis] -= btn_size * 0.5f;
+ }
-function Rect
-draw_ui_view_container(BeamformerUI *ui, Variable *var, v2 mouse, Rect bounds)
-{
- UIView *fw = &var->view;
- Rect result = fw->rect;
- if (fw->rect.size.x > 0 && fw->rect.size.y > 0) {
- f32 line_height = (f32)ui->small_font.baseSize;
-
- f32 pad = MAX(line_height + 5.0f, UI_REGION_PAD);
- if (fw->rect.pos.y < pad)
- fw->rect.pos.y += pad - fw->rect.pos.y;
- result = fw->rect;
-
- f32 delta_x = (result.pos.x + result.size.x) - (bounds.size.x + bounds.pos.x);
- if (delta_x > 0) {
- result.pos.x -= delta_x;
- result.pos.x = MAX(0, result.pos.x);
- }
+ ui_pads(3.f);
- Rect container = result;
- if (fw->close) {
- container.pos.y -= 5 + line_height;
- container.size.y += 2 + line_height;
- Rect handle = {container.pos, (v2){.x = container.size.w, .y = 2 + line_height}};
- Rect close;
- hover_interaction(ui, mouse, auto_interaction(container, var));
- cut_rect_horizontal(handle, handle.size.w - handle.size.h - 6, 0, &close);
- close.size.w = close.size.h;
- DrawRectangleRounded(rl_rect(handle), 0.1f, 0, colour_from_normalized(BG_COLOUR));
- DrawRectangleRoundedLinesEx(rl_rect(handle), 0.2f, 0, 2, BLACK);
- draw_close_button(ui, fw->close, mouse, close, (v2){{0.45f, 0.45f}});
- } else {
- hover_interaction(ui, mouse, auto_interaction(container, var));
- }
- f32 roundness = 12.0f / fw->rect.size.y;
- DrawRectangleRounded(rl_rect(result), roundness / 2.0f, 0, colour_from_normalized(BG_COLOUR));
- DrawRectangleRoundedLinesEx(rl_rect(result), roundness, 0, 2, BLACK);
- }
- return result;
-}
+ UISignalFlags bar_flags = 0;
-function void
-draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec text_spec)
-{
- assert(ui_view->type == VT_UI_VIEW || ui_view->type == VT_UI_MENU || ui_view->type == VT_UI_TEXT_BOX);
+ UIBorderColour((v4){0})
+ UIFlags(UINodeFlag_Clickable|UINodeFlag_DrawBorder|UINodeFlag_DrawHotEffects)
+ UIAxisSize(axis, ui_pct(before_pct, 0.5f))
+ bar_flags |= ui_signal_from_node(ui_node_from_string(0, str8("###before"))).flags;
- UIView *view = &ui_view->view;
+ UIBGColour(FG_COLOUR)
+ UIAxisSize(axis, ui_pct(used_pct, 0.5f))
+ UIFlags(UINodeFlag_Clickable|UINodeFlag_DrawBackground|UINodeFlag_DrawHotEffects)
+ signal = ui_signal_from_node(ui_node_from_string(0, str8("###used")));
+ bar_flags |= signal.flags;
- if (view->flags & UIViewFlag_Floating) {
- r = draw_ui_view_container(ui, ui_view, mouse, r);
- } else {
- if (view->rect.size.h - r.size.h < view->rect.pos.h)
- view->rect.pos.h = view->rect.size.h - r.size.h;
+ UIBorderColour((v4){0})
+ UIFlags(UINodeFlag_Clickable|UINodeFlag_DrawBorder|UINodeFlag_DrawHotEffects)
+ UIAxisSize(axis, ui_pct(after_pct , 0.5f))
+ bar_flags |= ui_signal_from_node(ui_node_from_string(0, str8("###after"))).flags;
- if (view->rect.size.h - r.size.h < 0)
- view->rect.pos.h = 0;
+ if (bar_flags & (UISignalFlag_Dragging|UISignalFlag_Pressed)) {
+ f32 off_pct = rect_uv(ui->last_mouse, ui_node_rect(clip)).E[axis] - 0.5f * used_pct;
+ scroll_offset.E[axis] = Clamp01(off_pct) * child->computed_size[axis];
+ }
- r.pos.y -= view->rect.pos.h;
- }
+ ui_pads(3.f);
- v2 size = {0};
+ UIFlags(UINodeFlag_IconText)
+ UIAxisSize(axis2_flip(axis), ui_text_dim(1.f, 1.f))
+ UIAxisSize(axis, ui_text_dim(1.f, 1.f))
+ signal = ui_label_button(labels[axis][1]);
+ if (signal.flags & UISignalFlag_LeftPressed) {
+ // TODO(rnp): handle repeat
+ scroll_offset.E[axis] += btn_size * 0.5f;
+ }
+ }
- Variable *var = view->child;
- switch (var->type) {
- case VT_GROUP:{
- if (ui_view->type == VT_UI_MENU)
- size = draw_ui_view_menu(ui, var, ui->arena, r, mouse, text_spec);
- else {
- size = draw_ui_view_listing(ui, var, ui->arena, r, mouse, text_spec);
- }
- }break;
- case VT_BEAMFORMER_FRAME_VIEW: {
- BeamformerFrameView *bv = var->generic;
- if (frame_view_ready_to_present(ui, bv)) {
- if (bv->kind == BeamformerFrameViewKind_3DXPlane)
- draw_3D_xplane_frame_view(ui, ui->arena, var, r, mouse);
- else
- draw_beamformer_frame_view(ui, ui->arena, var, r, mouse);
+ // NOTE(rnp): vertical scroll bar needs padding next to it but must share
+ // padding on the bottom with the horizontal scrollbar
+ if (axis == Axis2_Y) ui_padw(UI_NODE_PAD);
}
- } break;
- case VT_COMPUTE_PROGRESS_BAR: {
- size = draw_compute_progress_bar(ui, &var->compute_progress_bar, r);
- } break;
- case VT_COMPUTE_STATS_VIEW:{ size = draw_compute_stats_view(ui, ui->arena, var, r, mouse); }break;
- case VT_LIVE_CONTROLS_VIEW:{
- if (view->rect.size.h - r.size.h < 0)
- r.pos.y += 0.5f * (r.size.h - view->rect.size.h);
- if (ui->shared_memory->live_imaging_parameters.active)
- size = draw_live_controls_view(ui, var, r, mouse, ui->arena);
- }break;
- InvalidDefaultCase;
}
- view->rect.size = size;
+ // TODO(rnp): view scroll is being added to ViewScroll node in ui_signal_from_node maybe we are ignoring it?
+ UISignal signal = ui_signal_from_node(outer);
+ scroll_offset = v2_sub(scroll_offset, v2_scale(signal.scroll, btn_size * 0.5f));
+
+ scroll_offset.x = Max(0, Min(scroll_offset.x, child->computed_size[Axis2_X] - clip->computed_size[Axis2_X]));
+ scroll_offset.y = Max(0, Min(scroll_offset.y, child->computed_size[Axis2_Y] - clip->computed_size[Axis2_Y]));
+ clip->view_scroll_offset = scroll_offset;
}
-function void
-draw_layout_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse)
+typedef struct {
+ Axis2 axis;
+ f32 start_value;
+ f32 end_value;
+ u32 segments;
+} UIDrawScaleBarData;
+
+function UI_CUSTOM_DRAW_FUNCTION(ui_custom_draw_scale_bar)
{
- if (var->type != VT_UI_REGION_SPLIT) {
- v2 shrink = {.x = UI_REGION_PAD, .y = UI_REGION_PAD};
- draw_rect = shrink_rect_centered(draw_rect, shrink);
- draw_rect.size = v2_floor(draw_rect.size);
- BeginScissorMode((i32)draw_rect.pos.x, (i32)draw_rect.pos.y, (i32)draw_rect.size.w, (i32)draw_rect.size.h);
- draw_rect = draw_title_bar(ui, ui->arena, var, draw_rect, mouse);
- EndScissorMode();
- }
+ UIDrawScaleBarData *info = node->custom_draw_context;
- /* TODO(rnp): post order traversal of the ui tree will remove the need for this */
- if (!CheckCollisionPointRec(rl_v2(mouse), rl_rect(draw_rect)))
- mouse = (v2){.x = F32_INFINITY, .y = F32_INFINITY};
-
- draw_rect.size = v2_floor(draw_rect.size);
- BeginScissorMode((i32)draw_rect.pos.x, (i32)draw_rect.pos.y, (i32)draw_rect.size.w, (i32)draw_rect.size.h);
- switch (var->type) {
- case VT_UI_VIEW: {
- hover_interaction(ui, mouse, auto_interaction(draw_rect, var));
- TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED};
- draw_ui_view(ui, var, draw_rect, mouse, text_spec);
- } break;
- case VT_UI_REGION_SPLIT: {
- RegionSplit *rs = &var->region_split;
-
- Rect split = {0}, hover = {0};
- switch (rs->direction) {
- case RSD_VERTICAL: {
- split_rect_vertical(draw_rect, rs->fraction, 0, &split);
- split.pos.x += UI_REGION_PAD;
- split.pos.y -= UI_SPLIT_HANDLE_THICK / 2;
- split.size.h = UI_SPLIT_HANDLE_THICK;
- split.size.w -= 2 * UI_REGION_PAD;
- hover = extend_rect_centered(split, (v2){.y = 0.75f * UI_REGION_PAD});
- } break;
- case RSD_HORIZONTAL: {
- split_rect_horizontal(draw_rect, rs->fraction, 0, &split);
- split.pos.x -= UI_SPLIT_HANDLE_THICK / 2;
- split.pos.y += UI_REGION_PAD;
- split.size.w = UI_SPLIT_HANDLE_THICK;
- split.size.h -= 2 * UI_REGION_PAD;
- hover = extend_rect_centered(split, (v2){.x = 0.75f * UI_REGION_PAD});
- } break;
- }
+ b32 draw_plus = Sign(info->end_value) != Sign(info->start_value);
- Interaction drag = {.kind = InteractionKind_Drag, .rect = hover, .var = var};
- hover_interaction(ui, mouse, drag);
+ Font font = ui_font_for_node(node);
+ v2 start_point = node_rect.pos;
+ v2 end_point = node_rect.pos;
- v4 colour = HOVERED_COLOUR;
- colour.a = var->hover_t;
- DrawRectangleRounded(rl_rect(split), 0.6f, 0, colour_from_normalized(colour));
- } break;
- InvalidDefaultCase;
- }
- EndScissorMode();
-}
+ if (info->axis == Axis2_Y) start_point.y += node_rect.size.y;
+ else end_point.x += node_rect.size.x;
-function void
-draw_ui_regions(BeamformerUI *ui, Rect window, v2 mouse)
-{
- struct region_frame {
- Variable *var;
- Rect rect;
- } init[16];
+ end_point = v2_sub(end_point, start_point);
- struct {
- struct region_frame *data;
- da_count count;
- da_count capacity;
- } stack = {init, 0, countof(init)};
+ rlPushMatrix();
+ rlTranslatef(start_point.x, start_point.y, 0);
+ rlRotatef(atan2_f32(end_point.y, end_point.x) * 180 / PI, 0, 0, 1);
- TempArena arena_savepoint = begin_temp_arena(&ui->arena);
+ Stream buf = arena_stream(*ui_build_arena());
+ f32 inc = v2_magnitude(end_point) / (f32)info->segments;
+ f32 value_inc = (info->end_value - info->start_value) / (f32)info->segments;
+ f32 value = info->start_value;
- *da_push(&ui->arena, &stack) = (struct region_frame){ui->regions, window};
- while (stack.count) {
- struct region_frame *top = stack.data + --stack.count;
- Rect rect = top->rect;
- draw_layout_variable(ui, top->var, rect, mouse);
-
- if (top->var->type == VT_UI_REGION_SPLIT) {
- Rect first, second;
- RegionSplit *rs = &top->var->region_split;
- switch (rs->direction) {
- case RSD_VERTICAL: {
- split_rect_vertical(rect, rs->fraction, &first, &second);
- } break;
- case RSD_HORIZONTAL: {
- split_rect_horizontal(rect, rs->fraction, &first, &second);
- } break;
- }
+ v2 sp = {0}, ep = {.y = RULER_TICK_LENGTH};
+ v2 tp = {{(f32)font.baseSize / 2.0f, ep.y + RULER_TEXT_PAD}};
- *da_push(&ui->arena, &stack) = (struct region_frame){rs->right, second};
- *da_push(&ui->arena, &stack) = (struct region_frame){rs->left, first};
- }
+ TextSpec text_spec = {.font = &font, .rotation = 90.0f, .colour = node->text_colour, .flags = TF_ROTATED};
+ if (node->flags & UINodeFlag_DrawHotEffects)
+ text_spec.colour = v4_lerp(text_spec.colour, HOVERED_COLOUR, node->hot_t);
+
+ Color rl_txt_colour = colour_from_normalized(node->text_colour);
+ for (u32 j = 0; j <= info->segments; j++) {
+ DrawLineEx(rl_v2(sp), rl_v2(ep), 4.f, rl_txt_colour);
+
+ stream_reset(&buf, 0);
+ if (draw_plus && value > 0) stream_append_byte(&buf, '+');
+ stream_append_f64(&buf, value, Abs(value_inc) < 1 ? 100 : 10);
+ stream_append_s8(&buf, s8("mm"));
+ draw_text(stream_to_str8(&buf), tp, &text_spec);
+
+ value += value_inc;
+ sp.x += inc;
+ ep.x += inc;
+ tp.x += inc;
}
- end_temp_arena(arena_savepoint);
+ rlPopMatrix();
}
-function void
-draw_floating_widgets(BeamformerUI *ui, Rect window_rect, v2 mouse)
+function UISignal
+ui_build_scale_bar(Axis2 axis, v2 min, v2 max)
{
- TextSpec text_spec = {.font = &ui->small_font, .colour = FG_COLOUR};
- window_rect = shrink_rect_centered(window_rect, (v2){{UI_REGION_PAD, UI_REGION_PAD}});
- for (Variable *var = ui->floating_widget_sentinal.parent;
- var != &ui->floating_widget_sentinal;
- var = var->parent)
+ Font font = ui_font_for_node(ui_top_parent());
+ f32 label_size = measure_text(font, str8("-288.88mm")).w;
+
+ UISignal result;
+ UIAxisSize(axis2_flip(axis), ui_px(RULER_TICK_LENGTH + RULER_TEXT_PAD + label_size, 1.f))
+ UIFlags(UINodeFlag_Clickable|UINodeFlag_Scroll|UINodeFlag_DrawHotEffects|UINodeFlag_CustomDraw)
{
- if (var->type == VT_UI_TEXT_BOX) {
- UIView *fw = &var->view;
- InputState *is = &ui->text_input_state;
+ UINode *node = ui_node_from_string(0, str8("###scale_bar"));
+ result = ui_signal_from_node(node);
+
+ UIDrawScaleBarData *info = push_struct(ui_build_arena(), UIDrawScaleBarData);
+ node->custom_draw_function = ui_custom_draw_scale_bar;
+ node->custom_draw_context = info;
+
+ Rect tick_rect = ui_node_rect(node);
+ if (tick_rect.size.E[axis] > 0) {
+ info->axis = axis;
+ info->segments = (u32)(tick_rect.size.E[axis] / (1.5f * font.baseSize));
+ info->start_value = min.E[axis] * 1e3;
+ info->end_value = max.E[axis] * 1e3;
+ if (axis == Axis2_Y) swap(info->start_value, info->end_value);
+ }
+ }
+ return result;
+}
- draw_ui_view_container(ui, var, mouse, fw->rect);
+function void
+ui_build_frame_view_overlay(UINode *frame_view, BeamformerFrameView *view, v2 min_2d, v2 max_2d)
+{
+ BeamformerUI *ui = ui_context;
+ UIParent(frame_view)
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_pct(1.f, 0.5f))
+ UITextOutlineColour((v4){.a = 1.f})
+ UITextOutlineThickness(1.f)
+ UITextColour(RULER_COLOUR)
+ {
+ ui_padh(UI_NODE_PAD);
- f32 cursor_width = (is->cursor == is->count) ? 0.55f * (f32)is->font->baseSize : 4.0f;
- s8 text = {.len = is->count, .data = is->buf};
- v2 text_size = measure_text(*is->font, text);
+ if (view->kind != BeamformerFrameViewKind_3DXPlane)
+ UIFontSize(30.f)
+ UIParent(ui_spacer(0))
+ {
+ ui_spacer(0);
- f32 text_pad = 4.0f;
- f32 desired_width = text_pad + text_size.w + cursor_width;
- fw->rect.size = (v2){{MAX(desired_width, fw->rect.size.w), text_size.h + text_pad}};
+ UIPrefHeight(ui_text_dim(1.f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ ui_label(push_acquisition_kind(ui_build_arena(), view->frame.acquisition_kind,
+ view->frame.compound_count, view->frame.contrast_mode));
- v2 text_position = {{fw->rect.pos.x + text_pad / 2, fw->rect.pos.y + text_pad / 2}};
- f32 cursor_offset = measure_text(*is->font, (s8){is->cursor, text.data}).w;
- cursor_offset += text_position.x;
+ ui_padw(2.f * UI_NODE_PAD);
+ }
- Rect cursor;
- cursor.pos = (v2){{cursor_offset, text_position.y}};
- cursor.size = (v2){{cursor_width, text_size.h}};
+ UIPrefHeight(ui_pct(1.f, 0.5f)) ui_spacer(0);
- v4 cursor_colour = FOCUSED_COLOUR;
- cursor_colour.a = ease_in_out_cubic(is->cursor_blink.t);
- v4 text_colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, fw->child->hover_t);
+ UIFontSize(24.f)
+ UIAxisAlign(Axis2_Y, Right)
+ UIParent(ui_spacer(0))
+ {
+ ui_padw(2.f * UI_NODE_PAD);
+
+ UINode *label_column, *value_column, *unit_column;
+ UIAxisAlign(Axis2_X, Left)
+ UIAxisAlign(Axis2_Y, Left)
+ UIPrefWidth(ui_children_sum(1.f))
+ UIParent(ui_spacer(0))
+ UIChildLayoutAxis(Axis2_Y)
+ {
+ label_column = ui_node_from_string(0, str8("###labels"));
+ ui_padw(UI_NODE_PAD);
+ value_column = ui_node_from_string(0, str8("###values"));
+ ui_padw(UI_NODE_PAD);
+ unit_column = ui_node_from_string(0, str8("###units"));
+ }
+
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ UIPrefHeight(ui_text_dim(1.f, 1.f))
+ {
+ if (view->log_scale) {
+ UIParent(label_column) ui_label(str8("Dynamic Range:"));
+ UIParent(unit_column) ui_label(str8("[dB]"));
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###dynamic_range", view->dynamic_range);
+ view->dirty |= ui_tweak_f32_compute_variable(signal, &view->dynamic_range, 1.f, 0.5f, V2_INFINITY);
+ }
+ }
+
+ // TODO(rnp): ui_em after text height matches correctly
+ f32 spacer_height;
+ UIParent(label_column) spacer_height = ui_label(str8("Gamma:")).node->computed_size[Axis2_Y];
+ UIParent(unit_column) ui_padh(spacer_height);
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###gamma", view->gamma);
+ view->dirty |= ui_tweak_f32_compute_variable(signal, &view->gamma, 1.f, 0.025f, V2_INFINITY);
+ }
+
+ UIParent(label_column) spacer_height = ui_label(str8("Threshold:")).node->computed_size[Axis2_Y];
+ UIParent(unit_column) ui_padh(spacer_height);
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###threshold", view->threshold);
+ view->dirty |= ui_tweak_f32_compute_variable(signal, &view->threshold, 1.f, 1.f, V2_INFINITY);
+ }
+ }
+
+ UIPrefWidth(ui_pct(1.f, 0.5f)) ui_spacer(0);
+
+ Rect nr = ui_node_rect(frame_view);
+ if (view->kind != BeamformerFrameViewKind_3DXPlane && point_in_rect(ui->last_mouse, nr) && ui->drag_panel == 0) {
+ b32 is_1d = iv3_dimension(view->frame.points) == 1;
+ v2 world = screen_point_to_world_2d(ui->last_mouse, nr.pos, v2_add(nr.pos, nr.size),
+ min_2d, max_2d);
+ world = v2_scale(world, 1e3f);
+ if (is_1d) world.y = ((nr.pos.y + nr.size.y) - ui->last_mouse.y) / nr.size.y;
+
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ UIPrefHeight(ui_text_dim(1.f, 1.f))
+ ui_labelf("{%0.2f%s, %0.2f}", world.x, is_1d ? " mm" : "", world.y);
+ }
+
+ ui_padw(2.f * UI_NODE_PAD);
+ }
+
+ ui_padh(UI_NODE_PAD);
+ }
+}
+
+function void
+ui_build_3d_xplane_frame_view(UINode *container, BeamformerFrameView *view)
+{
+ assert(view->kind == BeamformerFrameViewKind_3DXPlane);
+ Rect display_rect = ui_node_rect(container);
+ Rect vr = rect_shrink_centered(display_rect, (v2){{UI_NODE_PAD, UI_NODE_PAD}});
+
+ f32 aspect = (f32)view->colour_image.width / (f32)view->colour_image.height;
+ if (aspect > 1.0f) vr.size.w = vr.size.h;
+ else vr.size.h = vr.size.w;
+
+ if (vr.size.w > display_rect.size.w) {
+ vr.size.w -= (vr.size.w - display_rect.size.w);
+ vr.size.h = vr.size.w / aspect;
+ } else if (vr.size.h > display_rect.size.h) {
+ vr.size.h -= (vr.size.h - display_rect.size.h);
+ vr.size.w = vr.size.h * aspect;
+ }
+
+ // TODO(rnp): probably we don't need frame_top in this path
+ UINode *frame_top, *frame_view;
+ UIParent(container)
+ {
+ ui_padh(UI_NODE_PAD);
+
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ frame_top = ui_node_from_string(0, str8("###frame_view_top"));
+
+ UIParent(frame_top)
+ UIPrefHeight(ui_px(vr.size.h, 1.f))
+ {
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_px(vr.size.w, 1.f))
+ frame_view = ui_node_from_string(UINodeFlag_Clickable|
+ UINodeFlag_CustomDraw|
+ UINodeFlag_Clip|
+ UINodeFlag_Scroll|
+ 0, str8("###frame_view"));
+ frame_view->custom_draw_function = beamformer_custom_draw_frame_view;
+ frame_view->custom_draw_context = push_struct(ui_build_arena(), BeamformerCustomDrawFrameViewData);
+ {
+ BeamformerCustomDrawFrameViewData *data = frame_view->custom_draw_context;
+ data->uv_start = (v2){0};
+ data->uv_end = (v2){{1.f, 1.f}};
+ data->view = view;
+ }
+
+ ui_build_frame_view_overlay(frame_view, view, (v2){0}, (v2){0});
+ }
+ }
+
+ UISignal signal = ui_signal_from_node(frame_view);
+ if (ui_tweak_f32_compute_variable(signal, &view->threshold, 1.f, 1.f, V2_INFINITY))
+ view->dirty = 1;
+
+ f32 test[countof(view->plane_active)] = {0};
+ ray mouse_rays[countof(view->plane_active)] = {0};
+ v2 mouse_uv = rect_uv_ndc(ui_context->last_mouse, vr);
+
+ i32 hovered_plane = -1;
+ if ui_node_hot(frame_view) {
+ for EachElement(test, it) if (view->plane_active[it]) {
+ BeamformerFrame *frame = ui_context->latest_plane + it;
+ v2 min_2d, max_2d;
+ plane_corners_from_transform(frame->voxel_transform, &min_2d, &max_2d);
+ v3 x_size = v3_scale(x_plane_display_size(frame), 0.5f);
+ m4 x_rotation = m4_rotation_about_y(x_plane_rotation_for_view_plane(view, it));
+ v3 x_position = x_plane_offset_position(view, frame, it);
+ mouse_rays[it] = x_plane_raycast(view, frame, mouse_uv);
+ test[it] = obb_raycast(x_rotation, x_size, x_position, mouse_rays[it]);
+ }
+
+ f32 min_valid_t = inf32();
+ for EachElement(test, it) {
+ if (view->plane_active[it] && Between(test[it], 0, min_valid_t)) {
+ hovered_plane = (i32)it;
+ min_valid_t = test[it];
+ }
+ }
+ }
+
+ if ui_pressed(signal) {
+ view->plane_drag_index = hovered_plane;
+ if (hovered_plane != -1) {
+ v3 origin = mouse_rays[hovered_plane].origin;
+ v3 p = v3_scale(mouse_rays[hovered_plane].direction, test[hovered_plane]);
+ view->hit_start_point = view->hit_test_point = v3_add(origin, p);
+ }
+ }
- TextSpec input_text_spec = {.font = is->font, .colour = text_colour};
- draw_text(text, text_position, &input_text_spec);
- DrawRectanglePro(rl_rect(cursor), (Vector2){0}, 0, colour_from_normalized(cursor_colour));
+ b32 active = ui_node_key_equal(ui_context->active_node_key[UIMouseButtonKind_Left], frame_view->key);
+ for EachElement(view->hot_t, it) {
+ b32 hot = active ? (view->plane_drag_index == (i32)it) : (hovered_plane == (i32)it);
+ if (hot) view->hot_t[it] += HOVER_SPEED * dt_for_frame;
+ else view->hot_t[it] -= HOVER_SPEED * dt_for_frame;
+ view->hot_t[it] = Clamp01(view->hot_t[it]);
+ }
+
+ if ui_dragging(signal) {
+ ui_disable_cursor();
+ // TODO(rnp): hide mouse
+ if (view->plane_drag_index != -1) {
+ /* NOTE(rnp): project start point onto ray */
+ BeamformerFrame *frame = ui_context->latest_plane + view->plane_drag_index;
+ ray mouse_ray = x_plane_raycast(view, frame, rect_uv_ndc(clamp_v2_rect(ui_context->last_mouse, vr), vr));
+ v3 s = v3_sub(view->hit_start_point, mouse_ray.origin);
+ v3 r = v3_sub(mouse_ray.direction, mouse_ray.origin);
+ f32 scale = v3_dot(s, r) / v3_magnitude_squared(r);
+ view->hit_test_point = v3_add(mouse_ray.origin, v3_scale(r, scale));
} else {
- draw_ui_view(ui, var, window_rect, mouse, text_spec);
+ f32 dMouseX = ui_context->current_mouse.x - ui_context->last_mouse.x;
+ view->rotation -= dMouseX / (f32)beamformer_context->window_size.w;
+ if (view->rotation > 1.0f) view->rotation -= 1.0f;
+ if (view->rotation < 0.0f) view->rotation += 1.0f;
+ }
+ }
+
+ if ui_released(signal) {
+ ui_enable_cursor();
+
+ if (view->plane_drag_index != -1) {
+ m4 x_rotation = m4_rotation_about_y(x_plane_rotation_for_view_plane(view, view->plane_drag_index));
+ v3 Z = x_rotation.c[2].xyz;
+ f32 delta = v3_dot(Z, v3_sub(view->hit_test_point, view->hit_start_point));
+
+ BeamformerSharedMemory *sm = beamformer_context->shared_memory;
+ BeamformerLiveImagingParameters *li = &sm->live_imaging_parameters;
+ li->image_plane_offsets[view->plane_drag_index] += delta;
+ atomic_or_u32(&sm->live_imaging_dirty_flags, BeamformerLiveImagingDirtyFlags_ImagePlaneOffsets);
}
+
+ view->plane_drag_index = -1;
+ view->hit_start_point = view->hit_test_point = (v3){0};
}
}
function void
-scroll_interaction(Variable *var, f32 delta)
+ui_build_frame_view(UINode *container, BeamformerFrameView *view)
{
- switch (var->type) {
- case VT_B32:{ var->bool32 = !var->bool32; }break;
- case VT_F32:{ var->real32 += delta; }break;
- case VT_I32:{ var->signed32 += (i32)delta; }break;
- case VT_SCALED_F32:{ var->scaled_real32.val += delta * var->scaled_real32.scale; }break;
- case VT_BEAMFORMER_FRAME_VIEW:{
- BeamformerFrameView *bv = var->generic;
- bv->threshold.real32 += delta;
- bv->dirty = 1;
- } break;
- case VT_BEAMFORMER_VARIABLE:{
- BeamformerVariable *bv = &var->beamformer_variable;
- f32 value = *bv->store + delta * bv->scroll_scale;
- *bv->store = Clamp(value, bv->limits.x, bv->limits.y);
- }break;
- case VT_CYCLER:{
- u32 new_state = *var->cycler.state += delta;
- *var->cycler.state = new_state % var->cycler.cycle_length;
- }break;
- case VT_UI_VIEW:{
- var->view.rect.pos.h += UI_SCROLL_SPEED * delta;
- var->view.rect.pos.h = MAX(0, var->view.rect.pos.h);
- }break;
- InvalidDefaultCase;
+ assert(view->kind != BeamformerFrameViewKind_3DXPlane);
+
+ BeamformerUI *ui = ui_context;
+ BeamformerFrame *frame = &view->frame;
+ b32 is_1d = iv3_dimension(frame->points) == 1;
+ f32 txt_w = measure_text(ui->small_font, str8(" -288.8 mm")).w;
+ f32 scale_bar_size = 1.2f * txt_w + RULER_TICK_LENGTH;
+
+ v3 U = frame->voxel_transform.c[0].xyz;
+ v3 V = frame->voxel_transform.c[1].xyz;
+
+ v2 output_dim;
+ output_dim.x = v3_magnitude(U);
+ output_dim.y = v3_magnitude(V);
+
+ U = v3_scale(U, 1.f / output_dim.x);
+ V = v3_scale(V, 1.f / output_dim.y);
+
+ v3 min_coordinate = m4_mul_v3(frame->voxel_transform, (v3){{0.f, 0.f, 0.f}});
+ v3 max_coordinate = m4_mul_v3(frame->voxel_transform, (v3){{1.f, 1.f, 1.f}});
+
+ v2 min_2d = {{v3_dot(U, min_coordinate), v3_dot(V, min_coordinate)}};
+ v2 max_2d = {{v3_dot(U, max_coordinate), v3_dot(V, max_coordinate)}};
+
+ f32 aspect = is_1d ? 1.0f : output_dim.w / output_dim.h;
+
+ Rect display_rect = ui_node_rect(container);
+ Rect vr = rect_shrink_centered(display_rect, (v2){{UI_NODE_PAD, UI_NODE_PAD}});
+
+ v2 scale_bar_area = {0};
+ if (view->scale_bar_active[Axis2_Y]) {
+ vr.pos.y += 0.5f * (f32)ui->small_font.baseSize;
+ scale_bar_area.x += scale_bar_size;
+ scale_bar_area.y += (f32)ui->small_font.baseSize;
+ }
+ if (view->scale_bar_active[Axis2_X]) {
+ vr.pos.x += 0.5f * (f32)ui->small_font.baseSize;
+ scale_bar_area.x += (f32)ui->small_font.baseSize;
+ scale_bar_area.y += scale_bar_size;
+ }
+
+ vr.size = v2_sub(vr.size, scale_bar_area);
+ if (aspect > 1) vr.size.h = vr.size.w / aspect;
+ else vr.size.w = vr.size.h * aspect;
+
+ v2 occupied = v2_add(vr.size, scale_bar_area);
+ if (occupied.w > display_rect.size.w) {
+ vr.size.w -= (occupied.w - display_rect.size.w);
+ vr.size.h = vr.size.w / aspect;
+ } else if (occupied.h > display_rect.size.h) {
+ vr.size.h -= (occupied.h - display_rect.size.h);
+ vr.size.w = vr.size.h * aspect;
+ }
+
+ b32 rebuild_transform = 0;
+
+ UIParent(container)
+ {
+ ui_padh(UI_NODE_PAD);
+
+ UINode *frame_top, *frame_view;
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ frame_top = ui_node_from_string(0, str8("###frame_view_top"));
+
+ UIParent(frame_top)
+ UIPrefHeight(ui_px(vr.size.h, 1.f))
+ {
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_px(vr.size.w, 1.f))
+ frame_view = ui_node_from_string(UINodeFlag_Clickable|
+ UINodeFlag_CustomDraw|
+ UINodeFlag_Clip|
+ UINodeFlag_Scroll|
+ 0, str8("###frame_view"));
+ frame_view->custom_draw_function = beamformer_custom_draw_frame_view;
+ frame_view->custom_draw_context = push_struct(ui_build_arena(), BeamformerCustomDrawFrameViewData);
+ {
+ BeamformerCustomDrawFrameViewData *data = frame_view->custom_draw_context;
+ data->uv_start = (v2){0};
+ data->uv_end = (v2){{1.f, 1.f}};
+ data->view = view;
+ }
+
+ ui_build_frame_view_overlay(frame_view, view, min_2d, max_2d);
+
+ UISignal signal = ui_signal_from_node(frame_view);
+ // TODO(rnp): is this correct for x-plane?
+ if (ui_tweak_f32_compute_variable(signal, &view->threshold, 1.f, 1.f, V2_INFINITY))
+ view->dirty = 1;
+
+ if ui_pressed(signal) {
+ view->ruler.state = circular_add(view->ruler.state, 1, RulerState_Count);
+ // TODO(rnp): cleanup: this
+ v3 p = world_point_from_plane_uv(frame->voxel_transform, rect_uv(ui->last_mouse, ui_node_rect(frame_view)));
+ switch (view->ruler.state) {
+ InvalidDefaultCase;
+ case RulerState_None:{}break;
+ case RulerState_Start:{view->ruler.start = p;}break;
+ case RulerState_Hold:{ view->ruler.end = p;}break;
+ }
+ }
+
+ if (view->scale_bar_active[Axis2_Y]) {
+ signal = ui_build_scale_bar(Axis2_Y, min_2d, max_2d);
+ if ui_scrolled(signal) {
+ max_2d.y += signal.scroll.y * 1e-3f;
+ rebuild_transform = 1;
+ }
+ }
+ }
+
+ if (view->scale_bar_active[Axis2_X])
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.0f))
+ UIPrefWidth(ui_children_sum(1.0f))
+ UIParent(ui_node_from_string(0, str8("###frame_view_bot")))
+ UIPrefWidth(ui_px(vr.size.w, 1.f))
+ {
+ f32 top_position_offset = frame_view->computed_position[Axis2_X] - display_rect.pos.x;
+ ui_padw(top_position_offset);
+
+ UISignal signal = ui_build_scale_bar(Axis2_X, min_2d, max_2d);
+ if ui_scrolled(signal) {
+ min_2d.x += signal.scroll.y * 0.5e-3f;
+ max_2d.x -= signal.scroll.y * 0.5e-3f;
+ rebuild_transform = 1;
+ }
+
+ ui_padw(display_rect.size.x - top_position_offset);
+ }
+ }
+
+ if (rebuild_transform) {
+ min_coordinate.E[0] = min_2d.E[0]; min_coordinate.E[1] = min_2d.E[1];
+ max_coordinate.E[0] = max_2d.E[0]; max_coordinate.E[1] = max_2d.E[1];
+ if (ui_rebuild_das_transform(frame->parameter_block, iv3_dimension(frame->points), min_coordinate, max_coordinate))
+ ui->flush_parameters = 1;
+ }
+}
+
+function UI_CUSTOM_DRAW_FUNCTION(beamformer_ui_custom_draw_compute_bar_graph)
+{
+ ComputeShaderStats *stats = beamformer_context->compute_shader_stats;
+
+ UINode *labels = node->previous_sibling->previous_sibling;
+
+ u32 label_count = labels->child_count;
+ f32 *total_times = push_array(ui_build_arena(), f32, label_count);
+ f32 compute_time_sum = 0;
+
+ u32 stages = stats->table.shader_count;
+ for (u32 index = 0; index < stages; index++)
+ compute_time_sum += stats->average_times[index];
+ for EachIndex(label_count, frame) {
+ u32 frame_index = (stats->latest_frame_index - frame - 1) % countof(stats->table.times);
+ for EachIndex(stages, stage)
+ total_times[frame] += stats->table.times[frame_index][stage];
+ }
+
+ f32 remaining_width = node_rect.size.w;
+ f32 average_width = 0.8f * remaining_width;
+
+ s8 mouse_text = s8("");
+ v2 text_pos;
+
+ u32 row_index = 0;
+ for (UINode *ln = labels->first_child; !ui_node_is_nil(ln); ln = ln->next_sibling, row_index++) {
+ u32 frame_index = (stats->latest_frame_index - row_index - 1) % countof(stats->table.times);
+ f32 total_width = average_width * total_times[row_index] / compute_time_sum;
+ Rect rect;
+ rect.pos = (v2){{node_rect.pos.x, ln->computed_position[Axis2_Y]}};
+ rect.size = (v2){.y = ln->computed_size[Axis2_Y]};
+ rect = rect_squish_centered(rect, (v2){.y = 0.4f});
+
+ for (u32 i = 0; i < stages; i++) {
+ rect.size.w = total_width * stats->table.times[frame_index][i] / total_times[row_index];
+ Color color = colour_from_normalized(g_colour_palette[i % countof(g_colour_palette)]);
+ DrawRectangleRec(rl_rect(rect), color);
+ if (point_in_rect(ui_context->last_mouse, rect)) {
+ // TODO(rnp): tooltips
+ text_pos = v2_add(rect.pos, (v2){{UI_NODE_PAD, 3.f}});
+ Stream sb = arena_stream(*ui_build_arena());
+ stream_append_s8s(&sb, beamformer_shader_names[stats->table.shader_ids[i]], s8(": "));
+ stream_append_f64_e(&sb, stats->table.times[frame_index][i]);
+ mouse_text = arena_stream_commit(ui_build_arena(), &sb);
+ }
+ rect.pos.x += rect.size.w;
+ }
+ }
+
+ v2 start = v2_add(node_rect.pos, (v2){.x = average_width, .y = 0.01f * node_rect.size.y});
+ v2 end = v2_add(start, (v2){.y = node_rect.size.y - 0.02f * node_rect.size.y});
+ DrawLineEx(rl_v2(start), rl_v2(end), 4, colour_from_normalized(FG_COLOUR));
+
+ if (mouse_text.len) {
+ TextSpec ts = {.font = &ui_context->small_font, .flags = TF_OUTLINED, .colour = FG_COLOUR,
+ .outline_colour = {.a = 1.f}, .outline_thick = 1.f};
+ draw_text(str8_from_s8(mouse_text), text_pos, &ts);
+ }
+}
+
+function void
+ui_build_compute_stats(BeamformerComputePlan *cp, f32 broken_shader_t)
+{
+ ComputeShaderStats *stats = beamformer_context->compute_shader_stats;
+ f32 compute_time_sum = 0;
+ u32 stages = stats->table.shader_count;
+
+ for (u32 index = 0; index < stages; index++)
+ compute_time_sum += stats->average_times[index];
+
+ UIFontSize(30.f)
+ UIScroll(Axis2_Count)
+ {
+ ui_top_parent()->child_layout_axis = Axis2_X;
+
+ UINode *label_column, *value_column, *unit_column;
+ UIAxisAlign(Axis2_X, Left)
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_children_sum(1.0f))
+ UIPrefHeight(ui_children_sum(1.0f))
+ {
+ label_column = ui_node_from_string(0, str8("###labels"));
+ ui_padw(UI_NODE_PAD);
+ value_column = ui_node_from_string(0, str8("###values"));
+ ui_padw(UI_NODE_PAD);
+ unit_column = ui_node_from_string(0, str8("###units"));
+ }
+
+ UIPrefWidth(ui_text_dim(1.0f, 1.0f))
+ UIPrefHeight(ui_text_dim(1.05f, 1.0f))
+ {
+ for EachIndex(stages, it) {
+ v4 label_colour = FG_COLOUR;
+ if (vk_pipeline_valid(cp->vulkan_pipelines[it]) == 0 &&
+ stats->table.shader_ids[it] != BeamformerShaderKind_Hilbert)
+ {
+ v4_lerp(FG_COLOUR, FOCUSED_COLOUR, ease_in_out_quartic(broken_shader_t));
+ }
+
+ str8 shader = str8_from_s8(beamformer_shader_names[stats->table.shader_ids[it]]);
+
+ UITextColour(label_colour)
+ UIParent(label_column) ui_labelf("%.*s:###csl%u", (i32)shader.length, shader.data, (u32)it);
+ UIParent(value_column) ui_labelf("%0.2e###csv%u", stats->average_times[it], (u32)it);
+ UIParent(unit_column) ui_labelf("[s]###csu%u", (u32)it);
+ }
+
+ UIParent(label_column) ui_label(str8("Compute Total:"));
+ UIParent(value_column) ui_labelf("%0.2e (%0.2f)###csv_total", compute_time_sum,
+ compute_time_sum > 0.f ? 1.0f / compute_time_sum : 0.f);
+ UIParent(unit_column) ui_label(str8("[s] (FPS)###csv_total"));
+
+ UIParent(label_column) ui_label(str8("RF Upload Delta:"));
+ UIParent(value_column) ui_labelf("%0.2e (%0.2f)###csv_upload", stats->rf_time_delta_average,
+ stats->rf_time_delta_average > 0.f ? 1.0f / stats->rf_time_delta_average
+ : 0.f);
+ UIParent(unit_column) ui_label(str8("[s] (FPS)###csv_upload"));
+
+ u32 rf_size = beamformer_context->compute_context.rf_buffer.active_rf_size;
+ UIParent(label_column) ui_label(str8("Input RF Size:"));
+ UIParent(value_column) ui_labelf("%u###csv_rf_size", rf_size);
+ UIParent(unit_column) ui_label(str8("[B/F]###csv_rf_size"));
+
+ UIParent(label_column) ui_label(str8("DAS RF Size:"));
+ UIParent(value_column) ui_labelf("%u###csv_das_size", cp->rf_size);
+ UIParent(unit_column) ui_label(str8("[B/F]###csv_das_size"));
+ }
+ }
+}
+
+function void
+ui_build_parameters_listing(BeamformerUIPanel *panel)
+{
+ BeamformerUI *ui = ui_context;
+
+ UIFontSize(30.f)
+ UIScroll(Axis2_Count)
+ {
+ ui_top_parent()->child_layout_axis = Axis2_X;
+
+ UINode *label_column, *value_column, *unit_column;
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_children_sum(1.0f))
+ UIPrefHeight(ui_children_sum(1.0f))
+ {
+ UIAxisAlign(Axis2_X, Left) label_column = ui_node_from_string(0, str8("###labels"));
+ ui_padw(UI_NODE_PAD);
+ UIAxisAlign(Axis2_X, Center) value_column = ui_node_from_string(0, str8("###values"));
+ ui_padw(UI_NODE_PAD);
+ UIAxisAlign(Axis2_X, Right) unit_column = ui_node_from_string(0, str8("###units"));
+ }
+
+ f32 line_pad_pct = 1.05f;
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ UIPrefHeight(ui_text_dim(line_pad_pct, 1.f))
+ {
+ BeamformerUIParameters *bp = &ui_context->parameters;
+ UIParent(label_column) ui_label(str8("Sampling Frequency"));
+ UIParent(value_column) ui_labelf("%0.2f##sampling", bp->sampling_frequency * 1e-6);
+ UIParent(unit_column) ui_label(str8("[MHz]##sampling"));
+
+ UIParent(label_column) ui_label(str8("Demodulation Frequency"));
+ UIParent(value_column) ui_labelf("%0.2f###demod", bp->demodulation_frequency * 1e-6);
+ UIParent(unit_column) ui_label(str8("[MHz]##demod"));
+
+ UIParent(label_column) ui_label(str8("Speed of Sound"));
+ UIParent(unit_column) ui_label(str8("[m/s]"));
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###sound", bp->speed_of_sound);
+ if (ui_tweak_f32_compute_variable(signal, &bp->speed_of_sound, 1.f, 10.f, (v2){{0, inf32()}}))
+ ui->flush_parameters = 1;
+ }
+
+ u32 parameter_block = panel->u.parameter_listing.parameter_block;
+ b32 rebuild_transform = 0;
+
+ BeamformerParameterBlock *pb = beamformer_parameter_block(beamformer_context->shared_memory, parameter_block);
+ BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[parameter_block];
+ m4 das_transform = pb->parameters.das_voxel_transform;
+ if (cp) das_transform = m4_mul(cp->ui_voxel_transform, das_transform);
+ v3 coordinates[2] = {
+ m4_mul_v3(das_transform, (v3){{0.0f, 0.0f, 0.0f}}),
+ m4_mul_v3(das_transform, (v3){{1.0f, 1.0f, 1.0f}}),
+ };
+
+ i32 dimension = iv3_dimension(bp->output_points.xyz);
+ if (dimension > 0) {
+ read_only local_persist str8 dimension_strings[3][2] = {
+ {str8_comp("Start Point"), str8_comp("End Point") },
+ {str8_comp("Lateral Extent"), str8_comp("Axial Extent")},
+ {str8_comp("Min Corner"), str8_comp("Max Corner") },
+ };
+
+ for (u32 index = 0; index < 2; index++) {
+ UIParent(label_column)
+ {
+ UISignal signal = ui_button(dimension_strings[dimension - 1][index]);
+ signal.node->flags &= ~(UINodeFlag_DrawBackground|UINodeFlag_DrawBorder);
+ if ui_pressed(signal)
+ panel->u.parameter_listing.expand_coordinate[index] ^= 1u;
+ }
+
+ f32 values[3] = {coordinates[index].x, coordinates[index].y, coordinates[index].z};
+ u32 value_count = dimension == 2 ? 2 : 3;
+ v3 normalized_axis = v3_normalize(das_transform.c[index].xyz);
+ if (dimension == 2) {
+ values[0] = v3_dot(normalized_axis, coordinates[0]);
+ values[1] = v3_dot(normalized_axis, coordinates[1]);
+ }
+
+ if (panel->u.parameter_listing.expand_coordinate[index]) {
+ UIPrefHeight(ui_px((f32)ui_font_for_node(value_column).baseSize * line_pad_pct, 1.f))
+ {
+ UIParent(value_column) ui_spacer(0);
+ UIParent(unit_column) ui_spacer(0);
+ }
+
+ read_only local_persist str8 axis_strings[2][3] = {
+ {str8_comp(" X:"), str8_comp(" Y:"), str8_comp(" Z:")},
+ {str8_comp(" Min:"), str8_comp(" Max:"), },
+ };
+ str8 *strs = dimension == 2 ? axis_strings[1] : axis_strings[0];
+ for EachIndex(value_count, it) {
+ UIParent(label_column) ui_labelf(" %.*s##label%u_%u",
+ (i32)strs[it].length, strs[it].data,
+ index, (u32)it);
+ UIParent(unit_column) ui_labelf("[mm]##%u_%u", index, (u32)it);
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###%u_%u", values[it] * 1e3f, index, (u32)it);
+ rebuild_transform |= ui_tweak_f32_compute_variable(signal, values + it,
+ 1e-3f, 0.5e-3f, V2_INFINITY);
+ }
+ }
+ } else {
+ UIParent(unit_column) ui_labelf("[mm]##dim%u", index);
+
+ UINode *group;
+ UIParent(value_column)
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefWidth(ui_children_sum(1.f))
+ UIPrefHeight(ui_children_sum(1.f))
+ group = ui_spacer(0);
+
+ UIParent(group)
+ {
+ ui_labelf("{##%u", index);
+ for EachIndex(value_count, it) {
+ if (it != 0) ui_labelf(", ##%u_%u", index, (u32)it);
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###%u_%u", values[it] * 1e3f, index, (u32)it);
+ rebuild_transform |= ui_tweak_f32_compute_variable(signal, values + it,
+ 1e-3f, 0.5e-3f, V2_INFINITY);
+ }
+ }
+ ui_labelf("}##%u", index);
+ }
+ }
+
+ if (dimension == 2) {
+ coordinates[0].E[index] = values[0];
+ coordinates[1].E[index] = values[1];
+ }
+ }
+ }
+
+ if (dimension == 2) {
+ UIParent(label_column) ui_label(str8("Off Axis Position"));
+ UIParent(unit_column) ui_label(str8("[mm]##off_axis"));
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###off_axis", plane_offset_from_transform(das_transform) * 1e3f);
+ rebuild_transform |= ui_tweak_f32_compute_variable(signal, &ui->off_axis_position,
+ 1e-3f, 0.1e-3f, V2_INFINITY);
+ }
+
+ UIParent(label_column) ui_label(str8("Beamform Plane"));
+ UIParent(unit_column) UIPrefHeight(ui_em(1.f, 1.f)) ui_spacer(0);
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###beamform_plane", ui->beamform_plane);
+ rebuild_transform |= ui_tweak_f32_compute_variable(signal, &ui->beamform_plane,
+ 1.f, 0.025f, (v2){{-1.f, 1.f}});
+ }
+ }
+
+ UIParent(label_column) ui_label(str8("F#"));
+ UIParent(unit_column) UIPrefHeight(ui_em(1.f, 1.f)) ui_spacer(0);
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll|UINodeFlag_TextInputNumeric)
+ {
+ UISignal signal = ui_text_boxf("%0.2f###f_number", bp->f_number);
+ if (ui_tweak_f32_compute_variable(signal, &bp->f_number, 1.f, 0.05f, (v2){{0, inf32()}}))
+ ui->flush_parameters = 1;
+ }
+
+ UIParent(label_column) ui_label(str8("Interpolation"));
+ UIParent(unit_column) ui_build_node_from_key(0, ui_node_key_zero());
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll)
+ {
+ str8 label = str8_from_s8(beamformer_interpolation_mode_strings[bp->interpolation_mode]);
+ UISignal signal = ui_label_button(label);
+ if (ui_pressed(signal) || ui_scrolled(signal)) {
+ i32 delta = signal.scroll.y + ui_pressed(signal);
+ bp->interpolation_mode = circular_add(bp->interpolation_mode, delta,
+ BeamformerInterpolationMode_Count);
+ ui->flush_parameters = 1;
+ }
+ }
+
+ UIParent(label_column) ui_label(str8("Coherency Weighting"));
+ UIParent(unit_column) UIPrefHeight(ui_em(1.0f, 1.0f)) ui_spacer(0);
+ UIParent(value_column)
+ UIFlags(UINodeFlag_Scroll)
+ {
+ UISignal signal = ui_label_button(bp->coherency_weighting ?
+ str8("True###coherency_weighting") :
+ str8("False###coherency_weighting"));
+ if (signal.flags & (UISignalFlag_Pressed|UISignalFlag_Scrolled)) {
+ bp->coherency_weighting = !bp->coherency_weighting;
+ ui->flush_parameters = 1;
+ }
+ }
+
+ if (rebuild_transform) {
+ if (ui_rebuild_das_transform(parameter_block, dimension, coordinates[0], coordinates[1]))
+ ui->flush_parameters = 1;
+ }
+ }
+ }
+}
+
+function f32
+ui_slider_update_from_signal(f32 percent, UISignal signal)
+{
+ f32 result = percent;
+ result += 0.05f * signal.scroll.y;
+ if ui_dragging(signal)
+ result = rect_uv(ui_context->last_mouse, ui_node_rect(signal.node)).E[signal.node->parent->child_layout_axis];
+ result = Clamp01(result);
+ return result;
+}
+
+function void
+ui_build_live_imaging_controls(BeamformerUIPanel *panel)
+{
+ BeamformerLiveImagingParameters *lip = &beamformer_context->shared_memory->live_imaging_parameters;
+
+ UIFontSize(30.f)
+ UIScroll(Axis2_Count)
+ {
+ ui_top_parent()->child_layout_axis = Axis2_Y;
+
+ if (popcount_u64(lip->acquisition_kind_enabled_flags) > 1)
+ UIPrefWidth(ui_children_sum(1.f))
+ UIPrefHeight(ui_children_sum(1.f))
+ UIChildLayoutAxis(Axis2_X)
+ UIParent(ui_spacer(0))
+ UIPrefHeight(ui_text_dim(1.1f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ {
+ u32 kind = lip->acquisition_kind;
+ ui_label(str8("Acquisition: "));
+ str8 kind_string = kind < BeamformerAcquisitionKind_Count ? beamformer_acquisition_kind_strings[kind]
+ : str8("Invalid");
+
+ UISignal signal = ui_label_button(kind_string);
+ if ui_pressed(signal)
+ ui_context_menu_open(signal.node->key, panel);
+ }
+
+ UIPrefHeight(ui_text_dim(1.1f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ {
+ UINode *spacer;
+ UISignal signal;
+
+ f32 row_height = ui_label(str8("Power:")).node->computed_size[Axis2_Y];
+
+ UIPrefWidth(ui_pct(1.f, 1.f))
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIChildLayoutAxis(Axis2_X)
+ spacer = ui_spacer(0);
+ UIParent(spacer)
+ {
+ ui_padw(2 * UI_NODE_PAD);
+ v4 hsv_power_slider = {{0.35f * ease_in_out_cubic(1.0f - lip->transmit_power), 0.65f, 0.65f, 1}};
+ UIBGColour(hsv_to_rgb(hsv_power_slider))
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIPrefWidth(ui_pct(1.f, 0.5f))
+ signal = ui_slider(lip->transmit_power, str8("###transmit_power"));
+ if (signal.flags) {
+ lip->transmit_power = ui_slider_update_from_signal(lip->transmit_power, signal);
+ atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags,
+ BeamformerLiveImagingDirtyFlags_TransmitPower);
+ }
+ ui_padw(2 * UI_NODE_PAD);
+ }
+
+ row_height = ui_label(str8("TGC:")).node->computed_size[Axis2_Y];
+ for EachElement(lip->tgc_control_points, it) {
+ UIPrefWidth(ui_pct(1.f, 1.f))
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIChildLayoutAxis(Axis2_X)
+ spacer = ui_spacer(0);
+ UIParent(spacer)
+ {
+ ui_padw(2 * UI_NODE_PAD);
+ UIBGColour(g_colour_palette[1])
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIPrefWidth(ui_pct(1.f, 0.5f))
+ signal = ui_sliderf(lip->tgc_control_points[it], "###tgc_%u", (u32)it);
+ if (signal.flags) {
+ lip->tgc_control_points[it] = ui_slider_update_from_signal(lip->tgc_control_points[it], signal);
+ atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags,
+ BeamformerLiveImagingDirtyFlags_TGCControlPoints);
+ }
+ ui_padw(2 * UI_NODE_PAD);
+ }
+ }
+
+ if (lip->save_enabled) {
+ ui_label(str8("File Name Tag:"));
+ str8 save_name = (str8){.data = (u8 *)lip->save_name_tag,
+ .length = Clamp(lip->save_name_tag_length, 0, countof(lip->save_name_tag))};
+
+
+ v4 save_text_colour = FG_COLOUR;
+ u64 text_input_flags = 0;
+ if (lip->save_name_tag_length <= 0) {
+ save_text_colour.a = 0.6f;
+ save_name = str8("Insert Text...");
+ text_input_flags = UINodeFlag_TextInputClearOnStart;
+ }
+
+ UIPrefWidth(ui_children_sum(1.f))
+ UIPrefHeight(ui_children_sum(1.f))
+ UIChildLayoutAxis(Axis2_X)
+ spacer = ui_spacer(0);
+ UIParent(spacer)
+ {
+ ui_padw(2 * UI_NODE_PAD);
+ UITextColour(save_text_colour)
+ UIFlags(text_input_flags)
+ signal = ui_text_box(push_str8_from_parts(ui_build_arena(), str8(""), save_name,
+ str8("###save_name_field")));
+ if (ui_node_key_equal(signal.node->key, ui_context->text_input_state.node_key))
+ signal.node->text_colour = FG_COLOUR;
+
+ if (signal.flags & UISignalFlag_TextCommit) {
+ str8 string = signal.string;
+ lip->save_name_tag_length = Min(string.length, countof(lip->save_name_tag));
+ memory_copy(lip->save_name_tag, string.data, lip->save_name_tag_length);
+ atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags,
+ BeamformerLiveImagingDirtyFlags_SaveNameTag);
+ }
+ }
+
+ ui_padh(UI_NODE_PAD);
+
+ UIAxisAlign(Axis2_X, Center)
+ UITextAlign(Center)
+ UIBGColour((v4){0})
+ UIPrefWidth(ui_text_dim(1.3f, 1.f))
+ UIPrefHeight(ui_text_dim(1.3f, 1.f))
+ {
+ UIPrefWidth(ui_pct(1.f, 1.f)) UIPrefHeight(ui_children_sum(1.f))
+ spacer = ui_spacer(0);
+
+ UIParent(spacer)
+ {
+ b32 active = lip->save_active;
+ str8 label = active ? str8("Saving...###save_button") : str8("Save Data###save_button");
+ f32 save_t = beamformer_ui_blinker_update(&panel->u.live_imaging_save_button_blinker, BLINK_SPEED);
+ v4 border_colour = (v4){.a = 0.6f};
+ if (active) border_colour = v4_lerp(BORDER_COLOUR, FOCUSED_COLOUR, ease_in_out_cubic(save_t));
+ UIBorderColour(border_colour)
+ signal = ui_button(label);
+ if ui_pressed(signal) {
+ lip->save_active = !active;
+ atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags,
+ BeamformerLiveImagingDirtyFlags_SaveData);
+ }
+ }
+
+ ui_padh(UI_NODE_PAD);
+
+ UIPrefWidth(ui_pct(1.f, 1.f)) UIPrefHeight(ui_children_sum(1.f))
+ spacer = ui_spacer(0);
+
+ UIParent(spacer)
+ {
+ UIBorderColour((v4){.a = 0.6f})
+ signal = ui_button(str8("Stop Imaging"));
+ if ui_pressed(signal)
+ atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags,
+ BeamformerLiveImagingDirtyFlags_StopImaging);
+ }
+ }
+ }
+ }
+ }
+}
+
+function UISignal
+ui_panel_label(BeamformerUIPanel *panel)
+{
+ Stream sb = arena_stream(*ui_build_arena());
+ switch (panel->kind) {
+ InvalidDefaultCase;
+ case BeamformerPanelKind_ComputeBarGraph:{stream_append_s8(&sb, s8("Compute Bar Graph"));}break;
+ case BeamformerPanelKind_ComputeStats:{stream_append_s8(&sb, s8("Compute Stats"));}break;
+ case BeamformerPanelKind_FrameViewLive:{stream_append_s8(&sb, s8("Frame View"));}break;
+ case BeamformerPanelKind_FrameViewXPlane:{stream_append_s8(&sb, s8("X-Plane View"));}break;
+ case BeamformerPanelKind_LiveImagingControls:{stream_append_s8(&sb, s8("Live Controls"));}break;
+ case BeamformerPanelKind_FrameViewCopy:{
+ stream_append_s8(&sb, s8("Frame Copy ["));
+ stream_append_hex_u64(&sb, panel->u.frame_view->frame.id);
+ stream_append_s8(&sb, s8("]#"));
+ }break;
+ case BeamformerPanelKind_ParameterListing:{
+ stream_append_s8(&sb, s8("Parameter Listing ["));
+ stream_append_u64(&sb, panel->u.parameter_listing.parameter_block);
+ stream_append_s8(&sb, s8("]#"));
+ }break;
+ }
+ stream_append_s8(&sb, s8("##"));
+ stream_append_hex_u64(&sb, (u64)panel);
+ s8 title_s8 = arena_stream_commit(ui_build_arena(), &sb);
+
+ UISignal result;
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ UIPrefHeight(ui_text_dim(1.4f, 1.f))
+ result = ui_label(str8_from_s8(title_s8));
+
+ return result;
+}
+
+function void
+ui_insert_drop_site_spacer_before(UINode *before, f32 pad_node_width)
+{
+ UIParent(0)
+ {
+ UINode *spacer, *spacer_gap;
+ UIPrefHeight(ui_pct(1.f, 0.5f))
+ UIPrefWidth(ui_px(24.f, 1.f))
+ UIBorderColour((v4){.a = 0.9f})
+ UIBGColour((v4){.a = 0.6f})
+ spacer = ui_spacer(UINodeFlag_DrawBackground|UINodeFlag_DrawBorder);
+
+ UIPrefWidth(ui_px(pad_node_width, 1.f))
+ spacer_gap = ui_spacer(0);
+
+ spacer->parent = before->parent;
+ spacer_gap->parent = before->parent;
+ spacer->parent->child_count += 2;
+
+ spacer->previous_sibling = before->previous_sibling;
+ spacer->next_sibling = spacer_gap;
+ before->previous_sibling->next_sibling = spacer;
+
+ spacer_gap->previous_sibling = spacer;
+ spacer_gap->next_sibling = before;
+
+ before->previous_sibling = spacer_gap;
+ }
+}
+
+function UINode *
+ui_box_pad(UINode *container, UISize pad, str8 tag)
+{
+ UINode *result;
+ UIParent(container)
+ UIAxisSize(Axis2_X, ui_pct(1.f, 0.5f))
+ {
+ UIAxisSize(Axis2_Y, pad) ui_spacer(0);
+
+ UIAxisSize(Axis2_Y, ui_pct(1.f, 0.5f))
+ UIChildLayoutAxis(Axis2_X)
+ UIParent(ui_spacer(0))
+ {
+ UIAxisSize(Axis2_X, pad) ui_spacer(0);
+ UIChildLayoutAxis(container->child_layout_axis)
+ result = ui_node_from_string(0, tag);
+ UIAxisSize(Axis2_X, pad) ui_spacer(0);
+ }
+
+ UIAxisSize(Axis2_Y, pad) ui_spacer(0);
+ }
+ return result;
+}
+
+function print_format(3, 4) UINode *
+ui_box_padf(UINode *container, UISize pad, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ UINode *result = ui_box_pad(container, pad, push_str8_fv(ui_build_arena(), format, args));
+ va_end(args);
+ return result;
+}
+
+function BeamformerUIPanel *
+ui_panel_group_equip(UINode *node, BeamformerUIPanel *group)
+{
+ BeamformerUIPanel *result = group;
+
+ if (group->kind != BeamformerPanelKind_Split)
+ UIPrefWidth(ui_children_sum(1.f))
+ UIPrefHeight(ui_children_sum(1.f))
+ {
+ assert(group->kind == BeamformerPanelKind_TabGroup);
+ BeamformerUIPanel *focus = result = group->u.tab_focus;
+
+ node->flags |= UINodeFlag_DropSite;
+
+ UINode *tab_bar_node, *tab_clip_node;
+ UIParent(node)
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefWidth(ui_pct(1.f, 1.f))
+ tab_bar_node = ui_node_from_string(UINodeFlag_Clip, str8("###tab_scroll"));
+
+ UIParent(tab_bar_node)
+ UIAxisAlign(Axis2_Y, Center)
+ UIChildLayoutAxis(Axis2_X)
+ tab_clip_node = ui_node_from_string(UINodeFlag_ViewScrollX, str8("###tab_clip"));
+
+ b32 drop_site = ui_node_key_equal(node->key, ui_context->drop_target_key) &&
+ ui_context->drag_panel &&
+ !beamformer_registers()->split_left_tree &&
+ !beamformer_registers()->split_right_tree;
+ b32 drop_site_handled = 0;
+ u32 drop_site_index = 0;
+ f32 tab_pad = 6.f;
+ UIParent(tab_clip_node)
+ UIFontSize(24.f)
+ {
+ for (BeamformerUIPanel *tab = group->first_child; tab; tab = tab->next_sibling) {
+ ui_padw(tab_pad);
+
+ // NOTE(rnp): push tab
+ UINode *tab_node;
+ UIAxisAlign(Axis2_Y, Center)
+ UIChildLayoutAxis(Axis2_X)
+ // TODO(rnp): per edge border colour
+ UIBorderColour((v4){.a = 0.9f})
+ UIBGColour(tab == focus ? BG_COLOUR : (v4){.a = 0.6f})
+ UIFlags(UINodeFlag_Clickable|
+ UINodeFlag_DrawBackground|
+ UINodeFlag_DrawBorder|
+ UINodeFlag_DrawHotEffects|
+ UINodeFlag_DrawActiveEffects)
+ tab_node = ui_node_from_stringf(tab == focus ? UINodeFlag_FocusActive : 0, "###tab%p", tab);
+
+ if (drop_site && !drop_site_handled &&
+ ui_context->last_mouse.x < (tab_node->computed_position[Axis2_X] + 0.5f * tab_node->computed_size[Axis2_X]))
+ {
+ drop_site_handled = 1;
+ if (ui_context->drag_panel != tab && ui_context->drag_panel != tab->previous_sibling)
+ ui_insert_drop_site_spacer_before(tab_node, tab_pad);
+ }
+
+ UISignal signal = {0};
+ UIParent(tab_node)
+ {
+ ui_padw(UI_BORDER_THICK + tab_pad);
+
+ v4 fg_colour = FG_COLOUR;
+ if (tab != focus) fg_colour.a = 0.8f;
+ UITextColour(fg_colour)
+ ui_panel_label(tab);
+
+ b32 has_settings = (beamformer_panel_infos[tab->kind].flags & BeamformerPanelFlags_HasSettings) != 0;
+ if (tab == focus && has_settings)
+ UIPrefWidth(ui_text_dim(2.f, 1.f))
+ UIPrefHeight(ui_pct(1.f, 1.f))
+ UITextAlign(Right)
+ UIFlags(UINodeFlag_IconText)
+ {
+ ui_padw(0.5f * UI_NODE_PAD);
+
+ signal = ui_label_button(str8("+"));
+ if ui_pressed(signal)
+ ui_context_menu_open(signal.node->key, tab);
+ }
+
+ ui_padw(0.5f * UI_NODE_PAD);
+
+ UIPrefWidth(ui_text_dim(2.f, 1.f))
+ UIPrefHeight(ui_pct(1.f, 1.f))
+ UITextAlign(Center)
+ UIFlags(UINodeFlag_IconText)
+ signal = ui_label_button(str8("x"));
+ if (ui_pressed(signal) || signal.flags & UISignalFlag_MiddlePressed)
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)tab);
+
+ ui_padw(0.5f * UI_NODE_PAD);
+ }
+
+ if (!drop_site_handled) drop_site_index++;
+
+ signal = ui_signal_from_node(tab_node);
+ if ui_pressed(signal)
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_FocusTab].string, .tree_node = (u64)tab);
+ if (signal.flags & UISignalFlag_MiddlePressed)
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)tab);
+ if (ui_dragging(signal) && !point_in_rect(ui_context->last_mouse, ui_node_rect(signal.node)))
+ ui_drag_begin(tab);
+ if ui_released(signal)
+ ui_context->drag_end = 1;
+ }
+
+ ui_padw(tab_pad);
+
+ // NOTE(rnp): context menu opener
+ UISignal signal;
+ UIPrefWidth(ui_text_dim(3.f, 1.f))
+ UIPrefHeight(ui_text_dim(3.f, 1.f))
+ UITextAlign(Center)
+ UIFlags(UINodeFlag_IconText)
+ signal = ui_label_button(str8("+"));
+ if ui_pressed(signal)
+ ui_context_menu_open(signal.node->key, group);
+
+ if (drop_site && !drop_site_handled && ui_context->drag_panel != group->last_child) {
+ drop_site_handled = 1;
+ ui_insert_drop_site_spacer_before(signal.node, tab_pad);
+ }
+
+ ui_padw(tab_pad);
+ }
+
+ ui_signal_from_node(tab_clip_node);
+
+ if (drop_site || drop_site_handled) {
+ beamformer_registers()->drop_target_tree = (u64)group;
+ beamformer_registers()->drop_child_index = drop_site_handled ? drop_site_index : group->child_count;
+ }
+ }
+
+ // NOTE(rnp): close tabgroup button
+ if (!result && group != ui_context->tree)
+ UIParent(ui_box_pad(node, ui_pct(0.5f, 0.5f), str8("")))
+ {
+ ui_top_parent()->semantic_size[Axis2_X] = ui_children_sum(1.f);
+
+ UISignal signal;
+ UIPrefWidth(ui_text_dim(1.5f, 1.f))
+ UIPrefHeight(ui_text_dim(2.f, 1.f))
+ UIBGColour((v4){.a = 0.3f})
+ UIBorderColour((v4){.a = 0.6f})
+ UITextAlign(Center)
+ signal = ui_button(str8("Close Panel"));
+ if ui_pressed(signal)
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)group);
+ }
+
+ ui_signal_from_node(node);
+
+ return result;
+}
+
+function void
+ui_build_regions(UINode *root_node, BeamformerUIPanel *tree_root)
+{
+ BeamformerUI *ui = ui_context;
+
+ struct tree_frame {
+ BeamformerUIPanel *tree;
+ UINode *node;
+ } init[64];
+
+ struct {
+ struct tree_frame *data;
+ da_count count;
+ da_count capacity;
+ } stack = {init, 0, countof(init)};
+
+ *da_push(ui_build_arena(), &stack) = (struct tree_frame){
+ .node = ui_box_padf(root_node, ui_px(UI_NODE_PAD, 1.f), "%p_padded", root_node),
+ .tree = tree_root,
+ };
+ while (stack.count) {
+ struct tree_frame *top = stack.data + --stack.count;
+
+ BeamformerUIPanel *panel = top->tree;
+ UINode *top_node = top->node;
+
+ UIParent(top_node)
+ switch (panel->kind) {
+
+ case BeamformerPanelKind_TabGroup:{
+ UINode *node;
+ UIChildLayoutAxis(Axis2_Y)
+ node = ui_node_from_stringf(UINodeFlag_Clip, "###%p_group", panel);
+ BeamformerUIPanel *next = ui_panel_group_equip(node, panel);
+ if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){
+ .tree = next,
+ .node = node,
+ };
+ }break;
+
+ case BeamformerPanelKind_Split:{
+ assert(panel->child_count == 2);
+
+ Axis2 axis = panel->u.split.axis;
+ top_node->child_layout_axis = axis;
+
+ UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f))
+ {
+ f32 split_pct = panel->u.split.fraction;
+
+ UINode *left;
+ UIAxisSize(axis, ui_pct(split_pct, 0.5f))
+ UIChildLayoutAxis(Axis2_Y)
+ left = ui_node_from_stringf(UINodeFlag_Clip, "###%p_left", panel);
+
+ BeamformerUIPanel *next = ui_panel_group_equip(left, panel->first_child);
+ if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){
+ .tree = next,
+ .node = left,
+ };
+
+ UIAxisSize(axis, ui_children_sum(1.f))
+ UIChildLayoutAxis(axis)
+ UIParent(ui_node_from_stringf(UINodeFlag_Clickable, "###%p_split", panel))
+ {
+ UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0);
+ UIAxisSize(axis, ui_px(UI_SPLIT_HANDLE_THICK, 1.f))
+ UIBGColour((v4){.a = 0.6f})
+ {
+ UINode *rn = ui_spacer(UINodeFlag_DrawBackground|UINodeFlag_DrawHotEffects|UINodeFlag_DrawActiveEffects);
+ rn->hot_t = ui_top_parent()->hot_t;
+ }
+ UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0);
+
+ UISignal signal = ui_signal_from_node(ui_top_parent());
+ if ui_dragging(signal) {
+ Rect nr = ui_node_rect(top_node);
+ v2 uv = rect_uv(clamp_v2_rect(ui->last_mouse, nr), nr);
+ panel->u.split.fraction = Clamp(uv.E[panel->u.split.axis], 0.03f, 0.97f);
+ }
+ }
+
+ UINode *right;
+ UIAxisSize(axis, ui_pct(1.f - split_pct, 0.5f))
+ UIChildLayoutAxis(Axis2_Y)
+ right = ui_node_from_stringf(UINodeFlag_Clip, "###%p_right", panel);
+
+ next = ui_panel_group_equip(right, panel->last_child);
+ if (next) *da_push(ui_build_arena(), &stack) = (struct tree_frame){
+ .tree = next,
+ .node = right,
+ };
+ }
+ }break;
+
+ case BeamformerPanelKind_ComputeBarGraph:{
+ UIFontSize(30.f)
+ UIScroll(Axis2_Y)
+ {
+ ui_top_parent()->child_layout_axis = Axis2_X;
+
+ UINode *label_column, *bar_column;
+ UIAxisAlign(Axis2_X, Left)
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_children_sum(1.f))
+ UIPrefHeight(ui_children_sum(1.f))
+ {
+ UIAxisAlign(Axis2_X, Right)
+ label_column = ui_node_from_string(0, str8("###labels"));
+ ui_padw(UI_NODE_PAD);
+
+ f32 bar_width = ui_top_parent()->parent->parent->parent->computed_size[Axis2_X]
+ - label_column->computed_size[Axis2_X] - UI_NODE_PAD;
+ bar_column = ui_node_from_string(UINodeFlag_CustomDraw, str8("###bars"));
+ bar_column->semantic_size[Axis2_X] = ui_px(bar_width, 0.1f);
+ bar_column->semantic_size[Axis2_Y] = ui_px(label_column->computed_size[Axis2_Y], 1.f);
+ bar_column->custom_draw_function = beamformer_ui_custom_draw_compute_bar_graph;
+ }
+ UIParent(label_column)
+ UIPrefHeight(ui_text_dim(1.3f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ for (i32 i = 0; i < 4; i++)
+ ui_labelf("%d:", -i);
+ }
+
+ }break;
+
+ case BeamformerPanelKind_ComputeStats:{
+ u32 selected_plan = ui->selected_parameter_block % BeamformerMaxParameterBlocks;
+ BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[selected_plan];
+ if (!cp) cp = &beamformer_nil_compute_plan;
+ f32 t = beamformer_ui_blinker_update(&panel->u.compute_stats_broken_shader_blinker, BLINK_SPEED);
+ ui_build_compute_stats(cp, t);
+ }break;
+
+ case BeamformerPanelKind_FrameViewXPlane:
+ {
+ BeamformerFrameView *view = panel->u.frame_view;
+ b32 any_valid = 0;
+ for EachElement(view->plane_active, plane)
+ any_valid |= (view->plane_active[plane] && ui_context->latest_plane[plane].timeline_valid_value);
+ if (any_valid) {
+ UINode *container;
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_pct(1.f, 0.5f))
+ UIPrefHeight(ui_pct(1.f, 0.5f))
+ UIAxisAlign(Axis2_X, Center)
+ container = ui_node_from_string(0, str8("###frame_view_container"));
+ ui_build_3d_xplane_frame_view(container, view);
+ }
+ }break;
+
+ case BeamformerPanelKind_FrameViewCopy:
+ case BeamformerPanelKind_FrameViewLive:
+ {
+ BeamformerFrameView *view = panel->u.frame_view;
+ if (iv3_dimension(view->frame.points) != 0) {
+ // TODO(rnp): cleanup, why do we need this extra container
+ UINode *container;
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_pct(1.f, 0.5f))
+ UIPrefHeight(ui_pct(1.f, 0.5f))
+ UIAxisAlign(Axis2_X, Center)
+ container = ui_node_from_string(0, str8("###frame_view_container"));
+ ui_build_frame_view(container, view);
+ }
+ }break;
+
+ case BeamformerPanelKind_ParameterListing:{ ui_build_parameters_listing(panel); }break;
+
+ case BeamformerPanelKind_LiveImagingControls:{ ui_build_live_imaging_controls(panel); }break;
+
+ InvalidDefaultCase;
+ }
+
+ ui_signal_from_node(top_node);
+ }
+}
+
+function UINode *
+ui_build_drag_hover_node(void)
+{
+ BeamformerUI *ui = ui_context;
+
+ BeamformerUIPanel *tree = (BeamformerUIPanel *)beamformer_registers()->drop_target_tree;
+ UINode *target = (UINode *)ui->drop_target_node;
+ Axis2 axis = beamformer_registers()->split_axis;
+ Axis2 flip = axis2_flip(axis);
+ Rect nr = ui_node_rect(target);
+ f32 pct = 4.0f;
+ f32 off_pct = 0.95f;
+ if (target == ui->root_node)
+ pct = 0.1f;
+ if (tree->kind == BeamformerPanelKind_TabGroup) {
+ off_pct = 0.8f;
+ pct = 0.3f;
+ }
+ if (beamformer_registers()->split_left_tree == beamformer_registers()->split_right_tree)
+ off_pct = pct = 0.8f;
+
+ UINode *result = push_struct(ui_build_arena(), UINode);
+ result->flags = UINodeFlag_DrawBackground;
+ result->bg_colour = NODE_SPLIT_COLOUR;
+ result->computed_size[flip] = off_pct * nr.size.E[flip];
+ result->computed_size[axis] = pct * nr.size.E[axis];
+ result->computed_position[axis] = nr.pos.E[axis];
+ result->computed_position[flip] = nr.pos.E[flip];
+
+ if (target == ui->root_node) {
+ result->computed_position[flip] += 0.5f * (nr.size.E[flip] - result->computed_size[flip]);
+ if (beamformer_registers()->split_left_tree == (u64)ui->tree)
+ result->computed_position[axis] = nr.pos.E[axis] + nr.size.E[axis] - result->computed_size[axis];
+ } else {
+ result->computed_position[axis] += 0.5f * (nr.size.E[axis] - result->computed_size[axis]);
+ result->computed_position[flip] += 0.5f * (nr.size.E[flip] - result->computed_size[flip]);
+
+ if (tree->kind == BeamformerPanelKind_TabGroup) {
+ if (beamformer_registers()->split_left_tree == (u64)tree)
+ result->computed_position[axis] += 0.45f * (nr.size.E[axis] - result->computed_size[axis]);
+ if (beamformer_registers()->split_right_tree == (u64)tree)
+ result->computed_position[axis] -= 0.45f * (nr.size.E[axis] - result->computed_size[axis]);
+ }
+ }
+
+ return result;
+}
+
+function b32
+ui_build_drag_split_box(Axis2 axis, b32 two_way, i32 highlight_index, str8 tag)
+{
+ UINode *split_box;
+ UIAxisAlign(axis2_flip(axis), Center)
+ UIAxisSize(axis2_flip(axis), ui_px(60.f, 1.f))
+ UIAxisSize(axis, ui_children_sum(1.f))
+ UIChildLayoutAxis(axis)
+ UIBorderColour((v4){.a = 0.8f})
+ UIBGColour(BG_COLOUR)
+ split_box = ui_node_from_string(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground,
+ push_str8_from_parts(ui_build_arena(), str8(""),
+ str8("drag_split_box_"), tag));
+ b32 result = point_in_rect(ui_context->last_mouse, ui_node_rect(split_box));
+
+ UIParent(split_box)
+ UIAxisSize(axis2_flip(axis), ui_pct(0.7f, 0.5f))
+ UIAxisSize(axis, ui_px(1.5f * UI_NODE_PAD, 1.f))
+ {
+ UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0);
+
+ UIBorderColour((v4){.a = highlight_index <= 0 ? 0.0f : 0.4f})
+ UIBGColour(highlight_index <= 0 ? NODE_SPLIT_COLOUR : (v4){0})
+ ui_spacer(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground);
+
+ UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0);
+
+ if (two_way) {
+ UIBorderColour((v4){.a = highlight_index != 0 ? 0.0f : 0.4f})
+ UIBGColour(highlight_index != 0 ? NODE_SPLIT_COLOUR : (v4){0})
+ ui_spacer(UINodeFlag_DrawBorder|UINodeFlag_DrawBackground);
+
+ UIAxisSize(axis, ui_px(UI_NODE_PAD, 1.f)) ui_spacer(0);
+ }
+ }
+ return result;
+}
+
+function b32
+ui_build_drag_overlay_splitter(Axis2 axis, b32 two_way, i32 highlight_index, str8 tag)
+{
+ b32 result = 0;
+
+ UINode *container;
+ UIChildLayoutAxis(axis2_flip(axis))
+ UIAxisAlign(axis2_flip(axis), Center)
+ UIAxisSize(axis, ui_children_sum(1.f))
+ UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f))
+ {
+ container = ui_spacer(0);
+ }
+
+ UIParent(container)
+ result = ui_build_drag_split_box(axis, two_way, highlight_index, tag);
+ return result;
+}
+
+function void
+ui_build_drag_overlay(Rect window_rect)
+{
+ BeamformerUI *ui = ui_context;
+
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_px(window_rect.size.x, 1.f))
+ UIPrefHeight(ui_px(window_rect.size.y, 1.f))
+ ui->drag_overlay_edges_root = ui_node_from_string(0, str8("drag_overlay_edges_root"));
+
+ UIParent(ui->drag_overlay_edges_root)
+ {
+ if (ui_build_drag_overlay_splitter(Axis2_Y, 0, 0, str8("top")))
+ {
+ beamformer_registers()->split_axis = Axis2_Y;
+ beamformer_registers()->split_left_tree = 0;
+ beamformer_registers()->split_right_tree = (u64)ui->tree;
+ beamformer_registers()->drop_target_tree = (u64)ui->tree;
+ ui->drop_target_node = ui->root_node;
+ }
+
+ UIPrefHeight(ui_pct(1.f, 0.5f))
+ UIParent(ui_spacer(0))
+ {
+ if (ui_build_drag_overlay_splitter(Axis2_X, 0, 0, str8("left")))
+ {
+ beamformer_registers()->split_axis = Axis2_X;
+ beamformer_registers()->split_left_tree = 0;
+ beamformer_registers()->split_right_tree = (u64)ui->tree;
+ beamformer_registers()->drop_target_tree = (u64)ui->tree;
+ ui->drop_target_node = ui->root_node;
+ }
+
+ UIPrefWidth(ui_pct(1.f, 0.5f)) ui_spacer(0);
+
+ if (ui_build_drag_overlay_splitter(Axis2_X, 0, 0, str8("right")))
+ {
+ beamformer_registers()->split_axis = Axis2_X;
+ beamformer_registers()->split_left_tree = (u64)ui->tree;
+ beamformer_registers()->split_right_tree = 0;
+ beamformer_registers()->drop_target_tree = (u64)ui->tree;
+ ui->drop_target_node = ui->root_node;
+ }
+ }
+
+ if (ui_build_drag_overlay_splitter(Axis2_Y, 0, 0, str8("bottom")))
+ {
+ beamformer_registers()->split_axis = Axis2_Y;
+ beamformer_registers()->split_left_tree = (u64)ui->tree;
+ beamformer_registers()->split_right_tree = 0;
+ beamformer_registers()->drop_target_tree = (u64)ui->tree;
+ ui->drop_target_node = ui->root_node;
+ }
+ }
+
+ struct tree_frame {
+ BeamformerUIPanel *tree;
+ UINode *node;
+ } init[64];
+
+ struct {
+ struct tree_frame *data;
+ da_count count;
+ da_count capacity;
+ } stack = {init, 0, countof(init)};
+
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefWidth(ui_px(window_rect.size.x, 1.f))
+ UIPrefHeight(ui_px(window_rect.size.y, 1.f))
+ ui->drag_overlay_root = ui_node_from_string(0, str8("drag_overlay_root"));
+
+ *da_push(ui_build_arena(), &stack) = (struct tree_frame){
+ .node = ui->drag_overlay_root,
+ .tree = ui->tree,
+ };
+ while (stack.count) {
+ struct tree_frame *top = stack.data + --stack.count;
+
+ BeamformerUIPanel *panel = top->tree;
+ UINode *top_node = top->node;
+
+ UIParent(top_node)
+ switch (panel->kind) {
+ default:{}break;
+ case BeamformerPanelKind_Split:{
+ Axis2 axis = panel->u.split.axis;
+ top_node->child_layout_axis = axis;
+
+ UIAxisSize(axis2_flip(axis), ui_pct(1.f, 0.5f))
+ {
+ f32 split_pct = panel->u.split.fraction;
+
+ UINode *spacer;
+ UIAxisSize(axis, ui_pct(split_pct, 0.5f)) spacer = ui_spacer(0);
+
+ if (panel->first_child->kind == BeamformerPanelKind_Split) {
+ *da_push(ui_build_arena(), &stack) = (struct tree_frame){
+ .tree = panel->first_child,
+ .node = spacer,
+ };
+ }
+
+ UIChildLayoutAxis(axis2_flip(axis))
+ UIAxisAlign(axis2_flip(axis), Center)
+ UIAxisSize(axis, ui_children_sum(1.f))
+ UIParent(ui_spacer(0))
+ {
+ // TODO(rnp): cleanup
+ Stream sb = arena_stream(*ui_build_arena());
+ stream_appendf(&sb, "###%p_split", panel);
+ s8 tag = arena_stream_commit(ui_build_arena(), &sb);
+
+ if (ui_build_drag_split_box(axis, 1, -1, str8_from_s8(tag))) {
+ beamformer_registers()->split_axis = axis;
+ beamformer_registers()->split_left_tree = (u64)panel;
+ beamformer_registers()->split_right_tree = (u64)ui->drag_panel;
+ beamformer_registers()->drop_target_tree = (u64)panel;
+ ui->drop_target_node = ui_top_parent();
+ }
+ }
+
+ UIAxisSize(axis, ui_pct(1.f - split_pct, 0.5f)) spacer = ui_spacer(0);
+
+ if (panel->last_child->kind == BeamformerPanelKind_Split) {
+ *da_push(ui_build_arena(), &stack) = (struct tree_frame){
+ .tree = panel->last_child,
+ .node = spacer,
+ };
+ }
+ }
+ }break;
+ }
+ }
+
+ ui->drag_overlay_tab_root = 0;
+ if (beamformer_registers()->drop_target_tree &&
+ !beamformer_registers()->split_left_tree &&
+ !beamformer_registers()->split_right_tree)
+ {
+ BeamformerUIPanel *group = (BeamformerUIPanel *)beamformer_registers()->drop_target_tree;
+ UINode *target = ui_node_from_key(ui->drop_target_key);
+
+ assert(!group->parent || (group->parent && group->parent->kind == BeamformerPanelKind_Split));
+ Axis2 parent_axis = group->parent ? group->parent->u.split.axis : Axis2_Count;
+
+ Rect tr = ui_node_rect(target);
+ if (point_in_rect(ui->last_mouse, tr)) {
+ UIPrefWidth(ui_px(tr.size.x, 1.f))
+ UIPrefHeight(ui_px(tr.size.h, 1.f))
+ UIAxisAlign(Axis2_X, Center)
+ UIAxisAlign(Axis2_Y, Center)
+ UIChildLayoutAxis(Axis2_Y)
+ {
+ ui->drag_overlay_tab_root = ui_node_from_string(0, str8("drag_overlay_tab_root"));
+ }
+
+ ui->drag_overlay_tab_root->computed_position[Axis2_X] = tr.pos.x;
+ ui->drag_overlay_tab_root->computed_position[Axis2_Y] = tr.pos.y;
+
+ UINode *inner;
+ UIAxisAlign(Axis2_X, Center)
+ UIChildLayoutAxis(Axis2_Y)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ UIParent(ui->drag_overlay_tab_root)
+ inner = ui_spacer(0);
+
+ UIParent(inner)
+ {
+ if (parent_axis != Axis2_Y && ui_build_drag_split_box(Axis2_Y, 1, 0, str8("top")))
+ {
+ beamformer_registers()->split_axis = Axis2_Y;
+ beamformer_registers()->split_left_tree = (u64)ui->drag_panel;
+ beamformer_registers()->split_right_tree = (u64)group;
+ ui->drop_target_node = target;
+ }
+
+ ui_padh(UI_NODE_PAD);
+
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ UIParent(ui_spacer(0))
+ {
+ if (parent_axis != Axis2_X && ui_build_drag_split_box(Axis2_X, 1, 0, str8("left")))
+ {
+ beamformer_registers()->split_axis = Axis2_X;
+ beamformer_registers()->split_left_tree = (u64)ui->drag_panel;
+ beamformer_registers()->split_right_tree = (u64)group;
+ ui->drop_target_node = target;
+ }
+
+ ui_padw(UI_NODE_PAD);
+
+ if (parent_axis != Axis2_Count &&
+ ui_build_drag_split_box(axis2_flip(parent_axis), 0, 0, str8("center")))
+ {
+ beamformer_registers()->split_left_tree = (u64)group;
+ beamformer_registers()->split_right_tree = (u64)group;
+ beamformer_registers()->drop_target_tree = (u64)group;
+ beamformer_registers()->drop_child_index = group->child_count;
+ ui->drop_target_node = target;
+ }
+
+ ui_padw(UI_NODE_PAD);
+
+ if (parent_axis != Axis2_X && ui_build_drag_split_box(Axis2_X, 1, 1, str8("right")))
+ {
+ beamformer_registers()->split_axis = Axis2_X;
+ beamformer_registers()->split_left_tree = (u64)group;
+ beamformer_registers()->split_right_tree = (u64)ui->drag_panel;
+ ui->drop_target_node = target;
+ }
+ }
+
+ ui_padh(UI_NODE_PAD);
+
+ if (parent_axis != Axis2_Y && ui_build_drag_split_box(Axis2_Y, 1, 1, str8("bottom")))
+ {
+ beamformer_registers()->split_axis = Axis2_Y;
+ beamformer_registers()->split_left_tree = (u64)group;
+ beamformer_registers()->split_right_tree = (u64)ui->drag_panel;
+ ui->drop_target_node = target;
+ }
+ }
+ }
+ }
+}
+
+function void
+ui_build_context_menu_for_panel(BeamformerUIPanel *panel)
+{
+ if (!panel)
+ {
+ // TODO(rnp): command pallete
+ }
+
+ if (panel) {
+ UIAxisSize(Axis2_X, ui_px(0.f, 0.5f)) ui_padh(0.8f * UI_NODE_PAD);
+ switch(panel->kind) {
+ InvalidDefaultCase;
+
+ case BeamformerPanelKind_TabGroup:{
+ for EachElement(beamformer_panel_infos, it) {
+ BeamformerPanelInfo *info = beamformer_panel_infos + it;
+ b32 list = (info->flags & BeamformerPanelFlags_List) != 0;
+ b32 needs_frame = (info->flags & BeamformerPanelFlags_NeedsFrame) != 0;
+ if (list && (!needs_frame || beamformer_frame_valid(beamformer_registers()->frame))) {
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ UIParent(ui_spacer(0))
+ {
+ UISignal signal;
+ ui_padw(UI_NODE_PAD);
+ UIPrefHeight(ui_text_dim(1.1f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ signal = ui_label_button(info->display);
+ ui_padw(UI_NODE_PAD);
+
+ if ui_pressed(signal) {
+ ui_context_menu_close();
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_OpenTab].string,
+ .tree_node = (u64)panel,
+ .string = info->string);
+ }
+ }
+ }
+ }
+ }break;
+
+ case BeamformerPanelKind_FrameViewCopy:
+ case BeamformerPanelKind_FrameViewLive:
+ {
+ BeamformerFrameView *view = panel->u.frame_view;
+
+ UINode *label_column, *button_column;
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ UIParent(ui_spacer(0))
+ UIChildLayoutAxis(Axis2_Y)
+ {
+ ui_padw(UI_NODE_PAD);
+ UIAxisAlign(Axis2_X, Left) label_column = ui_node_from_string(0, str8("###labels"));
+ ui_padw(UI_NODE_PAD * 2.f);
+ UIAxisAlign(Axis2_X, Center)
+ button_column = ui_node_from_string(0, str8("###buttons"));
+ ui_padw(UI_NODE_PAD);
+ }
+
+ UIPrefHeight(ui_text_dim(1.1f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ {
+ read_only local_persist str8 dimension_strings[2][2] = {
+ {str8_comp("Extent Scale Bar"), str8_comp("Magnitude Scale Bar")},
+ {str8_comp("Lateral Scale Bar"), str8_comp("Axial Scale Bar") },
+ };
+
+ UIParent(label_column) ui_label(str8("Plane Tag"));
+ UIParent(button_column)
+ UIFlags(UINodeFlag_Scroll)
+ {
+ str8 tag = str8("Any");
+ if (view->view_plane != BeamformerViewPlaneTag_Count)
+ tag = beamformer_view_plane_tag_strings[view->view_plane];
+ UISignal signal = ui_label_button(push_str8_from_parts(ui_build_arena(), str8(""),
+ tag, str8("###PlaneTagButton")));
+ i32 delta = signal.scroll.y + ui_pressed(signal);
+ view->view_plane = circular_add(view->view_plane, delta, BeamformerViewPlaneTag_Count + 1);
+ if (ui_pressed(signal) || ui_scrolled(signal))
+ view->dirty = 1;
+ }
+
+ i32 dimension = iv3_dimension(view->frame.points);
+ dimension = Min(dimension, 2);
+ if (dimension > 0) {
+ for EachEnumValue(Axis2, axis) {
+ f32 row_height;
+ UIParent(label_column)
+ row_height = ui_label(dimension_strings[dimension - 1][axis]).node->computed_size[Axis2_Y];
+
+ UIParent(button_column)
+ // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIPrefWidth(ui_px(row_height, 1.f))
+ {
+ UISignal signal = ui_toggle_buttonf(view->scale_bar_active[axis], "###axis_%u", axis);
+ if ui_pressed(signal)
+ view->scale_bar_active[axis] = !view->scale_bar_active[axis];
+ }
+ }
+ }
+
+ {
+ f32 row_height;
+ UIParent(label_column)
+ row_height = ui_label(str8("Log Scale")).node->computed_size[Axis2_Y];
+
+ UIParent(button_column)
+ // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIPrefWidth(ui_px(row_height, 1.f))
+ {
+ UISignal signal = ui_toggle_button(view->log_scale, str8("###log_scale"));
+ if ui_pressed(signal) {
+ view->log_scale = !view->log_scale;
+ view->dirty = 1;
+ }
+ }
+ }
+
+ if (dimension > 0 && panel->kind != BeamformerPanelKind_FrameViewCopy) {
+ f32 row_height;
+ UIParent(label_column)
+ {
+ UISignal signal = ui_label_button(str8("Copy Frame"));
+ row_height = signal.node->computed_size[Axis2_Y];
+ if ui_pressed(signal) {
+ ui_context_menu_close();
+ beamformer_command(beamformer_command_infos[BeamformerCommandKind_OpenTab].string,
+ .tree_node = (u64)panel->parent,
+ .frame_view = (u64)view,
+ .string = beamformer_panel_infos[BeamformerPanelKind_FrameViewCopy].string);
+ }
+ }
+
+ // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly
+ UIParent(button_column) ui_padh(row_height);
+ }
+
+ // TODO(rnp): extra frame view copy settings
+ if (panel->kind == BeamformerPanelKind_FrameViewCopy) {
+ }
+ }
+ }break;
+
+ case BeamformerPanelKind_FrameViewXPlane:{
+ BeamformerFrameView *view = panel->u.frame_view;
+
+ UINode *label_column, *button_column;
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ UIParent(ui_spacer(0))
+ UIChildLayoutAxis(Axis2_Y)
+ {
+ ui_padw(UI_NODE_PAD);
+ UIAxisAlign(Axis2_X, Left) label_column = ui_node_from_string(0, str8("###labels"));
+ ui_padw(UI_NODE_PAD * 2.f);
+ UIAxisAlign(Axis2_X, Center)
+ button_column = ui_node_from_string(0, str8("###buttons"));
+ ui_padw(UI_NODE_PAD);
+ }
+
+ UIPrefHeight(ui_text_dim(1.1f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ {
+ {
+ f32 row_height;
+ UIParent(label_column)
+ row_height = ui_label(str8("Log Scale")).node->computed_size[Axis2_Y];
+
+ UIParent(button_column)
+ // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIPrefWidth(ui_px(row_height, 1.f))
+ {
+ UISignal signal = ui_toggle_button(view->log_scale, str8("###log_scale"));
+ if ui_pressed(signal) {
+ view->log_scale = !view->log_scale;
+ view->dirty = 1;
+ }
+ }
+ }
+
+ {
+ f32 row_height;
+ UIParent(label_column)
+ row_height = ui_label(str8("Demo Mode")).node->computed_size[Axis2_Y];
+
+ UIParent(button_column)
+ // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIPrefWidth(ui_px(row_height, 1.f))
+ {
+ UISignal signal = ui_toggle_button(view->demo, str8("###demo_mode"));
+ if ui_pressed(signal)
+ view->demo = !view->demo;
+ }
+ }
+
+ UIParent(label_column)
+ {
+ f32 row_height = ui_label(str8("Planes:")).node->computed_size[Axis2_Y];
+ // TODO(rnp): ui_em(1.f, 1.f) once font size matches directly
+ UIParent(button_column) ui_padh(row_height);
+ }
+ for EachElement(view->plane_active, plane) {
+ f32 row_height;
+ UIParent(label_column)
+ {
+ str8 label = push_str8_from_parts(ui_build_arena(), str8(""), str8(" "),
+ beamformer_view_plane_tag_strings[plane]);
+ row_height = ui_label(label).node->computed_size[Axis2_Y];
+ }
+
+ UIParent(button_column)
+ UIPrefHeight(ui_px(row_height, 1.f))
+ UIPrefWidth(ui_px(row_height, 1.f))
+ {
+ UISignal signal = ui_toggle_button(view->plane_active[plane],
+ beamformer_view_plane_tag_strings[plane]);
+ if ui_pressed(signal)
+ view->plane_active[plane] = !view->plane_active[plane];
+ }
+ }
+ }
+ }break;
+
+ case BeamformerPanelKind_ParameterListing:{
+ UINode *label_column, *button_column;
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ UIParent(ui_spacer(0))
+ UIChildLayoutAxis(Axis2_Y)
+ {
+ ui_padw(UI_NODE_PAD);
+ UIAxisAlign(Axis2_X, Left) label_column = ui_node_from_string(0, str8("###labels"));
+ ui_padw(UI_NODE_PAD * 2.f);
+ UIAxisAlign(Axis2_X, Center)
+ button_column = ui_node_from_string(0, str8("###buttons"));
+ ui_padw(UI_NODE_PAD);
+ }
+
+ UIPrefHeight(ui_text_dim(1.1f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ {
+ UIParent(label_column) ui_label(str8("Block"));
+ UIParent(button_column)
+ {
+ UISignal signal;
+ u32 cycle = beamformer_context->shared_memory->reserved_parameter_blocks;
+ u32 block = panel->u.parameter_listing.parameter_block;
+ UIFlags(cycle <= 1 ? UINodeFlag_Disabled : 0)
+ signal = ui_label_buttonf("%u", block);
+ if (ui_pressed(signal) || ui_scrolled(signal)) {
+ i32 delta = signal.scroll.y + ui_pressed(signal);
+ panel->u.parameter_listing.parameter_block = circular_add(block, delta, cycle);
+ }
+ }
+ }
+ }break;
+
+ case BeamformerPanelKind_LiveImagingControls:{
+ BeamformerLiveImagingParameters *lip = &beamformer_context->shared_memory->live_imaging_parameters;
+ u64 enabled_kinds = atomic_load_u64(&lip->acquisition_kind_enabled_flags);
+
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ for EachBit(enabled_kinds, kind)
+ UIParent(ui_spacer(0))
+ {
+ UISignal signal;
+ ui_padw(UI_NODE_PAD);
+ UIPrefHeight(ui_text_dim(1.1f, 1.f))
+ UIPrefWidth(ui_text_dim(1.f, 1.f))
+ signal = ui_label_button(beamformer_acquisition_kind_strings[kind]);
+ ui_padw(UI_NODE_PAD);
+
+ if ui_pressed(signal) {
+ ui_context_menu_close();
+ lip->acquisition_kind = kind;
+ atomic_or_u32(&beamformer_context->shared_memory->live_imaging_dirty_flags,
+ BeamformerLiveImagingDirtyFlags_AcquisitionKind);
+ }
+ }
+ }break;
+
+ }
+ UIAxisSize(Axis2_X, ui_px(0.f, 0.5f)) ui_padh(0.8f * UI_NODE_PAD);
}
}
function void
-begin_text_input(InputState *is, Rect r, Variable *container, v2 mouse)
+ui_layout_constrain(UINode *root)
{
- assert(container->type == VT_UI_TEXT_BOX);
- Font *font = is->font = is->hot_font;
- Stream s = {.cap = countof(is->buf), .data = is->buf};
- stream_append_variable(&s, container->view.child);
- is->count = s.widx;
- is->container = container;
-
- is->numeric = container->view.child->type != VT_LIVE_CONTROLS_STRING;
- if (container->view.child->type == VT_LIVE_CONTROLS_STRING) {
- BeamformerLiveImagingParameters *lip = container->view.child->generic;
- if (lip->save_name_tag_length <= 0)
- is->count = 0;
+ assert(!ui_node_is_nil(root->first_child));
+
+ // NOTE(rnp): for violations in non-layout axis all we can do is clamp
+ {
+ Axis2 axis = axis2_flip(root->child_layout_axis);
+ if ((root->flags & (UINodeFlag_AllowOverflowX << axis)) == 0) {
+ for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling)
+ child->computed_size[axis] = Min(child->computed_size[axis], root->computed_size[axis]);
+ }
}
- /* NOTE: extra offset to help with putting a cursor at idx 0 */
- f32 text_half_char_width = 10.0f;
- f32 hover_p = CLAMP01((mouse.x - r.pos.x) / r.size.w);
- i32 i;
- f32 x_off = text_half_char_width, x_bounds = r.size.w * hover_p;
- for (i = 0; i < is->count && x_off < x_bounds; i++) {
- /* NOTE: assumes font glyphs are ordered ASCII */
- i32 idx = is->buf[i] - 0x20;
- x_off += (f32)font->glyphs[idx].advanceX;
- if (font->glyphs[idx].advanceX == 0)
- x_off += font->recs[idx].width;
+ Axis2 axis = root->child_layout_axis;
+ if ((root->flags & (UINodeFlag_AllowOverflowX << axis)) == 0) {
+ f32 allowed_size = root->computed_size[axis];
+ f32 total_size = 0;
+ f32 total_weighted_size = 0;
+
+ for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) {
+ total_size += child->computed_size[axis];
+ total_weighted_size += child->computed_size[axis] * (1.0f - child->semantic_size[axis].strictness);
+ }
+
+ f32 remaining_size = root->computed_size[axis];
+ f32 violation = total_size - allowed_size;
+ if (violation > 0 && total_weighted_size > 0) {
+ f32 fixup_fraction = Clamp01(violation / total_weighted_size);
+ for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling) {
+ f32 fixup = Max(0, child->computed_size[axis] * (1.0f - child->semantic_size[axis].strictness));
+ child->computed_size[axis] -= fixup * fixup_fraction;
+
+ if (child->semantic_size[axis].kind != UISizeKind_PercentOfParent)
+ remaining_size -= child->computed_size[axis];
+ }
+ }
+
+ // NOTE(rnp): fixup sizes dependant on parent
+ for (UINode *child = root->first_child; !ui_node_is_nil(child); child = child->next_sibling)
+ if (child->semantic_size[axis].kind == UISizeKind_PercentOfParent)
+ child->computed_size[axis] = remaining_size * child->semantic_size[axis].value;
}
- is->cursor = i;
}
function void
-end_text_input(InputState *is, Variable *var)
+ui_layout_nodes(UINode *root)
{
- f32 value = 0;
- if (is->numeric) {
- NumberConversion number = number_from_str8((str8){.length = is->count, .data = is->buf});
- value = number.F64;
- }
+ struct node_frame {
+ UINode *node;
+ // NOTE(rnp): for post order traversal
+ b32 visited;
+ } init[64] = {0};
- switch (var->type) {
- case VT_SCALED_F32:{ var->scaled_real32.val = value; }break;
- case VT_F32:{ var->real32 = value; }break;
- case VT_BEAMFORMER_VARIABLE:{
- BeamformerVariable *bv = &var->beamformer_variable;
- *bv->store = CLAMP(value / bv->display_scale, bv->limits.x, bv->limits.y);
- var->hover_t = 0;
- }break;
- case VT_LIVE_CONTROLS_STRING:{
- BeamformerLiveImagingParameters *lip = var->generic;
- mem_copy(lip->save_name_tag, is->buf, (uz)is->count % countof(lip->save_name_tag));
- lip->save_name_tag_length = is->count % countof(lip->save_name_tag);
- }break;
- InvalidDefaultCase;
- }
-}
+ struct {
+ struct node_frame *data;
+ da_count count;
+ da_count capacity;
+ } stack = {init, 0, countof(init)};
-function b32
-update_text_input(InputState *is, Variable *var)
-{
- assert(is->cursor != -1);
+ ///////////////////////
+ // NOTE(rnp): First Pass: non dependant sizes
+ da_push(ui_build_arena(), &stack)->node = root;
+ while (stack.count) {
+ struct node_frame *top = stack.data + --stack.count;
+ UINode *node = top->node;
+
+ if (node->flags & UINodeFlag_DrawText) {
+ Font font = ui_font_for_node(node);
+ str8 string = ui_draw_part_from_key_string(node->string);
+ if (node->flags & UINodeFlag_IconText)
+ node->text_size = measure_text_tight(font, string);
+ else
+ node->text_size = measure_text(font, string);
+ }
- ui_blinker_update(&is->cursor_blink, BLINK_SPEED);
+ for EachElement(node->semantic_size, it) {
+ switch (node->semantic_size[it].kind) {
+ case UISizeKind_Pixels:{node->computed_size[it] = node->semantic_size[it].value;}break;
- var->hover_t -= 2 * HOVER_SPEED * dt_for_frame;
- var->hover_t = CLAMP01(var->hover_t);
+ case UISizeKind_TextContent:{
+ node->computed_size[it] = node->semantic_size[it].value * node->text_size.E[it];
+ }break;
- /* NOTE: handle multiple input keys on a single frame */
- for (i32 key = GetCharPressed();
- is->count < countof(is->buf) && key > 0;
- key = GetCharPressed())
- {
- b32 allow_key = !is->numeric || (BETWEEN(key, '0', '9') || (key == '.') ||
- (key == '-' && is->cursor == 0));
- if (allow_key) {
- memory_move(is->buf + is->cursor + 1,
- is->buf + is->cursor,
- (uz)(is->count - is->cursor));
- is->buf[is->cursor++] = (u8)key;
- is->count++;
+ default:{}break;
+ }
}
+
+ // NOTE(rnp): push children
+ for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling)
+ da_push(ui_build_arena(), &stack)->node = child;
}
- is->cursor -= (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) && is->cursor > 0;
- is->cursor += (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) && is->cursor < is->count;
+ ///////////////////////
+ // NOTE(rnp): Second Pass (Pre Order): parent dependant sizes
+ da_push(ui_build_arena(), &stack)->node = root;
+ while (stack.count) {
+ struct node_frame *top = stack.data + --stack.count;
+ UINode *node = top->node;
- if ((IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) && is->cursor > 0) {
- is->cursor--;
- if (is->cursor < countof(is->buf) - 1) {
- memory_move(is->buf + is->cursor,
- is->buf + is->cursor + 1,
- (uz)(is->count - is->cursor - 1));
+ for EachElement(node->semantic_size, it) {
+ if (node->semantic_size[it].kind == UISizeKind_PercentOfParent) {
+ f32 parent_size = node->parent->computed_size[it];
+ node->computed_size[it] = node->semantic_size[it].value * parent_size;
+ }
}
- is->count--;
- }
- if ((IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) && is->cursor < is->count) {
- memory_move(is->buf + is->cursor,
- is->buf + is->cursor + 1,
- (uz)(is->count - is->cursor - 1));
- is->count--;
+ // NOTE(rnp): push children
+ for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling)
+ da_push(ui_build_arena(), &stack)->node = child;
}
- b32 result = IsKeyPressed(KEY_ENTER);
- return result;
-}
+ ///////////////////////
+ // NOTE(rnp): Third Pass (Post Order): child dependant sizes
+ da_push(ui_build_arena(), &stack)->node = root;
+ while (stack.count) {
+ struct node_frame *top = stack.data + stack.count - 1;
-function void
-scale_bar_interaction(BeamformerUI *ui, ScaleBar *sb, v2 mouse)
-{
- Interaction *it = &ui->interaction;
- b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
- b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT);
- f32 mouse_wheel = GetMouseWheelMoveV().y;
-
- if (mouse_left_pressed) {
- v2 world_mouse = screen_point_to_world_2d(mouse, it->rect.pos,
- v2_add(it->rect.pos, it->rect.size),
- (v2){{*sb->min_value, *sb->min_value}},
- (v2){{*sb->max_value, *sb->max_value}});
- f32 new_coord = F32_INFINITY;
- switch (sb->direction) {
- case SB_LATERAL: new_coord = world_mouse.x; break;
- case SB_AXIAL: new_coord = world_mouse.y; break;
- }
- if (sb->zoom_starting_coord == F32_INFINITY) {
- sb->zoom_starting_coord = new_coord;
+ UINode *node = top->node;
+ if (!top->visited && node->child_count) {
+ top->visited = 1;
+
+ // NOTE(rnp): push children
+ for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling)
+ da_push(ui_build_arena(), &stack)->node = child;
} else {
- f32 min = sb->zoom_starting_coord;
- f32 max = new_coord;
- if (min > max) swap(min, max);
+ // NOTE(rnp): pop
+ stack.count--;
+
+ for EachElement(node->semantic_size, it) {
+ if (node->semantic_size[it].kind == UISizeKind_ChildrenSum) {
+ f32 size_sum = 0;
+ for (UINode *child = node->first_child;
+ !ui_node_is_nil(child);
+ child = child->next_sibling)
+ {
+ if (it == node->child_layout_axis) {
+ size_sum += child->computed_size[it];
+ } else {
+ size_sum = Max(size_sum, child->computed_size[it]);
+ }
+ }
+ node->computed_size[it] = size_sum;
+ }
+ }
+ }
+ }
- v2_sll *savepoint = SLLPopFreelist(ui->scale_bar_savepoint_freelist);
- if (!savepoint) savepoint = push_struct(&ui->arena, v2_sll);
+ ///////////////////////
+ // NOTE(rnp): Fourth Pass (Pre Order): solve violations
+ da_push(ui_build_arena(), &stack)->node = root;
+ while (stack.count) {
+ struct node_frame *top = stack.data + --stack.count;
- savepoint->v.x = *sb->min_value;
- savepoint->v.y = *sb->max_value;
- SLLPush(savepoint, sb->savepoint_stack);
+ UINode *node = top->node;
+ if (node->child_count)
+ ui_layout_constrain(node);
- *sb->min_value = min;
- *sb->max_value = max;
+ // NOTE(rnp): push children
+ for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling)
+ da_push(ui_build_arena(), &stack)->node = child;
+ }
- sb->zoom_starting_coord = F32_INFINITY;
+ ///////////////////////
+ // NOTE(rnp): Final Pass (Pre Order): fill positions
+ da_push(ui_build_arena(), &stack)->node = root;
+ while (stack.count) {
+ struct node_frame *top = stack.data + --stack.count;
+
+ UINode *node = top->node;
+ Axis2 layout_axis = node->child_layout_axis;
+ Axis2 flipped_axis = axis2_flip(layout_axis);
+ f32 offset = 0;
+ for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) {
+ child->computed_position[flipped_axis] = node->computed_position[flipped_axis];
+ child->computed_position[layout_axis] = offset + node->computed_position[layout_axis];
+ offset += child->computed_size[layout_axis];
}
- }
- if (mouse_right_pressed) {
- v2_sll *savepoint = sb->savepoint_stack;
- if (savepoint) {
- *sb->min_value = savepoint->v.x;
- *sb->max_value = savepoint->v.y;
- sb->savepoint_stack = savepoint->next;
- SLLPushFreelist(savepoint, ui->scale_bar_savepoint_freelist);
+ for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) {
+ for EachElement(node->alignment, axis) {
+ f32 size_delta = node->computed_size[axis] - child->computed_size[axis];
+ child->computed_position[axis] += ui_alignment_correction(node->alignment[axis], size_delta);
+ }
}
- sb->zoom_starting_coord = F32_INFINITY;
- }
- if (mouse_wheel != 0) {
- *sb->min_value += mouse_wheel * sb->scroll_scale.x;
- *sb->max_value += mouse_wheel * sb->scroll_scale.y;
+ // NOTE(rnp): push children
+ for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling)
+ if (child->child_count > 0)
+ da_push(ui_build_arena(), &stack)->node = child;
}
}
function void
-ui_widget_bring_to_front(Variable *sentinal, Variable *widget)
+ui_draw_nodes(UINode *root, Rect window_rect)
{
- /* TODO(rnp): clean up the linkage so this can be a macro */
- widget->parent->next = widget->next;
- widget->next->parent = widget->parent;
-
- widget->parent = sentinal;
- widget->next = sentinal->next;
- widget->next->parent = widget;
- sentinal->next = widget;
-}
+ BeamformerUI *ui = ui_context;
-function void
-ui_view_close(BeamformerUI *ui, Variable *view)
-{
- switch (view->type) {
- case VT_UI_MENU:
- case VT_UI_TEXT_BOX:
- {
- UIView *fw = &view->view;
- if (view->type == VT_UI_MENU) {
- assert(fw->child->type == VT_GROUP);
- fw->child->group.expanded = 0;
- fw->child->group.container = 0;
- } else {
- end_text_input(&ui->text_input_state, fw->child);
- }
- view->parent->next = view->next;
- view->next->parent = view->parent;
- if (fw->close) SLLPushFreelist(fw->close, ui->variable_freelist);
- SLLPushFreelist(view, ui->variable_freelist);
- }break;
- case VT_UI_VIEW:{
- assert(view->parent->type == VT_UI_REGION_SPLIT);
- Variable *region = view->parent;
+ struct node_frame {
+ b32 visited;
+ UINode *node;
+ } init[64];
+
+ struct {
+ struct node_frame *data;
+ da_count count;
+ da_count capacity;
+ } stack = {init, 0, countof(init)};
- Variable *parent = region->parent;
- Variable *remaining = region->region_split.left;
- if (remaining == view) remaining = region->region_split.right;
+ u32 colour_index = 0;
+ (void)colour_index;
- ui_view_free(ui, view);
+ da_push(ui_build_arena(), &stack)->node = root;
+ while (stack.count) {
+ struct node_frame *top = stack.data + stack.count - 1;
- assert(parent->type == VT_UI_REGION_SPLIT);
- if (parent->region_split.left == region) {
- parent->region_split.left = remaining;
- } else {
- parent->region_split.right = remaining;
- }
- remaining->parent = parent;
+ UINode *node = top->node;
+ if (!top->visited) {
+ top->visited = 1;
- SLLPushFreelist(region, ui->variable_freelist);
- }break;
- InvalidDefaultCase;
- }
-}
+ Rect r = ui_node_rect(node);
+ if (node->flags & UINodeFlag_Clip)
+ BeginScissorMode(r.pos.x, r.pos.y, r.size.w, r.size.h);
-function void
-ui_button_interaction(BeamformerUI *ui, Variable *button)
-{
- assert(button->type == VT_UI_BUTTON);
- if (Between(button->button, UI_BID_ACQUISITION_KIND_FIRST, UI_BID_ACQUISITION_KIND_LAST)) {
- BeamformerSharedMemory *sm = ui->shared_memory;
- BeamformerLiveImagingParameters *lip = &sm->live_imaging_parameters;
-
- lip->acquisition_kind = button->button - UI_BID_ACQUISITION_KIND_FIRST;
- atomic_or_u32(&sm->live_imaging_dirty_flags, BeamformerLiveImagingDirtyFlags_AcquisitionKind);
- ui_view_close(ui, button->parent->group.container);
- } else {
- switch (button->button) {
- case UI_BID_VIEW_CLOSE:{ ui_view_close(ui, button->parent); }break;
- case UI_BID_FV_COPY_HORIZONTAL:{
- ui_copy_frame(ui, button->parent->parent, RSD_HORIZONTAL);
- }break;
- case UI_BID_FV_COPY_VERTICAL:{
- ui_copy_frame(ui, button->parent->parent, RSD_VERTICAL);
- }break;
- case UI_BID_GM_OPEN_VIEW_RIGHT:{
- ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL, BeamformerFrameViewKind_Latest);
- }break;
- case UI_BID_GM_OPEN_VIEW_BELOW:{
- ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL, BeamformerFrameViewKind_Latest);
- }break;
- InvalidDefaultCase;
- }
- }
-}
+ if (node->flags & UINodeFlag_ViewScroll) {
+ v2 view_off = node->view_scroll_offset;
+ rlPushMatrix();
+ rlTranslatef(-view_off.x, -view_off.y, 0);
+ }
-function void
-ui_begin_interact(BeamformerUI *ui, v2 mouse, b32 scroll)
-{
- Interaction hot = ui->hot_interaction;
- if (hot.kind != InteractionKind_None) {
- if (hot.kind == InteractionKind_Auto) {
- switch (hot.var->type) {
- case VT_NULL:{ hot.kind = InteractionKind_Nop; }break;
- case VT_B32:{ hot.kind = InteractionKind_Set; }break;
- case VT_SCALE_BAR:{ hot.kind = InteractionKind_Set; }break;
- case VT_UI_BUTTON:{ hot.kind = InteractionKind_Button; }break;
- case VT_GROUP:{ hot.kind = InteractionKind_Set; }break;
- case VT_UI_TEXT_BOX:
- case VT_UI_MENU:
- {
- if (hot.var->type == VT_UI_MENU) {
- hot.kind = InteractionKind_Drag;
- } else {
- hot.kind = InteractionKind_Text;
- begin_text_input(&ui->text_input_state, hot.rect, hot.var, mouse);
- }
- ui_widget_bring_to_front(&ui->floating_widget_sentinal, hot.var);
+ //v4 colour = g_colour_palette[(colour_index++) % countof(g_colour_palette)];
+ //DrawRectangleLinesEx(rl_rect(r), 4.0f, colour_from_normalized(colour));
- // TODO(rnp): hack. this won't be needed with a proper immediate mode UI
- if (ui->interaction.kind == InteractionKind_Text)
- hot.var = hot.var->view.child;
- }break;
- case VT_UI_VIEW:{
- if (scroll) hot.kind = InteractionKind_Scroll;
- else hot.kind = InteractionKind_Nop;
- }break;
- case VT_X_PLANE_SHIFT:{
- assert(hot.var->parent && hot.var->parent->type == VT_BEAMFORMER_FRAME_VIEW);
- BeamformerFrameView *bv = hot.var->parent->generic;
- if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
- XPlaneShift *xp = &hot.var->x_plane_shift;
- xp->start_point = xp->end_point = bv->hit_test_point;
- hot.kind = InteractionKind_Drag;
- } else {
- if (scroll) {
- hot.kind = InteractionKind_Scroll;
- hot.var = &bv->threshold;
- } else {
- hot.kind = InteractionKind_Nop;
- }
- }
- }break;
- case VT_BEAMFORMER_FRAME_VIEW:{
- if (scroll) {
- hot.kind = InteractionKind_Scroll;
- } else {
- BeamformerFrameView *bv = hot.var->generic;
- switch (bv->kind) {
- case BeamformerFrameViewKind_3DXPlane:{ hot.kind = InteractionKind_Drag; }break;
- default:{
- hot.kind = InteractionKind_Nop;
- switch (++bv->ruler.state) {
- case RulerState_Start:{
- hot.kind = InteractionKind_Ruler;
- bv->ruler.start = world_point_from_plane_uv(bv->frame.voxel_transform,
- rect_uv(mouse, hot.rect));
- }break;
- case RulerState_Hold:{}break;
- default:{ bv->ruler.state = RulerState_None; }break;
+ v4 bg_colour = node->bg_colour;
+ if (node->flags & UINodeFlag_DrawHotEffects)
+ bg_colour = v4_lerp(bg_colour, HOVERED_COLOUR, node->hot_t);
+
+ if (node->flags & UINodeFlag_DrawBackground)
+ DrawRectangleRec(rl_rect(r), colour_from_normalized(bg_colour));
+
+ if (node->flags & UINodeFlag_DrawBorder) {
+ v4 colour = node->border_colour;
+ u64 masked = node->flags & (UINodeFlag_DrawBackground|UINodeFlag_DrawHotEffects);
+ if (masked == UINodeFlag_DrawHotEffects)
+ colour = v4_lerp(colour, HOVERED_COLOUR, node->hot_t);
+
+ DrawRectangleLinesEx(rl_rect(r), node->border_thickness, colour_from_normalized(colour));
+ }
+
+ if (node->flags & UINodeFlag_CustomDraw) {
+ node->custom_draw_function(node, r);
+ } else {
+ if (node->flags & UINodeFlag_DrawText) {
+ Font font = ui_font_for_node(node);
+
+ TextSpec text_spec = {
+ .font = &font,
+ .flags = TF_LIMITED,
+ .colour = node->text_colour,
+ .outline_colour = node->text_outline_colour,
+ .outline_thick = node->text_outline_thickness,
+ .limits.size = r.size,
+ };
+ if (node->text_outline_thickness > 0)
+ text_spec.flags |= TF_OUTLINED;
+
+ v2 pos = ui_node_text_position(node);
+
+ UITextInputState *tis = &ui_context->text_input_state;
+ b32 input = ui_node_key_equal(node->key, tis->node_key);
+ // TODO(rnp): cleanup: visible part
+ str8 string = ui_draw_part_from_key_string(node->string);
+ if (!input && node->flags & UINodeFlag_DrawHotEffects && (node->flags & UINodeFlag_DrawBackground) == 0)
+ text_spec.colour = v4_lerp(text_spec.colour, HOVERED_COLOUR, node->hot_t);
+
+ if (node->flags & UINodeFlag_IconText)
+ draw_text_tight(*text_spec.font, string, pos, colour_from_normalized(text_spec.colour));
+ else
+ draw_text(string, pos, &text_spec);
+
+ if (input) {
+ iv2 range = ui_text_input_cursor_range();
+ str8 parts[2];
+ parts[0] = (str8){.data = string.data, .length = range.x};
+ parts[1] = (str8){.data = string.data + range.x, .length = range.y - range.x};
+
+ Rect cursor = {.pos = pos};
+ cursor.pos.x += measure_text(font, parts[0]).x;
+
+ v4 cursor_colour = FOCUSED_COLOUR;
+ if (parts[1].length > 0) {
+ cursor_colour = SELECTION_COLOUR;
+ cursor.size = measure_text(font, parts[1]);
+
+ if (range.x == 0) {
+ cursor.pos.x -= 2.f;
+ cursor.size.x += 2.f;
+ }
+
+ if (range.y == tis->count)
+ cursor.size.x += 2.f;
+ } else {
+ cursor_colour.a = ease_in_out_cubic(ui->text_input_state.blinker.t);
+ cursor.size.x = string.length - range.y > 0 ? 4.0f : 0.55f * (f32)font.baseSize;
+ cursor.size.y = font.baseSize;
}
- }break;
+
+ if (cursor.size.x > 0)
+ DrawRectanglePro(rl_rect(cursor), (Vector2){0}, 0, colour_from_normalized(cursor_colour));
}
}
- }break;
- case VT_CYCLER:{
- if (scroll) hot.kind = InteractionKind_Scroll;
- else hot.kind = InteractionKind_Set;
- }break;
- case VT_BEAMFORMER_VARIABLE:
- case VT_LIVE_CONTROLS_STRING:
- case VT_F32:
- case VT_SCALED_F32:
- {
- if (scroll) {
- hot.kind = InteractionKind_Scroll;
- } else if (hot.var->flags & V_TEXT) {
- hot.kind = InteractionKind_Text;
- Variable *w = add_floating_view(ui, &ui->arena, VT_UI_TEXT_BOX,
- hot.rect.pos, hot.var, 0);
- w->view.rect = hot.rect;
- begin_text_input(&ui->text_input_state, hot.rect, w, mouse);
- } else {
- hot.kind = InteractionKind_Drag;
- }
- }break;
- InvalidDefaultCase;
}
- }
- ui->interaction = hot;
+ // NOTE(rnp): push children
+ for (UINode *child = node->first_child; !ui_node_is_nil(child); child = child->next_sibling) {
+ Rect cr = ui_node_rect(child);
+ if ((cr.size.x > 0 && cr.size.y > 0) || ui_node_key_equal(child->key, ui->text_input_state.node_key))
+ da_push(ui_build_arena(), &stack)->node = child;
+ }
+ } else {
+ // NOTE(rnp): pop
+ stack.count--;
- if (ui->interaction.var->flags & V_LIVE_CONTROL) {
- assert(ui->interaction.var->parent->type == VT_LIVE_CONTROLS_VIEW);
- BeamformerLiveControlsView *lv = ui->interaction.var->parent->generic;
- lv->active_field_flag = lv->hot_field_flag;
- }
+ if (node->flags & UINodeFlag_ViewScroll) {
+ rlPopMatrix();
+ }
- if (ui->interaction.var->flags & V_HIDES_CURSOR) {
- HideCursor();
- DisableCursor();
- /* wtf raylib */
- SetMousePosition((i32)mouse.x, (i32)mouse.y);
+ if (node->flags & UINodeFlag_Clip)
+ EndScissorMode();
}
- } else {
- ui->interaction.kind = InteractionKind_Nop;
}
-}
-
-function u32
-ui_cycler_delta_for_frame(void)
-{
- u32 result = (u32)GetMouseWheelMoveV().y;
- if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) result += 1;
- if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) result -= 1;
- return result;
-}
-function void
-ui_extra_actions(BeamformerUI *ui, Variable *var)
-{
- switch (var->type) {
- case VT_CYCLER:{
- assert(var->parent && var->parent->parent && var->parent->parent->type == VT_UI_VIEW);
- Variable *view_var = var->parent->parent;
- UIView *view = &view_var->view;
- switch (view->child->type) {
- case VT_BEAMFORMER_FRAME_VIEW:{
- u32 delta = ui_cycler_delta_for_frame();
- BeamformerFrameView *old = view->child->generic;
- view->child->generic = ui_beamformer_frame_view_new(ui, &ui->arena);
- BeamformerFrameViewKind last_kind = ((u32)old->kind - delta) % BeamformerFrameViewKind_Count;
-
- /* NOTE(rnp): log_scale gets released below before its needed */
- b32 log_scale = old->log_scale->bool32;
- ui_variable_free_group_items(ui, view->menu);
-
- ui_beamformer_frame_view_release_subresources(ui, old, last_kind);
- ui_beamformer_frame_view_convert(ui, &ui->arena, view->child, view->menu, old->kind, old, log_scale);
-
- DLLRemove(0, old->next, old->prev, old, next, prev);
- SLLPushFreelist(old, ui->view_freelist);
- }break;
- InvalidDefaultCase;
- }
- }break;
- InvalidDefaultCase;
- }
+ // TODO(rnp): can we make the mouse latency not shit?
+ //if (ui->current_mouse.x > 0) DrawCircle(ui->current_mouse.x, ui->current_mouse.y, 6, GREEN);
}
function void
-ui_live_control_update(BeamformerUI *ui, Variable *controls)
+beamformer_ui_panel_unlink(BeamformerUIPanel *node)
{
- assert(controls->type == VT_LIVE_CONTROLS_VIEW);
- BeamformerLiveControlsView *lv = controls->generic;
- atomic_or_u32(&ui->shared_memory->live_imaging_dirty_flags, lv->active_field_flag);
+ BeamformerUIPanel *parent = node->parent;
+ if (parent->kind == BeamformerPanelKind_TabGroup && parent->u.tab_focus == node)
+ parent->u.tab_focus = node->previous_sibling ? node->previous_sibling : node->next_sibling;
+ DLLRemove(0, parent->first_child, parent->last_child, node, next_sibling, previous_sibling);
+ parent->child_count--;
}
function void
-ui_end_interact(BeamformerUI *ui, v2 mouse)
+ui_kill_panel(BeamformerUIPanel *node)
{
- Interaction *it = &ui->interaction;
- Variable *parent = it->var->parent;
- u32 flags = it->var->flags;
-
- switch (it->kind) {
- case InteractionKind_Nop:{}break;
- case InteractionKind_Drag:{
- switch (it->var->type) {
- case VT_X_PLANE_SHIFT:{
- assert(parent && parent->type == VT_BEAMFORMER_FRAME_VIEW);
- XPlaneShift *xp = &it->var->x_plane_shift;
- BeamformerFrameView *view = parent->generic;
- BeamformerViewPlaneTag plane = view_plane_tag_from_x_plane_shift(view, it->var);
- f32 rotation = x_plane_rotation_for_view_plane(view, plane);
- m4 x_rotation = m4_rotation_about_y(rotation);
- v3 Z = x_rotation.c[2].xyz;
- f32 delta = v3_dot(Z, v3_sub(xp->end_point, xp->start_point));
- xp->start_point = xp->end_point;
+ BeamformerUI *ui = ui_context;
+ BeamformerUIPanel *parent = node->parent;
- BeamformerSharedMemory * sm = ui->shared_memory;
- BeamformerLiveImagingParameters * li = &sm->live_imaging_parameters;
- li->image_plane_offsets[plane] += delta;
- atomic_or_u32(&sm->live_imaging_dirty_flags, BeamformerLiveImagingDirtyFlags_ImagePlaneOffsets);
- }break;
- default:{}break;
- }
- }break;
- case InteractionKind_Set:{
- switch (it->var->type) {
- case VT_B32:{ it->var->bool32 = !it->var->bool32; }break;
- case VT_GROUP:{ it->var->group.expanded = !it->var->group.expanded; }break;
- case VT_SCALE_BAR:{ scale_bar_interaction(ui, &it->var->scale_bar, mouse); }break;
- case VT_CYCLER:{
- *it->var->cycler.state += ui_cycler_delta_for_frame();
- *it->var->cycler.state %= it->var->cycler.cycle_length;
- }break;
- InvalidDefaultCase;
- }
- }break;
- case InteractionKind_AcquisitionMenu:
- case InteractionKind_Menu:
+ if (node->kind == BeamformerPanelKind_FrameViewLive ||
+ node->kind == BeamformerPanelKind_FrameViewCopy ||
+ node->kind == BeamformerPanelKind_FrameViewXPlane)
{
- assert(it->var->type == VT_GROUP);
- VariableGroup *g = &it->var->group;
- if (g->container) {
- ui_widget_bring_to_front(&ui->floating_widget_sentinal, g->container);
- } else {
- if (it->kind == InteractionKind_AcquisitionMenu) {
- for (Variable *var = g->first; var;) {
- Variable *next = var->next;
- var->next = var->parent = 0;
- ui_variable_free(ui, var);
- var = next;
- }
- g->first = g->last = 0;
-
- BeamformerLiveImagingParameters *lip = &ui->shared_memory->live_imaging_parameters;
- u64 enabled_kinds = atomic_load_u64(&lip->acquisition_kind_enabled_flags);
- for EachBit(enabled_kinds, kind) {
- Variable *button = add_variable(ui, it->var, &ui->arena, beamformer_acquisition_kind_strings[kind],
- V_INPUT, VT_UI_BUTTON, ui->small_font);
- button->button = UI_BID_ACQUISITION_KIND_FIRST + kind;
- }
- }
- g->container = add_floating_view(ui, &ui->arena, VT_UI_MENU, mouse, it->var, 1);
- }
- }break;
- case InteractionKind_Ruler:{
- assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW);
- ((BeamformerFrameView *)it->var->generic)->ruler.state = RulerState_None;
- }break;
- case InteractionKind_Button:{ ui_button_interaction(ui, it->var); }break;
- case InteractionKind_Scroll:{ scroll_interaction(it->var, GetMouseWheelMoveV().y); }break;
- case InteractionKind_Text:{ ui_view_close(ui, ui->text_input_state.container); }break;
- InvalidDefaultCase;
+ BeamformerFrameView *bv = node->u.frame_view;
+ beamformer_ui_frame_view_release_subresources(bv, bv->kind);
+ DLLRemove(0, ui->view_first, ui->view_last, bv, next, prev);
+ SLLStackPush(ui->view_freelist, bv, next);
}
- if (flags & V_CAUSES_COMPUTE)
- ui->flush_params = 1;
+ beamformer_ui_panel_unlink(node);
- if (flags & V_UPDATE_VIEW) {
- BeamformerFrameView *frame = parent->generic;
- /* TODO(rnp): more straight forward way of achieving this */
- if (parent->type != VT_BEAMFORMER_FRAME_VIEW) {
- assert(parent->parent->type == VT_UI_VIEW);
- assert(parent->parent->view.child->type == VT_BEAMFORMER_FRAME_VIEW);
- frame = parent->parent->view.child->generic;
- }
- frame->dirty = 1;
- }
+ if (node->kind == BeamformerPanelKind_TabGroup) {
+ assert(parent->kind == BeamformerPanelKind_Split);
- if (flags & V_LIVE_CONTROL)
- ui_live_control_update(ui, it->var->parent);
+ BeamformerUIPanel *old_child = parent->first_child;
+ parent->kind = old_child->kind;
+ parent->first_child = old_child->first_child;
+ parent->last_child = old_child->last_child;
+ parent->child_count = old_child->child_count;
+ memory_copy(&parent->u, &old_child->u, sizeof(parent->u));
- if (flags & V_HIDES_CURSOR)
- EnableCursor();
+ for (BeamformerUIPanel *child = parent->first_child; child; child = child->next_sibling)
+ child->parent = parent;
- if (flags & V_EXTRA_ACTION)
- ui_extra_actions(ui, it->var);
+ SLLStackPush(ui->tree_node_freelist, old_child, next_sibling);
+ }
- ui->interaction = (Interaction){.kind = InteractionKind_None};
+ SLLStackPush(ui->tree_node_freelist, node, next_sibling);
}
-function void
-ui_sticky_interaction_check_end(BeamformerUI *ui, v2 mouse)
+function BeamformerUIPanel *
+beamformer_ui_push_panel_node(BeamformerUIPanel *parent)
{
- Interaction *it = &ui->interaction;
- switch (it->kind) {
- case InteractionKind_Ruler:{
- if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) || !point_in_rect(mouse, it->rect))
- ui_end_interact(ui, mouse);
- }break;
- case InteractionKind_Text:{
- Interaction text_box = auto_interaction({0}, ui->text_input_state.container);
- if (!interactions_equal(text_box, ui->hot_interaction))
- ui_end_interact(ui, mouse);
- }break;
- InvalidDefaultCase;
+ BeamformerUI *ui = ui_context;
+ BeamformerUIPanel *result = ui->tree_node_freelist;
+ if (result) SLLStackPop(ui->tree_node_freelist, next_sibling);
+ else result = push_struct_no_zero(&ui->arena, BeamformerUIPanel);
+ zero_struct(result);
+
+ result->parent = parent;
+ if (parent) {
+ DLLInsertLast(0, parent->first_child, parent->last_child, result, next_sibling, previous_sibling);
+ parent->child_count++;
}
+
+ return result;
}
-function void
-ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect)
+function BeamformerUIPanel *
+beamformer_ui_push_panel(BeamformerUIPanel *parent, BeamformerPanelKind kind)
{
- v2 input_mouse = {{input->mouse_x, input->mouse_y}};
- Interaction *it = &ui->interaction;
- if (it->kind == InteractionKind_None || interaction_is_sticky(*it)) {
- ui->hot_interaction = ui->next_interaction;
-
- b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
- b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT);
- b32 wheel_moved = GetMouseWheelMoveV().y != 0;
- if (mouse_right_pressed || mouse_left_pressed || wheel_moved) {
- if (it->kind != InteractionKind_None)
- ui_sticky_interaction_check_end(ui, input_mouse);
- ui_begin_interact(ui, input_mouse, wheel_moved);
- }
- }
+ BeamformerUIPanel *result = beamformer_ui_push_panel_node(parent);
+ result->kind = kind;
+ if (parent && parent->kind == BeamformerPanelKind_TabGroup)
+ parent->u.tab_focus = result;
- switch (it->kind) {
- case InteractionKind_Nop:{ it->kind = InteractionKind_None; }break;
- case InteractionKind_None:{}break;
- case InteractionKind_Text:{
- if (update_text_input(&ui->text_input_state, it->var))
- ui_end_interact(ui, input_mouse);
- }break;
- case InteractionKind_Ruler:{
- assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW);
- BeamformerFrameView *bv = it->var->generic;
- v2 mouse = clamp_v2_rect(input_mouse, it->rect);
- bv->ruler.end = world_point_from_plane_uv(bv->frame.voxel_transform, rect_uv(mouse, it->rect));
- }break;
- case InteractionKind_Drag:{
- if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) {
- ui_end_interact(ui, input_mouse);
- } else {
- v2 ws = window_rect.size;
- v2 dMouse = v2_sub(input_mouse, ui->last_mouse);
-
- switch (it->var->type) {
- case VT_BEAMFORMER_VARIABLE:{
- BeamformerVariable *bv = &it->var->beamformer_variable;
- /* TODO(rnp): vertical sliders? */
- f32 mouse_frac = CLAMP01((input_mouse.x - it->rect.pos.x) / it->rect.size.w);
- *bv->store = bv->limits.x + mouse_frac * (bv->limits.y - bv->limits.x);
- }break;
- case VT_X_PLANE_SHIFT:{
- assert(it->var->parent && it->var->parent->type == VT_BEAMFORMER_FRAME_VIEW);
- v2 mouse = clamp_v2_rect(input_mouse, it->rect);
- XPlaneShift *xp = &it->var->x_plane_shift;
- ray mouse_ray = ray_for_x_plane_view(ui, it->var->parent->generic,
- normalized_p_in_rect(it->rect, mouse, 0));
- /* NOTE(rnp): project start point onto ray */
- v3 s = v3_sub(xp->start_point, mouse_ray.origin);
- v3 r = v3_sub(mouse_ray.direction, mouse_ray.origin);
- f32 scale = v3_dot(s, r) / v3_magnitude_squared(r);
- xp->end_point = v3_add(mouse_ray.origin, v3_scale(r, scale));
- }break;
- case VT_BEAMFORMER_FRAME_VIEW:{
- BeamformerFrameView *bv = it->var->generic;
- switch (bv->kind) {
- case BeamformerFrameViewKind_3DXPlane:{
- bv->rotation -= dMouse.x / ws.w;
- if (bv->rotation > 1.0f) bv->rotation -= 1.0f;
- if (bv->rotation < 0.0f) bv->rotation += 1.0f;
- }break;
- InvalidDefaultCase;
- }
- }break;
- case VT_UI_MENU:{
- v2 *pos = &ui->interaction.var->view.rect.pos;
- *pos = clamp_v2_rect(v2_add(*pos, dMouse), window_rect);
- }break;
- case VT_UI_REGION_SPLIT:{
- f32 min_fraction = 0;
- dMouse = v2_mul(dMouse, (v2){{1.0f / ws.w, 1.0f / ws.h}});
- RegionSplit *rs = &ui->interaction.var->region_split;
- switch (rs->direction) {
- case RSD_VERTICAL: {
- min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5f * UI_REGION_PAD) / ws.h;
- rs->fraction += dMouse.y;
- } break;
- case RSD_HORIZONTAL: {
- min_fraction = (UI_SPLIT_HANDLE_THICK + 0.5f * UI_REGION_PAD) / ws.w;
- rs->fraction += dMouse.x;
- } break;
- }
- rs->fraction = CLAMP(rs->fraction, min_fraction, 1 - min_fraction);
- }break;
- default:{}break;
- }
- if (it->var->flags & V_LIVE_CONTROL)
- ui_live_control_update(ui, it->var->parent);
- }
- } break;
- default:{ ui_end_interact(ui, input_mouse); }break;
+ if (kind == BeamformerPanelKind_FrameViewLive ||
+ kind == BeamformerPanelKind_FrameViewCopy ||
+ kind == BeamformerPanelKind_FrameViewXPlane)
+ {
+ BeamformerFrameViewKind view_kind = BeamformerFrameViewKind_Latest;
+ if (kind == BeamformerPanelKind_FrameViewCopy)
+ view_kind = BeamformerFrameViewKind_Copy;
+ if (kind == BeamformerPanelKind_FrameViewXPlane)
+ view_kind = BeamformerFrameViewKind_3DXPlane;
+ result->u.frame_view = beamformer_ui_frame_view_new(view_kind);
}
- ui->next_interaction = (Interaction){.kind = InteractionKind_None};
+ return result;
}
/* NOTE(rnp): this only exists to make asan less annoying. do not waste
@@ -4102,42 +4974,45 @@ DEBUG_EXPORT BEAMFORMER_DEBUG_UI_DEINIT_FN(beamformer_debug_ui_deinit)
function void
ui_init(BeamformerCtx *ctx, Arena store)
{
- BeamformerUI *ui = ctx->ui;
+ BeamformerUI *ui = ui_context = ctx->ui;
if (!ui) {
- ui = ctx->ui = push_struct(&store, typeof(*ui));
+ ui = ui_context = ctx->ui = push_struct(&store, typeof(*ui));
ui->arena = store;
- ui->shared_memory = ctx->shared_memory;
- ui->beamformer_context = ctx;
+
+ for EachElement(ui->build_arenas, it) {
+ ui->build_arenas[it] = sub_arena(&ui->arena, KB(128), KB(4));
+ ui->build_arena_savepoints[it] = begin_temp_arena(ui->build_arenas + it);
+ }
+ ui->node_freelist = &ui_node_nil;
/* TODO(rnp): better font, this one is jank at small sizes */
ui->font = LoadFontFromMemory(".ttf", beamformer_base_font, sizeof(beamformer_base_font), 28, 0, 0);
ui->small_font = LoadFontFromMemory(".ttf", beamformer_base_font, sizeof(beamformer_base_font), 20, 0, 0);
- ui->floating_widget_sentinal.parent = &ui->floating_widget_sentinal;
- ui->floating_widget_sentinal.next = &ui->floating_widget_sentinal;
-
- Variable *split = ui->regions = add_ui_split(ui, 0, &ui->arena, s8("UI Root"), 0.36f,
- RSD_HORIZONTAL, ui->font);
- split->region_split.left = add_ui_split(ui, split, &ui->arena, s8(""), 0.475f,
- RSD_VERTICAL, ui->font);
-
- split = split->region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.70f,
- RSD_HORIZONTAL, ui->font);
+ // NOTE(rnp): push default UI layout
+ // TODO(rnp): load last layout from file and only load default if not present
{
- split->region_split.left = add_beamformer_frame_view(ui, split, &ui->arena,
- BeamformerFrameViewKind_Latest, 0, 0);
- split->region_split.right = add_live_controls_view(ui, split, &ui->arena);
- }
- split = split->parent;
+ BeamformerUIPanel *node = ui->tree = beamformer_ui_push_panel(0, BeamformerPanelKind_Split);
+ node->u.split.fraction = 0.35f;
+ node->u.split.axis = Axis2_X;
- split = split->region_split.left;
- split->region_split.left = add_beamformer_parameters_view(split, ctx);
- split->region_split.right = add_ui_split(ui, split, &ui->arena, s8(""), 0.22f,
- RSD_VERTICAL, ui->font);
- split = split->region_split.right;
+ DeferLoop(node = beamformer_ui_push_panel(node, BeamformerPanelKind_Split), node = node->parent)
+ {
+ node->u.split.fraction = 0.65f;
+ node->u.split.axis = Axis2_Y;
+
+ BeamformerUIPanel *left = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup);
+ BeamformerUIPanel *right = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup);
+ beamformer_ui_push_panel(left, BeamformerPanelKind_ParameterListing);
+ beamformer_ui_push_panel(right, BeamformerPanelKind_ComputeBarGraph);
+ beamformer_ui_push_panel(right, BeamformerPanelKind_ComputeStats);
+ }
- split->region_split.left = add_compute_progress_bar(split, ctx);
- split->region_split.right = add_compute_stats_view(ui, split, &ui->arena, ctx);
+ DeferLoop(node = beamformer_ui_push_panel(node, BeamformerPanelKind_TabGroup), node = node->parent)
+ {
+ beamformer_ui_push_panel(node, BeamformerPanelKind_FrameViewLive);
+ }
+ }
u32 samples = vk_gpu_info()->max_msaa_samples;
vk_image_allocate(&ui->render_3d_image, FRAME_VIEW_RENDER_TARGET_SIZE, 1, samples, VulkanImageUsage_Colour, 0, 0, s8("Render Target Colour"));
@@ -4253,9 +5128,6 @@ ui_init(BeamformerCtx *ctx, Arena store)
vk_render_model_allocate(&rm->model, unit_cube_indices, countof(unit_cube_indices), model_size, s8("unit_cube_model"));
vk_render_model_range_upload(&rm->model, unit_cube_vertices, 0, sizeof(unit_cube_vertices), 0);
vk_render_model_range_upload(&rm->model, unit_cube_normals, rm->normals_offset, sizeof(unit_cube_normals), 0);
-
- /* NOTE(rnp): shrink variable size once this fires */
- assert((uz)(ui->arena.beg - (u8 *)ui) < KB(64));
}
for EachElement(beamformer_reloadable_render_shader_info_indices, it) {
@@ -4266,34 +5138,21 @@ ui_init(BeamformerCtx *ctx, Arena store)
}
function void
-validate_ui_parameters(BeamformerUI *ui)
-{
- if (ui->min_coordinate.x > ui->max_coordinate.x)
- swap(ui->min_coordinate.x, ui->max_coordinate.x);
- if (ui->min_coordinate.y > ui->max_coordinate.y)
- swap(ui->min_coordinate.y, ui->max_coordinate.y);
-}
-
-function void
-draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformerFrame *frame_to_draw, BeamformerViewPlaneTag frame_plane)
+beamformer_ui_frame(void)
{
- BeamformerUI *ui = ctx->ui;
+ BeamformerUI *ui = ui_context = beamformer_context->ui;
- if (frame_to_draw) {
- mem_copy(ui->latest_plane + BeamformerViewPlaneTag_Count, frame_to_draw, sizeof(*frame_to_draw));
- mem_copy(ui->latest_plane + frame_plane, frame_to_draw, sizeof(*frame_to_draw));
- ui->latest_plane_valid[BeamformerViewPlaneTag_Count] = 1;
- ui->latest_plane_valid[frame_plane] = 1;
- } else {
- ui->latest_plane_valid[BeamformerViewPlaneTag_Count] = 0;
- ui->latest_plane_valid[frame_plane] = 0;
+ {
+ BeamformerFrame *frame = beamformer_frame_from_index(beamformer_registers()->frame);
+ memory_copy(ui->latest_plane + frame->view_plane_tag, frame, sizeof(*frame));
}
+ BeamformerInput *input = beamformer_input;
for EachIndex(input->event_count, it) {
if (input->event_queue[it].kind == BeamformerInputEventKind_WindowResize) {
// TODO(rnp): match window against window list
- ctx->window_size.w = input->event_queue[it].window_resize.width;
- ctx->window_size.h = input->event_queue[it].window_resize.height;
+ beamformer_context->window_size.w = input->event_queue[it].window_resize.width;
+ beamformer_context->window_size.h = input->event_queue[it].window_resize.height;
}
}
@@ -4301,122 +5160,286 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformerFrame *frame_to_dr
u32 selected_block = ui->selected_parameter_block % BeamformerMaxParameterBlocks;
u32 selected_mask = 1 << selected_block;
- if (ctx->ui_dirty_parameter_blocks & selected_mask) {
- BeamformerParameterBlock *pb = beamformer_parameter_block_lock(ui->shared_memory, selected_block, 0);
+ if (beamformer_context->ui_dirty_parameter_blocks & selected_mask) {
+ BeamformerParameterBlock *pb = beamformer_parameter_block_lock(beamformer_context->shared_memory, selected_block, 0);
if (pb) {
- ui->flush_params = 0;
+ ui->flush_parameters = 0;
m4 das_transform;
- mem_copy(&ui->params, &pb->parameters_ui, sizeof(ui->params));
- mem_copy(das_transform.E, pb->parameters.das_voxel_transform.E, sizeof(das_transform));
+ memory_copy(&ui->parameters, &pb->parameters_ui, sizeof(ui->parameters));
+ memory_copy(das_transform.E, pb->parameters.das_voxel_transform.E, sizeof(das_transform));
- atomic_and_u32(&ctx->ui_dirty_parameter_blocks, ~selected_mask);
- beamformer_parameter_block_unlock(ui->shared_memory, selected_block);
+ atomic_and_u32(&beamformer_context->ui_dirty_parameter_blocks, ~selected_mask);
+ beamformer_parameter_block_unlock(beamformer_context->shared_memory, selected_block);
- BeamformerComputePlan *cp = ui->beamformer_context->compute_context.compute_plans[selected_block];
+ BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[selected_block];
m4 identity = m4_identity();
b32 recompute = !m4_equal(identity, cp->ui_voxel_transform);
- mem_copy(cp->ui_voxel_transform.E, identity.E, sizeof(identity));
+ memory_copy(cp->ui_voxel_transform.E, identity.E, sizeof(identity));
if (recompute) {
- mark_parameter_block_region_dirty(ui->shared_memory, selected_block,
+ mark_parameter_block_region_dirty(beamformer_context->shared_memory, selected_block,
BeamformerParameterBlockRegion_Parameters);
- beamformer_queue_compute(ctx, frame_to_draw, selected_block);
+ beamformer_queue_compute(beamformer_context,
+ beamformer_frame_from_index(beamformer_registers()->frame),
+ selected_block);
}
- v3 U = v3_normalize(das_transform.c[0].xyz);
- v3 V = v3_normalize(das_transform.c[1].xyz);
- v3 N = cross(V, U);
-
- ui->off_axis_position = v3_dot(N, das_transform.c[3].xyz);
+ ui->off_axis_position = plane_offset_from_transform(das_transform);
ui->beamform_plane = 0;
+ }
+ }
- v3 min_coordinate = m4_mul_v3(das_transform, (v3){{0.0f, 0.0f, 0.0f}});
- v3 max_coordinate = m4_mul_v3(das_transform, (v3){{1.0f, 1.0f, 1.0f}});
+ /* NOTE: process interactions first because the user interacted with
+ * the ui that was presented last frame */
+ Rect window_rect = {.size = {{(f32)beamformer_context->window_size.w, (f32)beamformer_context->window_size.h}}};
- ui->min_coordinate.x = v3_dot(U, min_coordinate);
- ui->min_coordinate.y = v3_dot(V, min_coordinate);
+ ui->last_mouse = ui->current_mouse;
+ ui->current_mouse.x = input->mouse_x;
+ ui->current_mouse.y = input->mouse_y;
+ for EachElement(ui->input_consumed, it)
+ ui->input_consumed[it] = 0;
- ui->max_coordinate.x = v3_dot(U, max_coordinate);
- ui->max_coordinate.y = v3_dot(V, max_coordinate);
+ if (ui->flush_parameters && beamformer_frame_valid(beamformer_registers()->frame)) {
+ BeamformerParameterBlock *pb = beamformer_parameter_block_lock(beamformer_context->shared_memory, selected_block, 0);
+ if (pb) {
+ ui->flush_parameters = 0;
+ memory_copy(&pb->parameters_ui, &ui->parameters, sizeof(ui->parameters));
+ mark_parameter_block_region_dirty(beamformer_context->shared_memory, selected_block,
+ BeamformerParameterBlockRegion_Parameters);
+ beamformer_parameter_block_unlock(beamformer_context->shared_memory, selected_block);
+ beamformer_queue_compute(beamformer_context,
+ beamformer_frame_from_index(beamformer_registers()->frame),
+ selected_block);
}
}
- /* NOTE: process interactions first because the user interacted with
- * the ui that was presented last frame */
- Rect window_rect = {.size = {{(f32)ctx->window_size.w, (f32)ctx->window_size.h}}};
- ui_interact(ui, input, window_rect);
+ /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */
+ update_frame_views(ui, window_rect);
+
+ ////////////////////////////
+ // NOTE(rnp): Text Input
+ {
+ UITextInputState *tis = &ui->text_input_state;
+ // NOTE(rnp): transition to new node
+ tis->last_node_key = ui_node_key_zero();
+ tis->last_count = 0;
+ if (tis->changed) {
+ tis->changed = 0;
+ ui_text_input_end();
+ if (!ui_node_key_nil(tis->next_node_key)) {
+ tis->node_key = tis->next_node_key;
+ tis->next_node_key = ui_node_key_zero();
+ if (point_in_rect(ui->current_mouse, ui_text_input_rect()))
+ tis->cursor = tis->mark = ui_text_input_index_from_point(ui->last_mouse.x);
+ tis->blinker.t = 1.0f;
+ }
+ }
- if (ui->flush_params) {
- validate_ui_parameters(ui);
- if (ctx->latest_frame) {
- BeamformerParameterBlock *pb = beamformer_parameter_block_lock(ui->shared_memory, selected_block, 0);
- if (pb) {
- ui->flush_params = 0;
+ if (!ui_node_key_nil(tis->node_key)) {
+ beamformer_ui_blinker_update(&tis->blinker, BLINK_SPEED);
- iv3 points = ctx->latest_frame->points;
- i32 dimension = iv3_dimension(points);
+ UISignal signal = ui_signal_from_node(ui_node_from_key(tis->node_key));
- // TODO(rnp): this is immediate mode code that should be in the ui building code
- m4 new_transform = m4_identity();
- switch (dimension) {
- case 1:{}break;
+ if (signal.flags & UISignalFlag_LeftPressed) {
+ if (point_in_rect(ui->current_mouse, ui_text_input_rect()))
+ tis->cursor = tis->mark = ui_text_input_index_from_point(ui->last_mouse.x);
+ tis->blinker.t = 1.0f;
+ }
- case 2:{
- v3 U = v3_normalize(pb->parameters.das_voxel_transform.c[0].xyz);
- v3 V = v3_normalize(pb->parameters.das_voxel_transform.c[1].xyz);
- v3 N = cross(V, U);
+ if (signal.flags & UISignalFlag_LeftDragging)
+ tis->mark = ui_text_input_index_from_point(ui->last_mouse.x);
- new_transform = das_transform_2d_with_normal(N, ui->min_coordinate, ui->max_coordinate, 0);
+ if (signal.flags & UISignalFlag_DoubleClicked) {
+ // TODO(rnp): select word
+ }
- v3 rotation_axis = cross(v3_normalize(new_transform.c[0].xyz), N);
+ if (signal.flags & UISignalFlag_TripleClicked) {
+ tis->cursor = 0;
+ tis->mark = tis->count;
+ }
- m4 R = m4_rotation_about_axis(rotation_axis, ui->beamform_plane);
- m4 T = m4_translation(v3_scale(m4_mul_v3(R, N), ui->off_axis_position));
+ if (ui_text_input_update(input))
+ ui_text_input_end();
+ }
+ }
- new_transform = m4_mul(m4_mul(T, m4_mul(R, new_transform)),
- m4_inverse(pb->parameters.das_voxel_transform));
- }break;
+ if (ui->context_menu_state_changed) {
+ ui->context_menu_state_changed = 0;
+ ui->context_menu_anchor_key = ui->context_menu_next_anchor_key;
+ ui->context_menu_panel = ui->context_menu_next_panel;
+ }
- case 3:{}break;
- }
+ {
+ ////////////////////////////
+ // NOTE(rnp): Build Pass
+ end_temp_arena(ui->build_arena_savepoints[ui->current_frame_index % countof(ui->build_arenas)]);
+ // NOTE(rnp): reset last frame's build stacks
+ {
+ #define X(type, name, ...) \
+ ui->name##_node_stack.top = &ui_##name##_node_nil; \
+ ui->name##_node_stack.free = 0; \
+ ui->name##_node_stack.count = 0;
+ UI_STACK_LIST
+ #undef X
+
+ UIPrefWidth(ui_px(window_rect.size.x, 1.f))
+ UIPrefHeight(ui_px(window_rect.size.y, 1.f))
+ UIChildLayoutAxis(Axis2_Y)
+ ui->root_node = ui_node_from_string(0, str8("UI Root Node"));
+ ui_push_semantic_width(ui_pct(1.f, 0.5f));
+ ui_push_semantic_height(ui_pct(1.f, 0.5f));
+ }
+
+ ui->drag_root = 0;
+ ui->drop_target_node = 0;
+ ui->drag_overlay_root = 0;
+ ui->drag_overlay_edges_root = 0;
+ ui->drag_overlay_tab_root = 0;
+
+ beamformer_registers()->split_left_tree = 0;
+ beamformer_registers()->split_right_tree = 0;
+ beamformer_registers()->drop_child_index = 0;
+
+ // NOTE(rnp): check for active nodes
+ {
+ b32 active = 0;
+ for EachEnumValue(UIMouseButtonKind, k)
+ active |= !ui_node_key_equal(ui->active_node_key[k], ui_node_key_zero());
+ // NOTE(rnp): clear hot node if there are no active nodes
+ if (!active) ui->hot_node_key = ui_node_key_zero();
+ }
+
+ // NOTE(rnp): context menu
+ if (!ui_node_key_nil(ui->context_menu_anchor_key)) {
+ // TODO(rnp): context_menu_open_t
+ UIPrefWidth(ui_children_sum(1.f))
+ UIPrefHeight(ui_children_sum(1.f))
+ UIChildLayoutAxis(Axis2_Y)
+ UIBGColour((v4){.a = 0.8f})
+ {
+ // TODO(rnp): this should be tied to the window state
+ ui->context_menu_root = ui_node_from_string(UINodeFlag_DrawBackground, str8("context_menu_root"));
+ }
+
+ UIParent(ui->context_menu_root)
+ {
+ ui_build_context_menu_for_panel(ui->context_menu_panel);
+ }
+ }
+
+ // NOTE(rnp): drag panel
+ if (ui->drag_panel) {
+ ui_build_drag_overlay(window_rect);
+
+ UIPrefWidth(ui_px(640.f, 1.f))
+ UIPrefHeight(ui_px(480.f, 1.f))
+ UIChildLayoutAxis(Axis2_Y)
+ UIBGColour((v4){.a = 0.8f})
+ {
+ ui->drag_root = ui_node_from_string(UINodeFlag_DrawBackground, str8("drag_panel_root"));
+ }
- // TODO(rnp): super janky code because of the retained mode parameters list.
- // when this code is run in the correct place we can just decide inline
- b32 recompute = !memory_equal(&pb->parameters_ui, &ui->params, sizeof(ui->params));
- BeamformerComputePlan *cp = ui->beamformer_context->compute_context.compute_plans[selected_block];
- if (cp) {
- recompute |= !m4_equal(new_transform, cp->ui_voxel_transform);
- mem_copy(cp->ui_voxel_transform.E, new_transform.E, sizeof(new_transform));
+ UIParent(ui->drag_root)
+ {
+ UIChildLayoutAxis(Axis2_X)
+ UIPrefHeight(ui_children_sum(1.f))
+ UIPrefWidth(ui_children_sum(1.f))
+ UIParent(ui_spacer(0))
+ {
+ ui_padw(UI_NODE_PAD);
+ ui_panel_label(ui->drag_panel);
}
+ }
- mem_copy(&pb->parameters_ui, &ui->params, sizeof(ui->params));
+ ui_build_regions(ui->drag_root, ui->drag_panel);
+ }
- mark_parameter_block_region_dirty(ui->shared_memory, selected_block,
- BeamformerParameterBlockRegion_Parameters);
- beamformer_parameter_block_unlock(ui->shared_memory, selected_block);
+ ui_build_regions(ui->root_node, ui->tree);
+
+ ////////////////////////////
+ // NOTE(rnp): Prune Dead UI Nodes
+ for EachElement(ui->node_hash_table, it) {
+ UINodeHashBucket *hb = ui->node_hash_table + it;
+ UINode *next = hb->first;
+ for (UINode *b = next; !ui_node_is_nil(b); b = next) {
+ next = b == b->hash_next ? 0 : b->hash_next;
+ if (b->last_frame_active_index != ui->current_frame_index) {
+ for EachEnumValue(UIMouseButtonKind, k)
+ if (ui_node_key_equal(ui->active_node_key[k], b->key))
+ ui->active_node_key[k] = ui_node_key_zero();
+
+ DLLRemove(&ui_node_nil, hb->first, hb->last, b, hash_next, hash_prev);
+ SLLStackPush(ui->node_freelist, b, next_sibling);
+ }
+ }
+ }
- if (recompute)
- beamformer_queue_compute(ctx, frame_to_draw, selected_block);
+ for (BeamformerInputEvent *event = ui_event_next(input, 0);
+ event;
+ event = ui_event_next(input, event))
+ {
+ if (event->kind == BeamformerInputEventKind_ButtonPress) {
+ if (event->button_id == BeamformerButtonID_Escape)
+ beamformer_context->state = BeamformerState_ShouldClose;
+
+ if (!Between(event->button_id, BeamformerButtonID_ModifierFirst, BeamformerButtonID_ModifierLast)) {
+ ui_context_menu_close();
+ ui->text_input_state.changed = 1;
+ ui->text_input_state.next_node_key = ui_node_key_zero();
+ }
}
}
- }
- /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */
- update_frame_views(ui, window_rect);
+ ////////////////////////////
+ // NOTE(rnp): Layout Pass
+ if (ui->drag_root) {
+ ui->drag_root->computed_position[Axis2_X] = ui->last_mouse.x;
+ ui->drag_root->computed_position[Axis2_Y] = ui->last_mouse.y;
+ ui_layout_nodes(ui->drag_root);
+ ui_layout_nodes(ui->drag_overlay_edges_root);
+ if (ui->drag_overlay_tab_root)
+ ui_layout_nodes(ui->drag_overlay_tab_root);
+ ui_layout_nodes(ui->drag_overlay_root);
+ }
+
+ ui_layout_nodes(ui->root_node);
+
+ if (!ui_node_key_nil(ui->context_menu_anchor_key)) {
+ UINode *anchor = ui_node_from_key(ui->context_menu_anchor_key);
+ v2 anchor_p = ui_node_final_position(anchor);
+ ui->context_menu_root->computed_position[Axis2_X] = anchor_p.x;
+ ui->context_menu_root->computed_position[Axis2_Y] = anchor_p.y + anchor->computed_size[Axis2_Y];
- BeginDrawing();
- v2 mouse = {{input->mouse_x, input->mouse_y}};
- glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_COLOUR.E);
- glClearNamedFramebufferfv(0, GL_DEPTH, 0, (f32 []){1});
+ ui_layout_nodes(ui->context_menu_root);
+ }
+
+ BeginDrawing();
+ glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_COLOUR.E);
+ glClearNamedFramebufferfv(0, GL_DEPTH, 0, (f32 []){1});
+ ui_draw_nodes(ui->root_node, window_rect);
+
+ if (!ui_node_key_nil(ui->context_menu_anchor_key))
+ ui_draw_nodes(ui->context_menu_root, window_rect);
+
+ if (ui->drag_root) {
+ if (beamformer_registers()->split_left_tree || beamformer_registers()->split_right_tree)
+ ui_draw_nodes(ui_build_drag_hover_node(), window_rect);
+ ui_draw_nodes(ui->drag_overlay_root, window_rect);
+ ui_draw_nodes(ui->drag_overlay_edges_root, window_rect);
+ if (ui->drag_overlay_tab_root)
+ ui_draw_nodes(ui->drag_overlay_tab_root, window_rect);
+ ui_draw_nodes(ui->drag_root, window_rect);
+ }
- draw_ui_regions(ui, window_rect, mouse);
- draw_floating_widgets(ui, window_rect, mouse);
+ // TODO(rnp): hack: until raylib is removed this happens in ui since raylib will cause
+ // glfw to call the input callbacks during EndDrawing()
+ input->event_count = 0;
+ EndDrawing();
- // TODO(rnp): hack: until raylib is removed this happens in ui since raylib will cause
- // glfw to call the input callbacks during EndDrawing()
- input->event_count = 0;
- EndDrawing();
+ if (ui->drag_end)
+ ui_drag_end();
- ui->last_mouse = (v2){{input->mouse_x, input->mouse_y}};
+ ui->current_frame_index++;
+ }
}
diff --git a/util.c b/util.c
@@ -536,16 +536,6 @@ stream_append_f64_e(Stream *s, f64 f)
stream_append_u64(s, (u64)Abs(scale));
}
-function void
-stream_append_v2(Stream *s, v2 v)
-{
- stream_append_byte(s, '{');
- stream_append_f64(s, v.x, 100);
- stream_append_s8(s, s8(", "));
- stream_append_f64(s, v.y, 100);
- stream_append_byte(s, '}');
-}
-
function Stream
arena_stream(Arena a)
{
@@ -865,16 +855,6 @@ push_str8_fv(Arena *arena, const char *format, va_list args)
return result;
}
-function str8
-push_f64_string(Arena *arena, f64 value, u64 precision)
-{
- Stream sb = arena_stream(*arena);
- stream_append_f64(&sb, value, precision);
- s8 s = arena_stream_commit(arena, &sb);
- str8 result = {.length = s.len, .data = s.data};
- return result;
-}
-
/* NOTE(rnp): from Hacker's Delight */
function force_inline u64
round_down_power_of_two(u64 a)
@@ -899,68 +879,6 @@ round_up_to(iz value, iz multiple)
return result;
}
-function void
-split_rect_horizontal(Rect rect, f32 fraction, Rect *left, Rect *right)
-{
- if (left) {
- left->pos = rect.pos;
- left->size.h = rect.size.h;
- left->size.w = rect.size.w * fraction;
- }
- if (right) {
- right->pos = rect.pos;
- right->pos.x += rect.size.w * fraction;
- right->size.h = rect.size.h;
- right->size.w = rect.size.w * (1.0f - fraction);
- }
-}
-
-function void
-split_rect_vertical(Rect rect, f32 fraction, Rect *top, Rect *bot)
-{
- if (top) {
- top->pos = rect.pos;
- top->size.w = rect.size.w;
- top->size.h = rect.size.h * fraction;
- }
- if (bot) {
- bot->pos = rect.pos;
- bot->pos.y += rect.size.h * fraction;
- bot->size.w = rect.size.w;
- bot->size.h = rect.size.h * (1.0f - fraction);
- }
-}
-
-function void
-cut_rect_horizontal(Rect rect, f32 at, Rect *left, Rect *right)
-{
- at = MIN(at, rect.size.w);
- if (left) {
- *left = rect;
- left->size.w = at;
- }
- if (right) {
- *right = rect;
- right->pos.x += at;
- right->size.w -= at;
- }
-}
-
-function void
-cut_rect_vertical(Rect rect, f32 at, Rect *top, Rect *bot)
-{
- at = MIN(at, rect.size.h);
- if (top) {
- *top = rect;
- top->size.h = at;
- }
- if (bot) {
- *bot = rect;
- bot->pos.y += at;
- bot->size.h -= at;
- }
-}
-
function NumberConversion
integer_from_str8(str8 raw)
{
diff --git a/util.h b/util.h
@@ -109,12 +109,9 @@ typedef u64 uptr;
#define countof(a) (iz)(sizeof(a) / sizeof(*a))
#define BETWEEN(x, a, b) ((x) >= (a) && (x) <= (b))
#define CLAMP(x, a, b) ((x) < (a) ? (a) : (x) > (b) ? (b) : (x))
-#define CLAMP01(x) CLAMP(x, 0, 1)
#define ISPOWEROF2(a) (((a) & ((a) - 1)) == 0)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
-#define ORONE(x) ((x)? (x) : 1)
-#define SIGN(x) ((x) < 0? -1 : 1)
#define swap(a, b) do {typeof(a) __tmp = (a); (a) = (b); (b) = __tmp;} while(0)
#define Abs(a) ((a) < 0 ? -(a) : (a))
@@ -205,6 +202,13 @@ typedef u64 uptr;
#include "intrinsics.c"
+typedef enum {
+ Axis2_X = 0,
+ Axis2_Y = 1,
+ Axis2_Count,
+} Axis2;
+#define axis2_flip(v) (!(v))
+
typedef alignas(16) union {
u8 U8[16];
u16 U16[8];
diff --git a/util_os_ui.c b/util_os_ui.c
@@ -10,6 +10,10 @@ void *GetPlatformWindowHandle(void);
typedef void glfw_window_resize_fn(void *window, i32 width, i32 height);
glfw_window_resize_fn *glfwSetWindowSizeCallback(void *window, glfw_window_resize_fn *callback);
+void *glfwSetCharCallback(void *window, void (*callback)(void *window, u32 codepoint));
+void *glfwSetKeyCallback(void *window, void (*callback)(void *window, i32 key, i32 scancode, i32 action, i32 mods));
+void *glfwSetMouseButtonCallback(void *window, void (*callback)(void *window, i32 button, i32 action, i32 mods));
+void *glfwSetScrollCallback(void *window, void (*callback)(void *window, f64 x_delta, f64 y_delta));
global BeamformerInput *beamformer_input;
global glfw_window_resize_fn *raylib_window_resize;
@@ -22,10 +26,197 @@ os_push_input_event(BeamformerInput *input, BeamformerInputEvent event)
input->event_queue[input->event_count++] = event;
}
+function BeamformerInputModifiers
+os_modifiers_from_glfw(i32 modifiers)
+{
+ BeamformerInputModifiers result = 0;
+ result |= ((modifiers & 0x1) != 0) * BeamformerInputModifier_Shift;
+ result |= ((modifiers & 0x2) != 0) * BeamformerInputModifier_Control;
+ result |= ((modifiers & 0x4) != 0) * BeamformerInputModifier_Alt;
+ result |= ((modifiers & 0x8) != 0) * BeamformerInputModifier_Meta;
+ return result;
+}
+
+function void
+os_scroll_callback(void *window, f64 xoff, f64 yoff)
+{
+ // TODO(rnp): GLFW doesn't pass modifiers through but the os event would have them
+ os_push_input_event(beamformer_input, (BeamformerInputEvent){
+ .kind = BeamformerInputEventKind_MouseScroll,
+ .scroll = {.x = xoff, .y = yoff},
+ });
+}
+
+function void
+os_key_callback(void *window, i32 key, i32 scancode, i32 action, i32 modifiers)
+{
+ BeamformerInputEvent input_event = {
+ .kind = action == 0 ? BeamformerInputEventKind_ButtonRelease : BeamformerInputEventKind_ButtonPress,
+ .modifiers = os_modifiers_from_glfw(modifiers),
+ };
+
+ switch (key) {
+ default:{}break;
+ case BeamformerButtonID_Space:
+ case BeamformerButtonID_Apostrophe:
+ case BeamformerButtonID_Comma:
+ case BeamformerButtonID_Minus:
+ case BeamformerButtonID_Period:
+ case BeamformerButtonID_Slash:
+ case BeamformerButtonID_0:
+ case BeamformerButtonID_1:
+ case BeamformerButtonID_2:
+ case BeamformerButtonID_3:
+ case BeamformerButtonID_4:
+ case BeamformerButtonID_5:
+ case BeamformerButtonID_6:
+ case BeamformerButtonID_7:
+ case BeamformerButtonID_8:
+ case BeamformerButtonID_9:
+ case BeamformerButtonID_Semicolon:
+ case BeamformerButtonID_Equal:
+ case BeamformerButtonID_A:
+ case BeamformerButtonID_B:
+ case BeamformerButtonID_C:
+ case BeamformerButtonID_D:
+ case BeamformerButtonID_E:
+ case BeamformerButtonID_F:
+ case BeamformerButtonID_G:
+ case BeamformerButtonID_H:
+ case BeamformerButtonID_I:
+ case BeamformerButtonID_J:
+ case BeamformerButtonID_K:
+ case BeamformerButtonID_L:
+ case BeamformerButtonID_M:
+ case BeamformerButtonID_N:
+ case BeamformerButtonID_O:
+ case BeamformerButtonID_P:
+ case BeamformerButtonID_Q:
+ case BeamformerButtonID_R:
+ case BeamformerButtonID_S:
+ case BeamformerButtonID_T:
+ case BeamformerButtonID_U:
+ case BeamformerButtonID_V:
+ case BeamformerButtonID_W:
+ case BeamformerButtonID_X:
+ case BeamformerButtonID_Y:
+ case BeamformerButtonID_Z:
+ case BeamformerButtonID_LeftBracket:
+ case BeamformerButtonID_Backslash:
+ case BeamformerButtonID_RightBracket:
+ case BeamformerButtonID_Grave:
+ {
+ input_event.button_id = key;
+ input_event.codepoint = key;
+ if (Between(key, BeamformerButtonID_A, BeamformerButtonID_Z) &&
+ ((input_event.modifiers & BeamformerInputModifier_Shift) == 0))
+ {
+ input_event.codepoint ^= 0x20;
+ }
+ }break;
+
+ case 256:{input_event.button_id = BeamformerButtonID_Escape; }break;
+ case 257:{input_event.button_id = BeamformerButtonID_Enter; }break;
+ case 258:{input_event.button_id = BeamformerButtonID_Tab; }break;
+ case 259:{input_event.button_id = BeamformerButtonID_Backspace; }break;
+ case 260:{input_event.button_id = BeamformerButtonID_Insert; }break;
+ case 261:{input_event.button_id = BeamformerButtonID_Delete; }break;
+ case 262:{input_event.button_id = BeamformerButtonID_Right; }break;
+ case 263:{input_event.button_id = BeamformerButtonID_Left; }break;
+ case 264:{input_event.button_id = BeamformerButtonID_Down; }break;
+ case 265:{input_event.button_id = BeamformerButtonID_Up; }break;
+ case 266:{input_event.button_id = BeamformerButtonID_PageUp; }break;
+ case 267:{input_event.button_id = BeamformerButtonID_PageDown; }break;
+ case 268:{input_event.button_id = BeamformerButtonID_Home; }break;
+ case 269:{input_event.button_id = BeamformerButtonID_End; }break;
+ case 280:{input_event.button_id = BeamformerButtonID_CapsLock; }break;
+ case 281:{input_event.button_id = BeamformerButtonID_ScrollLock; }break;
+ case 282:{input_event.button_id = BeamformerButtonID_NumLock; }break;
+ case 283:{input_event.button_id = BeamformerButtonID_PrintScreen; }break;
+ case 284:{input_event.button_id = BeamformerButtonID_Pause; }break;
+ case 290:{input_event.button_id = BeamformerButtonID_F1; }break;
+ case 291:{input_event.button_id = BeamformerButtonID_F2; }break;
+ case 292:{input_event.button_id = BeamformerButtonID_F3; }break;
+ case 293:{input_event.button_id = BeamformerButtonID_F4; }break;
+ case 294:{input_event.button_id = BeamformerButtonID_F5; }break;
+ case 295:{input_event.button_id = BeamformerButtonID_F6; }break;
+ case 296:{input_event.button_id = BeamformerButtonID_F7; }break;
+ case 297:{input_event.button_id = BeamformerButtonID_F8; }break;
+ case 298:{input_event.button_id = BeamformerButtonID_F9; }break;
+ case 299:{input_event.button_id = BeamformerButtonID_F10; }break;
+ case 300:{input_event.button_id = BeamformerButtonID_F11; }break;
+ case 301:{input_event.button_id = BeamformerButtonID_F12; }break;
+ case 302:{input_event.button_id = BeamformerButtonID_F13; }break;
+ case 303:{input_event.button_id = BeamformerButtonID_F14; }break;
+ case 304:{input_event.button_id = BeamformerButtonID_F15; }break;
+ case 305:{input_event.button_id = BeamformerButtonID_F16; }break;
+ case 306:{input_event.button_id = BeamformerButtonID_F17; }break;
+ case 307:{input_event.button_id = BeamformerButtonID_F18; }break;
+ case 308:{input_event.button_id = BeamformerButtonID_F19; }break;
+ case 309:{input_event.button_id = BeamformerButtonID_F20; }break;
+ case 310:{input_event.button_id = BeamformerButtonID_F21; }break;
+ case 311:{input_event.button_id = BeamformerButtonID_F22; }break;
+ case 312:{input_event.button_id = BeamformerButtonID_F23; }break;
+ case 313:{input_event.button_id = BeamformerButtonID_F24; }break;
+ case 314:{input_event.button_id = BeamformerButtonID_F25; }break;
+ case 320:{input_event.button_id = BeamformerButtonID_KP0; }break;
+ case 321:{input_event.button_id = BeamformerButtonID_KP1; }break;
+ case 322:{input_event.button_id = BeamformerButtonID_KP2; }break;
+ case 323:{input_event.button_id = BeamformerButtonID_KP3; }break;
+ case 324:{input_event.button_id = BeamformerButtonID_KP4; }break;
+ case 325:{input_event.button_id = BeamformerButtonID_KP5; }break;
+ case 326:{input_event.button_id = BeamformerButtonID_KP6; }break;
+ case 327:{input_event.button_id = BeamformerButtonID_KP7; }break;
+ case 328:{input_event.button_id = BeamformerButtonID_KP8; }break;
+ case 329:{input_event.button_id = BeamformerButtonID_KP9; }break;
+ case 330:{input_event.button_id = BeamformerButtonID_KPDecimal; }break;
+ case 331:{input_event.button_id = BeamformerButtonID_KPDivide; }break;
+ case 332:{input_event.button_id = BeamformerButtonID_KPMultiply; }break;
+ case 333:{input_event.button_id = BeamformerButtonID_KPSubtract; }break;
+ case 334:{input_event.button_id = BeamformerButtonID_KPAdd; }break;
+ case 335:{input_event.button_id = BeamformerButtonID_KPEnter; }break;
+ case 336:{input_event.button_id = BeamformerButtonID_KPEqual; }break;
+ case 340:{input_event.button_id = BeamformerButtonID_LeftShift; }break;
+ case 341:{input_event.button_id = BeamformerButtonID_LeftControl; }break;
+ case 342:{input_event.button_id = BeamformerButtonID_LeftAlt; }break;
+ case 343:{input_event.button_id = BeamformerButtonID_LeftSuper; }break;
+ case 344:{input_event.button_id = BeamformerButtonID_RightShift; }break;
+ case 345:{input_event.button_id = BeamformerButtonID_RightControl;}break;
+ case 346:{input_event.button_id = BeamformerButtonID_RightAlt; }break;
+ case 347:{input_event.button_id = BeamformerButtonID_RightSuper; }break;
+ case 348:{input_event.button_id = BeamformerButtonID_Menu; }break;
+ }
+
+ os_push_input_event(beamformer_input, input_event);
+}
+
+function void
+os_mouse_button_callback(void *window, i32 button, i32 action, i32 modifiers)
+{
+ BeamformerButtonID button_id = BeamformerButtonID_MouseLeft;
+ switch (button) {
+ default:{}break;
+ case 0:{button_id = BeamformerButtonID_MouseLeft; }break;
+ case 1:{button_id = BeamformerButtonID_MouseRight; }break;
+ case 2:{button_id = BeamformerButtonID_MouseMiddle;}break;
+ }
+
+ os_push_input_event(beamformer_input, (BeamformerInputEvent){
+ .kind = action == 0 ? BeamformerInputEventKind_ButtonRelease : BeamformerInputEventKind_ButtonPress,
+ .modifiers = os_modifiers_from_glfw(modifiers),
+ .button_id = button_id,
+ });
+}
+
function void
os_window_equip_common(BeamformerInput *input, void *window)
{
beamformer_input = input;
+
+ glfwSetCharCallback(window, 0);
+ glfwSetKeyCallback(window, os_key_callback);
+ glfwSetMouseButtonCallback(window, os_mouse_button_callback);
+ glfwSetScrollCallback(window, os_scroll_callback);
}
function void
@@ -35,14 +226,4 @@ os_build_frame_input(BeamformerInput *input)
if (IsWindowFocused()) new_mouse = GetMousePosition();
input->mouse_x = new_mouse.x;
input->mouse_y = new_mouse.y;
-
- input->input_modifiers = 0;
- input->input_modifiers |= BeamformerInputModifier_LeftAlt * IsKeyDown(KEY_LEFT_ALT);
- input->input_modifiers |= BeamformerInputModifier_RightAlt * IsKeyDown(KEY_RIGHT_ALT);
- input->input_modifiers |= BeamformerInputModifier_LeftControl * IsKeyDown(KEY_LEFT_CONTROL);
- input->input_modifiers |= BeamformerInputModifier_RightControl * IsKeyDown(KEY_RIGHT_CONTROL);
- input->input_modifiers |= BeamformerInputModifier_LeftShift * IsKeyDown(KEY_LEFT_SHIFT);
- input->input_modifiers |= BeamformerInputModifier_RightShift * IsKeyDown(KEY_RIGHT_SHIFT);
- input->input_modifiers |= BeamformerInputModifier_LeftMeta * IsKeyDown(KEY_LEFT_SUPER);
- input->input_modifiers |= BeamformerInputModifier_RightMeta * IsKeyDown(KEY_RIGHT_SUPER);
}