Add layout mockups for patch matrix

This commit is contained in:
2026-03-04 03:01:53 -05:00
parent 6f95c60381
commit 52de039ca0

View File

@@ -140,6 +140,19 @@ struct AppState {
S32 panel_drag; // 0=none, 1=browser, 2=right, 3=log
F32 panel_drag_start_mouse; // mouse X or Y when drag started
F32 panel_drag_start_size; // panel size when drag started
// Master layout: 0 = Edit, 1 = Mix, 2 = Patch
S32 master_layout;
// Mix view fader state (8 channels + master)
F32 mix_faders[8];
F32 mix_pans[8];
F32 mix_master_pan;
// Patch view state
S32 patch_tab; // 0 = Matrix, 1 = Graph
B32 patch_matrix[32][33]; // internal routing: 32 inputs x (Master + 32 outputs)
B32 hw_matrix[32][32]; // hardware routing: 32 channel outputs x 32 hw outputs
};
////////////////////////////////
@@ -175,6 +188,7 @@ static Clay_Color velocity_color(int32_t velocity) {
// Piano input: handle mouse clicks on piano keys
static void update_piano_input(AppState *app) {
if (app->master_layout != 0) return;
PlatformInput input = g_wstate.input;
if (!input.mouse_down) {
@@ -984,6 +998,7 @@ static void about_window_content(void *user_data) {
// Panel splitter drag logic (called each frame before build_ui)
static void update_panel_splitters(AppState *app) {
if (app->master_layout != 0) { platform_set_cursor(PLATFORM_CURSOR_ARROW); return; }
PlatformInput input = g_wstate.input;
B32 mouse_clicked = input.mouse_down && !input.was_mouse_down;
B32 mouse_released = !input.mouse_down && input.was_mouse_down;
@@ -1042,6 +1057,723 @@ static void update_panel_splitters(AppState *app) {
platform_set_cursor(cursor);
}
////////////////////////////////
// Header bar — transport / layout toggle
static void build_header_bar(AppState *app) {
Clay_Color bar_bg = g_theme.bg_dark;
Clay_Color border_bot = {(float)Max((int)bar_bg.r - 12, 0), (float)Max((int)bar_bg.g - 12, 0), (float)Max((int)bar_bg.b - 12, 0), 255};
static Clay_TextElementConfig header_btn_active_text = {};
header_btn_active_text.textColor = Clay_Color{255, 255, 255, 255};
header_btn_active_text.fontSize = FONT_SIZE_NORMAL;
header_btn_active_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig header_clock_text = {};
header_clock_text.textColor = g_theme.text;
header_clock_text.fontSize = uifs(22);
header_clock_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig header_indicator_text = {};
header_indicator_text.textColor = g_theme.text;
header_indicator_text.fontSize = FONT_SIZE_NORMAL;
header_indicator_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig header_indicator_label = {};
header_indicator_label.textColor = g_theme.text_dim;
header_indicator_label.fontSize = FONT_SIZE_SMALL;
header_indicator_label.wrapMode = CLAY_TEXT_WRAP_NONE;
Clay_Color inset_bg = {(float)Max((int)bar_bg.r - 8, 0), (float)Max((int)bar_bg.g - 8, 0), (float)Max((int)bar_bg.b - 8, 0), 255};
CLAY(CLAY_ID("HeaderBar"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(uis(64)) },
.padding = { uip(10), uip(10), uip(6), uip(6) },
.childGap = uip(8),
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
},
.backgroundColor = bar_bg,
.border = { .color = border_bot, .width = { .bottom = 2 } },
) {
// === LEFT: toolbar buttons ===
CLAY(CLAY_ID("ToolbarGroup"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() },
.childGap = uip(4),
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
// Rewind
CLAY(CLAY_ID("TbRewind"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(uis(32)), .height = CLAY_SIZING_FIXED(uis(32)) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) { CLAY_TEXT(CLAY_STRING("<<"), &g_text_config_dim); }
// Stop
CLAY(CLAY_ID("TbStop"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(uis(32)), .height = CLAY_SIZING_FIXED(uis(32)) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) { CLAY_TEXT(CLAY_STRING("[]"), &g_text_config_dim); }
// Play
CLAY(CLAY_ID("TbPlay"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(uis(32)), .height = CLAY_SIZING_FIXED(uis(32)) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) { CLAY_TEXT(CLAY_STRING(">"), &g_text_config_dim); }
// Record
CLAY(CLAY_ID("TbRecord"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(uis(32)), .height = CLAY_SIZING_FIXED(uis(32)) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) {
static Clay_TextElementConfig rec_text = {};
rec_text.textColor = Clay_Color{200, 60, 60, 255};
rec_text.fontSize = FONT_SIZE_NORMAL;
rec_text.wrapMode = CLAY_TEXT_WRAP_NONE;
CLAY_TEXT(CLAY_STRING("O"), &rec_text);
}
}
// Spacer
CLAY(CLAY_ID("HeaderSpacerL"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } }
) {}
// === CENTER: time display (nearly full header height) ===
CLAY(CLAY_ID("TimeDisplay"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(uis(200)), .height = CLAY_SIZING_FIXED(uis(48)) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = inset_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.border = { .color = border_bot, .width = { .left = 1, .right = 1, .top = 1, .bottom = 1 } },
) {
CLAY_TEXT(CLAY_STRING("00:00:00.000"), &header_clock_text);
}
// === Tempo & time sig indicators ===
CLAY(CLAY_ID("IndicatorGroup"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() },
.childGap = uip(10),
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
// Tempo
CLAY(CLAY_ID("TempoBox"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(uis(48)) },
.padding = { uip(10), uip(10), 0, 0 },
.childGap = uip(2),
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = inset_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.border = { .color = border_bot, .width = { .left = 1, .right = 1, .top = 1, .bottom = 1 } },
) {
CLAY_TEXT(CLAY_STRING("TEMPO"), &header_indicator_label);
CLAY_TEXT(CLAY_STRING("120.00"), &header_indicator_text);
}
// Time Signature
CLAY(CLAY_ID("TimeSigBox"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(uis(48)) },
.padding = { uip(10), uip(10), 0, 0 },
.childGap = uip(2),
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = inset_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.border = { .color = border_bot, .width = { .left = 1, .right = 1, .top = 1, .bottom = 1 } },
) {
CLAY_TEXT(CLAY_STRING("SIGNATURE"), &header_indicator_label);
CLAY_TEXT(CLAY_STRING("4 / 4"), &header_indicator_text);
}
}
// Spacer
CLAY(CLAY_ID("HeaderSpacerR"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } }
) {}
// === RIGHT: Edit / Mix buttons ===
// Edit button
{
Clay_ElementId edit_eid = CLAY_ID("BtnLayoutEdit");
B32 edit_hovered = Clay_PointerOver(edit_eid);
B32 edit_active = (app->master_layout == 0);
Clay_Color edit_bg = edit_active ? g_theme.accent : g_theme.bg_lighter;
if (edit_hovered && !edit_active) edit_bg = g_theme.accent_hover;
CLAY(edit_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(uis(28)) },
.padding = { uip(14), uip(14), 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = edit_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) {
CLAY_TEXT(CLAY_STRING("Edit"), edit_active ? &header_btn_active_text : &g_text_config_normal);
}
if (edit_hovered && g_wstate.mouse_clicked) {
app->master_layout = 0;
}
}
// Mix button
{
Clay_ElementId mix_eid = CLAY_ID("BtnLayoutMix");
B32 mix_hovered = Clay_PointerOver(mix_eid);
B32 mix_active = (app->master_layout == 1);
Clay_Color mix_bg = mix_active ? g_theme.accent : g_theme.bg_lighter;
if (mix_hovered && !mix_active) mix_bg = g_theme.accent_hover;
CLAY(mix_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(uis(28)) },
.padding = { uip(14), uip(14), 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = mix_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) {
CLAY_TEXT(CLAY_STRING("Mix"), mix_active ? &header_btn_active_text : &g_text_config_normal);
}
if (mix_hovered && g_wstate.mouse_clicked) {
app->master_layout = 1;
}
}
// Patch button
{
Clay_ElementId patch_eid = CLAY_ID("BtnLayoutPatch");
B32 patch_hovered = Clay_PointerOver(patch_eid);
B32 patch_active = (app->master_layout == 2);
Clay_Color patch_bg = patch_active ? g_theme.accent : g_theme.bg_lighter;
if (patch_hovered && !patch_active) patch_bg = g_theme.accent_hover;
CLAY(patch_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(uis(28)) },
.padding = { uip(14), uip(14), 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = patch_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) {
CLAY_TEXT(CLAY_STRING("Patch"), patch_active ? &header_btn_active_text : &g_text_config_normal);
}
if (patch_hovered && g_wstate.mouse_clicked) {
app->master_layout = 2;
}
}
}
}
////////////////////////////////
// Mix view — channel strip faders
static void build_mix_view(AppState *app) {
Clay_Color mv_top = g_theme.bg_medium;
Clay_Color mv_bot = {(float)Max((int)mv_top.r - 8, 0), (float)Max((int)mv_top.g - 8, 0), (float)Max((int)mv_top.b - 8, 0), 255};
CustomGradientData *mv_grad = alloc_gradient(mv_top, mv_bot);
CLAY(CLAY_ID("MixView"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { uip(4), uip(4), uip(4), uip(4) },
.childGap = 0,
.layoutDirection = CLAY_LEFT_TO_RIGHT,
},
.backgroundColor = mv_top,
.custom = { .customData = mv_grad },
) {
static char ch_label_bufs[8][8];
static char fader_id_bufs[8][16];
static char pan_id_bufs[8][16];
for (int i = 0; i < 8; i++) {
snprintf(ch_label_bufs[i], sizeof(ch_label_bufs[i]), "Ch %d", i + 1);
snprintf(fader_id_bufs[i], sizeof(fader_id_bufs[i]), "MixFader%d", i);
snprintf(pan_id_bufs[i], sizeof(pan_id_bufs[i]), "MixPan%d", i);
Clay_Color strip_bg = g_theme.bg_medium;
CLAY(CLAY_IDI("MixStrip", i),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { uip(8), uip(8), uip(8), uip(8) },
.childGap = uip(8),
.childAlignment = { .x = CLAY_ALIGN_X_CENTER },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = strip_bg,
.border = { .color = g_theme.border, .width = { .right = 1 } },
) {
// Channel label
int llen = snprintf(ch_label_bufs[i], sizeof(ch_label_bufs[i]), "Ch %d", i + 1);
Clay_String ch_str = { .isStaticallyAllocated = false, .length = llen, .chars = ch_label_bufs[i] };
CLAY_TEXT(ch_str, &g_text_config_dim);
// SEND/RECV button
Clay_ElementId sr_eid = CLAY_IDI("MixSendRecv", i);
B32 sr_hovered = Clay_PointerOver(sr_eid);
CLAY(sr_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(uis(22)) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = sr_hovered ? g_theme.accent_hover : g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) {
CLAY_TEXT(CLAY_STRING("SEND/RECV"), &g_text_config_dim);
}
// Spacer pushes pan + fader to bottom
CLAY(CLAY_IDI("MixSpacer", i),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() } }
) {}
// Pan knob
ui_knob(pan_id_bufs[i], "Pan", &app->mix_pans[i], 50.0f, 1, 0.0f, 1);
// Fader
ui_fader(fader_id_bufs[i], ch_label_bufs[i], &app->mix_faders[i], 10.0f, 0, 0.0f, 1);
}
}
// Master strip (slightly wider, accent-tinted)
{
Clay_Color master_bg = {
(float)Min((int)g_theme.bg_medium.r + 6, 255),
(float)Min((int)g_theme.bg_medium.g + 6, 255),
(float)Min((int)g_theme.bg_medium.b + 6, 255),
255
};
static F32 master_fader = 0.0f;
CLAY(CLAY_ID("MixMaster"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(0, uis(120)), .height = CLAY_SIZING_GROW() },
.padding = { uip(8), uip(8), uip(8), uip(8) },
.childGap = uip(8),
.childAlignment = { .x = CLAY_ALIGN_X_CENTER },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = master_bg,
) {
CLAY_TEXT(CLAY_STRING("Master"), &g_text_config_normal);
// SEND/RECV button
{
Clay_ElementId msr_eid = CLAY_ID("MixMasterSendRecv");
B32 msr_hovered = Clay_PointerOver(msr_eid);
CLAY(msr_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(uis(22)) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = msr_hovered ? g_theme.accent_hover : g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) {
CLAY_TEXT(CLAY_STRING("SEND/RECV"), &g_text_config_dim);
}
}
CLAY(CLAY_ID("MixMasterSpacer"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() } }
) {}
// Master pan knob
ui_knob("MixMasterPan", "Pan", &app->mix_master_pan, 50.0f, 1, 0.0f, 1);
ui_fader("MixMasterFader", "Master", &master_fader, 10.0f, 0, 0.0f, 1);
}
}
}
}
////////////////////////////////
// Patch view — routing matrix / graph
static void build_patch_view(AppState *app) {
Clay_Color pv_top = g_theme.bg_medium;
Clay_Color pv_bot = {(float)Max((int)pv_top.r - 8, 0), (float)Max((int)pv_top.g - 8, 0), (float)Max((int)pv_top.b - 8, 0), 255};
CustomGradientData *pv_grad = alloc_gradient(pv_top, pv_bot);
CLAY(CLAY_ID("PatchView"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = pv_top,
.custom = { .customData = pv_grad },
) {
static const char *patch_tabs[] = { "Matrix", "Graph" };
ui_tab_bar("PatchTabs", patch_tabs, 2, &app->patch_tab);
if (app->patch_tab == 0) {
// === Matrix routing view ===
#define MATRIX_INPUTS 32
#define MATRIX_OUTPUTS 33 // Master + Out 1..32
#define HW_OUTPUTS 32
static Clay_TextElementConfig matrix_hdr_text = {};
matrix_hdr_text.textColor = g_theme.text_dim;
matrix_hdr_text.fontSize = FONT_SIZE_SMALL;
matrix_hdr_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig matrix_axis_text = {};
matrix_axis_text.textColor = g_theme.text;
matrix_axis_text.fontSize = FONT_SIZE_SMALL;
matrix_axis_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig matrix_title_text = {};
matrix_title_text.textColor = g_theme.text;
matrix_title_text.fontSize = FONT_SIZE_NORMAL;
matrix_title_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig cell_x_text = {};
cell_x_text.textColor = Clay_Color{255, 255, 255, 255};
cell_x_text.fontSize = FONT_SIZE_SMALL;
cell_x_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig fb_text = {};
fb_text.textColor = Clay_Color{80, 80, 80, 255};
fb_text.fontSize = FONT_SIZE_SMALL;
fb_text.wrapMode = CLAY_TEXT_WRAP_NONE;
F32 cell_size = uis(20);
F32 label_w = uis(48);
// Scrollable container for both matrices side by side
CLAY(CLAY_ID("MatrixScroll"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { uip(12), uip(12), uip(8), uip(8) },
.childGap = uip(32),
.layoutDirection = CLAY_LEFT_TO_RIGHT,
},
.clip = { .horizontal = true, .vertical = true, .childOffset = Clay_GetScrollOffset() },
) {
// ============================================================
// INTERNAL ROUTING matrix (32 inputs x 33 outputs)
// ============================================================
CLAY(CLAY_ID("InternalMatrix"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() },
.childGap = uip(4),
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Title
CLAY_TEXT(CLAY_STRING("Internal Routing"), &matrix_title_text);
// Axis label: "OUTPUT >"
CLAY(CLAY_ID("IntOutputLabel"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() },
.padding = { (uint16_t)label_w, 0, 0, 0 },
}
) {
CLAY_TEXT(CLAY_STRING("OUTPUT >"), &matrix_axis_text);
}
// Grid
CLAY(CLAY_ID("IntGrid"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Column headers
CLAY(CLAY_ID("IntHeaderRow"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(cell_size) },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
CLAY(CLAY_ID("IntCorner"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(label_w), .height = CLAY_SIZING_FIXED(cell_size) },
.padding = { 0, uip(4), 0, 0 },
.childAlignment = { .x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_CENTER },
}
) {
CLAY_TEXT(CLAY_STRING("INPUT v"), &matrix_axis_text);
}
static char int_dst_bufs[MATRIX_OUTPUTS][8];
for (int d = 0; d < MATRIX_OUTPUTS; d++) {
int dlen;
if (d == 0)
dlen = snprintf(int_dst_bufs[d], sizeof(int_dst_bufs[d]), "Mst");
else
dlen = snprintf(int_dst_bufs[d], sizeof(int_dst_bufs[d]), "%d", d);
Clay_String dst_str = { .isStaticallyAllocated = false, .length = dlen, .chars = int_dst_bufs[d] };
CLAY(CLAY_IDI("IntDstHdr", d),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(cell_size), .height = CLAY_SIZING_FIXED(cell_size) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
) {
CLAY_TEXT(dst_str, d == 0 ? &matrix_axis_text : &matrix_hdr_text);
}
}
}
// Rows
static char int_src_bufs[MATRIX_INPUTS][8];
for (int s = 0; s < MATRIX_INPUTS; s++) {
int slen = snprintf(int_src_bufs[s], sizeof(int_src_bufs[s]), "Ch %d", s + 1);
Clay_String src_str = { .isStaticallyAllocated = false, .length = slen, .chars = int_src_bufs[s] };
CLAY(CLAY_IDI("IntRow", s),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(cell_size) },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
CLAY(CLAY_IDI("IntSrcLbl", s),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(label_w), .height = CLAY_SIZING_FIXED(cell_size) },
.padding = { 0, uip(4), 0, 0 },
.childAlignment = { .x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_CENTER },
},
) {
CLAY_TEXT(src_str, &matrix_hdr_text);
}
for (int d = 0; d < MATRIX_OUTPUTS; d++) {
int cell_idx = s * MATRIX_OUTPUTS + d;
Clay_ElementId cell_eid = CLAY_IDI("IntCell", cell_idx);
B32 cell_hovered = Clay_PointerOver(cell_eid);
B32 is_feedback = (d >= 1 && d == s + 1);
if (is_feedback) app->patch_matrix[s][d] = 0;
B32 active = app->patch_matrix[s][d];
Clay_Color cell_bg;
if (is_feedback) {
cell_bg = Clay_Color{
(float)Max((int)g_theme.bg_dark.r - 10, 0),
(float)Max((int)g_theme.bg_dark.g - 10, 0),
(float)Max((int)g_theme.bg_dark.b - 10, 0), 255
};
} else if (active) {
cell_bg = g_theme.accent;
} else if (cell_hovered) {
cell_bg = g_theme.bg_lighter;
} else if ((s + d) % 2 == 0) {
cell_bg = g_theme.bg_dark;
} else {
cell_bg = Clay_Color{
(float)Min((int)g_theme.bg_dark.r + 6, 255),
(float)Min((int)g_theme.bg_dark.g + 6, 255),
(float)Min((int)g_theme.bg_dark.b + 6, 255), 255
};
}
if (d == 0 && !active && !cell_hovered && !is_feedback) {
cell_bg = Clay_Color{
(float)Min((int)cell_bg.r + 10, 255),
(float)Min((int)cell_bg.g + 10, 255),
(float)Min((int)cell_bg.b + 12, 255), 255
};
}
CLAY(cell_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(cell_size), .height = CLAY_SIZING_FIXED(cell_size) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = cell_bg,
.border = { .color = g_theme.border, .width = { .right = 1, .bottom = 1 } },
) {
if (is_feedback) {
CLAY_TEXT(CLAY_STRING("-"), &fb_text);
} else if (active) {
CLAY_TEXT(CLAY_STRING("X"), &cell_x_text);
}
}
if (!is_feedback && cell_hovered && g_wstate.mouse_clicked) {
app->patch_matrix[s][d] = !app->patch_matrix[s][d];
}
}
}
}
}
}
// ============================================================
// HARDWARE ROUTING matrix (32 ch outputs x 32 hw outputs)
// ============================================================
CLAY(CLAY_ID("HardwareMatrix"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() },
.childGap = uip(4),
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Title
CLAY_TEXT(CLAY_STRING("Hardware Routing"), &matrix_title_text);
// Axis label: "HW OUTPUT >"
CLAY(CLAY_ID("HwOutputLabel"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() },
.padding = { (uint16_t)label_w, 0, 0, 0 },
}
) {
CLAY_TEXT(CLAY_STRING("HW OUTPUT >"), &matrix_axis_text);
}
// Grid
CLAY(CLAY_ID("HwGrid"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Column headers
CLAY(CLAY_ID("HwHeaderRow"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(cell_size) },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
CLAY(CLAY_ID("HwCorner"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(label_w), .height = CLAY_SIZING_FIXED(cell_size) },
.padding = { 0, uip(4), 0, 0 },
.childAlignment = { .x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_CENTER },
}
) {
CLAY_TEXT(CLAY_STRING("CH OUT v"), &matrix_axis_text);
}
static char hw_dst_bufs[HW_OUTPUTS][8];
for (int d = 0; d < HW_OUTPUTS; d++) {
int dlen = snprintf(hw_dst_bufs[d], sizeof(hw_dst_bufs[d]), "%d", d + 1);
Clay_String dst_str = { .isStaticallyAllocated = false, .length = dlen, .chars = hw_dst_bufs[d] };
CLAY(CLAY_IDI("HwDstHdr", d),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(cell_size), .height = CLAY_SIZING_FIXED(cell_size) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
) {
CLAY_TEXT(dst_str, &matrix_hdr_text);
}
}
}
// Rows
static char hw_src_bufs[HW_OUTPUTS][8];
for (int s = 0; s < HW_OUTPUTS; s++) {
int slen = snprintf(hw_src_bufs[s], sizeof(hw_src_bufs[s]), "Out %d", s + 1);
Clay_String src_str = { .isStaticallyAllocated = false, .length = slen, .chars = hw_src_bufs[s] };
CLAY(CLAY_IDI("HwRow", s),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(cell_size) },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
CLAY(CLAY_IDI("HwSrcLbl", s),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(label_w), .height = CLAY_SIZING_FIXED(cell_size) },
.padding = { 0, uip(4), 0, 0 },
.childAlignment = { .x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_CENTER },
},
) {
CLAY_TEXT(src_str, &matrix_hdr_text);
}
for (int d = 0; d < HW_OUTPUTS; d++) {
int cell_idx = s * HW_OUTPUTS + d;
Clay_ElementId cell_eid = CLAY_IDI("HwCell", cell_idx);
B32 cell_hovered = Clay_PointerOver(cell_eid);
B32 active = app->hw_matrix[s][d];
Clay_Color cell_bg;
if (active) {
cell_bg = g_theme.accent;
} else if (cell_hovered) {
cell_bg = g_theme.bg_lighter;
} else if ((s + d) % 2 == 0) {
cell_bg = g_theme.bg_dark;
} else {
cell_bg = Clay_Color{
(float)Min((int)g_theme.bg_dark.r + 6, 255),
(float)Min((int)g_theme.bg_dark.g + 6, 255),
(float)Min((int)g_theme.bg_dark.b + 6, 255), 255
};
}
CLAY(cell_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(cell_size), .height = CLAY_SIZING_FIXED(cell_size) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = cell_bg,
.border = { .color = g_theme.border, .width = { .right = 1, .bottom = 1 } },
) {
if (active) {
CLAY_TEXT(CLAY_STRING("X"), &cell_x_text);
}
}
if (cell_hovered && g_wstate.mouse_clicked) {
app->hw_matrix[s][d] = !app->hw_matrix[s][d];
}
}
}
}
}
}
}
} else {
// === Graph view (placeholder) ===
CLAY(CLAY_ID("GraphContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { uip(16), uip(16), uip(12), uip(12) },
.childGap = 0,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
}
) {
CLAY_TEXT(CLAY_STRING("Graph view coming soon"), &g_text_config_dim);
}
}
}
}
////////////////////////////////
// Build the full UI layout for one frame
@@ -1052,52 +1784,62 @@ static void build_ui(AppState *app) {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Top row: browser | main | right column
CLAY(CLAY_ID("TopRow"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
build_browser_panel(app);
build_header_bar(app);
// Browser splitter (vertical, 4px wide)
if (app->show_browser) {
CLAY(CLAY_ID("SplitBrowser"),
.layout = { .sizing = { .width = CLAY_SIZING_FIXED(uis(4)), .height = CLAY_SIZING_GROW() } },
.backgroundColor = g_theme.border
) {}
}
if (app->master_layout == 0) {
// === EDIT MODE (existing layout) ===
CLAY(CLAY_ID("TopRow"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
build_browser_panel(app);
build_main_panel(app);
// Browser splitter (vertical, 4px wide)
if (app->show_browser) {
CLAY(CLAY_ID("SplitBrowser"),
.layout = { .sizing = { .width = CLAY_SIZING_FIXED(uis(4)), .height = CLAY_SIZING_GROW() } },
.backgroundColor = g_theme.border
) {}
}
if (app->show_props || app->show_midi_devices) {
// Right splitter (vertical, 4px wide)
CLAY(CLAY_ID("SplitRight"),
.layout = { .sizing = { .width = CLAY_SIZING_FIXED(uis(4)), .height = CLAY_SIZING_GROW() } },
.backgroundColor = g_theme.border
) {}
build_main_panel(app);
CLAY(CLAY_ID("RightColumn"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(uis(app->right_col_width)), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
) {
build_right_panel(app);
if (app->show_props || app->show_midi_devices) {
// Right splitter (vertical, 4px wide)
CLAY(CLAY_ID("SplitRight"),
.layout = { .sizing = { .width = CLAY_SIZING_FIXED(uis(4)), .height = CLAY_SIZING_GROW() } },
.backgroundColor = g_theme.border
) {}
CLAY(CLAY_ID("RightColumn"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(uis(app->right_col_width)), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
) {
build_right_panel(app);
}
}
}
}
// Log splitter (horizontal, 4px tall)
if (app->show_log) {
CLAY(CLAY_ID("SplitLog"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(uis(4)) } },
.backgroundColor = g_theme.border
) {}
}
// Log splitter (horizontal, 4px tall)
if (app->show_log) {
CLAY(CLAY_ID("SplitLog"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(uis(4)) } },
.backgroundColor = g_theme.border
) {}
}
build_log_panel(app);
build_log_panel(app);
} else if (app->master_layout == 1) {
// === MIX MODE ===
build_mix_view(app);
} else {
// === PATCH MODE ===
build_patch_view(app);
}
}
// Draggable windows (rendered as floating elements above normal UI)
@@ -1300,6 +2042,12 @@ int main(int argc, char **argv) {
app.right_col_width = 250.0f;
app.log_height = 180.0f;
app.panel_drag = 0;
app.master_layout = 0;
for (int i = 0; i < 8; i++) { app.mix_faders[i] = 0.0f; app.mix_pans[i] = 0.0f; }
app.mix_master_pan = 0.0f;
app.patch_tab = 0;
memset(app.patch_matrix, 0, sizeof(app.patch_matrix));
memset(app.hw_matrix, 0, sizeof(app.hw_matrix));
snprintf(app.demo_text_a, sizeof(app.demo_text_a), "My Instrument");
#ifdef __APPLE__
snprintf(app.demo_text_b, sizeof(app.demo_text_b), "~/Samples/output");