From 52de039ca0c439ebe178fd26392d0996276970d7 Mon Sep 17 00:00:00 2001 From: Max Amundsen Date: Wed, 4 Mar 2026 03:01:53 -0500 Subject: [PATCH] Add layout mockups for patch matrix --- src/main.cpp | 824 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 786 insertions(+), 38 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 46e024c..119d75b 100644 --- a/src/main.cpp +++ b/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");