summaryrefslogtreecommitdiff
path: root/src/video/cocoa
diff options
context:
space:
mode:
authorMichael Lutz <michi@icosahedron.de>2020-12-26 16:07:47 +0100
committerMichael Lutz <michi@icosahedron.de>2021-01-03 13:25:32 +0100
commit9ccef816f90e9a7e3042cd89141ba3c0956d7bb9 (patch)
tree906ca656f585147070658dc14211b31c0689a83e /src/video/cocoa
parentab7da117e066983d076f540b9722e1b79cf01d98 (diff)
downloadopenttd-9ccef816f90e9a7e3042cd89141ba3c0956d7bb9.tar.xz
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.
Diffstat (limited to 'src/video/cocoa')
-rw-r--r--src/video/cocoa/CMakeLists.txt3
-rw-r--r--src/video/cocoa/cocoa_v.h57
-rw-r--r--src/video/cocoa/cocoa_v.mm1151
-rw-r--r--src/video/cocoa/cocoa_wnd.h77
-rw-r--r--src/video/cocoa/cocoa_wnd.mm879
-rw-r--r--src/video/cocoa/wnd_quartz.mm587
6 files changed, 1373 insertions, 1381 deletions
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 &param) 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 <NSTextInputClient>
-{
- 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 <NSWindowDelegate>
-{
- 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 <array>
#import <sys/param.h> /* 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 <NSApplicationDelegate>
-@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<std::string, NUM_SEARCHPATHS> _searchpaths;
+ virtual void GetDeviceInfo();
+ virtual bool SetVideoMode(int width, int height, int bpp);
- char tmp[MAXPATHLEN];
- CFAutoRelease<CFURLRef> 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file cocoa_wnd.h OS interface for the cocoa video driver. */
+
+#ifndef COCOA_WND_H
+#define COCOA_WND_H
+
+#import <Cocoa/Cocoa.h>
+
+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 <NSTextInputClient>
+{
+ 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 <NSWindowDelegate>
+{
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/******************************************************************************
+ * 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 <Cocoa/Cocoa.h>
+#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 <NSApplicationDelegate>
+@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 <http://www.gnu.org/licenses/>.
- */
-
-/******************************************************************************
- * 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 <Cocoa/Cocoa.h>
-#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 */