Use platform windows for windowing instead of custom draggable window system
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user