Add layout mockups for patch matrix
This commit is contained in:
824
src/main.cpp
824
src/main.cpp
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user