fix & improve platform popups
This commit is contained in:
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -8,7 +8,7 @@
|
||||
"program": "${workspaceFolder}/build_debug/autosample.app/Contents/MacOS/autosample",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
// "preLaunchTask": "build-debug"
|
||||
"preLaunchTask": "build-debug"
|
||||
},
|
||||
{
|
||||
"name": "Debug autosample (Windows)",
|
||||
@@ -18,7 +18,7 @@
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal",
|
||||
// "preLaunchTask": "build-debug"
|
||||
"preLaunchTask": "build-debug"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
102
src/main.cpp
102
src/main.cpp
@@ -102,6 +102,10 @@ struct AppState {
|
||||
B32 show_settings_window;
|
||||
B32 show_about_window;
|
||||
B32 show_confirm_dialog;
|
||||
|
||||
// Pop-out windows
|
||||
B32 show_mix_popout;
|
||||
B32 show_patch_popout;
|
||||
S32 settings_theme_sel;
|
||||
S32 settings_theme_prev;
|
||||
B32 settings_vsync;
|
||||
@@ -1165,7 +1169,39 @@ static void build_header_bar(AppState *app) {
|
||||
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;
|
||||
PopupWindow *mix_pop = popup_find_by_flag(&app->show_mix_popout);
|
||||
if (mix_pop) {
|
||||
platform_focus_window(mix_pop->platform_window);
|
||||
} else {
|
||||
app->master_layout = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mix pop-out / pop-in button
|
||||
{
|
||||
B32 mix_popped = app->show_mix_popout;
|
||||
Clay_ElementId pop_eid = CLAY_ID("BtnMixPopOut");
|
||||
B32 pop_hovered = Clay_PointerOver(pop_eid);
|
||||
CLAY(pop_eid,
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_FIXED(uis(22)), .height = CLAY_SIZING_FIXED(uis(22)) },
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
|
||||
},
|
||||
.backgroundColor = pop_hovered ? g_theme.accent_hover : g_theme.bg_lighter,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
|
||||
) {
|
||||
ui_icon(mix_popped ? UI_ICON_POP_IN : UI_ICON_POP_OUT, uis(12), g_theme.text_dim);
|
||||
}
|
||||
if (pop_hovered && g_wstate.mouse_clicked) {
|
||||
if (mix_popped) {
|
||||
PopupWindow *p = popup_find_by_flag(&app->show_mix_popout);
|
||||
if (p) popup_close(p);
|
||||
app->master_layout = 1;
|
||||
} else {
|
||||
app->show_mix_popout = 1;
|
||||
if (app->master_layout == 1) app->master_layout = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1188,7 +1224,39 @@ static void build_header_bar(AppState *app) {
|
||||
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;
|
||||
PopupWindow *patch_pop = popup_find_by_flag(&app->show_patch_popout);
|
||||
if (patch_pop) {
|
||||
platform_focus_window(patch_pop->platform_window);
|
||||
} else {
|
||||
app->master_layout = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Patch pop-out / pop-in button
|
||||
{
|
||||
B32 patch_popped = app->show_patch_popout;
|
||||
Clay_ElementId pop_eid = CLAY_ID("BtnPatchPopOut");
|
||||
B32 pop_hovered = Clay_PointerOver(pop_eid);
|
||||
CLAY(pop_eid,
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_FIXED(uis(22)), .height = CLAY_SIZING_FIXED(uis(22)) },
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
|
||||
},
|
||||
.backgroundColor = pop_hovered ? g_theme.accent_hover : g_theme.bg_lighter,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
|
||||
) {
|
||||
ui_icon(patch_popped ? UI_ICON_POP_IN : UI_ICON_POP_OUT, uis(12), g_theme.text_dim);
|
||||
}
|
||||
if (pop_hovered && g_wstate.mouse_clicked) {
|
||||
if (patch_popped) {
|
||||
PopupWindow *p = popup_find_by_flag(&app->show_patch_popout);
|
||||
if (p) popup_close(p);
|
||||
app->master_layout = 2;
|
||||
} else {
|
||||
app->show_patch_popout = 1;
|
||||
if (app->master_layout == 2) app->master_layout = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1676,6 +1744,17 @@ static void build_patch_view(AppState *app) {
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Pop-out window content callbacks
|
||||
|
||||
static void mix_popout_content(void *user_data) {
|
||||
build_mix_view((AppState *)user_data);
|
||||
}
|
||||
|
||||
static void patch_popout_content(void *user_data) {
|
||||
build_patch_view((AppState *)user_data);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Build the full UI layout for one frame
|
||||
|
||||
@@ -1736,11 +1815,15 @@ static void build_ui(AppState *app) {
|
||||
|
||||
build_log_panel(app);
|
||||
} else if (app->master_layout == 1) {
|
||||
// === MIX MODE ===
|
||||
build_mix_view(app);
|
||||
// === MIX MODE (skip if popped out) ===
|
||||
if (!app->show_mix_popout) {
|
||||
build_mix_view(app);
|
||||
}
|
||||
} else {
|
||||
// === PATCH MODE ===
|
||||
build_patch_view(app);
|
||||
// === PATCH MODE (skip if popped out) ===
|
||||
if (!app->show_patch_popout) {
|
||||
build_patch_view(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1783,6 +1866,9 @@ static void do_frame(AppState *app) {
|
||||
// Gather input
|
||||
PlatformInput input = platform_get_input(app->window);
|
||||
|
||||
// Sync scale from popups (they may have changed g_ui_scale)
|
||||
app->ui_scale = g_ui_scale;
|
||||
|
||||
// Cmd+= / Cmd+- (or Ctrl on Windows) to zoom UI, Cmd+0 to reset
|
||||
for (S32 k = 0; k < input.key_count; k++) {
|
||||
if (input.ctrl_held) {
|
||||
@@ -1973,6 +2059,10 @@ int main(int argc, char **argv) {
|
||||
popup_open(window, renderer, "Preferences", &app.show_settings_window, 480, 400, settings_window_content, &app);
|
||||
if (app.show_about_window && !popup_find_by_flag(&app.show_about_window))
|
||||
popup_open(window, renderer, "About", &app.show_about_window, 260, 200, about_window_content, nullptr);
|
||||
if (app.show_mix_popout && !popup_find_by_flag(&app.show_mix_popout))
|
||||
popup_open(window, renderer, "Mix", &app.show_mix_popout, 900, 600, mix_popout_content, &app, PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE, 1);
|
||||
if (app.show_patch_popout && !popup_find_by_flag(&app.show_patch_popout))
|
||||
popup_open(window, renderer, "Patch", &app.show_patch_popout, 900, 600, patch_popout_content, &app, PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE, 1);
|
||||
|
||||
// Check for OS close on popups
|
||||
popup_close_check();
|
||||
|
||||
@@ -54,8 +54,9 @@ struct PlatformInput {
|
||||
struct PlatformWindow;
|
||||
|
||||
enum PlatformWindowStyle {
|
||||
PLATFORM_WINDOW_STYLE_NORMAL = 0,
|
||||
PLATFORM_WINDOW_STYLE_POPUP = 1, // utility panel, owned by parent
|
||||
PLATFORM_WINDOW_STYLE_NORMAL = 0,
|
||||
PLATFORM_WINDOW_STYLE_POPUP = 1, // utility panel, owned by parent, fixed size
|
||||
PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE = 2, // utility panel, owned by parent, resizable
|
||||
};
|
||||
|
||||
struct PlatformWindowDesc {
|
||||
@@ -64,6 +65,7 @@ struct PlatformWindowDesc {
|
||||
S32 height = 720;
|
||||
PlatformWindowStyle style = PLATFORM_WINDOW_STYLE_NORMAL;
|
||||
PlatformWindow *parent = nullptr;
|
||||
B32 independent = 0; // if true, don't attach as child (independent top-level window)
|
||||
};
|
||||
|
||||
enum PlatformMsgBoxType {
|
||||
@@ -104,6 +106,9 @@ PlatformInput platform_get_input(PlatformWindow *window);
|
||||
// Returns true if the window's close button was clicked (for popup windows).
|
||||
B32 platform_window_should_close(PlatformWindow *window);
|
||||
|
||||
// Bring a window to front and give it keyboard focus.
|
||||
void platform_focus_window(PlatformWindow *window);
|
||||
|
||||
// Blocks until user responds. Returns 0=first button, 1=second button, -1=dismissed.
|
||||
S32 platform_message_box(PlatformWindow *parent, const char *title,
|
||||
const char *message, PlatformMsgBoxType type);
|
||||
|
||||
@@ -262,6 +262,8 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
NSWindowStyleMask style_mask;
|
||||
if (desc->style == PLATFORM_WINDOW_STYLE_POPUP) {
|
||||
style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
|
||||
} else if (desc->style == PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE) {
|
||||
style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
|
||||
} else {
|
||||
style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
|
||||
@@ -305,8 +307,8 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
g_main_window = window;
|
||||
}
|
||||
|
||||
// If popup, add as child of parent
|
||||
if (desc->style == PLATFORM_WINDOW_STYLE_POPUP && desc->parent) {
|
||||
// If popup, add as child of parent (unless independent)
|
||||
if (!desc->independent && (desc->style == PLATFORM_WINDOW_STYLE_POPUP || desc->style == PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE) && desc->parent) {
|
||||
[desc->parent->ns_window addChildWindow:ns_window ordered:NSWindowAbove];
|
||||
}
|
||||
|
||||
@@ -448,6 +450,11 @@ B32 platform_window_should_close(PlatformWindow *window) {
|
||||
return window ? window->should_close : 0;
|
||||
}
|
||||
|
||||
void platform_focus_window(PlatformWindow *window) {
|
||||
if (!window || !window->ns_window) return;
|
||||
[window->ns_window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
F32 platform_get_dpi_scale(PlatformWindow *window) {
|
||||
(void)window;
|
||||
return 1.0f; // macOS handles Retina via backing scale factor, not DPI
|
||||
|
||||
@@ -121,7 +121,10 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
HWND parent_hwnd = nullptr;
|
||||
if (desc->style == PLATFORM_WINDOW_STYLE_POPUP) {
|
||||
style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
|
||||
if (desc->parent) parent_hwnd = desc->parent->hwnd;
|
||||
if (desc->parent && !desc->independent) parent_hwnd = desc->parent->hwnd;
|
||||
} else if (desc->style == PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE) {
|
||||
style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX;
|
||||
if (desc->parent && !desc->independent) parent_hwnd = desc->parent->hwnd;
|
||||
} else {
|
||||
style = WS_OVERLAPPEDWINDOW;
|
||||
}
|
||||
@@ -264,6 +267,12 @@ B32 platform_window_should_close(PlatformWindow *window) {
|
||||
return window ? window->should_close : 0;
|
||||
}
|
||||
|
||||
void platform_focus_window(PlatformWindow *window) {
|
||||
if (!window || !window->hwnd) return;
|
||||
SetForegroundWindow(window->hwnd);
|
||||
SetFocus(window->hwnd);
|
||||
}
|
||||
|
||||
F32 platform_get_dpi_scale(PlatformWindow *window) {
|
||||
if (!window || !window->hwnd) return 1.0f;
|
||||
return (F32)GetDpiForWindow(window->hwnd) / 96.0f;
|
||||
|
||||
@@ -25,6 +25,7 @@ B32 renderer_begin_frame(Renderer *renderer);
|
||||
void renderer_end_frame(Renderer *renderer, Clay_RenderCommandArray render_commands);
|
||||
void renderer_resize(Renderer *renderer, S32 width, S32 height);
|
||||
void renderer_set_font_scale(Renderer *renderer, F32 scale);
|
||||
void renderer_sync_from_parent(Renderer *renderer); // sync shared font atlas from parent
|
||||
void renderer_set_clear_color(Renderer *renderer, F32 r, F32 g, F32 b);
|
||||
|
||||
// Text measurement callback compatible with UI_MeasureTextFn
|
||||
|
||||
@@ -1537,3 +1537,12 @@ void renderer_set_font_scale(Renderer *r, F32 scale) {
|
||||
if (r->font_texture) { r->font_texture->Release(); r->font_texture = nullptr; }
|
||||
create_font_atlas(r, target_size);
|
||||
}
|
||||
|
||||
void renderer_sync_from_parent(Renderer *r) {
|
||||
if (!r || !r->parent) return;
|
||||
Renderer *p = r->parent;
|
||||
r->font_texture = p->font_texture;
|
||||
r->font_atlas_size = p->font_atlas_size;
|
||||
r->font_line_height = p->font_line_height;
|
||||
memcpy(r->glyphs, p->glyphs, sizeof(r->glyphs));
|
||||
}
|
||||
|
||||
@@ -1003,6 +1003,16 @@ void renderer_set_font_scale(Renderer *r, F32 scale) {
|
||||
create_font_atlas(r, target_size);
|
||||
}
|
||||
|
||||
void renderer_sync_from_parent(Renderer *r) {
|
||||
if (!r || !r->parent) return;
|
||||
Renderer *p = r->parent;
|
||||
r->font_texture = p->font_texture;
|
||||
r->font_sampler = p->font_sampler;
|
||||
r->font_atlas_size = p->font_atlas_size;
|
||||
r->font_line_height = p->font_line_height;
|
||||
memcpy(r->glyphs, p->glyphs, sizeof(r->glyphs));
|
||||
}
|
||||
|
||||
void renderer_resize(Renderer *r, S32 width, S32 height) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
r->width = width;
|
||||
|
||||
@@ -126,6 +126,20 @@ static const char *g_icon_svgs[UI_ICON_COUNT] = {
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="8" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_POP_OUT - box with arrow pointing out (top-right)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="3" y="6" width="14" height="14" rx="2" stroke="white" stroke-width="2" fill="none"/>
|
||||
<path d="M14 3 L21 3 L21 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
<path d="M21 3 L12 12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_POP_IN - arrow pointing into a box (bottom-left)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="3" y="6" width="14" height="14" rx="2" stroke="white" stroke-width="2" fill="none"/>
|
||||
<path d="M21 3 L12 12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M12 5 L12 12 L19 12" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>)",
|
||||
};
|
||||
|
||||
U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size) {
|
||||
|
||||
@@ -14,6 +14,8 @@ enum UI_IconID {
|
||||
UI_ICON_TRANSPORT_STOP,
|
||||
UI_ICON_TRANSPORT_PLAY,
|
||||
UI_ICON_TRANSPORT_RECORD,
|
||||
UI_ICON_POP_OUT,
|
||||
UI_ICON_POP_IN,
|
||||
UI_ICON_COUNT
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
static PopupWindow g_popups[MAX_POPUP_WINDOWS];
|
||||
|
||||
static void popup_frame_callback(void *user_data) {
|
||||
PopupWindow *popup = (PopupWindow *)user_data;
|
||||
if (popup && popup->alive)
|
||||
popup_do_frame(popup, 1.0f / 60.0f);
|
||||
}
|
||||
|
||||
PopupWindow *popup_find_by_flag(B32 *flag) {
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++)
|
||||
if (g_popups[i].alive && g_popups[i].open_flag == flag)
|
||||
@@ -12,7 +18,8 @@ PopupWindow *popup_find_by_flag(B32 *flag) {
|
||||
PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer,
|
||||
const char *title, B32 *open_flag,
|
||||
S32 width, S32 height,
|
||||
UI_WindowContentFn content_fn, void *user_data) {
|
||||
UI_WindowContentFn content_fn, void *user_data,
|
||||
PlatformWindowStyle style, B32 independent) {
|
||||
// Find free slot
|
||||
PopupWindow *popup = nullptr;
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++) {
|
||||
@@ -27,8 +34,9 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
desc.title = title;
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
desc.style = PLATFORM_WINDOW_STYLE_POPUP;
|
||||
desc.parent = parent_window;
|
||||
desc.style = style;
|
||||
desc.parent = parent_window;
|
||||
desc.independent = independent;
|
||||
popup->platform_window = platform_create_window(&desc);
|
||||
if (!popup->platform_window) return nullptr;
|
||||
|
||||
@@ -61,6 +69,8 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
popup->title = title;
|
||||
popup->wstate = {};
|
||||
|
||||
platform_set_frame_callback(popup->platform_window, popup_frame_callback, popup);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
@@ -95,6 +105,19 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
// Gather input from popup window
|
||||
PlatformInput input = platform_get_input(popup->platform_window);
|
||||
|
||||
// Handle Cmd+/- zoom (propagate to global scale)
|
||||
for (S32 k = 0; k < input.key_count; k++) {
|
||||
if (input.ctrl_held) {
|
||||
if (input.keys[k] == PKEY_EQUAL) g_ui_scale *= 1.1f;
|
||||
if (input.keys[k] == PKEY_MINUS) g_ui_scale /= 1.1f;
|
||||
if (input.keys[k] == PKEY_0) g_ui_scale = 1.0f;
|
||||
}
|
||||
}
|
||||
g_ui_scale = Clamp(0.5f, g_ui_scale, 3.0f);
|
||||
|
||||
// Sync shared font atlas from parent renderer (picks up zoom changes)
|
||||
renderer_sync_from_parent(popup->renderer);
|
||||
|
||||
// Swap widget state
|
||||
UI_WidgetState saved_wstate = g_wstate;
|
||||
g_wstate = popup->wstate;
|
||||
|
||||
@@ -23,7 +23,9 @@ struct PopupWindow {
|
||||
PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer,
|
||||
const char *title, B32 *open_flag,
|
||||
S32 width, S32 height,
|
||||
UI_WindowContentFn content_fn, void *user_data);
|
||||
UI_WindowContentFn content_fn, void *user_data,
|
||||
PlatformWindowStyle style = PLATFORM_WINDOW_STYLE_POPUP,
|
||||
B32 independent = 0);
|
||||
void popup_close(PopupWindow *popup);
|
||||
PopupWindow *popup_find_by_flag(B32 *flag);
|
||||
void popup_do_frame(PopupWindow *popup, F32 dt);
|
||||
|
||||
Reference in New Issue
Block a user