OpenTTD Source 20260129-master-g2bb01bd0e4
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
20#if defined(WITH_COCOA) || defined(DOXYGEN_API)
21
22#include "../../stdafx.h"
23#include "../../os/macosx/macos.h"
24
25#include "../../os/macosx/macos_objective_c.h"
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
182- (void)stopEngine
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
236- (void)unregisterObserver
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{
366 [ _ottd_main unregisterObserver ];
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
572- (BOOL)acceptsFirstResponder
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}
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
657- (void)internalMouseButtonEvent
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;
709 [ self internalMouseButtonEvent ];
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;
725 [ self internalMouseButtonEvent ];
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;
746 [ self internalMouseButtonEvent ];
747}
748
753- (void)rightMouseUp:(NSEvent *)event
754{
755 _right_button_down = false;
756 [ self internalMouseButtonEvent ];
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
829- (BOOL)internalHandleKeycode:(unsigned short)keycode unicode:(char32_t)unicode pressed:(BOOL)down modifiers:(NSUInteger)modifiers
830{
831 switch (keycode) {
832 case QZ_UP: SB(_dirkeys, 1, 1, down); break;
833 case QZ_DOWN: SB(_dirkeys, 3, 1, down); break;
834 case QZ_LEFT: SB(_dirkeys, 0, 1, down); break;
835 case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break;
836
837 case QZ_TAB:
838 _tab_is_down = down;
839 if (down && EditBoxInGlobalFocus()) {
840 HandleKeypress(WKC_TAB, unicode);
841 }
842 break;
843
844 case QZ_RETURN:
845 case QZ_f:
846 if (down && (modifiers & NSEventModifierFlagCommand)) {
848 }
849 break;
850
851 case QZ_v:
852 if (down && EditBoxInGlobalFocus() && (modifiers & (NSEventModifierFlagCommand | NSEventModifierFlagControl))) {
853 HandleKeypress(WKC_CTRL | 'V', unicode);
854 }
855 break;
856 case QZ_u:
857 if (down && EditBoxInGlobalFocus() && (modifiers & (NSEventModifierFlagCommand | NSEventModifierFlagControl))) {
858 HandleKeypress(WKC_CTRL | 'U', unicode);
859 }
860 break;
861 }
862
863 BOOL interpret_keys = YES;
864 if (down) {
865 /* Map keycode to OTTD code. */
866 auto vk = std::ranges::find(_vk_mapping, keycode, &CocoaVkMapping::vk_from);
867 uint32_t pressed_key = vk != std::end(_vk_mapping) ? vk->map_to : 0;
868
869 if (modifiers & NSEventModifierFlagShift) pressed_key |= WKC_SHIFT;
870 if (modifiers & NSEventModifierFlagControl) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META);
871 if (modifiers & NSEventModifierFlagOption) pressed_key |= WKC_ALT;
872 if (modifiers & NSEventModifierFlagCommand) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL);
873
874 static bool console = false;
875
876 /* The second backquote may have a character, which we don't want to interpret. */
877 if (pressed_key == WKC_BACKQUOTE && (console || unicode == 0)) {
878 if (!console) {
879 /* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */
880 console = true;
881 return YES;
882 } else {
883 /* Second backquote, don't interpret as text input. */
884 interpret_keys = NO;
885 }
886 }
887 console = false;
888
889 /* Don't handle normal characters if an edit box has the focus. */
890 if (!EditBoxInGlobalFocus() || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) {
891 HandleKeypress(pressed_key, unicode);
892 }
893 Debug(driver, 3, "cocoa_v: QZ_KeyEvent: {:x} ({:x}), down, mapping: {:x}", keycode, (int)unicode, pressed_key);
894 } else {
895 Debug(driver, 3, "cocoa_v: QZ_KeyEvent: {:x} ({:x}), up", keycode, (int)unicode);
896 }
897
898 return interpret_keys;
899}
900
905- (void)keyDown:(NSEvent *)event
906{
907 /* Quit, hide and minimize */
908 switch (event.keyCode) {
909 case QZ_q:
910 case QZ_h:
911 case QZ_m:
912 if (event.modifierFlags & NSEventModifierFlagCommand) {
913 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
914 }
915 break;
916 }
917
918 /* Convert UTF-16 characters to UCS-4 chars. */
919 std::vector<char32_t> unicode_str = NSStringToUTF32([ event characters ]);
920 if (unicode_str.empty()) unicode_str.push_back(0);
921
922 if (EditBoxInGlobalFocus()) {
923 if ([ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ]) {
924 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
925 }
926 } else {
927 [ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ];
928 for (size_t i = 1; i < unicode_str.size(); i++) {
929 [ self internalHandleKeycode:0 unicode:unicode_str[i] pressed:YES modifiers:event.modifierFlags ];
930 }
931 }
932}
933
938- (void)keyUp:(NSEvent *)event
939{
940 /* Quit, hide and minimize */
941 switch (event.keyCode) {
942 case QZ_q:
943 case QZ_h:
944 case QZ_m:
945 if (event.modifierFlags & NSEventModifierFlagCommand) {
946 [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
947 }
948 break;
949 }
950
951 /* Convert UTF-16 characters to UCS-4 chars. */
952 std::vector<char32_t> unicode_str = NSStringToUTF32([ event characters ]);
953 if (unicode_str.empty()) unicode_str.push_back(0);
954
955 [ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:NO modifiers:event.modifierFlags ];
956}
957
962- (void)flagsChanged:(NSEvent *)event
963{
964 const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA };
965
966 NSUInteger newMods = event.modifierFlags;
967 if (self->_current_mods == newMods) return;
968
969 /* Iterate through the bits, testing each against the current modifiers */
970 for (unsigned int i = 0, bit = NSEventModifierFlagCapsLock; bit <= NSEventModifierFlagCommand; bit <<= 1, ++i) {
971 unsigned int currentMask, newMask;
972
973 currentMask = self->_current_mods & bit;
974 newMask = newMods & bit;
975
976 if (currentMask && currentMask != newMask) { // modifier up event
977 [ self internalHandleKeycode:mapping[i] unicode:0 pressed:NO modifiers:newMods ];
978 } else if (newMask && currentMask != newMask) { // modifier down event
979 [ self internalHandleKeycode:mapping[i] unicode:0 pressed:YES modifiers:newMods ];
980 }
981 }
982
983 _current_mods = newMods;
984}
985
986
988- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
989{
990 if (!EditBoxInGlobalFocus()) return;
991
992 NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString;
993
994 std::optional<size_t> insert_point;
995 std::optional<size_t> replace_range;
996 if (replacementRange.location != NSNotFound) {
997 /* Calculate the part to be replaced. */
998 std::string_view focused_text{_focused_window->GetFocusedTextbuf()->GetText()};
999 insert_point = Utf8AdvanceByUtf16Units(focused_text, replacementRange.location);
1000 replace_range = *insert_point + Utf8AdvanceByUtf16Units(focused_text.substr(*insert_point), replacementRange.length);
1001 }
1002
1003 HandleTextInput({}, true);
1004 HandleTextInput([ s UTF8String ], false, std::nullopt, insert_point, replace_range);
1005}
1006
1008- (void)insertText:(id)aString
1009{
1010 [ self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0) ];
1011}
1012
1014- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
1015{
1016 if (!EditBoxInGlobalFocus()) return;
1017
1018 NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString;
1019
1020 const char *utf8 = [ s UTF8String ];
1021 if (utf8 != nullptr) {
1022 std::optional<size_t> insert_point;
1023 std::optional<size_t> replace_range;
1024 if (replacementRange.location != NSNotFound) {
1025 /* Calculate the part to be replaced. */
1026 NSRange marked = [ self markedRange ];
1027 std::string_view focused_text{_focused_window->GetFocusedTextbuf()->GetText()};
1028 insert_point = Utf8AdvanceByUtf16Units(focused_text, replacementRange.location + (marked.location != NSNotFound ? marked.location : 0u));
1029 replace_range = *insert_point + Utf8AdvanceByUtf16Units(focused_text.substr(*insert_point), replacementRange.length);
1030 }
1031
1032 /* Convert caret index into a pointer in the UTF-8 string. */
1033 size_t selection = Utf8AdvanceByUtf16Units(utf8, selRange.location);
1034
1035 HandleTextInput(utf8, true, selection, insert_point, replace_range);
1036 }
1037}
1038
1040- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange
1041{
1042 [ self setMarkedText:aString selectedRange:selRange replacementRange:NSMakeRange(NSNotFound, 0) ];
1043}
1044
1046- (void)unmarkText
1047{
1048 HandleTextInput({}, true);
1049}
1050
1052- (NSRange)selectedRange
1053{
1054 if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0);
1055
1056 const Textbuf *text_buf = _focused_window->GetFocusedTextbuf();
1057 std::string_view text = text_buf->GetText();
1058 NSUInteger start = CountUtf16Units(text.substr(0, text_buf->caretpos));
1059 return NSMakeRange(start, 0);
1060}
1061
1063- (NSRange)markedRange
1064{
1065 if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0);
1066
1067 const Textbuf *text_buf = _focused_window->GetFocusedTextbuf();
1068 if (text_buf->markend != 0) {
1069 std::string_view text = text_buf->GetText();
1070 NSUInteger start = CountUtf16Units(text.substr(0, text_buf->markpos));
1071 NSUInteger len = CountUtf16Units(text.substr(text_buf->markpos, text_buf->markend - text_buf->markpos));
1072
1073 return NSMakeRange(start, len);
1074 }
1075
1076 return NSMakeRange(NSNotFound, 0);
1077}
1078
1080- (BOOL)hasMarkedText
1081{
1082 if (!EditBoxInGlobalFocus()) return NO;
1083
1084 return _focused_window->GetFocusedTextbuf()->markend != 0;
1085}
1086
1088- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange
1089{
1090 if (!EditBoxInGlobalFocus()) return nil;
1091
1092 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1093 NSString *s = [ [ NSString alloc] initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding ];
1094 NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange);
1095
1096 if (actualRange != nullptr) *actualRange = valid_range;
1097 if (valid_range.length == 0) return nil;
1098
1099 return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ];
1100}
1101
1103- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange
1104{
1105 return [ self attributedSubstringForProposedRange:theRange actualRange:nil ];
1106}
1107
1109- (NSAttributedString *)attributedString
1110{
1111 if (!EditBoxInGlobalFocus()) return [ [ [ NSAttributedString alloc ] initWithString:@"" ] autorelease ];
1112
1113 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1114 return [ [ [ NSAttributedString alloc ] initWithString:[ [ NSString alloc ] initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding ] ] autorelease ];
1115}
1116
1118- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
1119{
1120 if (!EditBoxInGlobalFocus()) return NSNotFound;
1121
1122 NSPoint view_pt = [ self convertRect:[ [ self window ] convertRectFromScreen:NSMakeRect(thePoint.x, thePoint.y, 0, 0) ] fromView:nil ].origin;
1123
1124 Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y };
1125
1126 auto index = _focused_window->GetTextCharacterAtPosition(pt);
1127 if (index == -1) return NSNotFound;
1128
1129 auto text = _focused_window->GetFocusedTextbuf()->GetText();
1130 return CountUtf16Units(text.substr(0, index));
1131}
1132
1134- (NSRect)firstRectForCharacterRange:(NSRange)aRange
1135{
1136 if (!EditBoxInGlobalFocus()) return NSMakeRect(0, 0, 0, 0);
1137
1138 std::string_view focused_text = _focused_window->GetFocusedTextbuf()->GetText();
1139 /* Convert range to UTF-8 string pointers. */
1140 size_t start = Utf8AdvanceByUtf16Units(focused_text, aRange.location);
1141 size_t end = start + Utf8AdvanceByUtf16Units(focused_text.substr(start), aRange.length);
1142
1143 /* Get the bounding rect for the text range.*/
1144 Rect r = _focused_window->GetTextBoundingRect(start, end);
1145 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);
1146
1147 return [ [ self window ] convertRectToScreen:[ self convertRect:view_rect toView:nil ] ];
1148}
1149
1151- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
1152{
1153 return [ self firstRectForCharacterRange:aRange ];
1154}
1155
1157- (NSArray*)validAttributesForMarkedText
1158{
1159 return [ NSArray array ];
1160}
1161
1166- (void)deleteBackward:(id)sender
1167{
1168 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE, 0);
1169}
1170
1175- (void)deleteWordBackward:(id)sender
1176{
1177 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE | WKC_CTRL, 0);
1178}
1179
1184- (void)deleteForward:(id)sender
1185{
1186 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE, 0);
1187}
1188
1193- (void)deleteWordForward:(id)sender
1194{
1195 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE | WKC_CTRL, 0);
1196}
1197
1202- (void)moveLeft:(id)sender
1203{
1204 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT, 0);
1205}
1206
1211- (void)moveWordLeft:(id)sender
1212{
1213 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT | WKC_CTRL, 0);
1214}
1215
1220- (void)moveRight:(id)sender
1221{
1222 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT, 0);
1223}
1224
1229- (void)moveWordRight:(id)sender
1230{
1231 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT | WKC_CTRL, 0);
1232}
1233
1238- (void)moveUp:(id)sender
1239{
1240 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP, 0);
1241}
1242
1247- (void)moveDown:(id)sender
1248{
1249 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN, 0);
1250}
1251
1256- (void)moveUpAndModifySelection:(id)sender
1257{
1258 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP | WKC_SHIFT, 0);
1259}
1260
1265- (void)moveDownAndModifySelection:(id)sender
1266{
1267 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN | WKC_SHIFT, 0);
1268}
1269
1274- (void)moveToBeginningOfLine:(id)sender
1275{
1276 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_HOME, 0);
1277}
1278
1283- (void)moveToEndOfLine:(id)sender
1284{
1285 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_END, 0);
1286}
1287
1292- (void)scrollPageUp:(id)sender
1293{
1294 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP, 0);
1295}
1296
1301- (void)scrollPageDown:(id)sender
1302{
1303 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN, 0);
1304}
1305
1310- (void)pageUpAndModifySelection:(id)sender
1311{
1312 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP | WKC_SHIFT, 0);
1313}
1314
1319- (void)pageDownAndModifySelection:(id)sender
1320{
1321 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN | WKC_SHIFT, 0);
1322}
1323
1328- (void)scrollToBeginningOfDocument:(id)sender
1329{
1330 /* For compatibility with OTTD on Win/Linux. */
1331 [ self moveToBeginningOfLine:sender ];
1332}
1333
1338- (void)scrollToEndOfDocument:(id)sender
1339{
1340 /* For compatibility with OTTD on Win/Linux. */
1341 [ self moveToEndOfLine:sender ];
1342}
1343
1348- (void)insertNewline:(id)sender
1349{
1350 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RETURN, '\r');
1351}
1352
1357- (void)cancelOperation:(id)sender
1358{
1359 if (EditBoxInGlobalFocus()) HandleKeypress(WKC_ESC, 0);
1360}
1361
1366- (void)doCommandBySelector:(SEL)aSelector
1367{
1368 if ([ self respondsToSelector:aSelector ]) [ self performSelector:aSelector ];
1369}
1370
1371@end
1372
1373
1374@implementation OTTD_CocoaWindowDelegate {
1375 VideoDriver_Cocoa *driver;
1376}
1377
1379- (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv
1380{
1381 if (self = [ super init ]) {
1382 self->driver = drv;
1383 }
1384 return self;
1385}
1390- (BOOL)windowShouldClose:(id)sender
1391{
1392 HandleExitGameRequest();
1393
1394 return NO;
1395}
1397- (void)windowDidEnterFullScreen:(NSNotification *)aNotification
1398{
1399 NSPoint loc = [ driver->cocoaview convertPoint:[ [ aNotification object ] mouseLocationOutsideOfEventStream ] fromView:nil ];
1400 BOOL inside = ([ driver->cocoaview hitTest:loc ] == driver->cocoaview);
1401
1402 if (inside) {
1403 /* We don't care about the event, but the compiler does. */
1404 NSEvent *e = [ [ NSEvent alloc ] init ];
1405 [ driver->cocoaview mouseEntered:e ];
1406 [ e release ];
1407 }
1408}
1410- (void)windowDidChangeBackingProperties:(NSNotification *)notification
1411{
1412 bool did_adjust = AdjustGUIZoom(true);
1413
1414 /* Reallocate screen buffer if necessary. */
1415 driver->AllocateBackingStore();
1416
1417 if (did_adjust) ReInitAllWindows(true);
1418}
1419
1421- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
1422{
1423 return NSApplicationPresentationFullScreen | NSApplicationPresentationHideMenuBar | NSApplicationPresentationHideDock;
1424}
1425
1426@end
1427
1428#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.
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
OS interface for the cocoa video driver.
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 _allow_hidpi_window
Storage for allow_hidpi setting. If true renders OTTD in native resolution.
Definition cocoa_wnd.mm:69
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
bool _tab_is_down
Is tab button pressed.
Definition cocoa_wnd.mm:77
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
NSString * OTTDMainLaunchGameEngine
Name of notification observer used to restart the game loop if necessary.
Definition cocoa_wnd.mm:75
#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:968
bool _left_button_down
Is left mouse button pressed?
Definition gfx.cpp:41
uint8_t _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
Definition gfx.cpp:34
bool _left_button_clicked
Is left mouse button clicked?
Definition gfx.cpp:42
bool _right_button_clicked
Is right mouse button clicked?
Definition gfx.cpp:44
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:1190
bool _right_button_down
Is right mouse button pressed?
Definition gfx.cpp:43
bool AdjustGUIZoom(bool automatic)
Resolve GUI zoom level and adjust GUI to new zoom, if auto-suggestion is requested.
Definition gfx.cpp:1818
void HandleToolbarHotkey(int hotkey)
Handle Toolbar hotkey events - can come from a source like the MacBook Touch Bar.
Definition window.cpp:2635
void HandleMouseEvents()
Handle a mouse event from the video driver.
Definition window.cpp:2965
void HandleKeypress(uint keycode, char32_t key)
Handle keyboard input.
Definition window.cpp:2652
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:2738
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
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
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
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
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
GUISettings gui
settings related to the GUI
T x
X coordinate.
bool UpdateCursorPosition(int x, int y)
Update cursor position on mouse movement.
Definition gfx.cpp:1748
bool fix_at
mouse is moving, but cursor is not (used for scrolling)
Definition gfx_type.h:128
void UpdateCursorPositionRelative(int delta_x, int delta_y)
Update cursor position based on a relative change.
Definition gfx.cpp:1734
bool in_window
mouse inside this window, determines drawing logic
Definition gfx_type.h:147
int wheel
mouse wheel movement
Definition gfx_type.h:127
Dimensions (a width and height) of a rectangle in 2D.
ZoomLevel zoom_min
minimum zoom out level
uint8_t right_mouse_btn_emulation
should we emulate right mouse clicking?
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
virtual const struct Textbuf * GetFocusedTextbuf() const
Get the current input text buffer.
Definition window.cpp:366
int left
x position of left edge of the window
Definition window_gui.h:310
int top
y position of top edge of the window
Definition window_gui.h:311
virtual ptrdiff_t GetTextCharacterAtPosition(const Point &pt) const
Get the character that is rendered at a position by the focused edit box.
Definition window.cpp:410
virtual Rect GetTextBoundingRect(size_t from, size_t to) const
Get the bounding rectangle for a text range if an edit box has the focus.
Definition window.cpp:395
void ReInitAllWindows(bool zoom_changed)
Re-initialize all windows.
Definition window.cpp:3417
bool EditBoxInGlobalFocus()
Check if an edit box is in global focus.
Definition window.cpp:449
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:20