set layout size draggble

This commit is contained in:
2026-03-04 01:25:59 -05:00
parent 5aa0d7466e
commit 8891dcffb8
6 changed files with 147 additions and 18 deletions

View File

@@ -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");

View File

@@ -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.

View File

@@ -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];

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}
}