Use platform windows for windowing instead of custom draggable window system

This commit is contained in:
2026-03-05 11:44:23 -05:00
parent b75cb920eb
commit 5eaae4deb9
15 changed files with 833 additions and 742 deletions

View File

@@ -47,7 +47,9 @@ static U8 macos_keycode_to_pkey(U16 keycode) {
// Forward declarations
struct PlatformWindow;
static PlatformWindow *g_current_window = nullptr;
// Main window receives menu commands
static PlatformWindow *g_main_window = nullptr;
////////////////////////////////
// Objective-C helper classes
@@ -60,31 +62,97 @@ static PlatformWindow *g_current_window = nullptr;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { (void)sender; return YES; }
@end
@interface ASmplWindowDelegate : NSObject <NSWindowDelegate>
@interface ASmplWindowDelegate : NSObject <NSWindowDelegate> {
@public
PlatformWindow *_platformWindow;
}
@end
@interface ASmplView : NSView <NSTextInputClient> {
@public
PlatformWindow *_platformWindow;
}
@end
////////////////////////////////
// PlatformWindow struct
struct PlatformWindow {
NSWindow *ns_window;
ASmplView *view;
ASmplWindowDelegate *delegate;
B32 should_close;
S32 width;
S32 height;
S32 pending_menu_cmd;
PlatformFrameCallback frame_callback;
void *frame_callback_user_data;
PlatformInput input;
B32 prev_mouse_down;
B32 mouse_down_state;
F32 backing_scale;
};
////////////////////////////////
// C callback helpers (called from ObjC via PlatformWindow pointer)
static void platform_macos_insert_text_pw(PlatformWindow *pw, const char *utf8) {
if (!pw || !utf8) return;
PlatformInput *ev = &pw->input;
while (*utf8 && ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME) {
U8 c = (U8)*utf8;
if (c < 32) { utf8++; continue; }
// Handle ASCII printable range (single-byte UTF-8)
if (c < 0x80) {
ev->chars[ev->char_count++] = (U16)c;
utf8++;
} else {
// Skip multi-byte UTF-8 sequences for now (UI only handles ASCII)
if (c < 0xE0) utf8 += 2;
else if (c < 0xF0) utf8 += 3;
else utf8 += 4;
}
}
}
static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventModifierFlags mods) {
if (!pw) return;
PlatformInput *ev = &pw->input;
U8 pkey = macos_keycode_to_pkey(keycode);
if (pkey && ev->key_count < PLATFORM_MAX_KEYS_PER_FRAME)
ev->keys[ev->key_count++] = pkey;
// Command = ctrl_held (macOS convention: Cmd+C, Cmd+V, etc.)
ev->ctrl_held = (mods & NSEventModifierFlagCommand) != 0;
ev->shift_held = (mods & NSEventModifierFlagShift) != 0;
}
////////////////////////////////
// ObjC class implementations
@implementation ASmplWindowDelegate
- (BOOL)windowShouldClose:(id)sender {
(void)sender;
if (g_current_window) {
// Set should_close flag (accessed via the struct below)
extern void platform_macos_set_should_close();
platform_macos_set_should_close();
if (_platformWindow) {
_platformWindow->should_close = true;
}
return NO; // We handle closing ourselves
}
- (void)windowDidResize:(NSNotification *)notification {
(void)notification;
if (!g_current_window) return;
extern void platform_macos_handle_resize();
platform_macos_handle_resize();
if (!_platformWindow) return;
PlatformWindow *pw = _platformWindow;
NSRect frame = [pw->view bounds];
F32 scale = pw->backing_scale;
pw->width = (S32)(frame.size.width * scale);
pw->height = (S32)(frame.size.height * scale);
if (pw->frame_callback)
pw->frame_callback(pw->frame_callback_user_data);
}
@end
@interface ASmplView : NSView <NSTextInputClient>
@end
@implementation ASmplView
- (BOOL)acceptsFirstResponder { return YES; }
@@ -117,13 +185,11 @@ static PlatformWindow *g_current_window = nullptr;
else
str = (NSString *)string;
extern void platform_macos_insert_text(const char *utf8);
platform_macos_insert_text([str UTF8String]);
platform_macos_insert_text_pw(_platformWindow, [str UTF8String]);
}
- (void)keyDown:(NSEvent *)event {
extern void platform_macos_key_down(U16 keycode, NSEventModifierFlags mods);
platform_macos_key_down([event keyCode], [event modifierFlags]);
platform_macos_key_down_pw(_platformWindow, [event keyCode], [event modifierFlags]);
// Feed into text input system for character generation
[self interpretKeyEvents:@[event]];
@@ -136,112 +202,29 @@ static PlatformWindow *g_current_window = nullptr;
- (void)mouseDown:(NSEvent *)event {
(void)event;
extern void platform_macos_mouse_down();
platform_macos_mouse_down();
if (_platformWindow) _platformWindow->mouse_down_state = 1;
}
- (void)mouseUp:(NSEvent *)event {
(void)event;
extern void platform_macos_mouse_up();
platform_macos_mouse_up();
if (_platformWindow) _platformWindow->mouse_down_state = 0;
}
- (void)mouseMoved:(NSEvent *)event { (void)event; }
- (void)mouseDragged:(NSEvent *)event { (void)event; }
- (void)scrollWheel:(NSEvent *)event {
extern void platform_macos_scroll(F32 dx, F32 dy);
if (!_platformWindow) return;
F32 dy = (F32)[event scrollingDeltaY];
if ([event hasPreciseScrollingDeltas])
dy /= 40.0f; // Normalize trackpad deltas to match discrete wheel steps
platform_macos_scroll(0, dy);
_platformWindow->input.scroll_delta.y += dy;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)event { (void)event; return YES; }
@end
////////////////////////////////
// PlatformWindow struct
struct PlatformWindow {
NSWindow *ns_window;
ASmplView *view;
ASmplWindowDelegate *delegate;
B32 should_close;
S32 width;
S32 height;
S32 pending_menu_cmd;
PlatformFrameCallback frame_callback;
void *frame_callback_user_data;
PlatformInput input;
B32 prev_mouse_down;
B32 mouse_down_state;
F32 backing_scale;
};
////////////////////////////////
// C callback helpers (called from ObjC)
void platform_macos_set_should_close() {
if (g_current_window) g_current_window->should_close = true;
}
void platform_macos_handle_resize() {
if (!g_current_window) return;
NSRect frame = [g_current_window->view bounds];
F32 scale = g_current_window->backing_scale;
g_current_window->width = (S32)(frame.size.width * scale);
g_current_window->height = (S32)(frame.size.height * scale);
if (g_current_window->frame_callback)
g_current_window->frame_callback(g_current_window->frame_callback_user_data);
}
void platform_macos_insert_text(const char *utf8) {
if (!g_current_window || !utf8) return;
PlatformInput *ev = &g_current_window->input;
while (*utf8 && ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME) {
U8 c = (U8)*utf8;
if (c < 32) { utf8++; continue; }
// Handle ASCII printable range (single-byte UTF-8)
if (c < 0x80) {
ev->chars[ev->char_count++] = (U16)c;
utf8++;
} else {
// Skip multi-byte UTF-8 sequences for now (UI only handles ASCII)
if (c < 0xE0) utf8 += 2;
else if (c < 0xF0) utf8 += 3;
else utf8 += 4;
}
}
}
void platform_macos_key_down(U16 keycode, NSEventModifierFlags mods) {
if (!g_current_window) return;
PlatformInput *ev = &g_current_window->input;
U8 pkey = macos_keycode_to_pkey(keycode);
if (pkey && ev->key_count < PLATFORM_MAX_KEYS_PER_FRAME)
ev->keys[ev->key_count++] = pkey;
// Command = ctrl_held (macOS convention: Cmd+C, Cmd+V, etc.)
ev->ctrl_held = (mods & NSEventModifierFlagCommand) != 0;
ev->shift_held = (mods & NSEventModifierFlagShift) != 0;
}
void platform_macos_mouse_down() {
if (g_current_window) g_current_window->mouse_down_state = 1;
}
void platform_macos_mouse_up() {
if (g_current_window) g_current_window->mouse_down_state = 0;
}
void platform_macos_scroll(F32 dx, F32 dy) {
(void)dx;
if (g_current_window) g_current_window->input.scroll_delta.y += dy;
}
////////////////////////////////
// Menu action handler
@@ -251,9 +234,9 @@ void platform_macos_scroll(F32 dx, F32 dy) {
@implementation ASmplMenuTarget
- (void)menuAction:(id)sender {
if (!g_current_window) return;
if (!g_main_window) return;
NSMenuItem *item = (NSMenuItem *)sender;
g_current_window->pending_menu_cmd = (S32)[item tag];
g_main_window->pending_menu_cmd = (S32)[item tag];
}
@end
@@ -276,10 +259,17 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
NSRect content_rect = NSMakeRect(0, 0, desc->width, desc->height);
NSWindowStyleMask style_mask;
if (desc->style == PLATFORM_WINDOW_STYLE_POPUP) {
style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
} else {
style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
}
NSWindow *ns_window = [[NSWindow alloc]
initWithContentRect:content_rect
styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable)
styleMask:style_mask
backing:NSBackingStoreBuffered
defer:NO];
@@ -306,7 +296,19 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
window->width = (S32)(desc->width * window->backing_scale);
window->height = (S32)(desc->height * window->backing_scale);
g_current_window = window;
// Wire up per-window pointers
view->_platformWindow = window;
delegate->_platformWindow = window;
// Track main window for menu commands
if (desc->style == PLATFORM_WINDOW_STYLE_NORMAL) {
g_main_window = window;
}
// If popup, add as child of parent
if (desc->style == PLATFORM_WINDOW_STYLE_POPUP && desc->parent) {
[desc->parent->ns_window addChildWindow:ns_window ordered:NSWindowAbove];
}
[ns_window makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
@@ -317,9 +319,15 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
void platform_destroy_window(PlatformWindow *window) {
if (!window) return;
// Remove from parent if it's a child window
NSWindow *parent = [window->ns_window parentWindow];
if (parent) {
[parent removeChildWindow:window->ns_window];
}
[window->ns_window close];
if (g_current_window == window)
g_current_window = nullptr;
if (g_main_window == window)
g_main_window = nullptr;
delete window;
}
@@ -436,6 +444,10 @@ PlatformInput platform_get_input(PlatformWindow *window) {
return result;
}
B32 platform_window_should_close(PlatformWindow *window) {
return window ? window->should_close : 0;
}
F32 platform_get_dpi_scale(PlatformWindow *window) {
(void)window;
return 1.0f; // macOS handles Retina via backing scale factor, not DPI
@@ -474,3 +486,38 @@ const char *platform_clipboard_get() {
return buf[0] ? buf : nullptr;
}
S32 platform_message_box(PlatformWindow *parent, const char *title,
const char *message, PlatformMsgBoxType type) {
@autoreleasepool {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:[NSString stringWithUTF8String:title]];
[alert setInformativeText:[NSString stringWithUTF8String:message]];
switch (type) {
case PLATFORM_MSGBOX_OK:
[alert addButtonWithTitle:@"OK"];
break;
case PLATFORM_MSGBOX_OK_CANCEL:
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancel"];
break;
case PLATFORM_MSGBOX_YES_NO:
[alert addButtonWithTitle:@"Yes"];
[alert addButtonWithTitle:@"No"];
break;
}
NSModalResponse response;
if (parent && parent->ns_window) {
response = [alert runModal];
} else {
response = [alert runModal];
}
// NSAlertFirstButtonReturn = 1000, Second = 1001
if (response == NSAlertFirstButtonReturn) return 0;
if (response == NSAlertSecondButtonReturn) return 1;
return -1;
}
}