OpenTTD Source 20260421-master-gc2fbc6fdeb
cocoa_wnd.mm
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
19
20#if defined(WITH_COCOA) || defined(DOXYGEN_API)
21
22#include "../../stdafx.h"
24
26#include "../../openttd.h"
27#include "../../debug.h"
28#include "cocoa_v.h"
29#include "cocoa_wnd.h"
30#include "../../settings_type.h"
31#include "../../string_func.h"
32#include "../../gfx_func.h"
33#include "../../window_func.h"
34#include "../../window_gui.h"
35#include "../../spritecache.h"
36#include "../../textbuf_type.h"
37#include "../../toolbar_gui.h"
38#include "../../core/utf8.hpp"
39
40#include "../../table/sprites.h"
41
42/* Table data for key mapping. */
43#include "cocoa_keys.h"
44
47 NSTouchBarItemIdentifier key;
49 MainToolbarHotkeys hotkey;
50 NSString *fallback_text;
51};
52
57static const std::array<TouchBarButton, 9> _touchbar_buttons{{
58 { @"openttd.pause", SPR_IMG_PAUSE, MTHK_PAUSE, @"Pause" },
59 { @"openttd.fastforward", SPR_IMG_FASTFORWARD, MTHK_FASTFORWARD, @"Fast Forward" },
60 { @"openttd.zoom_in", SPR_IMG_ZOOMIN, MTHK_ZOOM_IN, @"Zoom In" },
61 { @"openttd.zoom_out", SPR_IMG_ZOOMOUT, MTHK_ZOOM_OUT, @"Zoom Out" },
62 { @"openttd.build_rail", SPR_IMG_BUILDRAIL, MTHK_BUILD_RAIL, @"Rail" },
63 { @"openttd.build_road", SPR_IMG_BUILDROAD, MTHK_BUILD_ROAD, @"Road" },
64 { @"openttd.build_tram", SPR_IMG_BUILDTRAMS, MTHK_BUILD_TRAM, @"Tram" },
65 { @"openttd.build_docks", SPR_IMG_BUILDWATER, MTHK_BUILD_DOCKS, @"Docks" },
66 { @"openttd.build_airport", SPR_IMG_BUILDAIR, MTHK_BUILD_AIRPORT, @"Airport" }
67}};
68
70
71@interface OTTDMain : NSObject <NSApplicationDelegate>
72@end
73
75NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine";
76
78
79static bool _cocoa_video_dialog = false;
81
82
88static NSUInteger CountUtf16Units(std::string_view str)
89{
90 NSUInteger i = 0;
91 for (char32_t c : Utf8View(str)) {
92 i += c < 0x10000 ? 1 : 2; // Watch for surrogate pairs.
93 }
94 return i;
95}
96
103static size_t Utf8AdvanceByUtf16Units(std::string_view str, NSUInteger count)
104{
105 Utf8View view(str);
106 auto it = view.begin();
107 const auto end = view.end();
108 for (NSUInteger i = 0; it != end && i < count; ++it) {
109 i += *it < 0x10000 ? 1 : 2; // Watch for surrogate pairs.
110 }
111 return it.GetByteOffset();
112}
113
119static std::vector<char32_t> NSStringToUTF32(NSString *s)
120{
121 std::vector<char32_t> unicode_str;
122
123 unichar lead = 0;
124 for (NSUInteger i = 0; i < s.length; i++) {
125 unichar c = [ s characterAtIndex:i ];
126 if (Utf16IsLeadSurrogate(c)) {
127 lead = c;
128 continue;
129 } else if (Utf16IsTrailSurrogate(c)) {
130 if (lead != 0) unicode_str.push_back(Utf16DecodeSurrogate(lead, c));
131 } else {
132 unicode_str.push_back(c);
133 }
134 }
135
136 return unicode_str;
137}
138
143static void CGDataFreeCallback(void *, const void *data, size_t)
144{
145 delete[] (const uint32_t *)data;
146}
147
154static NSImage *NSImageFromSprite(SpriteID sprite_id, ZoomLevel zoom)
155{
156 if (!SpriteExists(sprite_id)) return nullptr;
157
158 /* Fetch the sprite and create a new bitmap */
159 Dimension dim = GetSpriteSize(sprite_id, nullptr, zoom);
160 std::unique_ptr<uint32_t[]> buffer = DrawSpriteToRgbaBuffer(sprite_id, zoom);
161 if (!buffer) return nullptr; // Failed to blit sprite for some reason.
162
163 CFAutoRelease<CGDataProvider> data(CGDataProviderCreateWithData(nullptr, buffer.release(), dim.width * dim.height * 4, &CGDataFreeCallback));
164 if (!data) return nullptr;
165
166 CGBitmapInfo info = kCGImageAlphaFirst | kCGBitmapByteOrder32Host;
167 CFAutoRelease<CGColorSpaceRef> colour_space(CGColorSpaceCreateWithName(kCGColorSpaceSRGB));
168 CFAutoRelease<CGImage> bitmap(CGImageCreate(dim.width, dim.height, 8, 32, dim.width * 4, colour_space.get(), info, data.get(), nullptr, false, kCGRenderingIntentDefault));
169 if (!bitmap) return nullptr;
170
171 return [ [ [ NSImage alloc ] initWithCGImage:bitmap.get() size:NSZeroSize ] autorelease ];
172}
173
174
178@implementation OTTDMain
183{
184 [ NSApp stop:self ];
185
186 /* Send an empty event to return from the run loop. Without that, application is stuck waiting for an event. */
187 NSEventType type = NSEventTypeApplicationDefined;
188 NSEvent *event = [ NSEvent otherEventWithType:type location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0.0 windowNumber:0 context:nil subtype:0 data1:0 data2:0 ];
189 [ NSApp postEvent:event atStart:YES ];
190}
191
196- (void)launchGameEngine: (NSNotification*) note
197{
198 auto *drv = static_cast<VideoDriver_Cocoa *>(VideoDriver::GetInstance());
199
200 /* Setup cursor for the current _game_mode. */
201 NSEvent *e = [ [ NSEvent alloc ] init ];
202 [ drv->cocoaview cursorUpdate:e ];
203 [ e release ];
204
205 /* Hand off to main application code. */
206 drv->MainLoopReal();
207
208 /* We are done, thank you for playing. */
209 [ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ];
210}
211
216- (void) applicationDidFinishLaunching: (NSNotification*) note
217{
218 /* Add a notification observer so we can restart the game loop later on if necessary. */
219 [ [ NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(launchGameEngine:) name:OTTDMainLaunchGameEngine object:nil ];
220
221 /* Start game loop. */
222 [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ];
223}
224
230- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender
231{
232 HandleExitGameRequest();
233
234 return NSTerminateCancel;
235}
236
241{
242 [ [ NSNotificationCenter defaultCenter ] removeObserver:self ];
243}
244
254- (BOOL)applicationSupportsSecureRestorableState:(NSApplication*) sender
255{
256 return YES;
257}
258@end
259
264{
265 NSString *appName = @"OpenTTD";
266 NSMenu *appleMenu = [ [ NSMenu alloc ] initWithTitle:appName ];
267
268 /* Add menu items */
269 NSString *title = [ @"About " stringByAppendingString:appName ];
270 [ appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@"" ];
271
272 [ appleMenu addItem:[ NSMenuItem separatorItem ] ];
273
274 title = [ @"Hide " stringByAppendingString:appName ];
275 [ appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h" ];
276
277 NSMenuItem *menuItem = [ appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h" ];
278 [ menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand) ];
279
280 [ appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@"" ];
281
282 [ appleMenu addItem:[ NSMenuItem separatorItem ] ];
283
284 title = [ @"Quit " stringByAppendingString:appName ];
285 [ appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q" ];
286
287 /* Put menu into the menubar */
288 menuItem = [ [ NSMenuItem alloc ] initWithTitle:@"" action:nil keyEquivalent:@"" ];
289 [ menuItem setSubmenu:appleMenu ];
290 [ [ NSApp mainMenu ] addItem:menuItem ];
291
292 /* Tell the application object that this is now the application menu.
293 * This interesting Objective-C construct is used because not all SDK
294 * versions define this method publicly. */
295 if ([ NSApp respondsToSelector:@selector(setAppleMenu:) ]) {
296 [ NSApp performSelector:@selector(setAppleMenu:) withObject:appleMenu ];
297 }
298
299 /* Finally give up our references to the objects */
300 [ appleMenu release ];
301 [ menuItem release ];
302}
303
307static void setupWindowMenu()
308{
309 NSMenu *windowMenu = [ [ NSMenu alloc ] initWithTitle:@"Window" ];
310
311 /* "Minimize" item */
312 [ windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m" ];
313
314 /* Put menu into the menubar */
315 NSMenuItem *menuItem = [ [ NSMenuItem alloc ] initWithTitle:@"Window" action:nil keyEquivalent:@"" ];
316 [ menuItem setSubmenu:windowMenu ];
317 [ [ NSApp mainMenu ] addItem:menuItem ];
318
319 /* The OS will change the name of this menu item automatically */
320 [ windowMenu addItemWithTitle:@"Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"^f" ];
321
322 /* Tell the application object that this is now the window menu */
323 [ NSApp setWindowsMenu:windowMenu ];
324
325 /* Finally give up our references to the objects */
326 [ windowMenu release ];
327 [ menuItem release ];
328}
329
335{
336 ProcessSerialNumber psn = { 0, kCurrentProcess };
337
338 /* Ensure the application object is initialised */
339 [ NSApplication sharedApplication ];
340
341 /* Tell the dock about us */
342 OSStatus returnCode = TransformProcessType(&psn, kProcessTransformToForegroundApplication);
343 if (returnCode != 0) Debug(driver, 0, "Could not change to foreground application. Error {}", (int)returnCode);
344
345 /* Disable the system-wide tab feature as we only have one window. */
346 if ([ NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:) ]) {
347 /* We use nil instead of NO as withObject requires an id. */
348 [ NSWindow performSelector:@selector(setAllowsAutomaticWindowTabbing:) withObject:nil];
349 }
350
351 /* Become the front process, important when start from the command line. */
352 [ [ NSApplication sharedApplication ] setActivationPolicy:NSApplicationActivationPolicyRegular ];
353 [ [ NSApplication sharedApplication ] activateIgnoringOtherApps:YES ];
354
355 /* Set up the menubar */
356 [ NSApp setMainMenu:[ [ NSMenu alloc ] init ] ];
359
360 /* Create OTTDMain and make it the app delegate */
361 _ottd_main = [ [ OTTDMain alloc ] init ];
362 [ NSApp setDelegate:_ottd_main ];
363
364 return true;
365}
366
371{
373 [ _ottd_main release ];
374}
375
385void CocoaDialog(std::string_view title, std::string_view message, std::string_view buttonLabel)
386{
387 _cocoa_video_dialog = true;
388
389 bool wasstarted = _cocoa_video_started;
390 if (VideoDriver::GetInstance() == nullptr) {
391 CocoaSetupApplication(); // Setup application before showing dialog
392 } else if (!_cocoa_video_started && VideoDriver::GetInstance()->Start({}).has_value()) {
393 fmt::print(stderr, "{}: {}\n", title, message);
394 return;
395 }
396
397 @autoreleasepool {
398 NSAlert *alert = [ [ NSAlert alloc ] init ];
399 [ alert setAlertStyle: NSAlertStyleCritical ];
400 [ alert setMessageText:[ [ NSString alloc ] initWithBytes:title.data() length:title.size() encoding:NSUTF8StringEncoding ] ];
401 [ alert setInformativeText:[ [ NSString alloc ] initWithBytes:message.data() length:message.size() encoding:NSUTF8StringEncoding ] ];
402 [ alert addButtonWithTitle: [ [ NSString alloc ] initWithBytes:buttonLabel.data() length:buttonLabel.size() encoding:NSUTF8StringEncoding ] ];
403 [ alert runModal ];
404 [ alert release ];
405 }
406
407 if (!wasstarted && VideoDriver::GetInstance() != nullptr) VideoDriver::GetInstance()->Stop();
408
409 _cocoa_video_dialog = false;
410}
411
412
416@implementation NSCursor (OTTD_CocoaCursor)
421+ (NSCursor *) clearCocoaCursor
422{
423 /* RAW 16x16 transparent GIF */
424 unsigned char clearGIFBytes[] = {
425 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, 0x00,
426 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00,
427 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00,
428 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4,
429 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B};
430 NSData *clearGIFData = [ NSData dataWithBytesNoCopy:&clearGIFBytes[0] length:55 freeWhenDone:NO ];
431 NSImage *clearImg = [ [ NSImage alloc ] initWithData:clearGIFData ];
432 return [ [ NSCursor alloc ] initWithImage:clearImg hotSpot:NSMakePoint(0.0,0.0) ];
433}
434@end
435
436@implementation OTTD_CocoaWindow {
437 VideoDriver_Cocoa *driver;
439}
440
450- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv
451{
452 if (self = [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]) {
453 self->driver = drv;
454 self->touchbar_created = false;
455
456 [ self setContentMinSize:NSMakeSize(64.0f, 64.0f) ];
457
458 std::string caption = VideoDriver::GetCaption();
459 NSString *nsscaption = [ [ NSString alloc ] initWithBytes:caption.data() length:caption.size() encoding:NSUTF8StringEncoding ];
460 [ self setTitle:nsscaption ];
461 [ self setMiniwindowTitle:nsscaption ];
462 [ nsscaption release ];
463 }
464
465 return self;
466}
467
473- (void)setFrame:(NSRect)frameRect display:(BOOL)flag
474{
475 [ super setFrame:frameRect display:flag ];
476
477 driver->AllocateBackingStore();
478}
479
480- (void)touchBarButtonAction:(id)sender
481{
482 NSButton *btn = (NSButton *)sender;
483 if (auto item = std::ranges::find(_touchbar_buttons, (NSTouchBarItemIdentifier)btn.identifier, &TouchBarButton::key); item != _touchbar_buttons.end()) {
484 HandleToolbarHotkey(item->hotkey);
485 }
486}
487
488- (nullable NSTouchBar *)makeTouchBar
489{
490 /* Make button identifier array. */
491 NSMutableArray<NSTouchBarItemIdentifier> *button_ids = [ [ NSMutableArray alloc ] init ];
492 for (const auto &button : _touchbar_buttons) {
493 [ button_ids addObject:button.key ];
494 }
495 [ button_ids addObject:NSTouchBarItemIdentifierOtherItemsProxy ];
496
497 NSTouchBar *bar = [ [ NSTouchBar alloc ] init ];
498 bar.delegate = self;
499 bar.defaultItemIdentifiers = button_ids;
500 [ button_ids release ];
501
502 self->touchbar_created = true;
503
504 return bar;
505}
506
507- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
508{
509 auto item = std::ranges::find(_touchbar_buttons, identifier, &TouchBarButton::key);
510 assert(item != _touchbar_buttons.end());
511
512 NSButton *button = [ NSButton buttonWithTitle:item->fallback_text target:self action:@selector(touchBarButtonAction:) ];
513 button.identifier = identifier;
514 button.imageScaling = NSImageScaleProportionallyDown;
515
516 NSCustomTouchBarItem *tb_item = [ [ NSCustomTouchBarItem alloc] initWithIdentifier:identifier ];
517 tb_item.view = button;
518 return tb_item;
519}
520
521- (void)refreshSystemSprites
522{
523 if (!self->touchbar_created || ![ self respondsToSelector:@selector(touchBar) ] || self.touchBar == nil) return;
524
525 /* Re-create button images from OTTD sprites. */
526 for (NSTouchBarItemIdentifier ident in self.touchBar.itemIdentifiers) {
527 auto item = std::ranges::find(_touchbar_buttons, ident, &TouchBarButton::key);
528 if (item == _touchbar_buttons.end()) continue;
529
530 NSCustomTouchBarItem *tb_item = [ self.touchBar itemForIdentifier:ident ];
531 NSButton *button = tb_item.view;
532
533 NSImage *image = NSImageFromSprite(item->sprite, _settings_client.gui.zoom_min);
534 if (image != nil) {
535 /* Human Interface Guidelines: Maximum touch bar glyph size 22 pt. */
536 CGFloat max_dim = std::max(image.size.width, image.size.height);
537 if (max_dim > 0.0) {
538 CGFloat scale = 22.0 / max_dim;
539 image.size = NSMakeSize(image.size.width * scale, image.size.height * scale);
540 }
541
542 button.image = image;
543 button.imagePosition = NSImageOnly;
544 } else {
545 button.image = nil;
546 button.imagePosition = NSNoImage;
547 }
548 }
549}
550
551@end
552
553@implementation OTTD_CocoaView {
554 float _current_magnification;
555 NSUInteger _current_mods;
558}
559
560- (instancetype)initWithFrame:(NSRect)frameRect
561{
562 if (self = [ super initWithFrame:frameRect ]) {
563 self->_use_hidpi = _allow_hidpi_window && [ self respondsToSelector:@selector(convertRectToBacking:) ] && [ self respondsToSelector:@selector(convertRectFromBacking:) ];
564 }
565 return self;
566}
567
568- (NSRect)getRealRect:(NSRect)rect
569{
570 return _use_hidpi ? [ self convertRectToBacking:rect ] : rect;
571}
572
573- (NSRect)getVirtualRect:(NSRect)rect
574{
575 return _use_hidpi ? [ self convertRectFromBacking:rect ] : rect;
576}
577
578- (CGFloat)getContentsScale
579{
580 return _use_hidpi && self.window != nil ? [ self.window backingScaleFactor ] : 1.0f;
581}
582
588{
589 return YES;
590}
591
592- (void)setNeedsDisplayInRect:(NSRect)invalidRect
593{
594 /* Drawing is handled by our sub-views. Just pass it along. */
595 for ( NSView *v in [ self subviews ]) {
596 [ v setNeedsDisplayInRect:[ v convertRect:invalidRect fromView:self ] ];
597 }
598}
599
604- (void)cursorUpdate:(NSEvent *)event
605{
606 [ (_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) set ];
607}
608
609- (void)viewWillMoveToWindow:(NSWindow *)win
610{
611 for (NSTrackingArea *a in [ self trackingAreas ]) {
612 [ self removeTrackingArea:a ];
613 }
614}
615
616- (void)viewDidMoveToWindow
617{
618 /* Install mouse tracking area. */
619 NSTrackingAreaOptions track_opt = NSTrackingInVisibleRect | NSTrackingActiveInActiveApp | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingCursorUpdate;
620 NSTrackingArea *track = [ [ NSTrackingArea alloc ] initWithRect:[ self bounds ] options:track_opt owner:self userInfo:nil ];
621 [ self addTrackingArea:track ];
622 [ track release ];
623}
628- (void)mouseEntered:(NSEvent *)theEvent
629{
630 _cursor.in_window = true;
631}
632
636- (void)mouseExited:(NSEvent *)theEvent
637{
638 if ([ self window ] != nil) UndrawMouseCursor();
639 _cursor.in_window = false;
640}
641
647- (NSPoint)mousePositionFromEvent:(NSEvent *)e
648{
649 NSPoint pt = e.locationInWindow;
650 if ([ e window ] == nil) pt = [ self.window convertRectFromScreen:NSMakeRect(pt.x, pt.y, 0, 0) ].origin;
651 pt = [ self convertPoint:pt fromView:nil ];
652
653 return [ self getRealRect:NSMakeRect(pt.x, self.bounds.size.height - pt.y, 0, 0) ].origin;
654}
655
660- (void)internalMouseMoveEvent:(NSEvent *)event
661{
662 if (_cursor.fix_at) {
663 _cursor.UpdateCursorPositionRelative(event.deltaX * self.getContentsScale, event.deltaY * self.getContentsScale);
664 } else {
665 NSPoint pt = [ self mousePositionFromEvent:event ];
666 _cursor.UpdateCursorPosition(pt.x, pt.y);
667 }
668
670}
671
676{
677 bool cur_fix = _cursor.fix_at;
679
680 /* Cursor fix mode was changed, synchronize with OS. */
681 if (cur_fix != _cursor.fix_at) CGAssociateMouseAndMouseCursorPosition(!_cursor.fix_at);
682}
683
689- (BOOL)emulateRightButton:(NSEvent *)event
690{
691 uint32_t keymask = 0;
692 if (_settings_client.gui.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSEventModifierFlagCommand;
693 if (_settings_client.gui.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSEventModifierFlagControl;
694
695 return (event.modifierFlags & keymask) != 0;
696}
697
702- (void)mouseMoved:(NSEvent *)event
703{
704 [ self internalMouseMoveEvent:event ];
705}
706
711- (void)mouseDragged:(NSEvent *)event
712{
713 [ self internalMouseMoveEvent:event ];
714}
715
720- (void)mouseDown:(NSEvent *)event
721{
722 if ([ self emulateRightButton:event ]) {
723 self->_emulated_down = true;
724 [ self rightMouseDown:event ];
725 } else {
726 _left_button_down = true;
728 }
729}
730
735- (void)mouseUp:(NSEvent *)event
736{
737 if (self->_emulated_down) {
738 self->_emulated_down = false;
739 [ self rightMouseUp:event ];
740 } else {
741 _left_button_down = false;
742 _left_button_clicked = false;
744 }
745}
746
751- (void)rightMouseDragged:(NSEvent *)event
752{
753 [ self internalMouseMoveEvent:event ];
754}
755
760- (void)rightMouseDown:(NSEvent *)event
761{
762 _right_button_down = true;
765}
766
771- (void)rightMouseUp:(NSEvent *)event
772{
773 _right_button_down = false;
775}
776
781- (void)scrollWheel:(NSEvent *)event
782{
783 if ([ event deltaY ] > 0.0) { /* Scroll up */
784 _cursor.wheel--;
785 } else if ([ event deltaY ] < 0.0) { /* Scroll down */
786 _cursor.wheel++;
787 } /* else: deltaY was 0.0 and we don't want to do anything */
788
789 /* Update the scroll count for 2D scrolling */
790 CGFloat deltaX;
791 CGFloat deltaY;
792
793 /* Use precise scrolling-specific deltas if they're supported. */
794 if ([ event respondsToSelector:@selector(hasPreciseScrollingDeltas) ]) {
795 /* No precise deltas indicates a scroll wheel is being used, so we don't want 2D scrolling. */
796 if (![ event hasPreciseScrollingDeltas ]) return;
797
798 deltaX = [ event scrollingDeltaX ] * 0.5f;
799 deltaY = [ event scrollingDeltaY ] * 0.5f;
800 } else {
801 deltaX = [ event deltaX ] * 5;
802 deltaY = [ event deltaY ] * 5;
803 }
804
805 _cursor.h_wheel -= static_cast<float>(deltaX * _settings_client.gui.scrollwheel_multiplier);
806 _cursor.v_wheel -= static_cast<float>(deltaY * _settings_client.gui.scrollwheel_multiplier);
807 _cursor.wheel_moved = true;
808}
809
814- (void)magnifyWithEvent:(NSEvent *)event
815{
816 /* Pinch open or close gesture. */
817 self->_current_magnification += [ event magnification ] * 5.0f;
818
819 while (self->_current_magnification >= 1.0f) {
820 self->_current_magnification -= 1.0f;
821 _cursor.wheel--;
823 }
824 while (self->_current_magnification <= -1.0f) {
825 self->_current_magnification += 1.0f;
826 _cursor.wheel++;
828 }
829}
830
835- (void)endGestureWithEvent:(NSEvent *)event
836{
837 /* Gesture ended. */
838 self->_current_magnification = 0.0f;
839}
840
841
850- (BOOL)internalHandleKeycode:(unsigned short)keycode unicode:(char32_t)unicode pressed:(BOOL)down modifiers:(NSUInteger)modifiers
851{
852 switch (keycode) {
853 case QZ_UP: SB(_dirkeys, 1, 1, down); break;
854 case QZ_DOWN: SB(_dirkeys, 3, 1, down); break;
855 case QZ_LEFT: SB(_dirkeys, 0, 1, down); break;
856 case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break;
857
858 case QZ_TAB:
859 _tab_is_down = down;
860 if (down && EditBoxInGlobalFocus()) {
861 HandleKeypress(WKC_TAB, unicode);
862 }
863 break;
864
865 case QZ_RETURN:
866 case QZ_f:
867 if (down && (modifiers & NSEventModifierFlagCommand)) {
869 }
870 break;
871
872 case QZ_v:
873 if (down && EditBoxInGlobalFocus() && (modifiers & (NSEventModifierFlagCommand | NSEventModifierFlagControl))) {
874 HandleKeypress(WKC_CTRL | 'V', unicode);
875 }
876 break;
877 case QZ_u:
878 if (down && EditBoxInGlobalFocus() && (modifiers & (NSEventModifierFlagCommand | NSEventModifierFlagControl))) {
879 HandleKeypress(WKC_CTRL | 'U', unicode);
880 }
881 break;
882 }
883
884 BOOL interpret_keys = YES;
885 if (down) {
886 /* Map keycode to OTTD code. */
887 auto vk = std::ranges::find(_vk_mapping, keycode, &CocoaVkMapping::vk_from);
888 uint32_t pressed_key = vk != std::end(_vk_mapping) ? vk->map_to : 0;
889
890 if (modifiers & NSEventModifierFlagShift) pressed_key |= WKC_SHIFT;
891 if (modifiers & NSEventModifierFlagControl) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META);
892 if (modifiers & NSEventModifierFlagOption) pressed_key |= WKC_ALT;
893 if (modifiers & NSEventModifierFlagCommand) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL);
894
895 static bool console = false;
896
897 /* The second backquote may have a character, which we don't want to interpret. */
898 if (pressed_key == WKC_BACKQUOTE && (console || unicode == 0)) {
899 if (!console) {
900 /* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */
901 console = true;
902 return YES;
903 } else {
904 /* Second backquote, don't interpret as text input. */
905 interpret_keys = NO;
906 }
907 }
908 console = false;
909
910 /* Don't handle normal characters if an edit box has the focus. */
911 if (!EditBoxInGlobalFocus() || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) {
912 HandleKeypress(pressed_key, unicode);
913 }
914 Debug(driver, 3, "cocoa_v: QZ_KeyEvent: {:x} ({:x}), down, mapping: {:x}", keycode, (int)unicode, pressed_key);
915 } else {
916 Debug(driver, 3, "cocoa_v: QZ_KeyEvent: {:x} ({:x}), up", keycode, (int)unicode);
917 }
918
919 return interpret_keys;
920}
921
926- (void)keyDown:(NSEvent *)event
927{
928 /* Quit, hide and minimize */
929 switch (event.keyCode) {
930 case QZ_q:
931 case QZ_h:
932 case QZ_m:
933 if (event.modifierFlags & NSEventModifierFlagCommand) {
934 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
935 }
936 break;
937 }
938
939 /* Convert UTF-16 characters to UCS-4 chars. */
940 std::vector<char32_t> unicode_str = NSStringToUTF32([ event characters ]);
941 if (unicode_str.empty()) unicode_str.push_back(0);
942
943 if (EditBoxInGlobalFocus()) {
944 if ([ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ]) {
945 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
946 }
947 } else {
948 [ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ];
949 for (size_t i = 1; i < unicode_str.size(); i++) {
950 [ self internalHandleKeycode:0 unicode:unicode_str[i] pressed:YES modifiers:event.modifierFlags ];
951 }
952 }
953}
954
959- (void)keyUp:(NSEvent *)event
960{
961 /* Quit, hide and minimize */
962 switch (event.keyCode) {
963 case QZ_q:
964 case QZ_h:
965 case QZ_m:
966 if (event.modifierFlags & NSEventModifierFlagCommand) {
967 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
968 }
969 break;
970 }
971
972 /* Convert UTF-16 characters to UCS-4 chars. */
973 std::vector<char32_t> unicode_str = NSStringToUTF32([ event characters ]);
974 if (unicode_str.empty()) unicode_str.push_back(0);
975
976 [ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:NO modifiers:event.modifierFlags ];
977}
978
983- (void)flagsChanged:(NSEvent *)event
984{
985 const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA };
986
987 NSUInteger newMods = event.modifierFlags;
988 if (self->_current_mods == newMods) return;
989
990 /* Iterate through the bits, testing each against the current modifiers */
991 for (unsigned int i = 0, bit = NSEventModifierFlagCapsLock; bit <= NSEventModifierFlagCommand; bit <<= 1, ++i) {
992 unsigned int currentMask, newMask;
993
994 currentMask = self->_current_mods & bit;
995 newMask = newMods & bit;
996
997 if (currentMask && currentMask != newMask) { // modifier up event
998 [ self internalHandleKeycode:mapping[i] unicode:0 pressed:NO modifiers:newMods ];
999 } else if (newMask && currentMask != newMask) { // modifier down event
1000 [ self internalHandleKeycode:mapping[i] unicode:0 pressed:YES modifiers:newMods ];
1001 }
1002 }
1003
1004 _current_mods = newMods;
1005}
1006
1007
1013- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
1014{
1015 if (!EditBoxInGlobalFocus()) return;
1016
1017 NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString;
1018
1019 std::optional<size_t> insert_point;
1020 std::optional<size_t> replace_range;
1021 if (replacementRange.location != NSNotFound) {
1022 /* Calculate the part to be replaced. */
1023 std::string_view focused_text{_focused_window->GetFocusedTextbuf()->GetText()};
1024 insert_point = Utf8AdvanceByUtf16Units(focused_text, replacementRange.location);
1025 replace_range = *insert_point + Utf8AdvanceByUtf16Units(focused_text.substr(*insert_point), replacementRange.length);
1026 }
1027
1028 HandleTextInput({}, true);
1029 HandleTextInput([ s UTF8String ], false, std::nullopt, insert_point, replace_range);
1030}
1031
1036- (void)insertText:(id)aString
1037{
1038 [ self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0) ];
1039}
1040
1047- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
1048{
1049 if (!EditBoxInGlobalFocus()) return;
1050
1051 NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString;
1052
1053 const char *utf8 = [ s UTF8String ];
1054 if (utf8 != nullptr) {
1055 std::optional<size_t> insert_point;
1056 std::optional<size_t> replace_range;
1057 if (replacementRange.location != NSNotFound) {
1058 /* Calculate the part to be replaced. */
1059 NSRange marked = [ self markedRange ];
1060 std::string_view focused_text{_focused_window->GetFocusedTextbuf()->GetText()};
1061 insert_point = Utf8AdvanceByUtf16Units(focused_text, replacementRange.location + (marked.location != NSNotFound ? marked.location : 0u));
1062 replace_range = *insert_point + Utf8AdvanceByUtf16Units(focused_text.substr(*insert_point), replacementRange.length);
1063 }
1064
1065 /* Convert caret index into a pointer in the UTF-8 string. */
1066 size_t selection = Utf8AdvanceByUtf16Units(utf8, selRange.location);
1067
1068 HandleTextInput(utf8, true, selection, insert_point, replace_range);
1069 }
1070}
1071
1077- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange
1078{
1079 [ self setMarkedText:aString selectedRange:selRange replacementRange:NSMakeRange(NSNotFound, 0) ];
1080}
1081
1084{
1085 HandleTextInput({}, true);
1086}
1087
1093{
1094 if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0);
1095
1096 const Textbuf *text_buf = _focused_window->GetFocusedTextbuf();
1097 std::string_view text = text_buf->GetText();
1098 NSUInteger start = CountUtf16Units(text.substr(0, text_buf->caretpos));
1099 return NSMakeRange(start, 0);
1100}
1101
1106- (NSRange)markedRange
1107{
1108 if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0);
1109
1110 const Textbuf *text_buf = _focused_window->GetFocusedTextbuf();
1111 if (text_buf->markend != 0) {
1112 std::string_view text = text_buf->GetText();
1113 NSUInteger start = CountUtf16Units(text.substr(0, text_buf->markpos));
1114 NSUInteger len = CountUtf16Units(text.substr(text_buf->markpos, text_buf->markend - text_buf->markpos));
1115
1116 return NSMakeRange(start, len);
1117 }
1118
1119 return NSMakeRange(NSNotFound, 0);
1120}
1121
1127{
1128 if (!EditBoxInGlobalFocus()) return NO;
1129
1130 return _focused_window->GetFocusedTextbuf()->markend != 0;
1131}
1132
1139- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange
1140{
1141 if (!EditBoxInGlobalFocus()) return nil;
1142
1143 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1144 NSString *s = [ [ NSString alloc] initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding ];
1145 NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange);
1146
1147 if (actualRange != nullptr) *actualRange = valid_range;
1148 if (valid_range.length == 0) return nil;
1149
1150 return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ];
1151}
1152
1158- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange
1159{
1160 return [ self attributedSubstringForProposedRange:theRange actualRange:nil ];
1161}
1162
1167- (NSAttributedString *)attributedString
1168{
1169 if (!EditBoxInGlobalFocus()) return [ [ [ NSAttributedString alloc ] initWithString:@"" ] autorelease ];
1170
1171 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1172 return [ [ [ NSAttributedString alloc ] initWithString:[ [ NSString alloc ] initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding ] ] autorelease ];
1173}
1174
1180- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
1181{
1182 if (!EditBoxInGlobalFocus()) return NSNotFound;
1183
1184 NSPoint view_pt = [ self convertRect:[ [ self window ] convertRectFromScreen:NSMakeRect(thePoint.x, thePoint.y, 0, 0) ] fromView:nil ].origin;
1185
1186 Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y };
1187
1188 auto index = _focused_window->GetTextCharacterAtPosition(pt);
1189 if (index == -1) return NSNotFound;
1190
1191 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1192 return CountUtf16Units(text.substr(0, index));
1193}
1194
1200- (NSRect)firstRectForCharacterRange:(NSRange)aRange
1201{
1202 if (!EditBoxInGlobalFocus()) return NSMakeRect(0, 0, 0, 0);
1203
1204 std::string_view focused_text = _focused_window->GetFocusedTextbuf()->GetText();
1205 /* Convert range to UTF-8 string pointers. */
1206 size_t start = Utf8AdvanceByUtf16Units(focused_text, aRange.location);
1207 size_t end = start + Utf8AdvanceByUtf16Units(focused_text.substr(start), aRange.length);
1208
1209 /* Get the bounding rect for the text range.*/
1210 Rect r = _focused_window->GetTextBoundingRect(start, end);
1211 NSRect view_rect = NSMakeRect(_focused_window->left + r.left, [ self frame ].size.height - _focused_window->top - r.bottom, r.right - r.left, r.bottom - r.top);
1212
1213 return [ [ self window ] convertRectToScreen:[ self convertRect:view_rect toView:nil ] ];
1214}
1215
1222- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
1223{
1224 return [ self firstRectForCharacterRange:aRange ];
1225}
1226
1232{
1233 return [ NSArray array ];
1234}
1235
1240- (void)deleteBackward:(id)sender
1241{
1242 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE, 0);
1243}
1244
1249- (void)deleteWordBackward:(id)sender
1250{
1251 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE | WKC_CTRL, 0);
1252}
1253
1258- (void)deleteForward:(id)sender
1259{
1260 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE, 0);
1261}
1262
1267- (void)deleteWordForward:(id)sender
1268{
1269 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE | WKC_CTRL, 0);
1270}
1271
1276- (void)moveLeft:(id)sender
1277{
1278 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT, 0);
1279}
1280
1285- (void)moveWordLeft:(id)sender
1286{
1287 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT | WKC_CTRL, 0);
1288}
1289
1294- (void)moveRight:(id)sender
1295{
1296 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT, 0);
1297}
1298
1303- (void)moveWordRight:(id)sender
1304{
1305 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT | WKC_CTRL, 0);
1306}
1307
1312- (void)moveUp:(id)sender
1313{
1314 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP, 0);
1315}
1316
1321- (void)moveDown:(id)sender
1322{
1323 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN, 0);
1324}
1325
1330- (void)moveUpAndModifySelection:(id)sender
1331{
1332 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP | WKC_SHIFT, 0);
1333}
1334
1339- (void)moveDownAndModifySelection:(id)sender
1340{
1341 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN | WKC_SHIFT, 0);
1342}
1343
1348- (void)moveToBeginningOfLine:(id)sender
1349{
1350 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_HOME, 0);
1351}
1352
1357- (void)moveToEndOfLine:(id)sender
1358{
1359 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_END, 0);
1360}
1361
1366- (void)scrollPageUp:(id)sender
1367{
1368 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP, 0);
1369}
1370
1375- (void)scrollPageDown:(id)sender
1376{
1377 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN, 0);
1378}
1379
1384- (void)pageUpAndModifySelection:(id)sender
1385{
1386 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP | WKC_SHIFT, 0);
1387}
1388
1393- (void)pageDownAndModifySelection:(id)sender
1394{
1395 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN | WKC_SHIFT, 0);
1396}
1397
1402- (void)scrollToBeginningOfDocument:(id)sender
1403{
1404 /* For compatibility with OTTD on Win/Linux. */
1405 [ self moveToBeginningOfLine:sender ];
1406}
1407
1412- (void)scrollToEndOfDocument:(id)sender
1413{
1414 /* For compatibility with OTTD on Win/Linux. */
1415 [ self moveToEndOfLine:sender ];
1416}
1417
1422- (void)insertNewline:(id)sender
1423{
1424 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RETURN, '\r');
1425}
1426
1431- (void)cancelOperation:(id)sender
1432{
1433 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_ESC, 0);
1434}
1435
1440- (void)doCommandBySelector:(SEL)aSelector
1441{
1442 if ([ self respondsToSelector:aSelector ]) [ self performSelector:aSelector ];
1443}
1444
1445@end
1446
1447
1448@implementation OTTD_CocoaWindowDelegate {
1449 VideoDriver_Cocoa *driver;
1450}
1451
1457- (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv
1458{
1459 if (self = [ super init ]) {
1460 self->driver = drv;
1461 }
1462 return self;
1463}
1464
1469- (BOOL)windowShouldClose:(id)sender
1470{
1471 HandleExitGameRequest();
1472
1473 return NO;
1474}
1475
1479- (void)windowDidEnterFullScreen:(NSNotification *)aNotification
1480{
1481 NSPoint loc = [ driver->cocoaview convertPoint:[ [ aNotification object ] mouseLocationOutsideOfEventStream ] fromView:nil ];
1482 BOOL inside = ([ driver->cocoaview hitTest:loc ] == driver->cocoaview);
1483
1484 if (inside) {
1485 /* We don't care about the event, but the compiler does. */
1486 NSEvent *e = [ [ NSEvent alloc ] init ];
1487 [ driver->cocoaview mouseEntered:e ];
1488 [ e release ];
1489 }
1490}
1491
1495- (void)windowDidChangeBackingProperties:(NSNotification *)notification
1496{
1497 bool did_adjust = AdjustGUIZoom(true);
1498
1499 /* Reallocate screen buffer if necessary. */
1500 driver->AllocateBackingStore();
1501
1502 if (did_adjust) ReInitAllWindows(true);
1503}
1504
1511- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
1512{
1513 return NSApplicationPresentationFullScreen | NSApplicationPresentationHideMenuBar | NSApplicationPresentationHideDock;
1514}
1515
1516@end
1517
1518#endif /* WITH_COCOA or DOXYGEN_API */
constexpr T SB(T &x, const uint8_t s, const uint8_t n, const U d)
Set n bits in x starting at bit s to d.
Re-implement the system cursor in order to allow hiding and showing it nicely.
Definition cocoa_wnd.mm:416
NSCursor * clearCocoaCursor()
Create clear cursor for cocoa driver.
Definition cocoa_wnd.mm:421
virtual void Stop()=0
Stop this driver.
Constant span of UTF-8 encoded data.
Definition utf8.hpp:28
virtual void AllocateBackingStore(bool force=false)=0
Resize the window.
void MainLoopReal()
Main game loop.
Definition cocoa_v.mm:454
virtual bool ToggleFullscreen(bool fullscreen)=0
Change the full screen setting.
static std::string GetCaption()
Get the caption to use for the game's title bar.
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
Mappings of Cocoa keys.
The Cocoa video driver.
bool _cocoa_video_started
Is the Cocoa video driver running.
Definition cocoa_v.mm:44
bool _tab_is_down
Is tab button pressed.
Definition cocoa_wnd.mm:77
OS interface for the cocoa video driver.
bool _allow_hidpi_window
Storage for allow_hidpi setting. If true renders OTTD in native resolution.
Definition cocoa_wnd.mm:69
NSString * OTTDMainLaunchGameEngine
Name of notification observer used to restart the game loop if necessary.
Definition cocoa_wnd.mm:75
static bool _cocoa_video_dialog
True iff inside the scope of CocoaDialog method.
Definition cocoa_wnd.mm:79
static NSImage * NSImageFromSprite(SpriteID sprite_id, ZoomLevel zoom)
Render an OTTD sprite to a Cocoa image.
Definition cocoa_wnd.mm:154
bool _emulated_down
Whether the mouse button is emulated or real.
Definition cocoa_wnd.mm:556
bool touchbar_created
Whether the touchbar exists.
Definition cocoa_wnd.mm:438
static void setupWindowMenu()
Create a window menu.
Definition cocoa_wnd.mm:307
void CocoaDialog(std::string_view title, std::string_view message, std::string_view buttonLabel)
Catch asserts prior to initialization of the videodriver.
Definition cocoa_wnd.mm:385
bool _use_hidpi
Render content in native resolution?
Definition cocoa_wnd.mm:557
static const std::array< TouchBarButton, 9 > _touchbar_buttons
Storage of defined touch bar buttons.
Definition cocoa_wnd.mm:57
static NSUInteger CountUtf16Units(std::string_view str)
Count the number of UTF-16 code points in a range of an UTF-8 string.
Definition cocoa_wnd.mm:88
static size_t Utf8AdvanceByUtf16Units(std::string_view str, NSUInteger count)
Advance an UTF-8 string by a number of equivalent UTF-16 code points.
Definition cocoa_wnd.mm:103
static void CGDataFreeCallback(void *, const void *data, size_t)
Free memory where data was stored.
Definition cocoa_wnd.mm:143
static void setApplicationMenu()
Initialize the application menu shown in top bar.
Definition cocoa_wnd.mm:263
static std::vector< char32_t > NSStringToUTF32(NSString *s)
Convert a NSString to an UTF-32 encoded string.
Definition cocoa_wnd.mm:119
NSUInteger _current_mods
Currently applied modifier flags.
Definition cocoa_wnd.mm:555
static OTTDMain * _ottd_main
App delegate instance of OTTDMain.
Definition cocoa_wnd.mm:80
bool CocoaSetupApplication()
Startup the application.
Definition cocoa_wnd.mm:334
void CocoaExitApplication()
Deregister app delegate.
Definition cocoa_wnd.mm:370
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:972
bool _left_button_down
Is left mouse button pressed?
Definition gfx.cpp:42
uint8_t _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
Definition gfx.cpp:35
bool _left_button_clicked
Is left mouse button clicked?
Definition gfx.cpp:43
bool _right_button_clicked
Is right mouse button clicked?
Definition gfx.cpp:45
std::unique_ptr< uint32_t[]> DrawSpriteToRgbaBuffer(SpriteID spriteId, ZoomLevel zoom)
Draws a sprite to a new RGBA buffer (see Colour union) instead of drawing to the screen.
Definition gfx.cpp:1195
bool _right_button_down
Is right mouse button pressed?
Definition gfx.cpp:44
bool AdjustGUIZoom(bool automatic)
Resolve GUI zoom level and adjust GUI to new zoom, if auto-suggestion is requested.
Definition gfx.cpp:1837
Functions related to the gfx engine.
void HandleToolbarHotkey(int hotkey)
Handle Toolbar hotkey events - can come from a source like the MacBook Touch Bar.
Definition window.cpp:2650
void HandleMouseEvents()
Handle a mouse event from the video driver.
Definition window.cpp:2986
void HandleKeypress(uint keycode, char32_t key)
Handle keyboard input.
Definition window.cpp:2667
void HandleTextInput(std::string_view str, bool marked=false, std::optional< size_t > caret=std::nullopt, std::optional< size_t > insert_location=std::nullopt, std::optional< size_t > replacement_end=std::nullopt)
Handle text input.
Definition window.cpp:2759
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
The main class of the application, the application's delegate.
Definition cocoa_wnd.mm:71
void unregisterObserver()
Remove ourself as a notification observer.
Definition cocoa_wnd.mm:240
void stopEngine()
Stop the game engine.
Definition cocoa_wnd.mm:182
Subclass of NSView to support mouse awareness and text input.
Definition cocoa_wnd.h:43
void unmarkText()
Unmark the current marked text.
NSRange selectedRange()
Get the caret position.
NSPoint mousePositionFromEvent:(NSEvent *e)
Return the mouse location.
Definition cocoa_wnd.mm:647
void rightMouseDown:(NSEvent *event)
Handler of right mouse button pressing.
Definition cocoa_wnd.mm:760
void setMarkedText:selectedRange:replacementRange:(id aString, [selectedRange] NSRange selRange, [replacementRange] NSRange replacementRange)
Set a new marked text and reposition the caret.
void internalMouseButtonEvent()
Internal handler of mouse buttons.
Definition cocoa_wnd.mm:675
void moveToEndOfLine:(id sender)
Move cursor to the end of the line.
NSAttributedString * attributedString()
Get the current edit box string.
BOOL hasMarkedText()
Is any text marked?
void internalMouseMoveEvent:(NSEvent *event)
Internal handler of mouse movement.
Definition cocoa_wnd.mm:660
BOOL internalHandleKeycode:unicode:pressed:modifiers:(unsigned short keycode, [unicode] char32_t unicode, [pressed] BOOL down, [modifiers] NSUInteger modifiers)
Internal handler of keyboard keys.
Definition cocoa_wnd.mm:850
void moveToBeginningOfLine:(id sender)
Move cursor to the start of the line.
void rightMouseUp:(NSEvent *event)
Handler of right mouse button releasing.
Definition cocoa_wnd.mm:771
BOOL acceptsFirstResponder()
Allow to handle events.
Definition cocoa_wnd.mm:587
NSRect firstRectForCharacterRange:(NSRange aRange)
Get the bounding rect for the given range.
NSArray * validAttributesForMarkedText()
Get all string attributes that we can process for marked text.
NSRange markedRange()
Get the currently marked range.
void insertText:replacementRange:(id aString, [replacementRange] NSRange replacementRange)
Insert the given text at the given range.
NSAttributedString * attributedSubstringForProposedRange:actualRange:(NSRange theRange, [actualRange] NSRangePointer actualRange)
Get a string corresponding to the given range.
Delegate for our NSWindow to send ask for quit on close.
Definition cocoa_wnd.h:51
Subclass of NSWindow to cater our special needs.
Definition cocoa_wnd.h:33
Functions related to MacOS support.
std::unique_ptr< typename std::remove_pointer< T >::type, CFDeleter< typename std::remove_pointer< T >::type > > CFAutoRelease
Specialisation of std::unique_ptr for CoreFoundation objects.
Definition macos.h:35
Includes of mac os specific headers wich contain objective c.
#define Point
Macro that prevents name conflicts between included headers.
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
Some generic types.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
Types related to global configuration settings.
Functions to cache sprites in memory.
This file contains all sprite-related enums and defines.
Definition of base types and functions in a cross-platform compatible way.
Functions related to low-level strings.
char32_t Utf16DecodeSurrogate(uint lead, uint trail)
Convert an UTF-16 surrogate pair to the corresponding Unicode character.
Definition string_func.h:84
bool Utf16IsLeadSurrogate(uint c)
Is the given character a lead surrogate code point?
Definition string_func.h:63
bool Utf16IsTrailSurrogate(uint c)
Is the given character a lead surrogate code point?
Definition string_func.h:73
Dimensions (a width and height) of a rectangle in 2D.
Specification of a rectangle with absolute coordinates of all edges.
Helper/buffer for input fields.
uint16_t caretpos
the current position of the caret in the buffer, in bytes
std::string_view GetText() const
Get the current text.
Definition textbuf.cpp:284
uint16_t markend
the end position of the marked area in the buffer, in bytes
uint16_t markpos
the start position of the marked area in the buffer, in bytes
Structure to store information about single touch bar button.
Definition cocoa_wnd.mm:46
NSString * fallback_text
Text to use if sprite is unavailable.
Definition cocoa_wnd.mm:50
NSTouchBarItemIdentifier key
Unique identifier for this button.
Definition cocoa_wnd.mm:47
SpriteID sprite
Sprite to display on button.
Definition cocoa_wnd.mm:48
MainToolbarHotkeys hotkey
Index of widget that corresponds to this button.
Definition cocoa_wnd.mm:49
Stuff related to text buffers.
Stuff related to the (main) toolbar.
Handling of UTF-8 encoded data.
Window * _focused_window
Window that currently has focus.
Definition window.cpp:85
void ReInitAllWindows(bool zoom_changed)
Re-initialize all windows.
Definition window.cpp:3439
bool EditBoxInGlobalFocus()
Check if an edit box is in global focus.
Definition window.cpp:461
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:20