From 9ccef816f90e9a7e3042cd89141ba3c0956d7bb9 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 26 Dec 2020 16:07:47 +0100 Subject: Codechange: [OSX] Re-arrange the OSX video driver code by combining all drawing code and moving the window/event handling to a different file. This is just a code move/rename, not a functionality change. --- src/video/cocoa/CMakeLists.txt | 3 +- src/video/cocoa/cocoa_v.h | 57 +- src/video/cocoa/cocoa_v.mm | 1151 ++++++++++++++-------------------------- src/video/cocoa/cocoa_wnd.h | 77 +++ src/video/cocoa/cocoa_wnd.mm | 879 ++++++++++++++++++++++++++++++ src/video/cocoa/wnd_quartz.mm | 587 -------------------- 6 files changed, 1373 insertions(+), 1381 deletions(-) create mode 100644 src/video/cocoa/cocoa_wnd.h create mode 100644 src/video/cocoa/cocoa_wnd.mm delete mode 100644 src/video/cocoa/wnd_quartz.mm (limited to 'src/video') diff --git a/src/video/cocoa/CMakeLists.txt b/src/video/cocoa/CMakeLists.txt index 4fff132f3..8bcc6b2e0 100644 --- a/src/video/cocoa/CMakeLists.txt +++ b/src/video/cocoa/CMakeLists.txt @@ -2,7 +2,8 @@ add_files( cocoa_keys.h cocoa_v.h cocoa_v.mm + cocoa_wnd.h + cocoa_wnd.mm event.mm - wnd_quartz.mm CONDITION APPLE ) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 3da6848d0..c05bd08e2 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -12,6 +12,8 @@ #include "../video_driver.hpp" +extern bool _cocoa_video_started; + class VideoDriver_Cocoa : public VideoDriver { public: const char *Start(const StringList ¶m) override; @@ -199,59 +201,4 @@ void QZ_GameLoop(); uint QZ_ListModes(OTTD_Point *modes, uint max_modes, CGDirectDisplayID display_id, int display_depth); -/** Category of NSCursor to allow cursor showing/hiding */ -@interface NSCursor (OTTD_QuickdrawCursor) -+ (NSCursor *) clearCocoaCursor; -@end - -/** Subclass of NSWindow to cater our special needs */ -@interface OTTD_CocoaWindow : NSWindow { - CocoaSubdriver *driver; -} - -- (void)setDriver:(CocoaSubdriver*)drv; - -- (void)miniaturize:(id)sender; -- (void)display; -- (void)setFrame:(NSRect)frameRect display:(BOOL)flag; -- (void)appDidHide:(NSNotification*)note; -- (void)appWillUnhide:(NSNotification*)note; -- (void)appDidUnhide:(NSNotification*)note; -- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; -@end - -/** Subclass of NSView to fix Quartz rendering and mouse awareness */ -@interface OTTD_CocoaView : NSView -{ - CocoaSubdriver *driver; - NSTrackingRectTag trackingtag; -} -- (void)setDriver:(CocoaSubdriver*)drv; -- (void)drawRect:(NSRect)rect; -- (BOOL)isOpaque; -- (BOOL)acceptsFirstResponder; -- (BOOL)becomeFirstResponder; -- (void)setTrackingRect; -- (void)clearTrackingRect; -- (void)resetCursorRects; -- (void)viewWillMoveToWindow:(NSWindow *)win; -- (void)viewDidMoveToWindow; -- (void)mouseEntered:(NSEvent *)theEvent; -- (void)mouseExited:(NSEvent *)theEvent; -@end - -/** Delegate for our NSWindow to send ask for quit on close */ -@interface OTTD_CocoaWindowDelegate : NSObject -{ - CocoaSubdriver *driver; -} - -- (void)setDriver:(CocoaSubdriver*)drv; - -- (BOOL)windowShouldClose:(id)sender; -- (void)windowDidEnterFullScreen:(NSNotification *)aNotification; -- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification; -@end - - #endif /* VIDEO_COCOA_H */ diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index e4a0a0c6f..c01d9c42e 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -26,13 +26,16 @@ #include "../../openttd.h" #include "../../debug.h" +#include "../../rev.h" #include "../../core/geometry_type.hpp" #include "cocoa_v.h" +#include "cocoa_wnd.h" #include "../../blitter/factory.hpp" -#include "../../fileio_func.h" #include "../../gfx_func.h" #include "../../window_func.h" #include "../../window_gui.h" +#include "../../core/math_func.hpp" +#include "../../framerate_type.h" #include #import /* for MAXPATHLEN */ @@ -45,188 +48,16 @@ * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. */ +/* On some old versions of MAC OS this may not be defined. + * Those versions generally only produce code for PPC. So it should be safe to + * set this to 0. */ +#ifndef kCGBitmapByteOrder32Host +#define kCGBitmapByteOrder32Host 0 +#endif -@interface OTTDMain : NSObject -@end - - -static NSAutoreleasePool *_ottd_autorelease_pool; -static OTTDMain *_ottd_main; -static bool _cocoa_video_started = false; -static bool _cocoa_video_dialog = false; - +bool _cocoa_video_started = false; CocoaSubdriver *_cocoa_subdriver = NULL; -static NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine"; - - -/** - * The main class of the application, the application's delegate. - */ -@implementation OTTDMain -/** - * Stop the game engine. Must be called on main thread. - */ -- (void)stopEngine -{ - [ NSApp stop:self ]; - - /* Send an empty event to return from the run loop. Without that, application is stuck waiting for an event. */ - NSEvent *event = [ NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0.0 windowNumber:0 context:nil subtype:0 data1:0 data2:0 ]; - [ NSApp postEvent:event atStart:YES ]; -} - -/** - * Start the game loop. - */ -- (void)launchGameEngine: (NSNotification*) note -{ - /* Setup cursor for the current _game_mode. */ - [ _cocoa_subdriver->cocoaview resetCursorRects ]; - - /* Hand off to main application code. */ - QZ_GameLoop(); - - /* We are done, thank you for playing. */ - [ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ]; -} - -/** - * Called when the internal event loop has just started running. - */ -- (void) applicationDidFinishLaunching: (NSNotification*) note -{ - /* Add a notification observer so we can restart the game loop later on if necessary. */ - [ [ NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(launchGameEngine:) name:OTTDMainLaunchGameEngine object:nil ]; - - /* Start game loop. */ - [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ]; -} - -/** - * Display the in game quit confirmation dialog. - */ -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender -{ - HandleExitGameRequest(); - - return NSTerminateCancel; // NSTerminateLater ? -} - -/** - * Remove ourself as a notification observer. - */ -- (void)unregisterObserver -{ - [ [ NSNotificationCenter defaultCenter ] removeObserver:self ]; -} -@end - -/** - * Initialize the application menu shown in top bar. - */ -static void setApplicationMenu() -{ - NSString *appName = @"OpenTTD"; - NSMenu *appleMenu = [ [ NSMenu alloc ] initWithTitle:appName ]; - - /* Add menu items */ - NSString *title = [ @"About " stringByAppendingString:appName ]; - [ appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@"" ]; - - [ appleMenu addItem:[ NSMenuItem separatorItem ] ]; - - title = [ @"Hide " stringByAppendingString:appName ]; - [ appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h" ]; - - NSMenuItem *menuItem = [ appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h" ]; - [ menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask) ]; - - [ appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@"" ]; - - [ appleMenu addItem:[ NSMenuItem separatorItem ] ]; - - title = [ @"Quit " stringByAppendingString:appName ]; - [ appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q" ]; - - /* Put menu into the menubar */ - menuItem = [ [ NSMenuItem alloc ] initWithTitle:@"" action:nil keyEquivalent:@"" ]; - [ menuItem setSubmenu:appleMenu ]; - [ [ NSApp mainMenu ] addItem:menuItem ]; - - /* Tell the application object that this is now the application menu. - * This interesting Objective-C construct is used because not all SDK - * versions define this method publicly. */ - if ([ NSApp respondsToSelector:@selector(setAppleMenu:) ]) { - [ NSApp performSelector:@selector(setAppleMenu:) withObject:appleMenu ]; - } - - /* Finally give up our references to the objects */ - [ appleMenu release ]; - [ menuItem release ]; -} - -/** - * Create a window menu. - */ -static void setupWindowMenu() -{ - NSMenu *windowMenu = [ [ NSMenu alloc ] initWithTitle:@"Window" ]; - - /* "Minimize" item */ - [ windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m" ]; - - /* Put menu into the menubar */ - NSMenuItem *menuItem = [ [ NSMenuItem alloc ] initWithTitle:@"Window" action:nil keyEquivalent:@"" ]; - [ menuItem setSubmenu:windowMenu ]; - [ [ NSApp mainMenu ] addItem:menuItem ]; - - if (MacOSVersionIsAtLeast(10, 7, 0)) { - /* The OS will change the name of this menu item automatically */ - [ windowMenu addItemWithTitle:@"Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"^f" ]; - } - - /* Tell the application object that this is now the window menu */ - [ NSApp setWindowsMenu:windowMenu ]; - - /* Finally give up our references to the objects */ - [ windowMenu release ]; - [ menuItem release ]; -} - -/** - * Startup the application. - */ -static void setupApplication() -{ - ProcessSerialNumber psn = { 0, kCurrentProcess }; - - /* Ensure the application object is initialised */ - [ NSApplication sharedApplication ]; - - /* Tell the dock about us */ - OSStatus returnCode = TransformProcessType(&psn, kProcessTransformToForegroundApplication); - if (returnCode != 0) DEBUG(driver, 0, "Could not change to foreground application. Error %d", (int)returnCode); - - /* Disable the system-wide tab feature as we only have one window. */ - if ([ NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:) ]) { - /* We use nil instead of NO as withObject requires an id. */ - [ NSWindow performSelector:@selector(setAllowsAutomaticWindowTabbing:) withObject:nil]; - } - - /* Become the front process, important when start from the command line. */ - [ [ NSApplication sharedApplication ] activateIgnoringOtherApps:YES ]; - - /* Set up the menubar */ - [ NSApp setMainMenu:[ [ NSMenu alloc ] init ] ]; - setApplicationMenu(); - setupWindowMenu(); - - /* Create OTTDMain and make it the app delegate */ - _ottd_main = [ [ OTTDMain alloc ] init ]; - [ NSApp setDelegate:_ottd_main ]; -} - static bool ModeSorter(const OTTD_Point &p1, const OTTD_Point &p2) { @@ -384,13 +215,11 @@ void VideoDriver_Cocoa::Stop() { if (!_cocoa_video_started) return; - [ _ottd_main unregisterObserver ]; + CocoaExitApplication(); delete _cocoa_subdriver; _cocoa_subdriver = NULL; - [ _ottd_main release ]; - _cocoa_video_started = false; } @@ -404,10 +233,8 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) if (_cocoa_video_started) return "Already started"; _cocoa_video_started = true; - setupApplication(); - /* Don't create a window or enter fullscreen if we're just going to show a dialog. */ - if (_cocoa_video_dialog) return NULL; + if (!CocoaSetupApplication()) return NULL; int width = _cur_resolution.width; int height = _cur_resolution.height; @@ -505,695 +332,543 @@ void VideoDriver_Cocoa::EditBoxLostFocus() HandleTextInput(NULL, true); } -/** - * Catch asserts prior to initialization of the videodriver. - * - * @param title Window title. - * @param message Message text. - * @param buttonLabel Button text. - * - * @note This is needed since sometimes assert is called before the videodriver is initialized . - */ -void CocoaDialog(const char *title, const char *message, const char *buttonLabel) -{ - _cocoa_video_dialog = true; - - bool wasstarted = _cocoa_video_started; - if (VideoDriver::GetInstance() == NULL) { - setupApplication(); // Setup application before showing dialog - } else if (!_cocoa_video_started && VideoDriver::GetInstance()->Start(StringList()) != NULL) { - fprintf(stderr, "%s: %s\n", title, message); - return; - } +#ifdef ENABLE_COCOA_QUARTZ - NSAlert *alert = [ [ NSAlert alloc ] init ]; - [ alert setAlertStyle: NSCriticalAlertStyle ]; - [ alert setMessageText:[ NSString stringWithUTF8String:title ] ]; - [ alert setInformativeText:[ NSString stringWithUTF8String:message ] ]; - [ alert addButtonWithTitle: [ NSString stringWithUTF8String:buttonLabel ] ]; - [ alert runModal ]; - [ alert release ]; +class WindowQuartzSubdriver; - if (!wasstarted && VideoDriver::GetInstance() != NULL) VideoDriver::GetInstance()->Stop(); +/* Subclass of OTTD_CocoaView to fix Quartz rendering */ +@interface OTTD_QuartzView : OTTD_CocoaView +- (void)setDriver:(WindowQuartzSubdriver*)drv; +- (void)drawRect:(NSRect)invalidRect; +@end - _cocoa_video_dialog = false; -} +class WindowQuartzSubdriver : public CocoaSubdriver { +private: + /** + * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. + * + * @param left The x coord for the left edge of the box to blit. + * @param top The y coord for the top edge of the box to blit. + * @param right The x coord for the right edge of the box to blit. + * @param bottom The y coord for the bottom edge of the box to blit. + */ + void BlitIndexedToView32(int left, int top, int right, int bottom); -/** Set the application's bundle directory. - * - * This is needed since OS X application bundles do not have a - * current directory and the data files are 'somewhere' in the bundle. - */ -void cocoaSetApplicationBundleDir() -{ - extern std::array _searchpaths; + virtual void GetDeviceInfo(); + virtual bool SetVideoMode(int width, int height, int bpp); - char tmp[MAXPATHLEN]; - CFAutoRelease url(CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle())); - if (CFURLGetFileSystemRepresentation(url.get(), true, (unsigned char*)tmp, MAXPATHLEN)) { - _searchpaths[SP_APPLICATION_BUNDLE_DIR] = tmp; - AppendPathSeparator(_searchpaths[SP_APPLICATION_BUNDLE_DIR]); - } else { - _searchpaths[SP_APPLICATION_BUNDLE_DIR].clear(); - } -} +public: + WindowQuartzSubdriver(); + virtual ~WindowQuartzSubdriver(); -/** - * Setup autorelease for the application pool. - * - * These are called from main() to prevent a _NSAutoreleaseNoPool error when - * exiting before the cocoa video driver has been loaded - */ -void cocoaSetupAutoreleasePool() -{ - _ottd_autorelease_pool = [ [ NSAutoreleasePool alloc ] init ]; -} + virtual void Draw(bool force_update); + virtual void MakeDirty(int left, int top, int width, int height); + virtual void UpdatePalette(uint first_color, uint num_colors); -/** - * Autorelease the application pool. - */ -void cocoaReleaseAutoreleasePool() -{ - [ _ottd_autorelease_pool release ]; -} + virtual uint ListModes(OTTD_Point *modes, uint max_modes); + virtual bool ChangeResolution(int w, int h, int bpp); -/** - * Re-implement the system cursor in order to allow hiding and showing it nicely - */ -@implementation NSCursor (OTTD_CocoaCursor) -+ (NSCursor *) clearCocoaCursor -{ - /* RAW 16x16 transparent GIF */ - unsigned char clearGIFBytes[] = { - 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, - 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, - 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4, - 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B}; - NSData *clearGIFData = [ NSData dataWithBytesNoCopy:&clearGIFBytes[0] length:55 freeWhenDone:NO ]; - NSImage *clearImg = [ [ NSImage alloc ] initWithData:clearGIFData ]; - return [ [ NSCursor alloc ] initWithImage:clearImg hotSpot:NSMakePoint(0.0,0.0) ]; -} -@end + virtual bool IsFullscreen() { return false; } + virtual bool ToggleFullscreen(); /* Full screen mode on OSX 10.7 */ + + virtual int GetWidth() { return window_width; } + virtual int GetHeight() { return window_height; } + virtual void *GetPixelBuffer() { return buffer_depth == 8 ? pixel_buffer : window_buffer; } + + /* Convert local coordinate to window server (CoreGraphics) coordinate */ + virtual CGPoint PrivateLocalToCG(NSPoint *p); + + virtual NSPoint GetMouseLocation(NSEvent *event); + virtual bool MouseIsInsideView(NSPoint *pt); + + virtual bool IsActive() { return active; } + void SetPortAlphaOpaque(); + bool WindowResized(); +}; -@implementation OTTD_CocoaWindow -- (void)setDriver:(CocoaSubdriver*)drv +@implementation OTTD_QuartzView + +- (void)setDriver:(WindowQuartzSubdriver*)drv { driver = drv; } -/** - * Minimize the window - */ -- (void)miniaturize:(id)sender +- (void)drawRect:(NSRect)invalidRect { - /* make the alpha channel opaque so anim won't have holes in it */ - driver->SetPortAlphaOpaque(); + if (driver->cgcontext == NULL) return; - /* window is hidden now */ - driver->active = false; + CGContextRef viewContext = (CGContextRef)[ [ NSGraphicsContext currentContext ] graphicsPort ]; + CGContextSetShouldAntialias(viewContext, FALSE); + CGContextSetInterpolationQuality(viewContext, kCGInterpolationNone); - [ super miniaturize:sender ]; -} + /* The obtained 'rect' is actually a union of all dirty rects, let's ask for an explicit list of rects instead */ + const NSRect *dirtyRects; + NSInteger dirtyRectCount; + [ self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount ]; -/** - * This method fires just before the window deminaturizes from the Dock. - * We'll save the current visible surface, let the window manager redraw any - * UI elements, and restore the surface. This way, no expose event - * is required, and the deminiaturize works perfectly. - */ -- (void)display -{ - driver->SetPortAlphaOpaque(); + /* We need an Image in order to do blitting, but as we don't touch the context between this call and drawing no copying will actually be done here */ + CGImageRef fullImage = CGBitmapContextCreateImage(driver->cgcontext); - /* save current visible surface */ - [ self cacheImageInRect:[ driver->cocoaview frame ] ]; - - /* let the window manager redraw controls, border, etc */ - [ super display ]; + /* Calculate total area we are blitting */ + uint32 blitArea = 0; + for (int n = 0; n < dirtyRectCount; n++) { + blitArea += (uint32)(dirtyRects[n].size.width * dirtyRects[n].size.height); + } - /* restore visible surface */ - [ self restoreCachedImage ]; + /* + * This might be completely stupid, but in my extremely subjective opinion it feels faster + * The point is, if we're blitting less than 50% of the dirty rect union then it's still a good idea to blit each dirty + * rect separately but if we blit more than that, it's just cheaper to blit the entire union in one pass. + * Feel free to remove or find an even better value than 50% ... / blackis + */ + NSRect frameRect = [ self frame ]; + if (blitArea / (float)(invalidRect.size.width * invalidRect.size.height) > 0.5f) { + NSRect rect = invalidRect; + CGRect clipRect; + CGRect blitRect; + + blitRect.origin.x = rect.origin.x; + blitRect.origin.y = rect.origin.y; + blitRect.size.width = rect.size.width; + blitRect.size.height = rect.size.height; + + clipRect.origin.x = rect.origin.x; + clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; + + clipRect.size.width = rect.size.width; + clipRect.size.height = rect.size.height; + + /* Blit dirty part of image */ + CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); + CGContextDrawImage(viewContext, blitRect, clippedImage); + CGImageRelease(clippedImage); + } else { + for (int n = 0; n < dirtyRectCount; n++) { + NSRect rect = dirtyRects[n]; + CGRect clipRect; + CGRect blitRect; + + blitRect.origin.x = rect.origin.x; + blitRect.origin.y = rect.origin.y; + blitRect.size.width = rect.size.width; + blitRect.size.height = rect.size.height; + + clipRect.origin.x = rect.origin.x; + clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; + + clipRect.size.width = rect.size.width; + clipRect.size.height = rect.size.height; + + /* Blit dirty part of image */ + CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); + CGContextDrawImage(viewContext, blitRect, clippedImage); + CGImageRelease(clippedImage); + } + } - /* window is visible again */ - driver->active = true; + CGImageRelease(fullImage); } -/** - * Define the rectangle we draw our window in - */ -- (void)setFrame:(NSRect)frameRect display:(BOOL)flag -{ - [ super setFrame:frameRect display:flag ]; - /* Don't do anything if the window is currently being created */ - if (driver->setup) return; +@end - if (!driver->WindowResized()) error("Cocoa: Failed to resize window."); -} -/** - * Handle hiding of the application - */ -- (void)appDidHide:(NSNotification*)note -{ - driver->active = false; -} -/** - * Fade-in the application and restore display plane - */ -- (void)appWillUnhide:(NSNotification*)note + +void WindowQuartzSubdriver::GetDeviceInfo() { - driver->SetPortAlphaOpaque (); + /* Initialize the video settings; this data persists between mode switches + * and gather some information that is useful to know about the display */ + + /* Use the new API when compiling for OSX 10.6 or later */ + CGDisplayModeRef cur_mode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay); + if (cur_mode == NULL) { return; } + + this->device_width = CGDisplayModeGetWidth(cur_mode); + this->device_height = CGDisplayModeGetHeight(cur_mode); + + CGDisplayModeRelease(cur_mode); } -/** - * Unhide and restore display plane and re-activate driver + +/** Switch to full screen mode on OSX 10.7 + * @return Whether we switched to full screen */ -- (void)appDidUnhide:(NSNotification*)note +bool WindowQuartzSubdriver::ToggleFullscreen() { - driver->active = true; + if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) { + [ this->window performSelector:@selector(toggleFullScreen:) withObject:this->window ]; + return true; + } + + return false; } -/** - * Initialize event system for the application rectangle - */ -- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag + +bool WindowQuartzSubdriver::SetVideoMode(int width, int height, int bpp) { - /* Make our window subclass receive these application notifications */ - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; + this->setup = true; + this->GetDeviceInfo(); - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; + if (width > this->device_width) width = this->device_width; + if (height > this->device_height) height = this->device_height; - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appWillUnhide:) name:NSApplicationWillUnhideNotification object:NSApp ]; + NSRect contentRect = NSMakeRect(0, 0, width, height); - return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; -} + /* Check if we should recreate the window */ + if (this->window == nil) { + OTTD_CocoaWindowDelegate *delegate; -@end + /* Set the window style */ + unsigned int style = NSTitledWindowMask; + style |= (NSMiniaturizableWindowMask | NSClosableWindowMask); + style |= NSResizableWindowMask; + /* Manually create a window, avoids having a nib file resource */ + this->window = [ [ OTTD_CocoaWindow alloc ] + initWithContentRect:contentRect + styleMask:style + backing:NSBackingStoreBuffered + defer:NO ]; + if (this->window == nil) { + DEBUG(driver, 0, "Could not create the Cocoa window."); + this->setup = false; + return false; + } -/** - * Count the number of UTF-16 code points in a range of an UTF-8 string. - * @param from Start of the range. - * @param to End of the range. - * @return Number of UTF-16 code points in the range. - */ -static NSUInteger CountUtf16Units(const char *from, const char *to) -{ - NSUInteger i = 0; + /* Add built in full-screen support when available (OS X 10.7 and higher) + * This code actually compiles for 10.5 and later, but only makes sense in conjunction + * with the quartz fullscreen support as found only in 10.7 and later + */ + if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) { + NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ]; + behavior |= NSWindowCollectionBehaviorFullScreenPrimary; + [ this->window setCollectionBehavior:behavior ]; - while (from < to) { - WChar c; - size_t len = Utf8Decode(&c, from); - i += len < 4 ? 1 : 2; // Watch for surrogate pairs. - from += len; - } + NSButton* fullscreenButton = [ this->window standardWindowButton:NSWindowFullScreenButton ]; + [ fullscreenButton setAction:@selector(toggleFullScreen:) ]; + [ fullscreenButton setTarget:this->window ]; - return i; -} + [ this->window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary ]; + } -/** - * Advance an UTF-8 string by a number of equivalent UTF-16 code points. - * @param str UTF-8 string. - * @param count Number of UTF-16 code points to advance the string by. - * @return Advanced string pointer. - */ -static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) -{ - for (NSUInteger i = 0; i < count && *str != '\0'; ) { - WChar c; - size_t len = Utf8Decode(&c, str); - i += len < 4 ? 1 : 2; // Watch for surrogates. - str += len; - } + [ this->window setDriver:this ]; - return str; -} + char caption[50]; + snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); + NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption ]; + [ this->window setTitle:nsscaption ]; + [ this->window setMiniwindowTitle:nsscaption ]; + [ nsscaption release ]; -@implementation OTTD_CocoaView -/** - * Initialize the driver - */ -- (void)setDriver:(CocoaSubdriver*)drv -{ - driver = drv; -} -/** - * Define the opaqueness of the window / screen - * @return opaqueness of window / screen - */ -- (BOOL)isOpaque -{ - return YES; -} -/** - * Draws a rectangle on the screen. - * It's overwritten by the individual drivers but must be defined - */ -- (void)drawRect:(NSRect)invalidRect -{ - return; -} -/** - * Allow to handle events - */ -- (BOOL)acceptsFirstResponder -{ - return YES; -} -/** - * Actually handle events - */ -- (BOOL)becomeFirstResponder -{ - return YES; -} -/** - * Define the rectangle where we draw our application window - */ -- (void)setTrackingRect -{ - NSPoint loc = [ self convertPoint:[ [ self window ] mouseLocationOutsideOfEventStream ] fromView:nil ]; - BOOL inside = ([ self hitTest:loc ]==self); - if (inside) [ [ self window ] makeFirstResponder:self ]; - trackingtag = [ self addTrackingRect:[ self visibleRect ] owner:self userData:nil assumeInside:inside ]; -} -/** - * Return responsibility for the application window to system - */ -- (void)clearTrackingRect -{ - [ self removeTrackingRect:trackingtag ]; -} -/** - * Declare responsibility for the cursor within our application rect - */ -- (void)resetCursorRects -{ - [ super resetCursorRects ]; - [ self clearTrackingRect ]; - [ self setTrackingRect ]; - [ self addCursorRect:[ self bounds ] cursor:(_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) ]; -} -/** - * Prepare for moving the application window - */ -- (void)viewWillMoveToWindow:(NSWindow *)win -{ - if (!win && [ self window ]) [ self clearTrackingRect ]; -} -/** - * Restore our responsibility for our application window after moving - */ -- (void)viewDidMoveToWindow -{ - if ([ self window ]) [ self setTrackingRect ]; -} -/** - * Make OpenTTD aware that it has control over the mouse - */ -- (void)mouseEntered:(NSEvent *)theEvent -{ - _cursor.in_window = true; -} -/** - * Make OpenTTD aware that it has NOT control over the mouse - */ -- (void)mouseExited:(NSEvent *)theEvent -{ - if (_cocoa_subdriver != NULL) UndrawMouseCursor(); - _cursor.in_window = false; -} + [ this->window setContentMinSize:NSMakeSize(64.0f, 64.0f) ]; + [ this->window setAcceptsMouseMovedEvents:YES ]; + [ this->window setViewsNeedDisplay:NO ]; -/** Insert the given text at the given range. */ -- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange -{ - if (!EditBoxInGlobalFocus()) return; + delegate = [ [ OTTD_CocoaWindowDelegate alloc ] init ]; + [ delegate setDriver:this ]; + [ this->window setDelegate:[ delegate autorelease ] ]; + } else { + /* We already have a window, just change its size */ + [ this->window setContentSize:contentRect.size ]; - NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString; + /* Ensure frame height - title bar height >= view height */ + contentRect.size.height = Clamp(height, 0, (int)[ this->window frame ].size.height - 22 /* 22 is the height of title bar of window*/); - const char *insert_point = NULL; - const char *replace_range = NULL; - if (replacementRange.location != NSNotFound) { - /* Calculate the part to be replaced. */ - insert_point = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), replacementRange.location); - replace_range = Utf8AdvanceByUtf16Units(insert_point, replacementRange.length); + if (this->cocoaview != nil) { + height = (int)contentRect.size.height; + [ this->cocoaview setFrameSize:contentRect.size ]; + } } - HandleTextInput(NULL, true); - HandleTextInput([ s UTF8String ], false, NULL, insert_point, replace_range); -} + this->window_width = width; + this->window_height = height; + this->buffer_depth = bpp; -/** Insert the given text at the caret. */ -- (void)insertText:(id)aString -{ - [ self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0) ]; -} + [ (OTTD_CocoaWindow *)this->window center ]; -/** Set a new marked text and reposition the caret. */ -- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange -{ - if (!EditBoxInGlobalFocus()) return; - - NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString; - - const char *utf8 = [ s UTF8String ]; - if (utf8 != NULL) { - const char *insert_point = NULL; - const char *replace_range = NULL; - if (replacementRange.location != NSNotFound) { - /* Calculate the part to be replaced. */ - NSRange marked = [ self markedRange ]; - insert_point = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), replacementRange.location + (marked.location != NSNotFound ? marked.location : 0u)); - replace_range = Utf8AdvanceByUtf16Units(insert_point, replacementRange.length); + /* Only recreate the view if it doesn't already exist */ + if (this->cocoaview == nil) { + this->cocoaview = [ [ OTTD_QuartzView alloc ] initWithFrame:contentRect ]; + if (this->cocoaview == nil) { + DEBUG(driver, 0, "Could not create the Quartz view."); + this->setup = false; + return false; } - /* Convert caret index into a pointer in the UTF-8 string. */ - const char *selection = Utf8AdvanceByUtf16Units(utf8, selRange.location); + [ this->cocoaview setDriver:this ]; - HandleTextInput(utf8, true, selection, insert_point, replace_range); + [ (NSView*)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; + [ this->window setContentView:cocoaview ]; + [ this->cocoaview release ]; + [ this->window makeKeyAndOrderFront:nil ]; } -} -/** Set a new marked text and reposition the caret. */ -- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange -{ - [ self setMarkedText:aString selectedRange:selRange replacementRange:NSMakeRange(NSNotFound, 0) ]; -} + [ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ]; + this->color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + if (this->color_space == nullptr) this->color_space = CGColorSpaceCreateDeviceRGB(); + if (this->color_space == nullptr) error("Could not get a valid colour space for drawing."); -/** Unmark the current marked text. */ -- (void)unmarkText -{ - HandleTextInput(NULL, true); -} + bool ret = WindowResized(); + this->UpdatePalette(0, 256); -/** Get the caret position. */ -- (NSRange)selectedRange -{ - if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0); + this->setup = false; - NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), _focused_window->GetCaret()); - return NSMakeRange(start, 0); + return ret; } -/** Get the currently marked range. */ -- (NSRange)markedRange +void WindowQuartzSubdriver::BlitIndexedToView32(int left, int top, int right, int bottom) { - if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0); - - size_t mark_len; - const char *mark = _focused_window->GetMarkedText(&mark_len); - if (mark != NULL) { - NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), mark); - NSUInteger len = CountUtf16Units(mark, mark + mark_len); + const uint32 *pal = this->palette; + const uint8 *src = (uint8*)this->pixel_buffer; + uint32 *dst = (uint32*)this->window_buffer; + uint width = this->window_width; + uint pitch = this->window_width; - return NSMakeRange(start, len); + for (int y = top; y < bottom; y++) { + for (int x = left; x < right; x++) { + dst[y * pitch + x] = pal[src[y * width + x]]; + } } - - return NSMakeRange(NSNotFound, 0); } -/** Is any text marked? */ -- (BOOL)hasMarkedText -{ - if (!EditBoxInGlobalFocus()) return NO; - - size_t len; - return _focused_window->GetMarkedText(&len) != NULL; -} -/** Get a string corresponding to the given range. */ -- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange +WindowQuartzSubdriver::WindowQuartzSubdriver() { - if (!EditBoxInGlobalFocus()) return nil; + this->window_width = 0; + this->window_height = 0; + this->buffer_depth = 0; + this->window_buffer = NULL; + this->pixel_buffer = NULL; + this->active = false; + this->setup = false; - NSString *s = [ NSString stringWithUTF8String:_focused_window->GetFocusedText() ]; - NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange); + this->window = nil; + this->cocoaview = nil; - if (actualRange != NULL) *actualRange = valid_range; - if (valid_range.length == 0) return nil; + this->cgcontext = NULL; - return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ]; + this->num_dirty_rects = MAX_DIRTY_RECTS; } -/** Get a string corresponding to the given range. */ -- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange +WindowQuartzSubdriver::~WindowQuartzSubdriver() { - return [ self attributedSubstringForProposedRange:theRange actualRange:NULL ]; -} + /* Release window mode resources */ + if (this->window != nil) [ this->window close ]; -/** Get the current edit box string. */ -- (NSAttributedString *)attributedString -{ - if (!EditBoxInGlobalFocus()) return [ [ [ NSAttributedString alloc ] initWithString:@"" ] autorelease ]; + CGContextRelease(this->cgcontext); - return [ [ [ NSAttributedString alloc ] initWithString:[ NSString stringWithUTF8String:_focused_window->GetFocusedText() ] ] autorelease ]; + CGColorSpaceRelease(this->color_space); + free(this->window_buffer); + free(this->pixel_buffer); } -/** Get the character that is rendered at the given point. */ -- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint +void WindowQuartzSubdriver::Draw(bool force_update) { - if (!EditBoxInGlobalFocus()) return NSNotFound; - - NSPoint view_pt = [ self convertRect:[ [ self window ] convertRectFromScreen:NSMakeRect(thePoint.x, thePoint.y, 0, 0) ] fromView:nil ].origin; - - Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y }; + PerformanceMeasurer framerate(PFE_VIDEO); - const char *ch = _focused_window->GetTextCharacterAtPosition(pt); - if (ch == NULL) return NSNotFound; + /* Check if we need to do anything */ + if (this->num_dirty_rects == 0 || [ this->window isMiniaturized ]) return; - return CountUtf16Units(_focused_window->GetFocusedText(), ch); -} + if (this->num_dirty_rects >= MAX_DIRTY_RECTS) { + this->num_dirty_rects = 1; + this->dirty_rects[0].left = 0; + this->dirty_rects[0].top = 0; + this->dirty_rects[0].right = this->window_width; + this->dirty_rects[0].bottom = this->window_height; + } -/** Get the bounding rect for the given range. */ -- (NSRect)firstRectForCharacterRange:(NSRange)aRange -{ - if (!EditBoxInGlobalFocus()) return NSMakeRect(0, 0, 0, 0); + /* Build the region of dirty rectangles */ + for (int i = 0; i < this->num_dirty_rects; i++) { + /* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */ + if (this->buffer_depth == 8) { + BlitIndexedToView32( + this->dirty_rects[i].left, + this->dirty_rects[i].top, + this->dirty_rects[i].right, + this->dirty_rects[i].bottom + ); + } - /* Convert range to UTF-8 string pointers. */ - const char *start = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), aRange.location); - const char *end = aRange.length != 0 ? Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), aRange.location + aRange.length) : start; + NSRect dirtyrect; + dirtyrect.origin.x = this->dirty_rects[i].left; + dirtyrect.origin.y = this->window_height - this->dirty_rects[i].bottom; + dirtyrect.size.width = this->dirty_rects[i].right - this->dirty_rects[i].left; + dirtyrect.size.height = this->dirty_rects[i].bottom - this->dirty_rects[i].top; - /* Get the bounding rect for the text range.*/ - Rect r = _focused_window->GetTextBoundingRect(start, end); - 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); + /* Normally drawRect will be automatically called by Mac OS X during next update cycle, + * and then blitting will occur. If force_update is true, it will be done right now. */ + [ this->cocoaview setNeedsDisplayInRect:dirtyrect ]; + if (force_update) [ this->cocoaview displayIfNeeded ]; + } - return [ [ self window ] convertRectToScreen:[ self convertRect:view_rect toView:nil ] ]; + this->num_dirty_rects = 0; } -/** Get the bounding rect for the given range. */ -- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange +void WindowQuartzSubdriver::MakeDirty(int left, int top, int width, int height) { - return [ self firstRectForCharacterRange:aRange ]; + if (this->num_dirty_rects < MAX_DIRTY_RECTS) { + dirty_rects[this->num_dirty_rects].left = left; + dirty_rects[this->num_dirty_rects].top = top; + dirty_rects[this->num_dirty_rects].right = left + width; + dirty_rects[this->num_dirty_rects].bottom = top + height; + } + this->num_dirty_rects++; } -/** Get all string attributes that we can process for marked text. */ -- (NSArray*)validAttributesForMarkedText +void WindowQuartzSubdriver::UpdatePalette(uint first_color, uint num_colors) { - return [ NSArray array ]; -} + if (this->buffer_depth != 8) return; -/** Delete single character left of the cursor. */ -- (void)deleteBackward:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE, 0); -} + for (uint i = first_color; i < first_color + num_colors; i++) { + uint32 clr = 0xff000000; + clr |= (uint32)_cur_palette.palette[i].r << 16; + clr |= (uint32)_cur_palette.palette[i].g << 8; + clr |= (uint32)_cur_palette.palette[i].b; + this->palette[i] = clr; + } -/** Delete word left of the cursor. */ -- (void)deleteWordBackward:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE | WKC_CTRL, 0); + this->num_dirty_rects = MAX_DIRTY_RECTS; } -/** Delete single character right of the cursor. */ -- (void)deleteForward:(id)sender +uint WindowQuartzSubdriver::ListModes(OTTD_Point *modes, uint max_modes) { - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE, 0); + return QZ_ListModes(modes, max_modes, kCGDirectMainDisplay, this->buffer_depth); } -/** Delete word right of the cursor. */ -- (void)deleteWordForward:(id)sender +bool WindowQuartzSubdriver::ChangeResolution(int w, int h, int bpp) { - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE | WKC_CTRL, 0); -} + int old_width = this->window_width; + int old_height = this->window_height; + int old_bpp = this->buffer_depth; -/** Move cursor one character left. */ -- (void)moveLeft:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT, 0); -} + if (this->SetVideoMode(w, h, bpp)) return true; + if (old_width != 0 && old_height != 0) this->SetVideoMode(old_width, old_height, old_bpp); -/** Move cursor one word left. */ -- (void)moveWordLeft:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT | WKC_CTRL, 0); + return false; } -/** Move cursor one character right. */ -- (void)moveRight:(id)sender +/* Convert local coordinate to window server (CoreGraphics) coordinate */ +CGPoint WindowQuartzSubdriver::PrivateLocalToCG(NSPoint *p) { - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT, 0); -} -/** Move cursor one word right. */ -- (void)moveWordRight:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT | WKC_CTRL, 0); -} + p->y = this->window_height - p->y; + *p = [ this->cocoaview convertPoint:*p toView:nil ]; + *p = [ this->window convertRectToScreen:NSMakeRect(p->x, p->y, 0, 0) ].origin; -/** Move cursor one line up. */ -- (void)moveUp:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP, 0); -} + p->y = this->device_height - p->y; -/** Move cursor one line down. */ -- (void)moveDown:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN, 0); -} + CGPoint cgp; + cgp.x = p->x; + cgp.y = p->y; -/** MScroll one line up. */ -- (void)moveUpAndModifySelection:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP | WKC_SHIFT, 0); + return cgp; } -/** Scroll one line down. */ -- (void)moveDownAndModifySelection:(id)sender +NSPoint WindowQuartzSubdriver::GetMouseLocation(NSEvent *event) { - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN | WKC_SHIFT, 0); -} + NSPoint pt; -/** Move cursor to the start of the line. */ -- (void)moveToBeginningOfLine:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_HOME, 0); -} + if ( [ event window ] == nil) { + pt = [ this->cocoaview convertPoint:[ [ this->cocoaview window ] convertRectFromScreen:NSMakeRect([ event locationInWindow ].x, [ event locationInWindow ].y, 0, 0) ].origin fromView:nil ]; + } else { + pt = [ event locationInWindow ]; + } -/** Move cursor to the end of the line. */ -- (void)moveToEndOfLine:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_END, 0); -} + pt.y = this->window_height - pt.y; -/** Scroll one page up. */ -- (void)scrollPageUp:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP, 0); + return pt; } -/** Scroll one page down. */ -- (void)scrollPageDown:(id)sender +bool WindowQuartzSubdriver::MouseIsInsideView(NSPoint *pt) { - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN, 0); + return [ cocoaview mouse:*pt inRect:[ this->cocoaview bounds ] ]; } -/** Move cursor (and selection) one page up. */ -- (void)pageUpAndModifySelection:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP | WKC_SHIFT, 0); -} -/** Move cursor (and selection) one page down. */ -- (void)pageDownAndModifySelection:(id)sender +/* This function makes the *game region* of the window 100% opaque. + * The genie effect uses the alpha component. Otherwise, + * it doesn't seem to matter what value it has. + */ +void WindowQuartzSubdriver::SetPortAlphaOpaque() { - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN | WKC_SHIFT, 0); -} + uint32 *pixels = (uint32*)this->window_buffer; + uint32 pitch = this->window_width; -/** Scroll to the beginning of the document. */ -- (void)scrollToBeginningOfDocument:(id)sender -{ - /* For compatibility with OTTD on Win/Linux. */ - [ self moveToBeginningOfLine:sender ]; + for (int y = 0; y < this->window_height; y++) + for (int x = 0; x < this->window_width; x++) { + pixels[y * pitch + x] |= 0xFF000000; + } } -/** Scroll to the end of the document. */ -- (void)scrollToEndOfDocument:(id)sender +bool WindowQuartzSubdriver::WindowResized() { - /* For compatibility with OTTD on Win/Linux. */ - [ self moveToEndOfLine:sender ]; -} + if (this->window == nil || this->cocoaview == nil) return true; -/** Return was pressed. */ -- (void)insertNewline:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RETURN, '\r'); -} + NSRect newframe = [ this->cocoaview frame ]; -/** Escape was pressed. */ -- (void)cancelOperation:(id)sender -{ - if (EditBoxInGlobalFocus()) HandleKeypress(WKC_ESC, 0); -} + this->window_width = (int)newframe.size.width; + this->window_height = (int)newframe.size.height; -/** Invoke the selector if we implement it. */ -- (void)doCommandBySelector:(SEL)aSelector -{ - if ([ self respondsToSelector:aSelector ]) [ self performSelector:aSelector ]; -} + /* Create Core Graphics Context */ + free(this->window_buffer); + this->window_buffer = (uint32*)malloc(this->window_width * this->window_height * 4); -@end + CGContextRelease(this->cgcontext); + this->cgcontext = CGBitmapContextCreate( + this->window_buffer, // data + this->window_width, // width + this->window_height, // height + 8, // bits per component + this->window_width * 4, // bytes per row + this->color_space, // color space + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host + ); + assert(this->cgcontext != NULL); + CGContextSetShouldAntialias(this->cgcontext, FALSE); + CGContextSetAllowsAntialiasing(this->cgcontext, FALSE); + CGContextSetInterpolationQuality(this->cgcontext, kCGInterpolationNone); + if (this->buffer_depth == 8) { + free(this->pixel_buffer); + this->pixel_buffer = malloc(this->window_width * this->window_height); + if (this->pixel_buffer == NULL) { + DEBUG(driver, 0, "Failed to allocate pixel buffer"); + return false; + } + } -@implementation OTTD_CocoaWindowDelegate -/** Initialize the video driver */ -- (void)setDriver:(CocoaSubdriver*)drv -{ - driver = drv; -} -/** Handle closure requests */ -- (BOOL)windowShouldClose:(id)sender -{ - HandleExitGameRequest(); + QZ_GameSizeChanged(); - return NO; -} -/** Handle key acceptance */ -- (void)windowDidBecomeKey:(NSNotification*)aNotification -{ - driver->active = true; -} -/** Resign key acceptance */ -- (void)windowDidResignKey:(NSNotification*)aNotification -{ - driver->active = false; -} -/** Handle becoming main window */ -- (void)windowDidBecomeMain:(NSNotification*)aNotification -{ - driver->active = true; -} -/** Resign being main window */ -- (void)windowDidResignMain:(NSNotification*)aNotification -{ - driver->active = false; + /* Redraw screen */ + this->num_dirty_rects = MAX_DIRTY_RECTS; + + return true; } -/** Window entered fullscreen mode (10.7). */ -- (void)windowDidEnterFullScreen:(NSNotification *)aNotification + + +CocoaSubdriver *QZ_CreateWindowQuartzSubdriver(int width, int height, int bpp) { - NSPoint loc = [ driver->cocoaview convertPoint:[ [ aNotification object ] mouseLocationOutsideOfEventStream ] fromView:nil ]; - BOOL inside = ([ driver->cocoaview hitTest:loc ] == driver->cocoaview); - - if (inside) { - /* We don't care about the event, but the compiler does. */ - NSEvent *e = [ [ NSEvent alloc ] init ]; - [ driver->cocoaview mouseEntered:e ]; - [ e release ]; + if (!MacOSVersionIsAtLeast(10, 7, 0)) { + DEBUG(driver, 0, "The cocoa quartz subdriver requires Mac OS X 10.7 or later."); + return NULL; } -} -/** The colour profile of the screen the window is on changed. */ -- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification -{ - if (!driver->setup) driver->WindowResized(); -} -@end + if (bpp != 8 && bpp != 32) { + DEBUG(driver, 0, "The cocoa quartz subdriver only supports 8 and 32 bpp."); + return NULL; + } + + WindowQuartzSubdriver *ret = new WindowQuartzSubdriver(); + + if (!ret->ChangeResolution(width, height, bpp)) { + delete ret; + return NULL; + } + + return ret; +} +#endif /* ENABLE_COCOA_QUARTZ */ #endif /* WITH_COCOA */ diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h new file mode 100644 index 000000000..f395114db --- /dev/null +++ b/src/video/cocoa/cocoa_wnd.h @@ -0,0 +1,77 @@ +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file cocoa_wnd.h OS interface for the cocoa video driver. */ + +#ifndef COCOA_WND_H +#define COCOA_WND_H + +#import + +class CocoaSubdriver; + +extern NSString *OTTDMainLaunchGameEngine; + +/** Category of NSCursor to allow cursor showing/hiding */ +@interface NSCursor (OTTD_QuickdrawCursor) ++ (NSCursor *) clearCocoaCursor; +@end + +/** Subclass of NSWindow to cater our special needs */ +@interface OTTD_CocoaWindow : NSWindow { + CocoaSubdriver *driver; +} + +- (void)setDriver:(CocoaSubdriver*)drv; + +- (void)miniaturize:(id)sender; +- (void)display; +- (void)setFrame:(NSRect)frameRect display:(BOOL)flag; +- (void)appDidHide:(NSNotification*)note; +- (void)appWillUnhide:(NSNotification*)note; +- (void)appDidUnhide:(NSNotification*)note; +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; +@end + +/** Subclass of NSView to fix Quartz rendering and mouse awareness */ +@interface OTTD_CocoaView : NSView +{ + CocoaSubdriver *driver; + NSTrackingRectTag trackingtag; +} +- (void)setDriver:(CocoaSubdriver*)drv; +- (void)drawRect:(NSRect)rect; +- (BOOL)isOpaque; +- (BOOL)acceptsFirstResponder; +- (BOOL)becomeFirstResponder; +- (void)setTrackingRect; +- (void)clearTrackingRect; +- (void)resetCursorRects; +- (void)viewWillMoveToWindow:(NSWindow *)win; +- (void)viewDidMoveToWindow; +- (void)mouseEntered:(NSEvent *)theEvent; +- (void)mouseExited:(NSEvent *)theEvent; +@end + +/** Delegate for our NSWindow to send ask for quit on close */ +@interface OTTD_CocoaWindowDelegate : NSObject +{ + CocoaSubdriver *driver; +} + +- (void)setDriver:(CocoaSubdriver*)drv; + +- (BOOL)windowShouldClose:(id)sender; +- (void)windowDidEnterFullScreen:(NSNotification *)aNotification; +- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification; +@end + + +bool CocoaSetupApplication(); +void CocoaExitApplication(); + +#endif /* COCOA_WND_H */ diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm new file mode 100644 index 000000000..d9687ac4a --- /dev/null +++ b/src/video/cocoa/cocoa_wnd.mm @@ -0,0 +1,879 @@ +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/****************************************************************************** + * Cocoa video driver * + * Known things left to do: * + * List available resolutions. * + ******************************************************************************/ + +#ifdef WITH_COCOA + +#include "../../stdafx.h" +#include "../../os/macosx/macos.h" + +#define Rect OTTDRect +#define Point OTTDPoint +#import +#undef Rect +#undef Point + +#include "../../openttd.h" +#include "../../debug.h" +#include "cocoa_v.h" +#include "cocoa_wnd.h" +#include "../../string_func.h" +#include "../../gfx_func.h" +#include "../../window_func.h" +#include "../../window_gui.h" + + +/** + * Important notice regarding all modifications!!!!!!! + * There are certain limitations because the file is objective C++. + * gdb has limitations. + * C++ and objective C code can't be joined in all cases (classes stuff). + * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. + */ + +@interface OTTDMain : NSObject +@end + +NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine"; + +static bool _cocoa_video_dialog = false; +static OTTDMain *_ottd_main; + +/** + * The main class of the application, the application's delegate. + */ +@implementation OTTDMain +/** + * Stop the game engine. Must be called on main thread. + */ +- (void)stopEngine +{ + [ NSApp stop:self ]; + + /* Send an empty event to return from the run loop. Without that, application is stuck waiting for an event. */ + NSEvent *event = [ NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0.0 windowNumber:0 context:nil subtype:0 data1:0 data2:0 ]; + [ NSApp postEvent:event atStart:YES ]; +} + +/** + * Start the game loop. + */ +- (void)launchGameEngine: (NSNotification*) note +{ + /* Setup cursor for the current _game_mode. */ + [ _cocoa_subdriver->cocoaview resetCursorRects ]; + + /* Hand off to main application code. */ + QZ_GameLoop(); + + /* We are done, thank you for playing. */ + [ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ]; +} + +/** + * Called when the internal event loop has just started running. + */ +- (void) applicationDidFinishLaunching: (NSNotification*) note +{ + /* Add a notification observer so we can restart the game loop later on if necessary. */ + [ [ NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(launchGameEngine:) name:OTTDMainLaunchGameEngine object:nil ]; + + /* Start game loop. */ + [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ]; +} + +/** + * Display the in game quit confirmation dialog. + */ +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender +{ + HandleExitGameRequest(); + + return NSTerminateCancel; // NSTerminateLater ? +} + +/** + * Remove ourself as a notification observer. + */ +- (void)unregisterObserver +{ + [ [ NSNotificationCenter defaultCenter ] removeObserver:self ]; +} +@end + +/** + * Initialize the application menu shown in top bar. + */ +static void setApplicationMenu() +{ + NSString *appName = @"OpenTTD"; + NSMenu *appleMenu = [ [ NSMenu alloc ] initWithTitle:appName ]; + + /* Add menu items */ + NSString *title = [ @"About " stringByAppendingString:appName ]; + [ appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@"" ]; + + [ appleMenu addItem:[ NSMenuItem separatorItem ] ]; + + title = [ @"Hide " stringByAppendingString:appName ]; + [ appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h" ]; + + NSMenuItem *menuItem = [ appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h" ]; + [ menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask) ]; + + [ appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@"" ]; + + [ appleMenu addItem:[ NSMenuItem separatorItem ] ]; + + title = [ @"Quit " stringByAppendingString:appName ]; + [ appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q" ]; + + /* Put menu into the menubar */ + menuItem = [ [ NSMenuItem alloc ] initWithTitle:@"" action:nil keyEquivalent:@"" ]; + [ menuItem setSubmenu:appleMenu ]; + [ [ NSApp mainMenu ] addItem:menuItem ]; + + /* Tell the application object that this is now the application menu. + * This interesting Objective-C construct is used because not all SDK + * versions define this method publicly. */ + if ([ NSApp respondsToSelector:@selector(setAppleMenu:) ]) { + [ NSApp performSelector:@selector(setAppleMenu:) withObject:appleMenu ]; + } + + /* Finally give up our references to the objects */ + [ appleMenu release ]; + [ menuItem release ]; +} + +/** + * Create a window menu. + */ +static void setupWindowMenu() +{ + NSMenu *windowMenu = [ [ NSMenu alloc ] initWithTitle:@"Window" ]; + + /* "Minimize" item */ + [ windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m" ]; + + /* Put menu into the menubar */ + NSMenuItem *menuItem = [ [ NSMenuItem alloc ] initWithTitle:@"Window" action:nil keyEquivalent:@"" ]; + [ menuItem setSubmenu:windowMenu ]; + [ [ NSApp mainMenu ] addItem:menuItem ]; + + if (MacOSVersionIsAtLeast(10, 7, 0)) { + /* The OS will change the name of this menu item automatically */ + [ windowMenu addItemWithTitle:@"Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"^f" ]; + } + + /* Tell the application object that this is now the window menu */ + [ NSApp setWindowsMenu:windowMenu ]; + + /* Finally give up our references to the objects */ + [ windowMenu release ]; + [ menuItem release ]; +} + +/** + * Startup the application. + */ +bool CocoaSetupApplication() +{ + ProcessSerialNumber psn = { 0, kCurrentProcess }; + + /* Ensure the application object is initialised */ + [ NSApplication sharedApplication ]; + + /* Tell the dock about us */ + OSStatus returnCode = TransformProcessType(&psn, kProcessTransformToForegroundApplication); + if (returnCode != 0) DEBUG(driver, 0, "Could not change to foreground application. Error %d", (int)returnCode); + + /* Disable the system-wide tab feature as we only have one window. */ + if ([ NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:) ]) { + /* We use nil instead of NO as withObject requires an id. */ + [ NSWindow performSelector:@selector(setAllowsAutomaticWindowTabbing:) withObject:nil]; + } + + /* Become the front process, important when start from the command line. */ + [ [ NSApplication sharedApplication ] activateIgnoringOtherApps:YES ]; + + /* Set up the menubar */ + [ NSApp setMainMenu:[ [ NSMenu alloc ] init ] ]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create OTTDMain and make it the app delegate */ + _ottd_main = [ [ OTTDMain alloc ] init ]; + [ NSApp setDelegate:_ottd_main ]; + + return true; +} + +/** + * + */ +void CocoaExitApplication() +{ + [ _ottd_main unregisterObserver ]; + [ _ottd_main release ]; +} + +/** + * Catch asserts prior to initialization of the videodriver. + * + * @param title Window title. + * @param message Message text. + * @param buttonLabel Button text. + * + * @note This is needed since sometimes assert is called before the videodriver is initialized . + */ +void CocoaDialog(const char *title, const char *message, const char *buttonLabel) +{ + _cocoa_video_dialog = true; + + bool wasstarted = _cocoa_video_started; + if (VideoDriver::GetInstance() == nullptr) { + CocoaSetupApplication(); // Setup application before showing dialog + } else if (!_cocoa_video_started && VideoDriver::GetInstance()->Start({}) != nullptr) { + fprintf(stderr, "%s: %s\n", title, message); + return; + } + + NSAlert *alert = [ [ NSAlert alloc ] init ]; + [ alert setAlertStyle: NSCriticalAlertStyle ]; + [ alert setMessageText:[ NSString stringWithUTF8String:title ] ]; + [ alert setInformativeText:[ NSString stringWithUTF8String:message ] ]; + [ alert addButtonWithTitle: [ NSString stringWithUTF8String:buttonLabel ] ]; + [ alert runModal ]; + [ alert release ]; + + if (!wasstarted && VideoDriver::GetInstance() != NULL) VideoDriver::GetInstance()->Stop(); + + _cocoa_video_dialog = false; +} + + +/** + * Re-implement the system cursor in order to allow hiding and showing it nicely + */ +@implementation NSCursor (OTTD_CocoaCursor) ++ (NSCursor *) clearCocoaCursor +{ + /* RAW 16x16 transparent GIF */ + unsigned char clearGIFBytes[] = { + 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, + 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, + 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4, + 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B}; + NSData *clearGIFData = [ NSData dataWithBytesNoCopy:&clearGIFBytes[0] length:55 freeWhenDone:NO ]; + NSImage *clearImg = [ [ NSImage alloc ] initWithData:clearGIFData ]; + return [ [ NSCursor alloc ] initWithImage:clearImg hotSpot:NSMakePoint(0.0,0.0) ]; +} +@end + +@implementation OTTD_CocoaWindow + +- (void)setDriver:(CocoaSubdriver*)drv +{ + driver = drv; +} +/** + * Minimize the window + */ +- (void)miniaturize:(id)sender +{ + /* make the alpha channel opaque so anim won't have holes in it */ + driver->SetPortAlphaOpaque(); + + /* window is hidden now */ + driver->active = false; + + [ super miniaturize:sender ]; +} + +/** + * This method fires just before the window deminaturizes from the Dock. + * We'll save the current visible surface, let the window manager redraw any + * UI elements, and restore the surface. This way, no expose event + * is required, and the deminiaturize works perfectly. + */ +- (void)display +{ + driver->SetPortAlphaOpaque(); + + /* save current visible surface */ + [ self cacheImageInRect:[ driver->cocoaview frame ] ]; + + /* let the window manager redraw controls, border, etc */ + [ super display ]; + + /* restore visible surface */ + [ self restoreCachedImage ]; + + /* window is visible again */ + driver->active = true; +} +/** + * Define the rectangle we draw our window in + */ +- (void)setFrame:(NSRect)frameRect display:(BOOL)flag +{ + [ super setFrame:frameRect display:flag ]; + + /* Don't do anything if the window is currently being created */ + if (driver->setup) return; + + if (!driver->WindowResized()) error("Cocoa: Failed to resize window."); +} +/** + * Handle hiding of the application + */ +- (void)appDidHide:(NSNotification*)note +{ + driver->active = false; +} +/** + * Fade-in the application and restore display plane + */ +- (void)appWillUnhide:(NSNotification*)note +{ + driver->SetPortAlphaOpaque (); +} +/** + * Unhide and restore display plane and re-activate driver + */ +- (void)appDidUnhide:(NSNotification*)note +{ + driver->active = true; +} +/** + * Initialize event system for the application rectangle + */ +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag +{ + /* Make our window subclass receive these application notifications */ + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; + + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; + + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appWillUnhide:) name:NSApplicationWillUnhideNotification object:NSApp ]; + + return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; +} + +@end + + + +/** + * Count the number of UTF-16 code points in a range of an UTF-8 string. + * @param from Start of the range. + * @param to End of the range. + * @return Number of UTF-16 code points in the range. + */ +static NSUInteger CountUtf16Units(const char *from, const char *to) +{ + NSUInteger i = 0; + + while (from < to) { + WChar c; + size_t len = Utf8Decode(&c, from); + i += len < 4 ? 1 : 2; // Watch for surrogate pairs. + from += len; + } + + return i; +} + +/** + * Advance an UTF-8 string by a number of equivalent UTF-16 code points. + * @param str UTF-8 string. + * @param count Number of UTF-16 code points to advance the string by. + * @return Advanced string pointer. + */ +static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) +{ + for (NSUInteger i = 0; i < count && *str != '\0'; ) { + WChar c; + size_t len = Utf8Decode(&c, str); + i += len < 4 ? 1 : 2; // Watch for surrogates. + str += len; + } + + return str; +} + +@implementation OTTD_CocoaView +/** + * Initialize the driver + */ +- (void)setDriver:(CocoaSubdriver*)drv +{ + driver = drv; +} +/** + * Define the opaqueness of the window / screen + * @return opaqueness of window / screen + */ +- (BOOL)isOpaque +{ + return YES; +} +/** + * Draws a rectangle on the screen. + * It's overwritten by the individual drivers but must be defined + */ +- (void)drawRect:(NSRect)invalidRect +{ + return; +} +/** + * Allow to handle events + */ +- (BOOL)acceptsFirstResponder +{ + return YES; +} +/** + * Actually handle events + */ +- (BOOL)becomeFirstResponder +{ + return YES; +} +/** + * Define the rectangle where we draw our application window + */ +- (void)setTrackingRect +{ + NSPoint loc = [ self convertPoint:[ [ self window ] mouseLocationOutsideOfEventStream ] fromView:nil ]; + BOOL inside = ([ self hitTest:loc ]==self); + if (inside) [ [ self window ] makeFirstResponder:self ]; + trackingtag = [ self addTrackingRect:[ self visibleRect ] owner:self userData:nil assumeInside:inside ]; +} +/** + * Return responsibility for the application window to system + */ +- (void)clearTrackingRect +{ + [ self removeTrackingRect:trackingtag ]; +} +/** + * Declare responsibility for the cursor within our application rect + */ +- (void)resetCursorRects +{ + [ super resetCursorRects ]; + [ self clearTrackingRect ]; + [ self setTrackingRect ]; + [ self addCursorRect:[ self bounds ] cursor:(_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) ]; +} +/** + * Prepare for moving the application window + */ +- (void)viewWillMoveToWindow:(NSWindow *)win +{ + if (!win && [ self window ]) [ self clearTrackingRect ]; +} +/** + * Restore our responsibility for our application window after moving + */ +- (void)viewDidMoveToWindow +{ + if ([ self window ]) [ self setTrackingRect ]; +} +/** + * Make OpenTTD aware that it has control over the mouse + */ +- (void)mouseEntered:(NSEvent *)theEvent +{ + _cursor.in_window = true; +} +/** + * Make OpenTTD aware that it has NOT control over the mouse + */ +- (void)mouseExited:(NSEvent *)theEvent +{ + if (_cocoa_subdriver != NULL) UndrawMouseCursor(); + _cursor.in_window = false; +} + + +/** Insert the given text at the given range. */ +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange +{ + if (!EditBoxInGlobalFocus()) return; + + NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString; + + const char *insert_point = NULL; + const char *replace_range = NULL; + if (replacementRange.location != NSNotFound) { + /* Calculate the part to be replaced. */ + insert_point = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), replacementRange.location); + replace_range = Utf8AdvanceByUtf16Units(insert_point, replacementRange.length); + } + + HandleTextInput(NULL, true); + HandleTextInput([ s UTF8String ], false, NULL, insert_point, replace_range); +} + +/** Insert the given text at the caret. */ +- (void)insertText:(id)aString +{ + [ self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0) ]; +} + +/** Set a new marked text and reposition the caret. */ +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange +{ + if (!EditBoxInGlobalFocus()) return; + + NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString; + + const char *utf8 = [ s UTF8String ]; + if (utf8 != NULL) { + const char *insert_point = NULL; + const char *replace_range = NULL; + if (replacementRange.location != NSNotFound) { + /* Calculate the part to be replaced. */ + NSRange marked = [ self markedRange ]; + insert_point = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), replacementRange.location + (marked.location != NSNotFound ? marked.location : 0u)); + replace_range = Utf8AdvanceByUtf16Units(insert_point, replacementRange.length); + } + + /* Convert caret index into a pointer in the UTF-8 string. */ + const char *selection = Utf8AdvanceByUtf16Units(utf8, selRange.location); + + HandleTextInput(utf8, true, selection, insert_point, replace_range); + } +} + +/** Set a new marked text and reposition the caret. */ +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange +{ + [ self setMarkedText:aString selectedRange:selRange replacementRange:NSMakeRange(NSNotFound, 0) ]; +} + +/** Unmark the current marked text. */ +- (void)unmarkText +{ + HandleTextInput(NULL, true); +} + +/** Get the caret position. */ +- (NSRange)selectedRange +{ + if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0); + + NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), _focused_window->GetCaret()); + return NSMakeRange(start, 0); +} + +/** Get the currently marked range. */ +- (NSRange)markedRange +{ + if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0); + + size_t mark_len; + const char *mark = _focused_window->GetMarkedText(&mark_len); + if (mark != NULL) { + NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), mark); + NSUInteger len = CountUtf16Units(mark, mark + mark_len); + + return NSMakeRange(start, len); + } + + return NSMakeRange(NSNotFound, 0); +} + +/** Is any text marked? */ +- (BOOL)hasMarkedText +{ + if (!EditBoxInGlobalFocus()) return NO; + + size_t len; + return _focused_window->GetMarkedText(&len) != NULL; +} + +/** Get a string corresponding to the given range. */ +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange +{ + if (!EditBoxInGlobalFocus()) return nil; + + NSString *s = [ NSString stringWithUTF8String:_focused_window->GetFocusedText() ]; + NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange); + + if (actualRange != NULL) *actualRange = valid_range; + if (valid_range.length == 0) return nil; + + return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ]; +} + +/** Get a string corresponding to the given range. */ +- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange +{ + return [ self attributedSubstringForProposedRange:theRange actualRange:NULL ]; +} + +/** Get the current edit box string. */ +- (NSAttributedString *)attributedString +{ + if (!EditBoxInGlobalFocus()) return [ [ [ NSAttributedString alloc ] initWithString:@"" ] autorelease ]; + + return [ [ [ NSAttributedString alloc ] initWithString:[ NSString stringWithUTF8String:_focused_window->GetFocusedText() ] ] autorelease ]; +} + +/** Get the character that is rendered at the given point. */ +- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint +{ + if (!EditBoxInGlobalFocus()) return NSNotFound; + + NSPoint view_pt = [ self convertRect:[ [ self window ] convertRectFromScreen:NSMakeRect(thePoint.x, thePoint.y, 0, 0) ] fromView:nil ].origin; + + Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y }; + + const char *ch = _focused_window->GetTextCharacterAtPosition(pt); + if (ch == NULL) return NSNotFound; + + return CountUtf16Units(_focused_window->GetFocusedText(), ch); +} + +/** Get the bounding rect for the given range. */ +- (NSRect)firstRectForCharacterRange:(NSRange)aRange +{ + if (!EditBoxInGlobalFocus()) return NSMakeRect(0, 0, 0, 0); + + /* Convert range to UTF-8 string pointers. */ + const char *start = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), aRange.location); + const char *end = aRange.length != 0 ? Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), aRange.location + aRange.length) : start; + + /* Get the bounding rect for the text range.*/ + Rect r = _focused_window->GetTextBoundingRect(start, end); + 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); + + return [ [ self window ] convertRectToScreen:[ self convertRect:view_rect toView:nil ] ]; +} + +/** Get the bounding rect for the given range. */ +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange +{ + return [ self firstRectForCharacterRange:aRange ]; +} + +/** Get all string attributes that we can process for marked text. */ +- (NSArray*)validAttributesForMarkedText +{ + return [ NSArray array ]; +} + +/** Delete single character left of the cursor. */ +- (void)deleteBackward:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE, 0); +} + +/** Delete word left of the cursor. */ +- (void)deleteWordBackward:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_BACKSPACE | WKC_CTRL, 0); +} + +/** Delete single character right of the cursor. */ +- (void)deleteForward:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE, 0); +} + +/** Delete word right of the cursor. */ +- (void)deleteWordForward:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DELETE | WKC_CTRL, 0); +} + +/** Move cursor one character left. */ +- (void)moveLeft:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT, 0); +} + +/** Move cursor one word left. */ +- (void)moveWordLeft:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_LEFT | WKC_CTRL, 0); +} + +/** Move cursor one character right. */ +- (void)moveRight:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT, 0); +} + +/** Move cursor one word right. */ +- (void)moveWordRight:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RIGHT | WKC_CTRL, 0); +} + +/** Move cursor one line up. */ +- (void)moveUp:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP, 0); +} + +/** Move cursor one line down. */ +- (void)moveDown:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN, 0); +} + +/** MScroll one line up. */ +- (void)moveUpAndModifySelection:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_UP | WKC_SHIFT, 0); +} + +/** Scroll one line down. */ +- (void)moveDownAndModifySelection:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_DOWN | WKC_SHIFT, 0); +} + +/** Move cursor to the start of the line. */ +- (void)moveToBeginningOfLine:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_HOME, 0); +} + +/** Move cursor to the end of the line. */ +- (void)moveToEndOfLine:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_END, 0); +} + +/** Scroll one page up. */ +- (void)scrollPageUp:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP, 0); +} + +/** Scroll one page down. */ +- (void)scrollPageDown:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN, 0); +} + +/** Move cursor (and selection) one page up. */ +- (void)pageUpAndModifySelection:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEUP | WKC_SHIFT, 0); +} + +/** Move cursor (and selection) one page down. */ +- (void)pageDownAndModifySelection:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_PAGEDOWN | WKC_SHIFT, 0); +} + +/** Scroll to the beginning of the document. */ +- (void)scrollToBeginningOfDocument:(id)sender +{ + /* For compatibility with OTTD on Win/Linux. */ + [ self moveToBeginningOfLine:sender ]; +} + +/** Scroll to the end of the document. */ +- (void)scrollToEndOfDocument:(id)sender +{ + /* For compatibility with OTTD on Win/Linux. */ + [ self moveToEndOfLine:sender ]; +} + +/** Return was pressed. */ +- (void)insertNewline:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_RETURN, '\r'); +} + +/** Escape was pressed. */ +- (void)cancelOperation:(id)sender +{ + if (EditBoxInGlobalFocus()) HandleKeypress(WKC_ESC, 0); +} + +/** Invoke the selector if we implement it. */ +- (void)doCommandBySelector:(SEL)aSelector +{ + if ([ self respondsToSelector:aSelector ]) [ self performSelector:aSelector ]; +} + +@end + + + +@implementation OTTD_CocoaWindowDelegate +/** Initialize the video driver */ +- (void)setDriver:(CocoaSubdriver*)drv +{ + driver = drv; +} +/** Handle closure requests */ +- (BOOL)windowShouldClose:(id)sender +{ + HandleExitGameRequest(); + + return NO; +} +/** Handle key acceptance */ +- (void)windowDidBecomeKey:(NSNotification*)aNotification +{ + driver->active = true; +} +/** Resign key acceptance */ +- (void)windowDidResignKey:(NSNotification*)aNotification +{ + driver->active = false; +} +/** Handle becoming main window */ +- (void)windowDidBecomeMain:(NSNotification*)aNotification +{ + driver->active = true; +} +/** Resign being main window */ +- (void)windowDidResignMain:(NSNotification*)aNotification +{ + driver->active = false; +} +/** Window entered fullscreen mode (10.7). */ +- (void)windowDidEnterFullScreen:(NSNotification *)aNotification +{ + NSPoint loc = [ driver->cocoaview convertPoint:[ [ aNotification object ] mouseLocationOutsideOfEventStream ] fromView:nil ]; + BOOL inside = ([ driver->cocoaview hitTest:loc ] == driver->cocoaview); + + if (inside) { + /* We don't care about the event, but the compiler does. */ + NSEvent *e = [ [ NSEvent alloc ] init ]; + [ driver->cocoaview mouseEntered:e ]; + [ e release ]; + } +} +/** The colour profile of the screen the window is on changed. */ +- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification +{ + if (!driver->setup) driver->WindowResized(); +} + +@end +#endif /* WITH_COCOA */ diff --git a/src/video/cocoa/wnd_quartz.mm b/src/video/cocoa/wnd_quartz.mm deleted file mode 100644 index 228198108..000000000 --- a/src/video/cocoa/wnd_quartz.mm +++ /dev/null @@ -1,587 +0,0 @@ -/* - * This file is part of OpenTTD. - * 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. - * 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. - * 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 . - */ - -/****************************************************************************** - * Cocoa video driver * - * Known things left to do: * - * List available resolutions. * - ******************************************************************************/ - -#ifdef WITH_COCOA -#ifdef ENABLE_COCOA_QUARTZ - -#include "../../stdafx.h" -#include "../../os/macosx/macos.h" - -#define Rect OTTDRect -#define Point OTTDPoint -#import -#undef Rect -#undef Point - -#include "../../debug.h" -#include "../../rev.h" -#include "../../core/geometry_type.hpp" -#include "cocoa_v.h" -#include "../../core/math_func.hpp" -#include "../../gfx_func.h" -#include "../../framerate_type.h" - -/* On some old versions of MAC OS this may not be defined. - * Those versions generally only produce code for PPC. So it should be safe to - * set this to 0. */ -#ifndef kCGBitmapByteOrder32Host -#define kCGBitmapByteOrder32Host 0 -#endif - -/** - * Important notice regarding all modifications!!!!!!! - * There are certain limitations because the file is objective C++. - * gdb has limitations. - * C++ and objective C code can't be joined in all cases (classes stuff). - * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. - */ - -class WindowQuartzSubdriver; - -/* Subclass of OTTD_CocoaView to fix Quartz rendering */ -@interface OTTD_QuartzView : OTTD_CocoaView -- (void)setDriver:(WindowQuartzSubdriver*)drv; -- (void)drawRect:(NSRect)invalidRect; -@end - -class WindowQuartzSubdriver : public CocoaSubdriver { -private: - /** - * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. - * - * @param left The x coord for the left edge of the box to blit. - * @param top The y coord for the top edge of the box to blit. - * @param right The x coord for the right edge of the box to blit. - * @param bottom The y coord for the bottom edge of the box to blit. - */ - void BlitIndexedToView32(int left, int top, int right, int bottom); - - virtual void GetDeviceInfo(); - virtual bool SetVideoMode(int width, int height, int bpp); - -public: - WindowQuartzSubdriver(); - virtual ~WindowQuartzSubdriver(); - - virtual void Draw(bool force_update); - virtual void MakeDirty(int left, int top, int width, int height); - virtual void UpdatePalette(uint first_color, uint num_colors); - - virtual uint ListModes(OTTD_Point *modes, uint max_modes); - - virtual bool ChangeResolution(int w, int h, int bpp); - - virtual bool IsFullscreen() { return false; } - virtual bool ToggleFullscreen(); /* Full screen mode on OSX 10.7 */ - - virtual int GetWidth() { return window_width; } - virtual int GetHeight() { return window_height; } - virtual void *GetPixelBuffer() { return buffer_depth == 8 ? pixel_buffer : window_buffer; } - - /* Convert local coordinate to window server (CoreGraphics) coordinate */ - virtual CGPoint PrivateLocalToCG(NSPoint *p); - - virtual NSPoint GetMouseLocation(NSEvent *event); - virtual bool MouseIsInsideView(NSPoint *pt); - - virtual bool IsActive() { return active; } - - - void SetPortAlphaOpaque(); - bool WindowResized(); -}; - - -@implementation OTTD_QuartzView - -- (void)setDriver:(WindowQuartzSubdriver*)drv -{ - driver = drv; -} -- (void)drawRect:(NSRect)invalidRect -{ - if (driver->cgcontext == NULL) return; - - CGContextRef viewContext = (CGContextRef)[ [ NSGraphicsContext currentContext ] graphicsPort ]; - CGContextSetShouldAntialias(viewContext, FALSE); - CGContextSetInterpolationQuality(viewContext, kCGInterpolationNone); - - /* The obtained 'rect' is actually a union of all dirty rects, let's ask for an explicit list of rects instead */ - const NSRect *dirtyRects; - NSInteger dirtyRectCount; - [ self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount ]; - - /* We need an Image in order to do blitting, but as we don't touch the context between this call and drawing no copying will actually be done here */ - CGImageRef fullImage = CGBitmapContextCreateImage(driver->cgcontext); - - /* Calculate total area we are blitting */ - uint32 blitArea = 0; - for (int n = 0; n < dirtyRectCount; n++) { - blitArea += (uint32)(dirtyRects[n].size.width * dirtyRects[n].size.height); - } - - /* - * This might be completely stupid, but in my extremely subjective opinion it feels faster - * The point is, if we're blitting less than 50% of the dirty rect union then it's still a good idea to blit each dirty - * rect separately but if we blit more than that, it's just cheaper to blit the entire union in one pass. - * Feel free to remove or find an even better value than 50% ... / blackis - */ - NSRect frameRect = [ self frame ]; - if (blitArea / (float)(invalidRect.size.width * invalidRect.size.height) > 0.5f) { - NSRect rect = invalidRect; - CGRect clipRect; - CGRect blitRect; - - blitRect.origin.x = rect.origin.x; - blitRect.origin.y = rect.origin.y; - blitRect.size.width = rect.size.width; - blitRect.size.height = rect.size.height; - - clipRect.origin.x = rect.origin.x; - clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; - - clipRect.size.width = rect.size.width; - clipRect.size.height = rect.size.height; - - /* Blit dirty part of image */ - CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); - CGContextDrawImage(viewContext, blitRect, clippedImage); - CGImageRelease(clippedImage); - } else { - for (int n = 0; n < dirtyRectCount; n++) { - NSRect rect = dirtyRects[n]; - CGRect clipRect; - CGRect blitRect; - - blitRect.origin.x = rect.origin.x; - blitRect.origin.y = rect.origin.y; - blitRect.size.width = rect.size.width; - blitRect.size.height = rect.size.height; - - clipRect.origin.x = rect.origin.x; - clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; - - clipRect.size.width = rect.size.width; - clipRect.size.height = rect.size.height; - - /* Blit dirty part of image */ - CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); - CGContextDrawImage(viewContext, blitRect, clippedImage); - CGImageRelease(clippedImage); - } - } - - CGImageRelease(fullImage); -} - -@end - - -void WindowQuartzSubdriver::GetDeviceInfo() -{ - /* Initialize the video settings; this data persists between mode switches - * and gather some information that is useful to know about the display */ - - /* Use the new API when compiling for OSX 10.6 or later */ - CGDisplayModeRef cur_mode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay); - if (cur_mode == NULL) { return; } - - this->device_width = CGDisplayModeGetWidth(cur_mode); - this->device_height = CGDisplayModeGetHeight(cur_mode); - - CGDisplayModeRelease(cur_mode); -} - -/** Switch to full screen mode on OSX 10.7 - * @return Whether we switched to full screen - */ -bool WindowQuartzSubdriver::ToggleFullscreen() -{ - if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) { - [ this->window performSelector:@selector(toggleFullScreen:) withObject:this->window ]; - return true; - } - - return false; -} - -bool WindowQuartzSubdriver::SetVideoMode(int width, int height, int bpp) -{ - this->setup = true; - this->GetDeviceInfo(); - - if (width > this->device_width) width = this->device_width; - if (height > this->device_height) height = this->device_height; - - NSRect contentRect = NSMakeRect(0, 0, width, height); - - /* Check if we should recreate the window */ - if (this->window == nil) { - OTTD_CocoaWindowDelegate *delegate; - - /* Set the window style */ - unsigned int style = NSTitledWindowMask; - style |= (NSMiniaturizableWindowMask | NSClosableWindowMask); - style |= NSResizableWindowMask; - - /* Manually create a window, avoids having a nib file resource */ - this->window = [ [ OTTD_CocoaWindow alloc ] - initWithContentRect:contentRect - styleMask:style - backing:NSBackingStoreBuffered - defer:NO ]; - - if (this->window == nil) { - DEBUG(driver, 0, "Could not create the Cocoa window."); - this->setup = false; - return false; - } - - /* Add built in full-screen support when available (OS X 10.7 and higher) - * This code actually compiles for 10.5 and later, but only makes sense in conjunction - * with the quartz fullscreen support as found only in 10.7 and later - */ - if ([this->window respondsToSelector:@selector(toggleFullScreen:)]) { - NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ]; - behavior |= NSWindowCollectionBehaviorFullScreenPrimary; - [ this->window setCollectionBehavior:behavior ]; - - NSButton* fullscreenButton = [ this->window standardWindowButton:NSWindowFullScreenButton ]; - [ fullscreenButton setAction:@selector(toggleFullScreen:) ]; - [ fullscreenButton setTarget:this->window ]; - - [ this->window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary ]; - } - - [ this->window setDriver:this ]; - - char caption[50]; - snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); - NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption ]; - [ this->window setTitle:nsscaption ]; - [ this->window setMiniwindowTitle:nsscaption ]; - [ nsscaption release ]; - - [ this->window setContentMinSize:NSMakeSize(64.0f, 64.0f) ]; - - [ this->window setAcceptsMouseMovedEvents:YES ]; - [ this->window setViewsNeedDisplay:NO ]; - - delegate = [ [ OTTD_CocoaWindowDelegate alloc ] init ]; - [ delegate setDriver:this ]; - [ this->window setDelegate:[ delegate autorelease ] ]; - } else { - /* We already have a window, just change its size */ - [ this->window setContentSize:contentRect.size ]; - - /* Ensure frame height - title bar height >= view height */ - contentRect.size.height = Clamp(height, 0, (int)[ this->window frame ].size.height - 22 /* 22 is the height of title bar of window*/); - - if (this->cocoaview != nil) { - height = (int)contentRect.size.height; - [ this->cocoaview setFrameSize:contentRect.size ]; - } - } - - this->window_width = width; - this->window_height = height; - this->buffer_depth = bpp; - - [ (OTTD_CocoaWindow *)this->window center ]; - - /* Only recreate the view if it doesn't already exist */ - if (this->cocoaview == nil) { - this->cocoaview = [ [ OTTD_QuartzView alloc ] initWithFrame:contentRect ]; - if (this->cocoaview == nil) { - DEBUG(driver, 0, "Could not create the Quartz view."); - this->setup = false; - return false; - } - - [ this->cocoaview setDriver:this ]; - - [ (NSView*)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; - [ this->window setContentView:cocoaview ]; - [ this->cocoaview release ]; - [ this->window makeKeyAndOrderFront:nil ]; - } - - [this->window setColorSpace:[NSColorSpace sRGBColorSpace]]; - this->color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); - if (this->color_space == nullptr) this->color_space = CGColorSpaceCreateDeviceRGB(); - if (this->color_space == nullptr) error("Could not get a valid colour space for drawing."); - - bool ret = WindowResized(); - this->UpdatePalette(0, 256); - - this->setup = false; - - return ret; -} - -void WindowQuartzSubdriver::BlitIndexedToView32(int left, int top, int right, int bottom) -{ - const uint32 *pal = this->palette; - const uint8 *src = (uint8*)this->pixel_buffer; - uint32 *dst = (uint32*)this->window_buffer; - uint width = this->window_width; - uint pitch = this->window_width; - - for (int y = top; y < bottom; y++) { - for (int x = left; x < right; x++) { - dst[y * pitch + x] = pal[src[y * width + x]]; - } - } -} - - -WindowQuartzSubdriver::WindowQuartzSubdriver() -{ - this->window_width = 0; - this->window_height = 0; - this->buffer_depth = 0; - this->window_buffer = NULL; - this->pixel_buffer = NULL; - this->active = false; - this->setup = false; - - this->window = nil; - this->cocoaview = nil; - - this->cgcontext = NULL; - - this->num_dirty_rects = MAX_DIRTY_RECTS; -} - -WindowQuartzSubdriver::~WindowQuartzSubdriver() -{ - /* Release window mode resources */ - if (this->window != nil) [ this->window close ]; - - CGContextRelease(this->cgcontext); - - CGColorSpaceRelease(this->color_space); - free(this->window_buffer); - free(this->pixel_buffer); -} - -void WindowQuartzSubdriver::Draw(bool force_update) -{ - PerformanceMeasurer framerate(PFE_VIDEO); - - /* Check if we need to do anything */ - if (this->num_dirty_rects == 0 || [ this->window isMiniaturized ]) return; - - if (this->num_dirty_rects >= MAX_DIRTY_RECTS) { - this->num_dirty_rects = 1; - this->dirty_rects[0].left = 0; - this->dirty_rects[0].top = 0; - this->dirty_rects[0].right = this->window_width; - this->dirty_rects[0].bottom = this->window_height; - } - - /* Build the region of dirty rectangles */ - for (int i = 0; i < this->num_dirty_rects; i++) { - /* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */ - if (this->buffer_depth == 8) { - BlitIndexedToView32( - this->dirty_rects[i].left, - this->dirty_rects[i].top, - this->dirty_rects[i].right, - this->dirty_rects[i].bottom - ); - } - - NSRect dirtyrect; - dirtyrect.origin.x = this->dirty_rects[i].left; - dirtyrect.origin.y = this->window_height - this->dirty_rects[i].bottom; - dirtyrect.size.width = this->dirty_rects[i].right - this->dirty_rects[i].left; - dirtyrect.size.height = this->dirty_rects[i].bottom - this->dirty_rects[i].top; - - /* Normally drawRect will be automatically called by Mac OS X during next update cycle, - * and then blitting will occur. If force_update is true, it will be done right now. */ - [ this->cocoaview setNeedsDisplayInRect:dirtyrect ]; - if (force_update) [ this->cocoaview displayIfNeeded ]; - } - - this->num_dirty_rects = 0; -} - -void WindowQuartzSubdriver::MakeDirty(int left, int top, int width, int height) -{ - if (this->num_dirty_rects < MAX_DIRTY_RECTS) { - dirty_rects[this->num_dirty_rects].left = left; - dirty_rects[this->num_dirty_rects].top = top; - dirty_rects[this->num_dirty_rects].right = left + width; - dirty_rects[this->num_dirty_rects].bottom = top + height; - } - this->num_dirty_rects++; -} - -void WindowQuartzSubdriver::UpdatePalette(uint first_color, uint num_colors) -{ - if (this->buffer_depth != 8) return; - - for (uint i = first_color; i < first_color + num_colors; i++) { - uint32 clr = 0xff000000; - clr |= (uint32)_cur_palette.palette[i].r << 16; - clr |= (uint32)_cur_palette.palette[i].g << 8; - clr |= (uint32)_cur_palette.palette[i].b; - this->palette[i] = clr; - } - - this->num_dirty_rects = MAX_DIRTY_RECTS; -} - -uint WindowQuartzSubdriver::ListModes(OTTD_Point *modes, uint max_modes) -{ - return QZ_ListModes(modes, max_modes, kCGDirectMainDisplay, this->buffer_depth); -} - -bool WindowQuartzSubdriver::ChangeResolution(int w, int h, int bpp) -{ - int old_width = this->window_width; - int old_height = this->window_height; - int old_bpp = this->buffer_depth; - - if (this->SetVideoMode(w, h, bpp)) return true; - if (old_width != 0 && old_height != 0) this->SetVideoMode(old_width, old_height, old_bpp); - - return false; -} - -/* Convert local coordinate to window server (CoreGraphics) coordinate */ -CGPoint WindowQuartzSubdriver::PrivateLocalToCG(NSPoint *p) -{ - - p->y = this->window_height - p->y; - *p = [ this->cocoaview convertPoint:*p toView:nil ]; - *p = [ this->window convertRectToScreen:NSMakeRect(p->x, p->y, 0, 0) ].origin; - - p->y = this->device_height - p->y; - - CGPoint cgp; - cgp.x = p->x; - cgp.y = p->y; - - return cgp; -} - -NSPoint WindowQuartzSubdriver::GetMouseLocation(NSEvent *event) -{ - NSPoint pt; - - if ( [ event window ] == nil) { - pt = [ this->cocoaview convertPoint:[ [ this->cocoaview window ] convertRectFromScreen:NSMakeRect([ event locationInWindow ].x, [ event locationInWindow ].y, 0, 0) ].origin fromView:nil ]; - } else { - pt = [ event locationInWindow ]; - } - - pt.y = this->window_height - pt.y; - - return pt; -} - -bool WindowQuartzSubdriver::MouseIsInsideView(NSPoint *pt) -{ - return [ cocoaview mouse:*pt inRect:[ this->cocoaview bounds ] ]; -} - - -/* This function makes the *game region* of the window 100% opaque. - * The genie effect uses the alpha component. Otherwise, - * it doesn't seem to matter what value it has. - */ -void WindowQuartzSubdriver::SetPortAlphaOpaque() -{ - uint32 *pixels = (uint32*)this->window_buffer; - uint32 pitch = this->window_width; - - for (int y = 0; y < this->window_height; y++) - for (int x = 0; x < this->window_width; x++) { - pixels[y * pitch + x] |= 0xFF000000; - } -} - -bool WindowQuartzSubdriver::WindowResized() -{ - if (this->window == nil || this->cocoaview == nil) return true; - - NSRect newframe = [ this->cocoaview frame ]; - - this->window_width = (int)newframe.size.width; - this->window_height = (int)newframe.size.height; - - /* Create Core Graphics Context */ - free(this->window_buffer); - this->window_buffer = (uint32*)malloc(this->window_width * this->window_height * 4); - - CGContextRelease(this->cgcontext); - this->cgcontext = CGBitmapContextCreate( - this->window_buffer, // data - this->window_width, // width - this->window_height, // height - 8, // bits per component - this->window_width * 4, // bytes per row - this->color_space, // color space - kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host - ); - - assert(this->cgcontext != NULL); - CGContextSetShouldAntialias(this->cgcontext, FALSE); - CGContextSetAllowsAntialiasing(this->cgcontext, FALSE); - CGContextSetInterpolationQuality(this->cgcontext, kCGInterpolationNone); - - if (this->buffer_depth == 8) { - free(this->pixel_buffer); - this->pixel_buffer = malloc(this->window_width * this->window_height); - if (this->pixel_buffer == NULL) { - DEBUG(driver, 0, "Failed to allocate pixel buffer"); - return false; - } - } - - QZ_GameSizeChanged(); - - /* Redraw screen */ - this->num_dirty_rects = MAX_DIRTY_RECTS; - - return true; -} - - -CocoaSubdriver *QZ_CreateWindowQuartzSubdriver(int width, int height, int bpp) -{ - if (!MacOSVersionIsAtLeast(10, 7, 0)) { - DEBUG(driver, 0, "The cocoa quartz subdriver requires Mac OS X 10.7 or later."); - return NULL; - } - - if (bpp != 8 && bpp != 32) { - DEBUG(driver, 0, "The cocoa quartz subdriver only supports 8 and 32 bpp."); - return NULL; - } - - WindowQuartzSubdriver *ret = new WindowQuartzSubdriver(); - - if (!ret->ChangeResolution(width, height, bpp)) { - delete ret; - return NULL; - } - - return ret; -} - - -#endif /* ENABLE_COCOA_QUARTZ */ -#endif /* WITH_COCOA */ -- cgit v1.2.3-54-g00ecf