diff --git a/src/main.cpp b/src/main.cpp index 1f072ed..a3f37c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -126,13 +126,23 @@ struct AppState { // UI scale (Cmd+/Cmd- to zoom) F32 ui_scale; + + // Panel sizes (in unscaled px, will be multiplied by uis()) + F32 browser_width; // default 200 + F32 right_col_width; // default 250 + F32 log_height; // default 180 + + // Panel drag state + 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 }; //////////////////////////////// // Panel builders -static void build_browser_panel(B32 show) { - if (!show) return; +static void build_browser_panel(AppState *app) { + if (!app->show_browser) return; Clay_Color bp_top = g_theme.bg_medium; Clay_Color bp_bot = {(float)Max((int)bp_top.r-8,0), (float)Max((int)bp_top.g-8,0), (float)Max((int)bp_top.b-8,0), 255}; @@ -140,12 +150,11 @@ static void build_browser_panel(B32 show) { CLAY(CLAY_ID("BrowserPanel"), .layout = { - .sizing = { .width = CLAY_SIZING_FIXED(uis(200)), .height = CLAY_SIZING_GROW() }, + .sizing = { .width = CLAY_SIZING_FIXED(uis(app->browser_width)), .height = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, }, .backgroundColor = bp_top, .custom = { .customData = bp_grad }, - .border = { .color = g_theme.border, .width = { .right = 1 } } ) { { S32 sel = 0; @@ -672,8 +681,8 @@ static void build_right_panel(AppState *app) { } } -static void build_log_panel(B32 show) { - if (!show) return; +static void build_log_panel(AppState *app) { + if (!app->show_log) return; Clay_Color lp_top = g_theme.bg_medium; Clay_Color lp_bot = {(float)Max((int)lp_top.r-8,0), (float)Max((int)lp_top.g-8,0), (float)Max((int)lp_top.b-8,0), 255}; @@ -681,12 +690,11 @@ static void build_log_panel(B32 show) { CLAY(CLAY_ID("LogPanel"), .layout = { - .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(uis(180)) }, + .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(uis(app->log_height)) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, }, .backgroundColor = lp_top, .custom = { .customData = lp_grad }, - .border = { .color = g_theme.border, .width = { .top = 1 } } ) { { S32 sel = 0; @@ -833,6 +841,68 @@ static void about_window_content(void *user_data) { ui_label("AboutLbl3", "Built with Clay UI."); } +//////////////////////////////// +// Panel splitter drag logic (called each frame before build_ui) + +static void update_panel_splitters(AppState *app) { + 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; + PlatformCursor cursor = PLATFORM_CURSOR_ARROW; + + // Check hover on splitter elements (uses previous-frame layout) + B32 hover_browser = Clay_PointerOver(CLAY_ID("SplitBrowser")); + B32 hover_right = Clay_PointerOver(CLAY_ID("SplitRight")); + B32 hover_log = Clay_PointerOver(CLAY_ID("SplitLog")); + + // Start drag + if (mouse_clicked && app->panel_drag == 0) { + if (hover_browser) { + app->panel_drag = 1; + app->panel_drag_start_mouse = input.mouse_pos.x; + app->panel_drag_start_size = app->browser_width; + } else if (hover_right) { + app->panel_drag = 2; + app->panel_drag_start_mouse = input.mouse_pos.x; + app->panel_drag_start_size = app->right_col_width; + } else if (hover_log) { + app->panel_drag = 3; + app->panel_drag_start_mouse = input.mouse_pos.y; + app->panel_drag_start_size = app->log_height; + } + } + + // During drag + if (app->panel_drag == 1) { + F32 delta = (input.mouse_pos.x - app->panel_drag_start_mouse) / g_ui_scale; + app->browser_width = Clamp(100.0f, app->panel_drag_start_size + delta, 500.0f); + cursor = PLATFORM_CURSOR_SIZE_WE; + } else if (app->panel_drag == 2) { + F32 delta = (input.mouse_pos.x - app->panel_drag_start_mouse) / g_ui_scale; + // Right panel: dragging left increases width, right decreases + app->right_col_width = Clamp(150.0f, app->panel_drag_start_size - delta, 500.0f); + cursor = PLATFORM_CURSOR_SIZE_WE; + } else if (app->panel_drag == 3) { + F32 delta = (input.mouse_pos.y - app->panel_drag_start_mouse) / g_ui_scale; + // Log panel: dragging up increases height, down decreases + app->log_height = Clamp(80.0f, app->panel_drag_start_size - delta, 500.0f); + cursor = PLATFORM_CURSOR_SIZE_NS; + } + + // End drag + if (mouse_released) { + app->panel_drag = 0; + } + + // Set cursor on hover (when not dragging) + if (app->panel_drag == 0) { + if (hover_browser || hover_right) cursor = PLATFORM_CURSOR_SIZE_WE; + else if (hover_log) cursor = PLATFORM_CURSOR_SIZE_NS; + } + + platform_set_cursor(cursor); +} + //////////////////////////////// // Build the full UI layout for one frame @@ -850,23 +920,45 @@ static void build_ui(AppState *app) { .layoutDirection = CLAY_LEFT_TO_RIGHT, } ) { - build_browser_panel(app->show_browser); + build_browser_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 + ) {} + } + build_main_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(250)), .height = CLAY_SIZING_GROW() }, + .sizing = { .width = CLAY_SIZING_FIXED(uis(app->right_col_width)), .height = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, }, - .border = { .color = g_theme.border, .width = { .left = 1 } } ) { build_right_panel(app); } } } - build_log_panel(app->show_log); + // 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); } // Draggable windows (rendered as floating elements above normal UI) @@ -976,6 +1068,7 @@ static void do_frame(AppState *app) { } ui_widgets_begin_frame(input); + update_panel_splitters(app); // Build UI with Clay ui_begin_frame(app->ui, (F32)w, (F32)h, input.mouse_pos, input.mouse_down, @@ -1061,6 +1154,10 @@ int main(int argc, char **argv) { app.demo_fader = 0.0f; app.demo_dropdown_sel = 1; // default to 48000 Hz app.radius_sel = 1; // default to "Small" (4.0f) + app.browser_width = 200.0f; + app.right_col_width = 250.0f; + app.log_height = 180.0f; + app.panel_drag = 0; 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"); diff --git a/src/platform/platform.h b/src/platform/platform.h index bc9abe2..88d3ad4 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -90,6 +90,15 @@ int32_t platform_poll_menu_command(PlatformWindow *window); // Returns accumulated input since last call (keyboard events + polled mouse state), then clears the buffer. PlatformInput platform_get_input(PlatformWindow *window); +// Cursor shapes for resize handles +enum PlatformCursor { + PLATFORM_CURSOR_ARROW = 0, + PLATFORM_CURSOR_SIZE_WE = 1, // horizontal resize + PLATFORM_CURSOR_SIZE_NS = 2, // vertical resize +}; + +void platform_set_cursor(PlatformCursor cursor); + // Clipboard operations (null-terminated UTF-8 strings). // platform_clipboard_set copies text to the system clipboard. // platform_clipboard_get returns a pointer to a static buffer (valid until next call), or nullptr. diff --git a/src/platform/platform_macos.mm b/src/platform/platform_macos.mm index 93abd6a..21eb6c5 100644 --- a/src/platform/platform_macos.mm +++ b/src/platform/platform_macos.mm @@ -411,6 +411,14 @@ PlatformInput platform_get_input(PlatformWindow *window) { return result; } +void platform_set_cursor(PlatformCursor cursor) { + switch (cursor) { + case PLATFORM_CURSOR_SIZE_WE: [[NSCursor resizeLeftRightCursor] set]; break; + case PLATFORM_CURSOR_SIZE_NS: [[NSCursor resizeUpDownCursor] set]; break; + default: [[NSCursor arrowCursor] set]; break; + } +} + void platform_clipboard_set(const char *text) { if (!text) return; NSPasteboard *pb = [NSPasteboard generalPasteboard]; diff --git a/src/platform/platform_win32.cpp b/src/platform/platform_win32.cpp index 4471af2..ed59a1c 100644 --- a/src/platform/platform_win32.cpp +++ b/src/platform/platform_win32.cpp @@ -19,6 +19,7 @@ struct PlatformWindow { }; static PlatformWindow *g_current_window = nullptr; +static HCURSOR g_current_cursor = nullptr; static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { @@ -61,11 +62,9 @@ static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM g_current_window->pending_menu_cmd = (int32_t)LOWORD(wparam); return 0; case WM_SETCURSOR: - // When the cursor is in our client area, force it to an arrow. - // Without this, moving from a resize border back into the client - // area would leave the resize cursor shape stuck. + // When the cursor is in our client area, use the app-set cursor. if (LOWORD(lparam) == HTCLIENT) { - SetCursor(LoadCursor(nullptr, IDC_ARROW)); + SetCursor(g_current_cursor ? g_current_cursor : LoadCursor(nullptr, IDC_ARROW)); return TRUE; } break; @@ -227,6 +226,14 @@ PlatformInput platform_get_input(PlatformWindow *window) { return result; } +void platform_set_cursor(PlatformCursor cursor) { + switch (cursor) { + case PLATFORM_CURSOR_SIZE_WE: g_current_cursor = LoadCursor(nullptr, IDC_SIZEWE); break; + case PLATFORM_CURSOR_SIZE_NS: g_current_cursor = LoadCursor(nullptr, IDC_SIZENS); break; + default: g_current_cursor = LoadCursor(nullptr, IDC_ARROW); break; + } +} + void platform_clipboard_set(const char *text) { if (!text) return; int len = (int)strlen(text); diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index 52ffdff..fabc451 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -145,6 +145,7 @@ struct CustomRotatedIconData { #define FONT_SIZE_NORMAL uifs(15) #define FONT_SIZE_SMALL uifs(12) +#define FONT_SIZE_TAB uifs(13) //////////////////////////////// // Widget sizing diff --git a/src/ui/ui_widgets.cpp b/src/ui/ui_widgets.cpp index 469a2f6..0381056 100644 --- a/src/ui/ui_widgets.cpp +++ b/src/ui/ui_widgets.cpp @@ -165,6 +165,7 @@ static Clay_TextElementConfig g_widget_text_config_dim; static Clay_TextElementConfig g_widget_text_config_sel; static Clay_TextElementConfig g_widget_text_config_btn; static Clay_TextElementConfig g_widget_text_config_tab; +static Clay_TextElementConfig g_widget_text_config_tab_inactive; static F32 g_widget_text_configs_scale = 0; void ui_widgets_theme_changed() { @@ -200,8 +201,14 @@ static void ensure_widget_text_configs() { // Tab text (always light — readable on colored tab gradient) g_widget_text_config_tab = {}; g_widget_text_config_tab.textColor = g_theme.tab_text; - g_widget_text_config_tab.fontSize = FONT_SIZE_NORMAL; + g_widget_text_config_tab.fontSize = FONT_SIZE_TAB; g_widget_text_config_tab.wrapMode = CLAY_TEXT_WRAP_NONE; + + // Inactive tab text (theme text color, smaller font) + g_widget_text_config_tab_inactive = {}; + g_widget_text_config_tab_inactive.textColor = g_theme.text; + g_widget_text_config_tab_inactive.fontSize = FONT_SIZE_TAB; + g_widget_text_config_tab_inactive.wrapMode = CLAY_TEXT_WRAP_NONE; } static Clay_String clay_str(const char *s) { @@ -1368,7 +1375,7 @@ S32 ui_tab_bar(const char *id, const char **labels, S32 count, S32 *selected) { .backgroundColor = bg, .cornerRadius = { .topLeft = TAB_CORNER_RADIUS, .topRight = TAB_CORNER_RADIUS, .bottomLeft = 0, .bottomRight = 0 }, ) { - CLAY_TEXT(lbl_str, &g_widget_text_config); + CLAY_TEXT(lbl_str, &g_widget_text_config_tab_inactive); } }