summaryrefslogtreecommitdiff
path: root/src/macos.m
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-09-25 10:59:28 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-09-25 10:59:28 +0300
commitc80331992585bfee3d65a7ba24f3a4b640c48735 (patch)
tree5eaa85a0a3a1c46bdca7cccfae0e7349e22f6d13 /src/macos.m
parent562a0d2d38c0621a296e8343270f3f1efc268156 (diff)
parent242e8231ea61278fe482020658be86c2dec0ae53 (diff)
Merge branch 'work/v1.7' into dev
Diffstat (limited to 'src/macos.m')
-rw-r--r--src/macos.m224
1 files changed, 177 insertions, 47 deletions
diff --git a/src/macos.m b/src/macos.m
index d588fa4a..53a6da00 100644
--- a/src/macos.m
+++ b/src/macos.m
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include "ui/window.h" 30#include "ui/window.h"
31 31
32#include <SDL_timer.h> 32#include <SDL_timer.h>
33#include <SDL_syswm.h>
33 34
34#import <AppKit/AppKit.h> 35#import <AppKit/AppKit.h>
35 36
@@ -51,6 +52,16 @@ static iInt2 macVer_(void) {
51 return init_I2(10, 10); 52 return init_I2(10, 10);
52} 53}
53 54
55static NSWindow *nsWindow_(SDL_Window *window) {
56 SDL_SysWMinfo wm;
57 SDL_VERSION(&wm.version);
58 if (SDL_GetWindowWMInfo(window, &wm)) {
59 return wm.info.cocoa.window;
60 }
61 iAssert(false);
62 return nil;
63}
64
54static NSString *currentSystemAppearance_(void) { 65static NSString *currentSystemAppearance_(void) {
55 /* This API does not exist on 10.13. */ 66 /* This API does not exist on 10.13. */
56 if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) { 67 if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) {
@@ -66,6 +77,14 @@ iBool shouldDefaultToMetalRenderer_MacOS(void) {
66 return ver.x > 10 || ver.y > 13;*/ 77 return ver.x > 10 || ver.y > 13;*/
67} 78}
68 79
80static void ignoreImmediateKeyDownEvents_(void) {
81 /* SDL ignores menu key equivalents so the keydown events will be posted regardless.
82 However, we shouldn't double-activate menu items when a shortcut key is used in our
83 widgets. Quite a kludge: take advantage of Window's focus-acquisition threshold to
84 ignore the immediately following key down events. */
85 get_Window()->focusGainedAt = SDL_GetTicks();
86}
87
69/*----------------------------------------------------------------------------------------------*/ 88/*----------------------------------------------------------------------------------------------*/
70 89
71@interface CommandButton : NSCustomTouchBarItem { 90@interface CommandButton : NSCustomTouchBarItem {
@@ -135,11 +154,60 @@ iBool shouldDefaultToMetalRenderer_MacOS(void) {
135 154
136/*----------------------------------------------------------------------------------------------*/ 155/*----------------------------------------------------------------------------------------------*/
137 156
157@interface MenuCommands : NSObject {
158 NSMutableDictionary<NSString *, NSString *> *commands;
159 iWidget *source;
160}
161@end
162
163@implementation MenuCommands
164
165- (id)init {
166 commands = [[NSMutableDictionary<NSString *, NSString *> alloc] init];
167 source = NULL;
168 return self;
169}
170
171- (void)setCommand:(NSString *)command forMenuItem:(NSMenuItem *)menuItem {
172 [commands setObject:command forKey:[menuItem title]];
173}
174
175- (void)setSource:(iWidget *)widget {
176 source = widget;
177}
178
179- (void)clear {
180 [commands removeAllObjects];
181}
182
183- (NSString *)commandForMenuItem:(NSMenuItem *)menuItem {
184 return [commands objectForKey:[menuItem title]];
185}
186
187- (void)postMenuItemCommand:(id)sender {
188 NSString *command = [commands objectForKey:[(NSMenuItem *)sender title]];
189 if (command) {
190 const char *cstr = [command cStringUsingEncoding:NSUTF8StringEncoding];
191 if (source) {
192 postCommand_Widget(source, "%s", cstr);
193 }
194 else {
195 postCommand_Root(NULL, cstr);
196 }
197 ignoreImmediateKeyDownEvents_();
198 }
199}
200
201@end
202
203/*----------------------------------------------------------------------------------------------*/
204
138@interface MyDelegate : NSResponder<NSApplicationDelegate, NSTouchBarDelegate> { 205@interface MyDelegate : NSResponder<NSApplicationDelegate, NSTouchBarDelegate> {
139 enum iTouchBarVariant touchBarVariant; 206 enum iTouchBarVariant touchBarVariant;
140 NSString *currentAppearanceName; 207 NSString *currentAppearanceName;
141 NSObject<NSApplicationDelegate> *sdlDelegate; 208 NSObject<NSApplicationDelegate> *sdlDelegate;
142 NSMutableDictionary<NSString *, NSString*> *menuCommands; 209 //NSMutableDictionary<NSString *, NSString*> *menuCommands;
210 MenuCommands *menuCommands;
143} 211}
144- (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl; 212- (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl;
145- (NSTouchBar *)makeTouchBar; 213- (NSTouchBar *)makeTouchBar;
@@ -154,7 +222,7 @@ iBool shouldDefaultToMetalRenderer_MacOS(void) {
154- (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl { 222- (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl {
155 [super init]; 223 [super init];
156 currentAppearanceName = nil; 224 currentAppearanceName = nil;
157 menuCommands = [[NSMutableDictionary<NSString *, NSString *> alloc] init]; 225 menuCommands = [[MenuCommands alloc] init];
158 touchBarVariant = default_TouchBarVariant; 226 touchBarVariant = default_TouchBarVariant;
159 sdlDelegate = sdl; 227 sdlDelegate = sdl;
160 return self; 228 return self;
@@ -171,6 +239,14 @@ iBool shouldDefaultToMetalRenderer_MacOS(void) {
171 self.touchBar = nil; 239 self.touchBar = nil;
172} 240}
173 241
242- (MenuCommands *)menuCommands {
243 return menuCommands;
244}
245
246- (void)postMenuItemCommand:(id)sender {
247 [menuCommands postMenuItemCommand:sender];
248}
249
174static void appearanceChanged_MacOS_(NSString *name) { 250static void appearanceChanged_MacOS_(NSString *name) {
175 const iBool isDark = [name containsString:@"Dark"]; 251 const iBool isDark = [name containsString:@"Dark"];
176 const iBool isHighContrast = [name containsString:@"HighContrast"]; 252 const iBool isHighContrast = [name containsString:@"HighContrast"];
@@ -187,10 +263,6 @@ static void appearanceChanged_MacOS_(NSString *name) {
187 } 263 }
188} 264}
189 265
190- (void)setCommand:(NSString *)command forMenuItem:(NSMenuItem *)menuItem {
191 [menuCommands setObject:command forKey:[menuItem title]];
192}
193
194- (BOOL)application:(NSApplication *)app openFile:(NSString *)filename { 266- (BOOL)application:(NSApplication *)app openFile:(NSString *)filename {
195 return [sdlDelegate application:app openFile:filename]; 267 return [sdlDelegate application:app openFile:filename];
196} 268}
@@ -247,31 +319,11 @@ static void appearanceChanged_MacOS_(NSString *name) {
247 ignoreImmediateKeyDownEvents_(); 319 ignoreImmediateKeyDownEvents_();
248} 320}
249 321
250static void ignoreImmediateKeyDownEvents_(void) {
251 /* SDL ignores menu key equivalents so the keydown events will be posted regardless.
252 However, we shouldn't double-activate menu items when a shortcut key is used in our
253 widgets. Quite a kludge: take advantage of Window's focus-acquisition threshold to
254 ignore the immediately following key down events. */
255 get_Window()->focusGainedAt = SDL_GetTicks();
256}
257
258- (void)closeTab { 322- (void)closeTab {
259 postCommand_App("tabs.close"); 323 postCommand_App("tabs.close");
260 ignoreImmediateKeyDownEvents_(); 324 ignoreImmediateKeyDownEvents_();
261} 325}
262 326
263- (NSString *)commandForItem:(NSMenuItem *)menuItem {
264 return [menuCommands objectForKey:[menuItem title]];
265}
266
267- (void)postMenuItemCommand:(id)sender {
268 NSString *command = [menuCommands objectForKey:[(NSMenuItem *)sender title]];
269 if (command) {
270 postCommand_App([command cStringUsingEncoding:NSUTF8StringEncoding]);
271 ignoreImmediateKeyDownEvents_();
272 }
273}
274
275- (void)sidebarModePressed:(id)sender { 327- (void)sidebarModePressed:(id)sender {
276 NSSegmentedControl *seg = sender; 328 NSSegmentedControl *seg = sender;
277 postCommandf_App("sidebar.mode arg:%d toggle:1", (int) [seg selectedSegment]); 329 postCommandf_App("sidebar.mode arg:%d toggle:1", (int) [seg selectedSegment]);
@@ -370,6 +422,11 @@ void setupApplication_MacOS(void) {
370 windowCloseItem.action = @selector(closeTab); 422 windowCloseItem.action = @selector(closeTab);
371} 423}
372 424
425void hideTitleBar_MacOS(iWindow *window) {
426 NSWindow *w = nsWindow_(window->win);
427 w.styleMask = 0; /* borderless */
428}
429
373void enableMenu_MacOS(const char *menuLabel, iBool enable) { 430void enableMenu_MacOS(const char *menuLabel, iBool enable) {
374 menuLabel = translateCStr_Lang(menuLabel); 431 menuLabel = translateCStr_Lang(menuLabel);
375 NSApplication *app = [NSApplication sharedApplication]; 432 NSApplication *app = [NSApplication sharedApplication];
@@ -377,7 +434,6 @@ void enableMenu_MacOS(const char *menuLabel, iBool enable) {
377 NSString *label = [NSString stringWithUTF8String:menuLabel]; 434 NSString *label = [NSString stringWithUTF8String:menuLabel];
378 NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]]; 435 NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]];
379 [menuItem setEnabled:enable]; 436 [menuItem setEnabled:enable];
380 [label release];
381} 437}
382 438
383void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) { 439void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) {
@@ -388,7 +444,7 @@ void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) {
388 NSMenu *menu = mainMenuItem.submenu; 444 NSMenu *menu = mainMenuItem.submenu;
389 if (menu) { 445 if (menu) {
390 for (NSMenuItem *menuItem in menu.itemArray) { 446 for (NSMenuItem *menuItem in menu.itemArray) {
391 NSString *command = [myDel commandForItem:menuItem]; 447 NSString *command = [[myDel menuCommands] commandForMenuItem:menuItem];
392 if (command) { 448 if (command) {
393 if (!iCmpStr([command cStringUsingEncoding:NSUTF8StringEncoding], 449 if (!iCmpStr([command cStringUsingEncoding:NSUTF8StringEncoding],
394 menuItemCommand)) { 450 menuItemCommand)) {
@@ -468,35 +524,58 @@ void removeMenu_MacOS(int atIndex) {
468 [appMenu removeItemAtIndex:atIndex]; 524 [appMenu removeItemAtIndex:atIndex];
469} 525}
470 526
471void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem *items, size_t count) { 527enum iColorId removeColorEscapes_String(iString *d) {
472 NSApplication *app = [NSApplication sharedApplication]; 528 enum iColorId color = none_ColorId;
473 MyDelegate *myDel = (MyDelegate *) app.delegate; 529 for (;;) {
474 NSMenu *appMenu = [app mainMenu]; 530 const char *esc = strchr(cstr_String(d), '\v');
475 menuLabel = translateCStr_Lang(menuLabel); 531 if (esc) {
476 NSMenuItem *mainItem = [appMenu insertItemWithTitle:[NSString stringWithUTF8String:menuLabel] 532 const char *endp;
477 action:nil 533 color = parseEscape_Color(esc, &endp);
478 keyEquivalent:@"" 534 remove_Block(&d->chars, esc - cstr_String(d), endp - esc);
479 atIndex:atIndex];
480 NSMenu *menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:menuLabel]];
481 [menu setAutoenablesItems:NO];
482 for (size_t i = 0; i < count; ++i) {
483 const char *label = translateCStr_Lang(items[i].label);
484 if (label[0] == '\v') {
485 /* Skip the formatting escape. */
486 label += 2;
487 } 535 }
536 else break;
537 }
538 return color;
539}
540
541static void makeMenuItems_(NSMenu *menu, MenuCommands *commands, const iMenuItem *items, size_t n) {
542 for (size_t i = 0; i < n && items[i].label; ++i) {
543 const char *label = translateCStr_Lang(items[i].label);
488 if (equal_CStr(label, "---")) { 544 if (equal_CStr(label, "---")) {
489 [menu addItem:[NSMenuItem separatorItem]]; 545 [menu addItem:[NSMenuItem separatorItem]];
490 } 546 }
491 else { 547 else {
492 const iBool hasCommand = (items[i].command && items[i].command[0]); 548 const iBool hasCommand = (items[i].command && items[i].command[0]);
493 NSMenuItem *item = [menu addItemWithTitle:[NSString stringWithUTF8String:label] 549 iBool isChecked = iFalse;
550 iBool isDisabled = iFalse;
551 if (startsWith_CStr(label, "###")) {
552 isChecked = iTrue;
553 label += 3;
554 }
555 else if (startsWith_CStr(label, "///")) {
556 isDisabled = iTrue;
557 label += 3;
558 }
559 iString itemTitle;
560 initCStr_String(&itemTitle, label);
561 removeIconPrefix_String(&itemTitle);
562 if (removeColorEscapes_String(&itemTitle) == uiTextCaution_ColorId) {
563// prependCStr_String(&itemTitle, "\u26a0\ufe0f ");
564 }
565 NSMenuItem *item = [menu addItemWithTitle:[NSString stringWithUTF8String:cstr_String(&itemTitle)]
494 action:(hasCommand ? @selector(postMenuItemCommand:) : nil) 566 action:(hasCommand ? @selector(postMenuItemCommand:) : nil)
495 keyEquivalent:@""]; 567 keyEquivalent:@""];
568 deinit_String(&itemTitle);
569 [item setTarget:commands];
570 if (isChecked) {
571 [item setState:NSControlStateValueOn];
572 }
573 [item setEnabled:!isDisabled];
496 int key = items[i].key; 574 int key = items[i].key;
497 int kmods = items[i].kmods; 575 int kmods = items[i].kmods;
498 if (hasCommand) { 576 if (hasCommand) {
499 [myDel setCommand:[NSString stringWithUTF8String:items[i].command] forMenuItem:item]; 577 [commands setCommand:[NSString stringWithUTF8String:items[i].command]
578 forMenuItem:item];
500 /* Bindings may have a different key. */ 579 /* Bindings may have a different key. */
501 const iBinding *bind = findCommand_Keys(items[i].command); 580 const iBinding *bind = findCommand_Keys(items[i].command);
502 if (bind && bind->id < builtIn_BindingId) { 581 if (bind && bind->id < builtIn_BindingId) {
@@ -507,6 +586,20 @@ void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem *
507 setShortcut_NSMenuItem_(item, key, kmods); 586 setShortcut_NSMenuItem_(item, key, kmods);
508 } 587 }
509 } 588 }
589}
590
591void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem *items, size_t count) {
592 NSApplication *app = [NSApplication sharedApplication];
593 MyDelegate *myDel = (MyDelegate *) app.delegate;
594 NSMenu *appMenu = [app mainMenu];
595 menuLabel = translateCStr_Lang(menuLabel);
596 NSMenuItem *mainItem = [appMenu insertItemWithTitle:[NSString stringWithUTF8String:menuLabel]
597 action:nil
598 keyEquivalent:@""
599 atIndex:atIndex];
600 NSMenu *menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:menuLabel]];
601 [menu setAutoenablesItems:NO];
602 makeMenuItems_(menu, [myDel menuCommands], items, count);
510 [mainItem setSubmenu:menu]; 603 [mainItem setSubmenu:menu];
511 [menu release]; 604 [menu release];
512} 605}
@@ -527,7 +620,7 @@ void handleCommand_MacOS(const char *cmd) {
527 if (menu) { 620 if (menu) {
528 int itemIndex = 0; 621 int itemIndex = 0;
529 for (NSMenuItem *menuItem in menu.itemArray) { 622 for (NSMenuItem *menuItem in menu.itemArray) {
530 NSString *command = [myDel commandForItem:menuItem]; 623 NSString *command = [[myDel menuCommands] commandForMenuItem:menuItem];
531 if (!command && mainIndex == 6 && itemIndex == 0) { 624 if (!command && mainIndex == 6 && itemIndex == 0) {
532 /* Window > Close */ 625 /* Window > Close */
533 command = @"tabs.close"; 626 command = @"tabs.close";
@@ -553,3 +646,40 @@ void handleCommand_MacOS(const char *cmd) {
553void log_MacOS(const char *msg) { 646void log_MacOS(const char *msg) {
554 NSLog(@"%s", msg); 647 NSLog(@"%s", msg);
555} 648}
649
650void showPopupMenu_MacOS(iWidget *source, iInt2 windowCoord, const iMenuItem *items, size_t n) {
651 NSMenu * menu = [[NSMenu alloc] init];
652 MenuCommands *menuCommands = [[MenuCommands alloc] init];
653 iWindow * window = as_Window(mainWindow_App());
654 NSWindow * nsWindow = nsWindow_(window->win);
655 /* View coordinates are flipped. */
656 iBool isCentered = iFalse;
657 if (isEqual_I2(windowCoord, zero_I2())) {
658 windowCoord = divi_I2(window->size, 2);
659 isCentered = iTrue;
660 }
661 windowCoord.y = window->size.y - windowCoord.y;
662 windowCoord = divf_I2(windowCoord, window->pixelRatio);
663 NSPoint screenPoint = [nsWindow convertPointToScreen:(CGPoint){ windowCoord.x, windowCoord.y }];
664 makeMenuItems_(menu, menuCommands, items, n);
665 [menuCommands setSource:source];
666 if (isCentered) {
667 NSSize menuSize = [menu size];
668 screenPoint.x -= menuSize.width / 2;
669 screenPoint.y += menuSize.height / 2;
670 }
671 [menu setAutoenablesItems:NO];
672 [menu popUpMenuPositioningItem:nil atLocation:screenPoint inView:nil];
673 [menu release];
674 [menuCommands release];
675 /* The right mouse button has now been released so let SDL know about it. The button up event
676 was consumed by the popup menu so it got never passed to SDL. */
677 SEL sel = NSSelectorFromString(@"syncMouseButtonState"); /* custom method */
678 if ([[nsWindow delegate] respondsToSelector:sel]) {
679 NSInvocation *call = [NSInvocation invocationWithMethodSignature:
680 [NSMethodSignature signatureWithObjCTypes:"v@:"]];
681 [call setSelector:sel];
682 [call invokeWithTarget:[nsWindow delegate]];
683 }
684}
685