OpenTTD Source 20260208-master-g43af8e94d0
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
195- (void)launchGameEngine: (NSNotification*) note
196{
197 auto *drv = static_cast<VideoDriver_Cocoa *>(VideoDriver::GetInstance());
198
199 /* Setup cursor for the current _game_mode. */
200 NSEvent *e = [ [ NSEvent alloc ] init ];
201 [ drv->cocoaview cursorUpdate:e ];
202 [ e release ];
203
204 /* Hand off to main application code. */
205 drv->MainLoopReal();
206
207 /* We are done, thank you for playing. */
208 [ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ];
209}
210
214- (void) applicationDidFinishLaunching: (NSNotification*) note
215{
216 /* Add a notification observer so we can restart the game loop later on if necessary. */
217 [ [ NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(launchGameEngine:) name:OTTDMainLaunchGameEngine object:nil ];
218
219 /* Start game loop. */
220 [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ];
221}
222
226- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender
227{
228 HandleExitGameRequest();
229
230 return NSTerminateCancel;
231}
232
237{
238 [ [ NSNotificationCenter defaultCenter ] removeObserver:self ];
239}
240
248- (BOOL)applicationSupportsSecureRestorableState:(NSApplication*) sender
249{
250 return YES;
251}
252@end
253
258{
259 NSString *appName = @"OpenTTD";
260 NSMenu *appleMenu = [ [ NSMenu alloc ] initWithTitle:appName ];
261
262 /* Add menu items */
263 NSString *title = [ @"About " stringByAppendingString:appName ];
264 [ appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@"" ];
265
266 [ appleMenu addItem:[ NSMenuItem separatorItem ] ];
267
268 title = [ @"Hide " stringByAppendingString:appName ];
269 [ appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h" ];
270
271 NSMenuItem *menuItem = [ appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h" ];
272 [ menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand) ];
273
274 [ appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@"" ];
275
276 [ appleMenu addItem:[ NSMenuItem separatorItem ] ];
277
278 title = [ @"Quit " stringByAppendingString:appName ];
279 [ appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q" ];
280
281 /* Put menu into the menubar */
282 menuItem = [ [ NSMenuItem alloc ] initWithTitle:@"" action:nil keyEquivalent:@"" ];
283 [ menuItem setSubmenu:appleMenu ];
284 [ [ NSApp mainMenu ] addItem:menuItem ];
285
286 /* Tell the application object that this is now the application menu.
287 * This interesting Objective-C construct is used because not all SDK
288 * versions define this method publicly. */
289 if ([ NSApp respondsToSelector:@selector(setAppleMenu:) ]) {
290 [ NSApp performSelector:@selector(setAppleMenu:) withObject:appleMenu ];
291 }
292
293 /* Finally give up our references to the objects */
294 [ appleMenu release ];
295 [ menuItem release ];
296}
297
301static void setupWindowMenu()
302{
303 NSMenu *windowMenu = [ [ NSMenu alloc ] initWithTitle:@"Window" ];
304
305 /* "Minimize" item */
306 [ windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m" ];
307
308 /* Put menu into the menubar */
309 NSMenuItem *menuItem = [ [ NSMenuItem alloc ] initWithTitle:@"Window" action:nil keyEquivalent:@"" ];
310 [ menuItem setSubmenu:windowMenu ];
311 [ [ NSApp mainMenu ] addItem:menuItem ];
312
313 /* The OS will change the name of this menu item automatically */
314 [ windowMenu addItemWithTitle:@"Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"^f" ];
315
316 /* Tell the application object that this is now the window menu */
317 [ NSApp setWindowsMenu:windowMenu ];
318
319 /* Finally give up our references to the objects */
320 [ windowMenu release ];
321 [ menuItem release ];
322}
323
329{
330 ProcessSerialNumber psn = { 0, kCurrentProcess };
331
332 /* Ensure the application object is initialised */
333 [ NSApplication sharedApplication ];
334
335 /* Tell the dock about us */
336 OSStatus returnCode = TransformProcessType(&psn, kProcessTransformToForegroundApplication);
337 if (returnCode != 0) Debug(driver, 0, "Could not change to foreground application. Error {}", (int)returnCode);
338
339 /* Disable the system-wide tab feature as we only have one window. */
340 if ([ NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:) ]) {
341 /* We use nil instead of NO as withObject requires an id. */
342 [ NSWindow performSelector:@selector(setAllowsAutomaticWindowTabbing:) withObject:nil];
343 }
344
345 /* Become the front process, important when start from the command line. */
346 [ [ NSApplication sharedApplication ] setActivationPolicy:NSApplicationActivationPolicyRegular ];
347 [ [ NSApplication sharedApplication ] activateIgnoringOtherApps:YES ];
348
349 /* Set up the menubar */
350 [ NSApp setMainMenu:[ [ NSMenu alloc ] init ] ];
353
354 /* Create OTTDMain and make it the app delegate */
355 _ottd_main = [ [ OTTDMain alloc ] init ];
356 [ NSApp setDelegate:_ottd_main ];
357
358 return true;
359}
360
365{
367 [ _ottd_main release ];
368}
369
379void CocoaDialog(std::string_view title, std::string_view message, std::string_view buttonLabel)
380{
381 _cocoa_video_dialog = true;
382
383 bool wasstarted = _cocoa_video_started;
384 if (VideoDriver::GetInstance() == nullptr) {
385 CocoaSetupApplication(); // Setup application before showing dialog
386 } else if (!_cocoa_video_started && VideoDriver::GetInstance()->Start({}).has_value()) {
387 fmt::print(stderr, "{}: {}\n", title, message);
388 return;
389 }
390
391 @autoreleasepool {
392 NSAlert *alert = [ [ NSAlert alloc ] init ];
393 [ alert setAlertStyle: NSAlertStyleCritical ];
394 [ alert setMessageText:[ [ NSString alloc ] initWithBytes:title.data() length:title.size() encoding:NSUTF8StringEncoding ] ];
395 [ alert setInformativeText:[ [ NSString alloc ] initWithBytes:message.data() length:message.size() encoding:NSUTF8StringEncoding ] ];
396 [ alert addButtonWithTitle: [ [ NSString alloc ] initWithBytes:buttonLabel.data() length:buttonLabel.size() encoding:NSUTF8StringEncoding ] ];
397 [ alert runModal ];
398 [ alert release ];
399 }
400
401 if (!wasstarted && VideoDriver::GetInstance() != nullptr) VideoDriver::GetInstance()->Stop();
402
403 _cocoa_video_dialog = false;
404}
405
406
410@implementation NSCursor (OTTD_CocoaCursor)
415+ (NSCursor *) clearCocoaCursor
416{
417 /* RAW 16x16 transparent GIF */
418 unsigned char clearGIFBytes[] = {
419 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, 0x00,
420 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00,
421 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00,
422 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4,
423 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B};
424 NSData *clearGIFData = [ NSData dataWithBytesNoCopy:&clearGIFBytes[0] length:55 freeWhenDone:NO ];
425 NSImage *clearImg = [ [ NSImage alloc ] initWithData:clearGIFData ];
426 return [ [ NSCursor alloc ] initWithImage:clearImg hotSpot:NSMakePoint(0.0,0.0) ];
427}
428@end
429
430@implementation OTTD_CocoaWindow {
431 VideoDriver_Cocoa *driver;
433}
434
438- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv
439{
440 if (self = [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]) {
441 self->driver = drv;
442 self->touchbar_created = false;
443
444 [ self setContentMinSize:NSMakeSize(64.0f, 64.0f) ];
445
446 std::string caption = VideoDriver::GetCaption();
447 NSString *nsscaption = [ [ NSString alloc ] initWithBytes:caption.data() length:caption.size() encoding:NSUTF8StringEncoding ];
448 [ self setTitle:nsscaption ];
449 [ self setMiniwindowTitle:nsscaption ];
450 [ nsscaption release ];
451 }
452
453 return self;
454}
455
459- (void)setFrame:(NSRect)frameRect display:(BOOL)flag
460{
461 [ super setFrame:frameRect display:flag ];
462
463 driver->AllocateBackingStore();
464}
465
466- (void)touchBarButtonAction:(id)sender
467{
468 NSButton *btn = (NSButton *)sender;
469 if (auto item = std::ranges::find(_touchbar_buttons, (NSTouchBarItemIdentifier)btn.identifier, &TouchBarButton::key); item != _touchbar_buttons.end()) {
470 HandleToolbarHotkey(item->hotkey);
471 }
472}
473
474- (nullable NSTouchBar *)makeTouchBar
475{
476 /* Make button identifier array. */
477 NSMutableArray<NSTouchBarItemIdentifier> *button_ids = [ [ NSMutableArray alloc ] init ];
478 for (const auto &button : _touchbar_buttons) {
479 [ button_ids addObject:button.key ];
480 }
481 [ button_ids addObject:NSTouchBarItemIdentifierOtherItemsProxy ];
482
483 NSTouchBar *bar = [ [ NSTouchBar alloc ] init ];
484 bar.delegate = self;
485 bar.defaultItemIdentifiers = button_ids;
486 [ button_ids release ];
487
488 self->touchbar_created = true;
489
490 return bar;
491}
492
493- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
494{
495 auto item = std::ranges::find(_touchbar_buttons, identifier, &TouchBarButton::key);
496 assert(item != _touchbar_buttons.end());
497
498 NSButton *button = [ NSButton buttonWithTitle:item->fallback_text target:self action:@selector(touchBarButtonAction:) ];
499 button.identifier = identifier;
500 button.imageScaling = NSImageScaleProportionallyDown;
501
502 NSCustomTouchBarItem *tb_item = [ [ NSCustomTouchBarItem alloc] initWithIdentifier:identifier ];
503 tb_item.view = button;
504 return tb_item;
505}
506
507- (void)refreshSystemSprites
508{
509 if (!self->touchbar_created || ![ self respondsToSelector:@selector(touchBar) ] || self.touchBar == nil) return;
510
511 /* Re-create button images from OTTD sprites. */
512 for (NSTouchBarItemIdentifier ident in self.touchBar.itemIdentifiers) {
513 auto item = std::ranges::find(_touchbar_buttons, ident, &TouchBarButton::key);
514 if (item == _touchbar_buttons.end()) continue;
515
516 NSCustomTouchBarItem *tb_item = [ self.touchBar itemForIdentifier:ident ];
517 NSButton *button = tb_item.view;
518
519 NSImage *image = NSImageFromSprite(item->sprite, _settings_client.gui.zoom_min);
520 if (image != nil) {
521 /* Human Interface Guidelines: Maximum touch bar glyph size 22 pt. */
522 CGFloat max_dim = std::max(image.size.width, image.size.height);
523 if (max_dim > 0.0) {
524 CGFloat scale = 22.0 / max_dim;
525 image.size = NSMakeSize(image.size.width * scale, image.size.height * scale);
526 }
527
528 button.image = image;
529 button.imagePosition = NSImageOnly;
530 } else {
531 button.image = nil;
532 button.imagePosition = NSNoImage;
533 }
534 }
535}
536
537@end
538
539@implementation OTTD_CocoaView {
540 float _current_magnification;
541 NSUInteger _current_mods;
544}
545
546- (instancetype)initWithFrame:(NSRect)frameRect
547{
548 if (self = [ super initWithFrame:frameRect ]) {
549 self->_use_hidpi = _allow_hidpi_window && [ self respondsToSelector:@selector(convertRectToBacking:) ] && [ self respondsToSelector:@selector(convertRectFromBacking:) ];
550 }
551 return self;
552}
553
554- (NSRect)getRealRect:(NSRect)rect
555{
556 return _use_hidpi ? [ self convertRectToBacking:rect ] : rect;
557}
558
559- (NSRect)getVirtualRect:(NSRect)rect
560{
561 return _use_hidpi ? [ self convertRectFromBacking:rect ] : rect;
562}
563
564- (CGFloat)getContentsScale
565{
566 return _use_hidpi && self.window != nil ? [ self.window backingScaleFactor ] : 1.0f;
567}
568
573{
574 return YES;
575}
576
577- (void)setNeedsDisplayInRect:(NSRect)invalidRect
578{
579 /* Drawing is handled by our sub-views. Just pass it along. */
580 for ( NSView *v in [ self subviews ]) {
581 [ v setNeedsDisplayInRect:[ v convertRect:invalidRect fromView:self ] ];
582 }
583}
584
586- (void)cursorUpdate:(NSEvent *)event
587{
588 [ (_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) set ];
589}
590
591- (void)viewWillMoveToWindow:(NSWindow *)win
592{
593 for (NSTrackingArea *a in [ self trackingAreas ]) {
594 [ self removeTrackingArea:a ];
595 }
596}
597
598- (void)viewDidMoveToWindow
599{
600 /* Install mouse tracking area. */
601 NSTrackingAreaOptions track_opt = NSTrackingInVisibleRect | NSTrackingActiveInActiveApp | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingCursorUpdate;
602 NSTrackingArea *track = [ [ NSTrackingArea alloc ] initWithRect:[ self bounds ] options:track_opt owner:self userInfo:nil ];
603 [ self addTrackingArea:track ];
604 [ track release ];
605}
610- (void)mouseEntered:(NSEvent *)theEvent
611{
612 _cursor.in_window = true;
613}
614
618- (void)mouseExited:(NSEvent *)theEvent
619{
620 if ([ self window ] != nil) UndrawMouseCursor();
621 _cursor.in_window = false;
622}
623
629- (NSPoint)mousePositionFromEvent:(NSEvent *)e
630{
631 NSPoint pt = e.locationInWindow;
632 if ([ e window ] == nil) pt = [ self.window convertRectFromScreen:NSMakeRect(pt.x, pt.y, 0, 0) ].origin;
633 pt = [ self convertPoint:pt fromView:nil ];
634
635 return [ self getRealRect:NSMakeRect(pt.x, self.bounds.size.height - pt.y, 0, 0) ].origin;
636}
637
642- (void)internalMouseMoveEvent:(NSEvent *)event
643{
644 if (_cursor.fix_at) {
645 _cursor.UpdateCursorPositionRelative(event.deltaX * self.getContentsScale, event.deltaY * self.getContentsScale);
646 } else {
647 NSPoint pt = [ self mousePositionFromEvent:event ];
648 _cursor.UpdateCursorPosition(pt.x, pt.y);
649 }
650
652}
653
658{
659 bool cur_fix = _cursor.fix_at;
661
662 /* Cursor fix mode was changed, synchronize with OS. */
663 if (cur_fix != _cursor.fix_at) CGAssociateMouseAndMouseCursorPosition(!_cursor.fix_at);
664}
665
671- (BOOL)emulateRightButton:(NSEvent *)event
672{
673 uint32_t keymask = 0;
674 if (_settings_client.gui.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSEventModifierFlagCommand;
675 if (_settings_client.gui.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSEventModifierFlagControl;
676
677 return (event.modifierFlags & keymask) != 0;
678}
679
684- (void)mouseMoved:(NSEvent *)event
685{
686 [ self internalMouseMoveEvent:event ];
687}
688
693- (void)mouseDragged:(NSEvent *)event
694{
695 [ self internalMouseMoveEvent:event ];
696}
697
702- (void)mouseDown:(NSEvent *)event
703{
704 if ([ self emulateRightButton:event ]) {
705 self->_emulated_down = true;
706 [ self rightMouseDown:event ];
707 } else {
708 _left_button_down = true;
710 }
711}
712
717- (void)mouseUp:(NSEvent *)event
718{
719 if (self->_emulated_down) {
720 self->_emulated_down = false;
721 [ self rightMouseUp:event ];
722 } else {
723 _left_button_down = false;
724 _left_button_clicked = false;
726 }
727}
728
733- (void)rightMouseDragged:(NSEvent *)event
734{
735 [ self internalMouseMoveEvent:event ];
736}
737
742- (void)rightMouseDown:(NSEvent *)event
743{
744 _right_button_down = true;
747}
748
753- (void)rightMouseUp:(NSEvent *)event
754{
755 _right_button_down = false;
757}
758
763- (void)scrollWheel:(NSEvent *)event
764{
765 if ([ event deltaY ] > 0.0) { /* Scroll up */
766 _cursor.wheel--;
767 } else if ([ event deltaY ] < 0.0) { /* Scroll down */
768 _cursor.wheel++;
769 } /* else: deltaY was 0.0 and we don't want to do anything */
770
771 /* Update the scroll count for 2D scrolling */
772 CGFloat deltaX;
773 CGFloat deltaY;
774
775 /* Use precise scrolling-specific deltas if they're supported. */
776 if ([ event respondsToSelector:@selector(hasPreciseScrollingDeltas) ]) {
777 /* No precise deltas indicates a scroll wheel is being used, so we don't want 2D scrolling. */
778 if (![ event hasPreciseScrollingDeltas ]) return;
779
780 deltaX = [ event scrollingDeltaX ] * 0.5f;
781 deltaY = [ event scrollingDeltaY ] * 0.5f;
782 } else {
783 deltaX = [ event deltaX ] * 5;
784 deltaY = [ event deltaY ] * 5;
785 }
786
787 _cursor.h_wheel -= static_cast<float>(deltaX * _settings_client.gui.scrollwheel_multiplier);
788 _cursor.v_wheel -= static_cast<float>(deltaY * _settings_client.gui.scrollwheel_multiplier);
789 _cursor.wheel_moved = true;
790}
791
796- (void)magnifyWithEvent:(NSEvent *)event
797{
798 /* Pinch open or close gesture. */
799 self->_current_magnification += [ event magnification ] * 5.0f;
800
801 while (self->_current_magnification >= 1.0f) {
802 self->_current_magnification -= 1.0f;
803 _cursor.wheel--;
805 }
806 while (self->_current_magnification <= -1.0f) {
807 self->_current_magnification += 1.0f;
808 _cursor.wheel++;
810 }
811}
812
817- (void)endGestureWithEvent:(NSEvent *)event
818{
819 /* Gesture ended. */
820 self->_current_magnification = 0.0f;
821}
822
823
832- (BOOL)internalHandleKeycode:(unsigned short)keycode unicode:(char32_t)unicode pressed:(BOOL)down modifiers:(NSUInteger)modifiers
833{
834 switch (keycode) {
835 case QZ_UP: SB(_dirkeys, 1, 1, down); break;
836 case QZ_DOWN: SB(_dirkeys, 3, 1, down); break;
837 case QZ_LEFT: SB(_dirkeys, 0, 1, down); break;
838 case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break;
839
840 case QZ_TAB:
841 _tab_is_down = down;
842 if (down && EditBoxInGlobalFocus()) {
843 HandleKeypress(WKC_TAB, unicode);
844 }
845 break;
846
847 case QZ_RETURN:
848 case QZ_f:
849 if (down && (modifiers & NSEventModifierFlagCommand)) {
851 }
852 break;
853
854 case QZ_v:
855 if (down && EditBoxInGlobalFocus() && (modifiers & (NSEventModifierFlagCommand | NSEventModifierFlagControl))) {
856 HandleKeypress(WKC_CTRL | 'V', unicode);
857 }
858 break;
859 case QZ_u:
860 if (down && EditBoxInGlobalFocus() && (modifiers & (NSEventModifierFlagCommand | NSEventModifierFlagControl))) {
861 HandleKeypress(WKC_CTRL | 'U', unicode);
862 }
863 break;
864 }
865
866 BOOL interpret_keys = YES;
867 if (down) {
868 /* Map keycode to OTTD code. */
869 auto vk = std::ranges::find(_vk_mapping, keycode, &CocoaVkMapping::vk_from);
870 uint32_t pressed_key = vk != std::end(_vk_mapping) ? vk->map_to : 0;
871
872 if (modifiers & NSEventModifierFlagShift) pressed_key |= WKC_SHIFT;
873 if (modifiers & NSEventModifierFlagControl) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META);
874 if (modifiers & NSEventModifierFlagOption) pressed_key |= WKC_ALT;
875 if (modifiers & NSEventModifierFlagCommand) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL);
876
877 static bool console = false;
878
879 /* The second backquote may have a character, which we don't want to interpret. */
880 if (pressed_key == WKC_BACKQUOTE && (console || unicode == 0)) {
881 if (!console) {
882 /* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */
883 console = true;
884 return YES;
885 } else {
886 /* Second backquote, don't interpret as text input. */
887 interpret_keys = NO;
888 }
889 }
890 console = false;
891
892 /* Don't handle normal characters if an edit box has the focus. */
893 if (!EditBoxInGlobalFocus() || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) {
894 HandleKeypress(pressed_key, unicode);
895 }
896 Debug(driver, 3, "cocoa_v: QZ_KeyEvent: {:x} ({:x}), down, mapping: {:x}", keycode, (int)unicode, pressed_key);
897 } else {
898 Debug(driver, 3, "cocoa_v: QZ_KeyEvent: {:x} ({:x}), up", keycode, (int)unicode);
899 }
900
901 return interpret_keys;
902}
903
908- (void)keyDown:(NSEvent *)event
909{
910 /* Quit, hide and minimize */
911 switch (event.keyCode) {
912 case QZ_q:
913 case QZ_h:
914 case QZ_m:
915 if (event.modifierFlags & NSEventModifierFlagCommand) {
916 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
917 }
918 break;
919 }
920
921 /* Convert UTF-16 characters to UCS-4 chars. */
922 std::vector<char32_t> unicode_str = NSStringToUTF32([ event characters ]);
923 if (unicode_str.empty()) unicode_str.push_back(0);
924
925 if (EditBoxInGlobalFocus()) {
926 if ([ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ]) {
927 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
928 }
929 } else {
930 [ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ];
931 for (size_t i = 1; i < unicode_str.size(); i++) {
932 [ self internalHandleKeycode:0 unicode:unicode_str[i] pressed:YES modifiers:event.modifierFlags ];
933 }
934 }
935}
936
941- (void)keyUp:(NSEvent *)event
942{
943 /* Quit, hide and minimize */
944 switch (event.keyCode) {
945 case QZ_q:
946 case QZ_h:
947 case QZ_m:
948 if (event.modifierFlags & NSEventModifierFlagCommand) {
949 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
950 }
951 break;
952 }
953
954 /* Convert UTF-16 characters to UCS-4 chars. */
955 std::vector<char32_t> unicode_str = NSStringToUTF32([ event characters ]);
956 if (unicode_str.empty()) unicode_str.push_back(0);
957
958 [ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:NO modifiers:event.modifierFlags ];
959}
960
965- (void)flagsChanged:(NSEvent *)event
966{
967 const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA };
968
969 NSUInteger newMods = event.modifierFlags;
970 if (self->_current_mods == newMods) return;
971
972 /* Iterate through the bits, testing each against the current modifiers */
973 for (unsigned int i = 0, bit = NSEventModifierFlagCapsLock; bit <= NSEventModifierFlagCommand; bit <<= 1, ++i) {
974 unsigned int currentMask, newMask;
975
976 currentMask = self->_current_mods & bit;
977 newMask = newMods & bit;
978
979 if (currentMask && currentMask != newMask) { // modifier up event
980 [ self internalHandleKeycode:mapping[i] unicode:0 pressed:NO modifiers:newMods ];
981 } else if (newMask && currentMask != newMask) { // modifier down event
982 [ self internalHandleKeycode:mapping[i] unicode:0 pressed:YES modifiers:newMods ];
983 }
984 }
985
986 _current_mods = newMods;
987}
988
989
991- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
992{
993 if (!EditBoxInGlobalFocus()) return;
994
995 NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString;
996
997 std::optional<size_t> insert_point;
998 std::optional<size_t> replace_range;
999 if (replacementRange.location != NSNotFound) {
1000 /* Calculate the part to be replaced. */
1001 std::string_view focused_text{_focused_window->GetFocusedTextbuf()->GetText()};
1002 insert_point = Utf8AdvanceByUtf16Units(focused_text, replacementRange.location);
1003 replace_range = *insert_point + Utf8AdvanceByUtf16Units(focused_text.substr(*insert_point), replacementRange.length);
1004 }
1005
1006 HandleTextInput({}, true);
1007 HandleTextInput([ s UTF8String ], false, std::nullopt, insert_point, replace_range);
1008}
1009
1011- (void)insertText:(id)aString
1012{
1013 [ self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0) ];
1014}
1015
1017- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
1018{
1019 if (!EditBoxInGlobalFocus()) return;
1020
1021 NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString;
1022
1023 const char *utf8 = [ s UTF8String ];
1024 if (utf8 != nullptr) {
1025 std::optional<size_t> insert_point;
1026 std::optional<size_t> replace_range;
1027 if (replacementRange.location != NSNotFound) {
1028 /* Calculate the part to be replaced. */
1029 NSRange marked = [ self markedRange ];
1030 std::string_view focused_text{_focused_window->GetFocusedTextbuf()->GetText()};
1031 insert_point = Utf8AdvanceByUtf16Units(focused_text, replacementRange.location + (marked.location != NSNotFound ? marked.location : 0u));
1032 replace_range = *insert_point + Utf8AdvanceByUtf16Units(focused_text.substr(*insert_point), replacementRange.length);
1033 }
1034
1035 /* Convert caret index into a pointer in the UTF-8 string. */
1036 size_t selection = Utf8AdvanceByUtf16Units(utf8, selRange.location);
1037
1038 HandleTextInput(utf8, true, selection, insert_point, replace_range);
1039 }
1040}
1041
1043- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange
1044{
1045 [ self setMarkedText:aString selectedRange:selRange replacementRange:NSMakeRange(NSNotFound, 0) ];
1046}
1047
1050{
1051 HandleTextInput({}, true);
1052}
1053
1056{
1057 if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0);
1058
1059 const Textbuf *text_buf = _focused_window->GetFocusedTextbuf();
1060 std::string_view text = text_buf->GetText();
1061 NSUInteger start = CountUtf16Units(text.substr(0, text_buf->caretpos));
1062 return NSMakeRange(start, 0);
1063}
1064
1066- (NSRange)markedRange
1067{
1068 if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0);
1069
1070 const Textbuf *text_buf = _focused_window->GetFocusedTextbuf();
1071 if (text_buf->markend != 0) {
1072 std::string_view text = text_buf->GetText();
1073 NSUInteger start = CountUtf16Units(text.substr(0, text_buf->markpos));
1074 NSUInteger len = CountUtf16Units(text.substr(text_buf->markpos, text_buf->markend - text_buf->markpos));
1075
1076 return NSMakeRange(start, len);
1077 }
1078
1079 return NSMakeRange(NSNotFound, 0);
1080}
1081
1084{
1085 if (!EditBoxInGlobalFocus()) return NO;
1086
1087 return _focused_window->GetFocusedTextbuf()->markend != 0;
1088}
1089
1091- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange
1092{
1093 if (!EditBoxInGlobalFocus()) return nil;
1094
1095 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1096 NSString *s = [ [ NSString alloc] initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding ];
1097 NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange);
1098
1099 if (actualRange != nullptr) *actualRange = valid_range;
1100 if (valid_range.length == 0) return nil;
1101
1102 return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ];
1103}
1104
1106- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange
1107{
1108 return [ self attributedSubstringForProposedRange:theRange actualRange:nil ];
1109}
1110
1112- (NSAttributedString *)attributedString
1113{
1114 if (!EditBoxInGlobalFocus()) return [ [ [ NSAttributedString alloc ] initWithString:@"" ] autorelease ];
1115
1116 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1117 return [ [ [ NSAttributedString alloc ] initWithString:[ [ NSString alloc ] initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding ] ] autorelease ];
1118}
1119
1121- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
1122{
1123 if (!EditBoxInGlobalFocus()) return NSNotFound;
1124
1125 NSPoint view_pt = [ self convertRect:[ [ self window ] convertRectFromScreen:NSMakeRect(thePoint.x, thePoint.y, 0, 0) ] fromView:nil ].origin;
1126
1127 Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y };
1128
1129 auto index = _focused_window->GetTextCharacterAtPosition(pt);
1130 if (index == -1) return NSNotFound;
1131
1132 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1133 return CountUtf16Units(text.substr(0, index));
1134}
1135
1137- (NSRect)firstRectForCharacterRange:(NSRange)aRange
1138{
1139 if (!EditBoxInGlobalFocus()) return NSMakeRect(0, 0, 0, 0);
1140
1141 std::string_view focused_text = _focused_window->GetFocusedTextbuf()->GetText();
1142 /* Convert range to UTF-8 string pointers. */
1143 size_t start = Utf8AdvanceByUtf16Units(focused_text, aRange.location);
1144 size_t end = start + Utf8AdvanceByUtf16Units(focused_text.substr(start), aRange.length);
1145
1146 /* Get the bounding rect for the text range.*/
1147 Rect r = _focused_window->GetTextBoundingRect(start, end);
1148 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);
1149
1150 return [ [ self window ] convertRectToScreen:[ self convertRect:view_rect toView:nil ] ];
1151}
1152
1154- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
1155{
1156 return [ self firstRectForCharacterRange:aRange ];
1157}
1158
1161{
1162 return [ NSArray array ];
1163}
1164
1169- (void)deleteBackward:(id)sender
1170{
1171 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE, 0);
1172}
1173
1178- (void)deleteWordBackward:(id)sender
1179{
1180 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE | WKC_CTRL, 0);
1181}
1182
1187- (void)deleteForward:(id)sender
1188{
1189 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE, 0);
1190}
1191
1196- (void)deleteWordForward:(id)sender
1197{
1198 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE | WKC_CTRL, 0);
1199}
1200
1205- (void)moveLeft:(id)sender
1206{
1207 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT, 0);
1208}
1209
1214- (void)moveWordLeft:(id)sender
1215{
1216 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT | WKC_CTRL, 0);
1217}
1218
1223- (void)moveRight:(id)sender
1224{
1225 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT, 0);
1226}
1227
1232- (void)moveWordRight:(id)sender
1233{
1234 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT | WKC_CTRL, 0);
1235}
1236
1241- (void)moveUp:(id)sender
1242{
1243 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP, 0);
1244}
1245
1250- (void)moveDown:(id)sender
1251{
1252 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN, 0);
1253}
1254
1259- (void)moveUpAndModifySelection:(id)sender
1260{
1261 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP | WKC_SHIFT, 0);
1262}
1263
1268- (void)moveDownAndModifySelection:(id)sender
1269{
1270 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN | WKC_SHIFT, 0);
1271}
1272
1277- (void)moveToBeginningOfLine:(id)sender
1278{
1279 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_HOME, 0);
1280}
1281
1286- (void)moveToEndOfLine:(id)sender
1287{
1288 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_END, 0);
1289}
1290
1295- (void)scrollPageUp:(id)sender
1296{
1297 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP, 0);
1298}
1299
1304- (void)scrollPageDown:(id)sender
1305{
1306 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN, 0);
1307}
1308
1313- (void)pageUpAndModifySelection:(id)sender
1314{
1315 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP | WKC_SHIFT, 0);
1316}
1317
1322- (void)pageDownAndModifySelection:(id)sender
1323{
1324 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN | WKC_SHIFT, 0);
1325}
1326
1331- (void)scrollToBeginningOfDocument:(id)sender
1332{
1333 /* For compatibility with OTTD on Win/Linux. */
1334 [ self moveToBeginningOfLine:sender ];
1335}
1336
1341- (void)scrollToEndOfDocument:(id)sender
1342{
1343 /* For compatibility with OTTD on Win/Linux. */
1344 [ self moveToEndOfLine:sender ];
1345}
1346
1351- (void)insertNewline:(id)sender
1352{
1353 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RETURN, '\r');
1354}
1355
1360- (void)cancelOperation:(id)sender
1361{
1362 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_ESC, 0);
1363}
1364
1369- (void)doCommandBySelector:(SEL)aSelector
1370{
1371 if ([ self respondsToSelector:aSelector ]) [ self performSelector:aSelector ];
1372}
1373
1374@end
1375
1376
1377@implementation OTTD_CocoaWindowDelegate {
1378 VideoDriver_Cocoa *driver;
1379}
1380
1382- (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv
1383{
1384 if (self = [ super init ]) {
1385 self->driver = drv;
1386 }
1387 return self;
1388}
1389
1393- (BOOL)windowShouldClose:(id)sender
1394{
1395 HandleExitGameRequest();
1396
1397 return NO;
1398}
1399
1400- (void)windowDidEnterFullScreen:(NSNotification *)aNotification
1401{
1402 NSPoint loc = [ driver->cocoaview convertPoint:[ [ aNotification object ] mouseLocationOutsideOfEventStream ] fromView:nil ];
1403 BOOL inside = ([ driver->cocoaview hitTest:loc ] == driver->cocoaview);
1404
1405 if (inside) {
1406 /* We don't care about the event, but the compiler does. */
1407 NSEvent *e = [ [ NSEvent alloc ] init ];
1408 [ driver->cocoaview mouseEntered:e ];
1409 [ e release ];
1410 }
1411}
1412
1413- (void)windowDidChangeBackingProperties:(NSNotification *)notification
1414{
1415 bool did_adjust = AdjustGUIZoom(true);
1416
1417 /* Reallocate screen buffer if necessary. */
1418 driver->AllocateBackingStore();
1419
1420 if (did_adjust) ReInitAllWindows(true);
1421}
1422
1424- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
1425{
1426 return NSApplicationPresentationFullScreen | NSApplicationPresentationHideMenuBar | NSApplicationPresentationHideDock;
1427}
1428
1429@end
1430
1431#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:410
NSCursor * clearCocoaCursor()
Create clear cursor for cocoa driver.
Definition cocoa_wnd.mm:415
virtual void Stop()=0
Stop this driver.
Constant span of UTF-8 encoded data.
Definition utf8.hpp:28
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:542
bool touchbar_created
Whether the touchbar exists.
Definition cocoa_wnd.mm:432
static void setupWindowMenu()
Create a window menu.
Definition cocoa_wnd.mm:301
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:379
bool _use_hidpi
Render content in native resolution?
Definition cocoa_wnd.mm:543
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:257
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:541
static OTTDMain * _ottd_main
App delegate instance of OTTDMain.
Definition cocoa_wnd.mm:80
bool CocoaSetupApplication()
Startup the application.
Definition cocoa_wnd.mm:328
void CocoaExitApplication()
Deregister app delegate.
Definition cocoa_wnd.mm:364
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:2639
void HandleMouseEvents()
Handle a mouse event from the video driver.
Definition window.cpp:2975
void HandleKeypress(uint keycode, char32_t key)
Handle keyboard input.
Definition window.cpp:2656
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:2748
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:236
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:629
void rightMouseDown:(NSEvent *event)
Handler of right mouse button pressing.
Definition cocoa_wnd.mm:742
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:657
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:642
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:832
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:753
BOOL acceptsFirstResponder()
Allow to handle events.
Definition cocoa_wnd.mm:572
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.
Definition cocoa_wnd.mm:991
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.
void ReInitAllWindows(bool zoom_changed)
Re-initialize all windows.
Definition window.cpp:3427
bool EditBoxInGlobalFocus()
Check if an edit box is in global focus.
Definition window.cpp:450
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