mirror of
https://github.com/JGRennison/OpenTTD-patches.git
synced 2024-11-02 09:40:35 +00:00
2076 lines
55 KiB
Objective-C
2076 lines
55 KiB
Objective-C
/* $Id$ */
|
|
|
|
/******************************************************************************
|
|
* Cocoa video driver *
|
|
* Known things left to do: *
|
|
* Nothing at the moment. *
|
|
******************************************************************************/
|
|
|
|
#ifdef WITH_COCOA
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <sys/time.h> /* gettimeofday */
|
|
#import <sys/param.h> /* for MAXPATHLEN */
|
|
#import <unistd.h>
|
|
|
|
/* Portions of CPS.h */
|
|
typedef struct CPSProcessSerNum {
|
|
UInt32 lo;
|
|
UInt32 hi;
|
|
} CPSProcessSerNum;
|
|
|
|
extern OSErr CPSGetCurrentProcess(CPSProcessSerNum* psn);
|
|
extern OSErr CPSEnableForegroundOperation(CPSProcessSerNum* psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
|
|
extern OSErr CPSSetFrontProcess(CPSProcessSerNum* psn);
|
|
|
|
/* From Menus.h (according to Xcode Developer Documentation) */
|
|
extern void ShowMenuBar(void);
|
|
extern void HideMenuBar(void);
|
|
|
|
/* Disables a warning. This is needed since the method exists but has been dropped from the header, supposedly as of 10.4. */
|
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
|
|
@interface NSApplication(NSAppleMenu)
|
|
- (void)setAppleMenu:(NSMenu *)menu;
|
|
@end
|
|
#endif
|
|
|
|
|
|
/* Defined in ppc/param.h or i386/param.h included from sys/param.h */
|
|
#undef ALIGN
|
|
/* Defined in stdbool.h */
|
|
#ifndef __cplusplus
|
|
# ifndef __BEOS__
|
|
# undef bool
|
|
# undef false
|
|
# undef true
|
|
# endif
|
|
#endif
|
|
|
|
#include "../stdafx.h"
|
|
#include "../openttd.h"
|
|
#include "../debug.h"
|
|
#include "../functions.h"
|
|
#include "../gfx.h"
|
|
#include "../macros.h"
|
|
#include "../sdl.h"
|
|
#include "../window.h"
|
|
#include "../network.h"
|
|
#include "../variables.h"
|
|
#include "../os/macosx/splash.h"
|
|
|
|
#include "cocoa_v.h"
|
|
#include "cocoa_keys.h"
|
|
|
|
#undef Point
|
|
#undef Rect
|
|
|
|
|
|
/* Subclass of NSWindow to fix genie effect and support resize events */
|
|
@interface OTTD_QuartzWindow : NSWindow
|
|
- (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:(unsigned int)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag;
|
|
@end
|
|
|
|
/* Delegate for our NSWindow to send ask for quit on close */
|
|
@interface OTTD_QuartzWindowDelegate : NSObject
|
|
- (BOOL)windowShouldClose:(id)sender;
|
|
@end
|
|
|
|
@interface OTTDMain : NSObject
|
|
@end
|
|
|
|
|
|
/* Structure for rez switch gamma fades
|
|
* We can hide the monitor flicker by setting the gamma tables to 0
|
|
*/
|
|
#define QZ_GAMMA_TABLE_SIZE 256
|
|
|
|
typedef struct {
|
|
CGGammaValue red[QZ_GAMMA_TABLE_SIZE];
|
|
CGGammaValue green[QZ_GAMMA_TABLE_SIZE];
|
|
CGGammaValue blue[QZ_GAMMA_TABLE_SIZE];
|
|
} OTTD_QuartzGammaTable;
|
|
|
|
/* Add methods to get at private members of NSScreen.
|
|
* Since there is a bug in Apple's screen switching code that does not update
|
|
* this variable when switching to fullscreen, we'll set it manually (but only
|
|
* for the main screen).
|
|
*/
|
|
@interface NSScreen (NSScreenAccess)
|
|
- (void) setFrame:(NSRect)frame;
|
|
@end
|
|
|
|
@implementation NSScreen (NSScreenAccess)
|
|
- (void) setFrame:(NSRect)frame;
|
|
{
|
|
_frame = frame;
|
|
}
|
|
@end
|
|
|
|
|
|
static void QZ_Draw(void);
|
|
static void QZ_UnsetVideoMode(void);
|
|
static void QZ_UpdatePalette(uint start, uint count);
|
|
static void QZ_WarpCursor(int x, int y);
|
|
static void QZ_ShowMouse(void);
|
|
static void QZ_HideMouse(void);
|
|
static void CocoaVideoFullScreen(bool full_screen);
|
|
|
|
|
|
static NSAutoreleasePool *_ottd_autorelease_pool;
|
|
static OTTDMain *_ottd_main;
|
|
|
|
|
|
static struct CocoaVideoData {
|
|
bool isset;
|
|
bool issetting;
|
|
|
|
CGDirectDisplayID display_id; /* 0 == main display (only support single display) */
|
|
CFDictionaryRef mode; /* current mode of the display */
|
|
CFDictionaryRef save_mode; /* original mode of the display */
|
|
CFArrayRef mode_list; /* list of available fullscreen modes */
|
|
CGDirectPaletteRef palette; /* palette of an 8-bit display */
|
|
|
|
uint32 device_width;
|
|
uint32 device_height;
|
|
uint32 device_bpp;
|
|
|
|
void *realpixels;
|
|
uint8 *pixels;
|
|
uint32 width;
|
|
uint32 height;
|
|
uint32 pitch;
|
|
bool fullscreen;
|
|
|
|
unsigned int current_mods;
|
|
bool tab_is_down;
|
|
bool emulating_right_button;
|
|
|
|
bool cursor_visible;
|
|
bool active;
|
|
|
|
#ifdef _DEBUG
|
|
uint32 tEvent;
|
|
#endif
|
|
|
|
OTTD_QuartzWindow *window;
|
|
NSQuickDrawView *qdview;
|
|
|
|
#define MAX_DIRTY_RECTS 100
|
|
OTTDRect dirty_rects[MAX_DIRTY_RECTS];
|
|
int num_dirty_rects;
|
|
|
|
uint16 palette16[256];
|
|
uint32 palette32[256];
|
|
} _cocoa_video_data;
|
|
|
|
static bool _cocoa_video_started = false;
|
|
static bool _cocoa_video_dialog = false;
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
* Game loop and accessories *
|
|
******************************************************************************/
|
|
|
|
static uint32 GetTick(void)
|
|
{
|
|
struct timeval tim;
|
|
|
|
gettimeofday(&tim, NULL);
|
|
return tim.tv_usec / 1000 + tim.tv_sec * 1000;
|
|
}
|
|
|
|
static void QZ_CheckPaletteAnim(void)
|
|
{
|
|
if (_pal_last_dirty != -1) {
|
|
QZ_UpdatePalette(_pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1);
|
|
_pal_last_dirty = -1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
typedef struct VkMapping {
|
|
unsigned short vk_from;
|
|
byte map_to;
|
|
} VkMapping;
|
|
|
|
#define AS(x, z) {x, z}
|
|
|
|
static const VkMapping _vk_mapping[] = {
|
|
AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1'
|
|
AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode
|
|
|
|
// Pageup stuff + up/down
|
|
//AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN), <==== Does this include HOME/END?
|
|
AS(QZ_PAGEUP, WKC_PAGEUP),
|
|
AS(QZ_PAGEDOWN, WKC_PAGEDOWN),
|
|
|
|
AS(QZ_UP, WKC_UP),
|
|
AS(QZ_DOWN, WKC_DOWN),
|
|
AS(QZ_LEFT, WKC_LEFT),
|
|
AS(QZ_RIGHT, WKC_RIGHT),
|
|
|
|
AS(QZ_HOME, WKC_HOME),
|
|
AS(QZ_END, WKC_END),
|
|
|
|
AS(QZ_INSERT, WKC_INSERT),
|
|
AS(QZ_DELETE, WKC_DELETE),
|
|
|
|
// Letters. QZ_[a-z] is not in numerical order so we can't use AM(...)
|
|
AS(QZ_a, 'A'),
|
|
AS(QZ_b, 'B'),
|
|
AS(QZ_c, 'C'),
|
|
AS(QZ_d, 'D'),
|
|
AS(QZ_e, 'E'),
|
|
AS(QZ_f, 'F'),
|
|
AS(QZ_g, 'G'),
|
|
AS(QZ_h, 'H'),
|
|
AS(QZ_i, 'I'),
|
|
AS(QZ_j, 'J'),
|
|
AS(QZ_k, 'K'),
|
|
AS(QZ_l, 'L'),
|
|
AS(QZ_m, 'M'),
|
|
AS(QZ_n, 'N'),
|
|
AS(QZ_o, 'O'),
|
|
AS(QZ_p, 'P'),
|
|
AS(QZ_q, 'Q'),
|
|
AS(QZ_r, 'R'),
|
|
AS(QZ_s, 'S'),
|
|
AS(QZ_t, 'T'),
|
|
AS(QZ_u, 'U'),
|
|
AS(QZ_v, 'V'),
|
|
AS(QZ_w, 'W'),
|
|
AS(QZ_x, 'X'),
|
|
AS(QZ_y, 'Y'),
|
|
AS(QZ_z, 'Z'),
|
|
// Same thing for digits
|
|
AS(QZ_0, '0'),
|
|
AS(QZ_1, '1'),
|
|
AS(QZ_2, '2'),
|
|
AS(QZ_3, '3'),
|
|
AS(QZ_4, '4'),
|
|
AS(QZ_5, '5'),
|
|
AS(QZ_6, '6'),
|
|
AS(QZ_7, '7'),
|
|
AS(QZ_8, '8'),
|
|
AS(QZ_9, '9'),
|
|
|
|
AS(QZ_ESCAPE, WKC_ESC),
|
|
AS(QZ_PAUSE, WKC_PAUSE),
|
|
AS(QZ_BACKSPACE, WKC_BACKSPACE),
|
|
|
|
AS(QZ_SPACE, WKC_SPACE),
|
|
AS(QZ_RETURN, WKC_RETURN),
|
|
AS(QZ_TAB, WKC_TAB),
|
|
|
|
// Function keys
|
|
AS(QZ_F1, WKC_F1),
|
|
AS(QZ_F2, WKC_F2),
|
|
AS(QZ_F3, WKC_F3),
|
|
AS(QZ_F4, WKC_F4),
|
|
AS(QZ_F5, WKC_F5),
|
|
AS(QZ_F6, WKC_F6),
|
|
AS(QZ_F7, WKC_F7),
|
|
AS(QZ_F8, WKC_F8),
|
|
AS(QZ_F9, WKC_F9),
|
|
AS(QZ_F10, WKC_F10),
|
|
AS(QZ_F11, WKC_F11),
|
|
AS(QZ_F12, WKC_F12),
|
|
|
|
// Numeric part.
|
|
AS(QZ_KP0, WKC_NUM_0),
|
|
AS(QZ_KP1, WKC_NUM_1),
|
|
AS(QZ_KP2, WKC_NUM_2),
|
|
AS(QZ_KP3, WKC_NUM_3),
|
|
AS(QZ_KP4, WKC_NUM_4),
|
|
AS(QZ_KP5, WKC_NUM_5),
|
|
AS(QZ_KP6, WKC_NUM_6),
|
|
AS(QZ_KP7, WKC_NUM_7),
|
|
AS(QZ_KP8, WKC_NUM_8),
|
|
AS(QZ_KP9, WKC_NUM_9),
|
|
AS(QZ_KP_DIVIDE, WKC_NUM_DIV),
|
|
AS(QZ_KP_MULTIPLY, WKC_NUM_MUL),
|
|
AS(QZ_KP_MINUS, WKC_NUM_MINUS),
|
|
AS(QZ_KP_PLUS, WKC_NUM_PLUS),
|
|
AS(QZ_KP_ENTER, WKC_NUM_ENTER),
|
|
AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL)
|
|
};
|
|
|
|
|
|
static uint32 QZ_MapKey(unsigned short sym)
|
|
{
|
|
const VkMapping *map;
|
|
uint32 key = 0;
|
|
|
|
for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
|
|
if (sym == map->vk_from) {
|
|
key = map->map_to;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_cocoa_video_data.current_mods & NSShiftKeyMask) key |= WKC_SHIFT;
|
|
if (_cocoa_video_data.current_mods & NSControlKeyMask) key |= WKC_CTRL;
|
|
if (_cocoa_video_data.current_mods & NSAlternateKeyMask) key |= WKC_ALT;
|
|
if (_cocoa_video_data.current_mods & NSCommandKeyMask) key |= WKC_META;
|
|
|
|
return key << 16;
|
|
}
|
|
|
|
static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down)
|
|
{
|
|
switch (keycode) {
|
|
case QZ_UP: SB(_dirkeys, 1, 1, down); break;
|
|
case QZ_DOWN: SB(_dirkeys, 3, 1, down); break;
|
|
case QZ_LEFT: SB(_dirkeys, 0, 1, down); break;
|
|
case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break;
|
|
|
|
case QZ_TAB: _cocoa_video_data.tab_is_down = down; break;
|
|
|
|
case QZ_RETURN:
|
|
case QZ_f:
|
|
if (down && (
|
|
(_cocoa_video_data.current_mods & NSControlKeyMask) ||
|
|
(_cocoa_video_data.current_mods & NSCommandKeyMask)
|
|
)) {
|
|
CocoaVideoFullScreen(!_fullscreen);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (down) {
|
|
_pressed_key = QZ_MapKey(keycode) | unicode;
|
|
DEBUG(driver, 2)("cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, _pressed_key);
|
|
} else {
|
|
DEBUG(driver, 2)("cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode);
|
|
}
|
|
}
|
|
|
|
static void QZ_DoUnsidedModifiers(unsigned int newMods)
|
|
{
|
|
const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA };
|
|
|
|
int i;
|
|
int bit;
|
|
|
|
if (_cocoa_video_data.current_mods == newMods) return;
|
|
|
|
/* Iterate through the bits, testing each against the current modifiers */
|
|
for (i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) {
|
|
unsigned int currentMask, newMask;
|
|
|
|
currentMask = _cocoa_video_data.current_mods & bit;
|
|
newMask = newMods & bit;
|
|
|
|
if (currentMask && currentMask != newMask) { /* modifier up event */
|
|
/* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */
|
|
if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, YES);
|
|
QZ_KeyEvent(mapping[i], 0, NO);
|
|
} else if (newMask && currentMask != newMask) { /* modifier down event */
|
|
QZ_KeyEvent(mapping[i], 0, YES);
|
|
/* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */
|
|
if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, NO);
|
|
}
|
|
}
|
|
|
|
_cocoa_video_data.current_mods = newMods;
|
|
}
|
|
|
|
static void QZ_MouseMovedEvent(int x, int y)
|
|
{
|
|
if (_cursor.fix_at) {
|
|
int dx = x - _cursor.pos.x;
|
|
int dy = y - _cursor.pos.y;
|
|
|
|
if (dx != 0 || dy != 0) {
|
|
_cursor.delta.x += dx;
|
|
_cursor.delta.y += dy;
|
|
|
|
QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y);
|
|
}
|
|
} else {
|
|
_cursor.delta.x = x - _cursor.pos.x;
|
|
_cursor.delta.y = y - _cursor.pos.y;
|
|
_cursor.pos.x = x;
|
|
_cursor.pos.y = y;
|
|
_cursor.dirty = true;
|
|
}
|
|
}
|
|
|
|
|
|
static void QZ_MouseButtonEvent(int button, BOOL down)
|
|
{
|
|
switch (button) {
|
|
case 0:
|
|
if (down) {
|
|
_left_button_down = true;
|
|
} else {
|
|
_left_button_down = false;
|
|
_left_button_clicked = false;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
if (down) {
|
|
_right_button_down = true;
|
|
_right_button_clicked = true;
|
|
} else {
|
|
_right_button_down = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static inline NSPoint QZ_GetMouseLocation(NSEvent *event)
|
|
{
|
|
NSPoint pt;
|
|
|
|
if (_cocoa_video_data.fullscreen) {
|
|
pt = [ NSEvent mouseLocation ];
|
|
pt.y = _cocoa_video_data.height - pt.y;
|
|
} else {
|
|
pt = [event locationInWindow];
|
|
pt = [_cocoa_video_data.qdview convertPoint:pt fromView:nil];
|
|
}
|
|
|
|
return pt;
|
|
}
|
|
|
|
static bool QZ_MouseIsInsideView(NSPoint *pt)
|
|
{
|
|
if (_cocoa_video_data.fullscreen) {
|
|
return pt->x >= 0 && pt->y >= 0 && pt->x < _cocoa_video_data.width && pt->y < _cocoa_video_data.height;
|
|
} else {
|
|
return [ _cocoa_video_data.qdview mouse:*pt inRect:[ _cocoa_video_data.qdview bounds ] ];
|
|
}
|
|
}
|
|
|
|
|
|
static bool QZ_PollEvent(void)
|
|
{
|
|
NSEvent *event;
|
|
NSPoint pt;
|
|
NSString *chars;
|
|
#ifdef _DEBUG
|
|
uint32 et0, et;
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
et0 = GetTick();
|
|
#endif
|
|
event = [ NSApp nextEventMatchingMask:NSAnyEventMask
|
|
untilDate: [ NSDate distantPast ]
|
|
inMode: NSDefaultRunLoopMode dequeue:YES ];
|
|
#ifdef _DEBUG
|
|
et = GetTick();
|
|
_cocoa_video_data.tEvent+= et - et0;
|
|
#endif
|
|
|
|
if (event == nil) return false;
|
|
if (!_cocoa_video_data.active) {
|
|
QZ_ShowMouse();
|
|
[NSApp sendEvent:event];
|
|
return true;
|
|
}
|
|
|
|
QZ_DoUnsidedModifiers( [ event modifierFlags ] );
|
|
|
|
switch ([event type]) {
|
|
case NSMouseMoved:
|
|
case NSOtherMouseDragged:
|
|
case NSRightMouseDragged:
|
|
case NSLeftMouseDragged:
|
|
pt = QZ_GetMouseLocation(event);
|
|
if (!QZ_MouseIsInsideView(&pt) &&
|
|
!_cocoa_video_data.emulating_right_button) {
|
|
QZ_ShowMouse();
|
|
[NSApp sendEvent:event];
|
|
break;
|
|
}
|
|
|
|
QZ_HideMouse();
|
|
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
|
break;
|
|
|
|
case NSLeftMouseDown:
|
|
if (!([ event modifierFlags ] & NSCommandKeyMask) ||
|
|
!QZ_MouseIsInsideView(&pt)) {
|
|
[NSApp sendEvent:event];
|
|
}
|
|
|
|
pt = QZ_GetMouseLocation(event);
|
|
if (!QZ_MouseIsInsideView(&pt)) {
|
|
QZ_ShowMouse();
|
|
break;
|
|
}
|
|
|
|
QZ_HideMouse();
|
|
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
|
|
|
/* Right mouse button emulation */
|
|
if ([ event modifierFlags ] & NSCommandKeyMask) {
|
|
_cocoa_video_data.emulating_right_button = true;
|
|
QZ_MouseButtonEvent(1, YES);
|
|
} else {
|
|
QZ_MouseButtonEvent(0, YES);
|
|
}
|
|
break;
|
|
|
|
case NSLeftMouseUp:
|
|
[NSApp sendEvent:event];
|
|
|
|
pt = QZ_GetMouseLocation(event);
|
|
if (!QZ_MouseIsInsideView(&pt)) {
|
|
QZ_ShowMouse();
|
|
break;
|
|
}
|
|
|
|
QZ_HideMouse();
|
|
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
|
|
|
/* Right mouse button emulation */
|
|
if (_cocoa_video_data.emulating_right_button) {
|
|
_cocoa_video_data.emulating_right_button = false;
|
|
QZ_MouseButtonEvent(1, NO);
|
|
} else {
|
|
QZ_MouseButtonEvent(0, NO);
|
|
}
|
|
break;
|
|
|
|
case NSRightMouseDown:
|
|
pt = QZ_GetMouseLocation(event);
|
|
if (!QZ_MouseIsInsideView(&pt)) {
|
|
QZ_ShowMouse();
|
|
[NSApp sendEvent:event];
|
|
break;
|
|
}
|
|
|
|
QZ_HideMouse();
|
|
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
|
QZ_MouseButtonEvent(1, YES);
|
|
break;
|
|
|
|
case NSRightMouseUp:
|
|
pt = QZ_GetMouseLocation(event);
|
|
if (!QZ_MouseIsInsideView(&pt)) {
|
|
QZ_ShowMouse();
|
|
[NSApp sendEvent:event];
|
|
break;
|
|
}
|
|
|
|
QZ_HideMouse();
|
|
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
|
QZ_MouseButtonEvent(1, NO);
|
|
break;
|
|
|
|
#if 0
|
|
/* This is not needed since openttd currently only use two buttons */
|
|
case NSOtherMouseDown:
|
|
pt = QZ_GetMouseLocation(event);
|
|
if (!QZ_MouseIsInsideView(&pt)) {
|
|
QZ_ShowMouse();
|
|
[NSApp sendEvent:event];
|
|
break;
|
|
}
|
|
|
|
QZ_HideMouse();
|
|
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
|
QZ_MouseButtonEvent([ event buttonNumber ], YES);
|
|
break;
|
|
|
|
case NSOtherMouseUp:
|
|
pt = QZ_GetMouseLocation(event);
|
|
if (!QZ_MouseIsInsideView(&pt)) {
|
|
QZ_ShowMouse();
|
|
[NSApp sendEvent:event];
|
|
break;
|
|
}
|
|
|
|
QZ_HideMouse();
|
|
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
|
QZ_MouseButtonEvent([ event buttonNumber ], NO);
|
|
break;
|
|
#endif
|
|
|
|
case NSKeyDown:
|
|
/* Quit, hide and minimize */
|
|
switch ([event keyCode]) {
|
|
case QZ_q:
|
|
case QZ_h:
|
|
case QZ_m:
|
|
if ([ event modifierFlags ] & NSCommandKeyMask) {
|
|
[NSApp sendEvent:event];
|
|
}
|
|
break;
|
|
}
|
|
|
|
chars = [ event characters ];
|
|
QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, YES);
|
|
break;
|
|
|
|
case NSKeyUp:
|
|
/* Quit, hide and minimize */
|
|
switch ([event keyCode]) {
|
|
case QZ_q:
|
|
case QZ_h:
|
|
case QZ_m:
|
|
if ([ event modifierFlags ] & NSCommandKeyMask) {
|
|
[NSApp sendEvent:event];
|
|
}
|
|
break;
|
|
}
|
|
|
|
chars = [ event characters ];
|
|
QZ_KeyEvent([event keyCode], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO);
|
|
break;
|
|
|
|
case NSScrollWheel:
|
|
if ([ event deltaY ] > 0.0) { /* Scroll up */
|
|
_cursor.wheel--;
|
|
} else if ([ event deltaY ] < 0.0) { /* Scroll down */
|
|
_cursor.wheel++;
|
|
} /* else: deltaY was 0.0 and we don't want to do anything */
|
|
break;
|
|
|
|
default:
|
|
[NSApp sendEvent:event];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static void QZ_GameLoop(void)
|
|
{
|
|
uint32 next_tick = GetTick() + 30;
|
|
uint32 cur_ticks;
|
|
uint32 pal_tick = 0;
|
|
#ifdef _DEBUG
|
|
uint32 et0, et, st0, st;
|
|
#endif
|
|
int i;
|
|
|
|
DEBUG(driver, 1)("cocoa_v: QZ_GameLoop");
|
|
|
|
#ifdef _DEBUG
|
|
et0 = GetTick();
|
|
st = 0;
|
|
#endif
|
|
|
|
_screen.dst_ptr = _cocoa_video_data.pixels;
|
|
DisplaySplashImage();
|
|
QZ_CheckPaletteAnim();
|
|
QZ_Draw();
|
|
CSleep(1);
|
|
|
|
for (i = 0; i < 2; i++) GameLoop();
|
|
|
|
_screen.dst_ptr = _cocoa_video_data.pixels;
|
|
UpdateWindows();
|
|
QZ_CheckPaletteAnim();
|
|
QZ_Draw();
|
|
CSleep(1);
|
|
|
|
for (;;) {
|
|
InteractiveRandom(); // randomness
|
|
|
|
while (QZ_PollEvent()) {}
|
|
|
|
if (_exit_game) break;
|
|
|
|
#if defined(_DEBUG)
|
|
if (_cocoa_video_data.current_mods & NSShiftKeyMask)
|
|
#else
|
|
if (_cocoa_video_data.tab_is_down)
|
|
#endif
|
|
{
|
|
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
|
|
} else if (_fast_forward & 2) {
|
|
_fast_forward = 0;
|
|
}
|
|
|
|
cur_ticks = GetTick();
|
|
if ((_fast_forward && !_pause) || cur_ticks > next_tick)
|
|
next_tick = cur_ticks;
|
|
|
|
if (cur_ticks == next_tick) {
|
|
next_tick += 30;
|
|
|
|
_ctrl_pressed = !!(_cocoa_video_data.current_mods & NSControlKeyMask);
|
|
_shift_pressed = !!(_cocoa_video_data.current_mods & NSShiftKeyMask);
|
|
#ifdef _DEBUG
|
|
_dbg_screen_rect = !!(_cocoa_video_data.current_mods & NSAlphaShiftKeyMask);
|
|
#endif
|
|
|
|
GameLoop();
|
|
|
|
_screen.dst_ptr = _cocoa_video_data.pixels;
|
|
UpdateWindows();
|
|
if (++pal_tick > 4) {
|
|
QZ_CheckPaletteAnim();
|
|
pal_tick = 1;
|
|
}
|
|
QZ_Draw();
|
|
} else {
|
|
#ifdef _DEBUG
|
|
st0 = GetTick();
|
|
#endif
|
|
CSleep(1);
|
|
#ifdef _DEBUG
|
|
st += GetTick() - st0;
|
|
#endif
|
|
_screen.dst_ptr = _cocoa_video_data.pixels;
|
|
DrawTextMessage();
|
|
DrawMouseCursor();
|
|
QZ_Draw();
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
et = GetTick();
|
|
|
|
DEBUG(driver, 1)("cocoa_v: nextEventMatchingMask took %i ms total", _cocoa_video_data.tEvent);
|
|
DEBUG(driver, 1)("cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st);
|
|
DEBUG(driver, 1)("cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_cocoa_video_data.tEvent / (double)(et - et0) * 100);
|
|
DEBUG(driver, 1)("cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_cocoa_video_data.tEvent / (double)(et - et0 - st) * 100);
|
|
#endif
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Windowed mode *
|
|
******************************************************************************/
|
|
|
|
/* 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.
|
|
*/
|
|
static void QZ_SetPortAlphaOpaque(void)
|
|
{
|
|
if (_cocoa_video_data.device_bpp == 32) {
|
|
uint32* pixels = (uint32*)_cocoa_video_data.realpixels;
|
|
uint32 rowPixels = _cocoa_video_data.pitch / 4;
|
|
uint32 i;
|
|
uint32 j;
|
|
|
|
for (i = 0; i < _cocoa_video_data.height; i++)
|
|
for (j = 0; j < _cocoa_video_data.width; j++) {
|
|
pixels[i * rowPixels + j] |= 0xFF000000;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@implementation OTTD_QuartzWindow
|
|
|
|
/* we override these methods to fix the miniaturize animation/dock icon bug */
|
|
- (void)miniaturize:(id)sender
|
|
{
|
|
/* make the alpha channel opaque so anim won't have holes in it */
|
|
QZ_SetPortAlphaOpaque ();
|
|
|
|
/* window is hidden now */
|
|
_cocoa_video_data.active = false;
|
|
|
|
QZ_ShowMouse();
|
|
|
|
[ super miniaturize:sender ];
|
|
}
|
|
|
|
- (void)display
|
|
{
|
|
/* 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.
|
|
*/
|
|
|
|
QZ_SetPortAlphaOpaque();
|
|
|
|
/* save current visible surface */
|
|
[ self cacheImageInRect:[ _cocoa_video_data.qdview frame ] ];
|
|
|
|
/* let the window manager redraw controls, border, etc */
|
|
[ super display ];
|
|
|
|
/* restore visible surface */
|
|
[ self restoreCachedImage ];
|
|
|
|
/* window is visible again */
|
|
_cocoa_video_data.active = true;
|
|
}
|
|
|
|
- (void)setFrame:(NSRect)frameRect display:(BOOL)flag
|
|
{
|
|
NSRect newViewFrame;
|
|
CGrafPtr thePort;
|
|
|
|
[ super setFrame:frameRect display:flag ];
|
|
|
|
/* Don't do anything if the window is currently beign created */
|
|
if (_cocoa_video_data.issetting) return;
|
|
|
|
if (_cocoa_video_data.window == nil) return;
|
|
|
|
newViewFrame = [ _cocoa_video_data.qdview frame ];
|
|
|
|
/* Update the pixels and pitch */
|
|
thePort = [ _cocoa_video_data.qdview qdPort ];
|
|
LockPortBits(thePort);
|
|
|
|
_cocoa_video_data.realpixels = GetPixBaseAddr(GetPortPixMap(thePort));
|
|
_cocoa_video_data.pitch = GetPixRowBytes(GetPortPixMap(thePort));
|
|
|
|
/* _cocoa_video_data.realpixels now points to the window's pixels
|
|
* We want it to point to the *view's* pixels
|
|
*/
|
|
{
|
|
int vOffset = [ _cocoa_video_data.window frame ].size.height - newViewFrame.size.height - newViewFrame.origin.y;
|
|
int hOffset = newViewFrame.origin.x;
|
|
|
|
_cocoa_video_data.realpixels = (uint8*)_cocoa_video_data.realpixels + (vOffset * _cocoa_video_data.pitch) + hOffset * (_cocoa_video_data.device_bpp / 8);
|
|
}
|
|
|
|
UnlockPortBits(thePort);
|
|
|
|
/* Allocate new buffer */
|
|
free(_cocoa_video_data.pixels);
|
|
_cocoa_video_data.pixels = (uint8*)malloc(newViewFrame.size.width * newViewFrame.size.height);
|
|
assert(_cocoa_video_data.pixels != NULL);
|
|
|
|
|
|
/* Tell the game that the resolution changed */
|
|
_cocoa_video_data.width = newViewFrame.size.width;
|
|
_cocoa_video_data.height = newViewFrame.size.height;
|
|
|
|
_screen.width = _cocoa_video_data.width;
|
|
_screen.height = _cocoa_video_data.height;
|
|
_screen.pitch = _cocoa_video_data.width;
|
|
|
|
GameSizeChanged();
|
|
|
|
/* Redraw screen */
|
|
_cocoa_video_data.num_dirty_rects = MAX_DIRTY_RECTS;
|
|
}
|
|
|
|
- (void)appDidHide:(NSNotification*)note
|
|
{
|
|
_cocoa_video_data.active = false;
|
|
}
|
|
|
|
|
|
- (void)appWillUnhide:(NSNotification*)note
|
|
{
|
|
QZ_SetPortAlphaOpaque ();
|
|
|
|
/* save current visible surface */
|
|
[ self cacheImageInRect:[ _cocoa_video_data.qdview frame ] ];
|
|
}
|
|
|
|
- (void)appDidUnhide:(NSNotification*)note
|
|
{
|
|
/* restore cached image, since it may not be current, post expose event too */
|
|
[ self restoreCachedImage ];
|
|
|
|
_cocoa_video_data.active = true;
|
|
}
|
|
|
|
|
|
- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)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
|
|
|
|
@implementation OTTD_QuartzWindowDelegate
|
|
- (BOOL)windowShouldClose:(id)sender
|
|
{
|
|
HandleExitGameRequest();
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void)windowDidBecomeKey:(NSNotification*)aNotification
|
|
{
|
|
_cocoa_video_data.active = true;
|
|
}
|
|
|
|
- (void)windowDidResignKey:(NSNotification*)aNotification
|
|
{
|
|
_cocoa_video_data.active = false;
|
|
}
|
|
|
|
- (void)windowDidBecomeMain:(NSNotification*)aNotification
|
|
{
|
|
_cocoa_video_data.active = true;
|
|
}
|
|
|
|
- (void)windowDidResignMain:(NSNotification*)aNotification
|
|
{
|
|
_cocoa_video_data.active = false;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
static void QZ_UpdateWindowPalette(uint start, uint count)
|
|
{
|
|
uint i;
|
|
|
|
switch (_cocoa_video_data.device_bpp) {
|
|
case 32:
|
|
for (i = start; i < start + count; i++) {
|
|
uint32 clr32 = 0xff000000;
|
|
clr32 |= (uint32)_cur_palette[i].r << 16;
|
|
clr32 |= (uint32)_cur_palette[i].g << 8;
|
|
clr32 |= (uint32)_cur_palette[i].b;
|
|
_cocoa_video_data.palette32[i] = clr32;
|
|
}
|
|
break;
|
|
case 16:
|
|
for (i = start; i < start + count; i++) {
|
|
uint16 clr16 = 0x0000;
|
|
clr16 |= (uint16)((_cur_palette[i].r >> 3) & 0x1f) << 10;
|
|
clr16 |= (uint16)((_cur_palette[i].g >> 3) & 0x1f) << 5;
|
|
clr16 |= (uint16)((_cur_palette[i].b >> 3) & 0x1f);
|
|
_cocoa_video_data.palette16[i] = clr16;
|
|
}
|
|
break;
|
|
}
|
|
|
|
_cocoa_video_data.num_dirty_rects = MAX_DIRTY_RECTS;
|
|
}
|
|
|
|
static inline void QZ_WindowBlitIndexedPixelsToView32(uint left, uint top, uint right, uint bottom)
|
|
{
|
|
const uint32* pal = _cocoa_video_data.palette32;
|
|
const uint8* src = _cocoa_video_data.pixels;
|
|
uint32* dst = (uint32*)_cocoa_video_data.realpixels;
|
|
uint width = _cocoa_video_data.width;
|
|
uint pitch = _cocoa_video_data.pitch / 4;
|
|
uint x;
|
|
uint y;
|
|
|
|
for (y = top; y < bottom; y++) {
|
|
for (x = left; x < right; x++) {
|
|
dst[y * pitch + x] = pal[src[y * width + x]];
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void QZ_WindowBlitIndexedPixelsToView16(uint left, uint top, uint right, uint bottom)
|
|
{
|
|
const uint16* pal = _cocoa_video_data.palette16;
|
|
const uint8* src = _cocoa_video_data.pixels;
|
|
uint16* dst = (uint16*)_cocoa_video_data.realpixels;
|
|
uint width = _cocoa_video_data.width;
|
|
uint pitch = _cocoa_video_data.pitch / 2;
|
|
uint x;
|
|
uint y;
|
|
|
|
for (y = top; y < bottom; y++) {
|
|
for (x = left; x < right; x++) {
|
|
dst[y * pitch + x] = pal[src[y * width + x]];
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void QZ_WindowBlitIndexedPixelsToView(int left, int top, int right, int bottom)
|
|
{
|
|
switch (_cocoa_video_data.device_bpp) {
|
|
case 32: QZ_WindowBlitIndexedPixelsToView32(left, top, right, bottom); break;
|
|
case 16: QZ_WindowBlitIndexedPixelsToView16(left, top, right, bottom); break;
|
|
}
|
|
}
|
|
|
|
static bool _resize_icon[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
|
|
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
|
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,
|
|
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
|
|
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
|
|
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
|
|
0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,
|
|
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0
|
|
};
|
|
|
|
static void QZ_DrawResizeIcon(void)
|
|
{
|
|
int xoff = _cocoa_video_data.width - 16;
|
|
int yoff = _cocoa_video_data.height - 16;
|
|
int x;
|
|
int y;
|
|
|
|
for (y = 0; y < 16; y++) {
|
|
uint16* trg16 = (uint16*)_cocoa_video_data.realpixels + (yoff + y) * _cocoa_video_data.pitch / 2 + xoff;
|
|
uint32* trg32 = (uint32*)_cocoa_video_data.realpixels + (yoff + y) * _cocoa_video_data.pitch / 4 + xoff;
|
|
|
|
for (x = 0; x < 16; x++, trg16++, trg32++) {
|
|
if (!_resize_icon[y * 16 + x]) continue;
|
|
|
|
switch (_cocoa_video_data.device_bpp) {
|
|
case 32: *trg32 = 0xff000000; break;
|
|
case 16: *trg16 = 0x0000; break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void QZ_DrawWindow(void)
|
|
{
|
|
int i;
|
|
RgnHandle dirty, temp;
|
|
|
|
/* Check if we need to do anything */
|
|
if (_cocoa_video_data.num_dirty_rects == 0 ||
|
|
[ _cocoa_video_data.window isMiniaturized ]) {
|
|
return;
|
|
}
|
|
|
|
if (_cocoa_video_data.num_dirty_rects >= MAX_DIRTY_RECTS) {
|
|
_cocoa_video_data.num_dirty_rects = 1;
|
|
_cocoa_video_data.dirty_rects[0].left = 0;
|
|
_cocoa_video_data.dirty_rects[0].top = 0;
|
|
_cocoa_video_data.dirty_rects[0].right = _cocoa_video_data.width;
|
|
_cocoa_video_data.dirty_rects[0].bottom = _cocoa_video_data.height;
|
|
}
|
|
|
|
dirty = NewRgn();
|
|
temp = NewRgn();
|
|
|
|
SetEmptyRgn(dirty);
|
|
|
|
/* Build the region of dirty rectangles */
|
|
for (i = 0; i < _cocoa_video_data.num_dirty_rects; i++) {
|
|
QZ_WindowBlitIndexedPixelsToView(
|
|
_cocoa_video_data.dirty_rects[i].left,
|
|
_cocoa_video_data.dirty_rects[i].top,
|
|
_cocoa_video_data.dirty_rects[i].right,
|
|
_cocoa_video_data.dirty_rects[i].bottom
|
|
);
|
|
|
|
MacSetRectRgn(
|
|
temp,
|
|
_cocoa_video_data.dirty_rects[i].left,
|
|
_cocoa_video_data.dirty_rects[i].top,
|
|
_cocoa_video_data.dirty_rects[i].right,
|
|
_cocoa_video_data.dirty_rects[i].bottom
|
|
);
|
|
MacUnionRgn(dirty, temp, dirty);
|
|
}
|
|
|
|
QZ_DrawResizeIcon();
|
|
|
|
/* Flush the dirty region */
|
|
QDFlushPortBuffer([ _cocoa_video_data.qdview qdPort ], dirty);
|
|
DisposeRgn(dirty);
|
|
DisposeRgn(temp);
|
|
|
|
_cocoa_video_data.num_dirty_rects = 0;
|
|
}
|
|
|
|
|
|
extern const char _openttd_revision[];
|
|
|
|
static const char* QZ_SetVideoWindowed(uint width, uint height)
|
|
{
|
|
char caption[50];
|
|
NSString *nsscaption;
|
|
unsigned int style;
|
|
NSRect contentRect;
|
|
BOOL isCustom = NO;
|
|
|
|
if (width > _cocoa_video_data.device_width)
|
|
width = _cocoa_video_data.device_width;
|
|
if (height > _cocoa_video_data.device_height)
|
|
height = _cocoa_video_data.device_height;
|
|
|
|
_cocoa_video_data.width = width;
|
|
_cocoa_video_data.height = height;
|
|
|
|
contentRect = NSMakeRect(0, 0, width, height);
|
|
|
|
/* Check if we should completely destroy the previous mode
|
|
* - If it is fullscreen
|
|
*/
|
|
if (_cocoa_video_data.isset && _cocoa_video_data.fullscreen)
|
|
QZ_UnsetVideoMode();
|
|
|
|
/* Check if we should recreate the window */
|
|
if (_cocoa_video_data.window == nil) {
|
|
/* Set the window style */
|
|
style = NSTitledWindowMask;
|
|
style |= (NSMiniaturizableWindowMask | NSClosableWindowMask);
|
|
style |= NSResizableWindowMask;
|
|
|
|
/* Manually create a window, avoids having a nib file resource */
|
|
_cocoa_video_data.window = [ [ OTTD_QuartzWindow alloc ]
|
|
initWithContentRect:contentRect
|
|
styleMask:style
|
|
backing:NSBackingStoreBuffered
|
|
defer:NO ];
|
|
|
|
if (_cocoa_video_data.window == nil)
|
|
return "Could not create the Cocoa window";
|
|
|
|
snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision);
|
|
nsscaption = [ [ NSString alloc ] initWithCString:caption ];
|
|
[ _cocoa_video_data.window setTitle:nsscaption ];
|
|
[ _cocoa_video_data.window setMiniwindowTitle:nsscaption ];
|
|
[ nsscaption release ];
|
|
|
|
[ _cocoa_video_data.window setAcceptsMouseMovedEvents:YES ];
|
|
[ _cocoa_video_data.window setViewsNeedDisplay:NO ];
|
|
|
|
[ _cocoa_video_data.window setDelegate: [ [ [ OTTD_QuartzWindowDelegate alloc ] init ] autorelease ] ];
|
|
} else {
|
|
/* We already have a window, just change its size */
|
|
if (!isCustom) {
|
|
[ _cocoa_video_data.window setContentSize:contentRect.size ];
|
|
[ _cocoa_video_data.qdview setFrameSize:contentRect.size ];
|
|
}
|
|
}
|
|
|
|
[ _cocoa_video_data.window center ];
|
|
|
|
/* Only recreate the view if it doesn't already exist */
|
|
if (_cocoa_video_data.qdview == nil) {
|
|
_cocoa_video_data.qdview = [ [ NSQuickDrawView alloc ] initWithFrame:contentRect ];
|
|
[ _cocoa_video_data.qdview setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
|
|
[ [ _cocoa_video_data.window contentView ] addSubview:_cocoa_video_data.qdview ];
|
|
[ _cocoa_video_data.qdview release ];
|
|
[ _cocoa_video_data.window makeKeyAndOrderFront:nil ];
|
|
}
|
|
|
|
LockPortBits([ _cocoa_video_data.qdview qdPort ]);
|
|
_cocoa_video_data.realpixels = GetPixBaseAddr(GetPortPixMap([ _cocoa_video_data.qdview qdPort ]));
|
|
_cocoa_video_data.pitch = GetPixRowBytes(GetPortPixMap([ _cocoa_video_data.qdview qdPort ]));
|
|
UnlockPortBits([ _cocoa_video_data.qdview qdPort ]);
|
|
|
|
/* _cocoa_video_data.realpixels now points to the window's pixels
|
|
* We want it to point to the *view's* pixels
|
|
*/
|
|
{
|
|
int vOffset = [ _cocoa_video_data.window frame ].size.height - [ _cocoa_video_data.qdview frame ].size.height - [ _cocoa_video_data.qdview frame ].origin.y;
|
|
int hOffset = [ _cocoa_video_data.qdview frame ].origin.x;
|
|
|
|
_cocoa_video_data.realpixels = (uint8*)_cocoa_video_data.realpixels + (vOffset * _cocoa_video_data.pitch) + hOffset * (_cocoa_video_data.device_bpp / 8);
|
|
}
|
|
|
|
free(_cocoa_video_data.pixels);
|
|
_cocoa_video_data.pixels = (uint8*)malloc(width * height);
|
|
if (_cocoa_video_data.pixels == NULL) return "Failed to allocate 8-bit buffer";
|
|
|
|
_cocoa_video_data.fullscreen = false;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Fullscreen mode *
|
|
******************************************************************************/
|
|
|
|
/* Gamma functions to try to hide the flash from a rez switch
|
|
* Fade the display from normal to black
|
|
* Save gamma tables for fade back to normal
|
|
*/
|
|
static uint32 QZ_FadeGammaOut(OTTD_QuartzGammaTable* table)
|
|
{
|
|
CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE];
|
|
CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE];
|
|
CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE];
|
|
float percent;
|
|
int j;
|
|
unsigned int actual;
|
|
|
|
if (CGGetDisplayTransferByTable(
|
|
_cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE,
|
|
table->red, table->green, table->blue, &actual
|
|
) != CGDisplayNoErr ||
|
|
actual != QZ_GAMMA_TABLE_SIZE) {
|
|
return 1;
|
|
}
|
|
|
|
memcpy(redTable, table->red, sizeof(redTable));
|
|
memcpy(greenTable, table->green, sizeof(greenTable));
|
|
memcpy(blueTable, table->blue, sizeof(greenTable));
|
|
|
|
for (percent = 1.0; percent >= 0.0; percent -= 0.01) {
|
|
for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) {
|
|
redTable[j] = redTable[j] * percent;
|
|
greenTable[j] = greenTable[j] * percent;
|
|
blueTable[j] = blueTable[j] * percent;
|
|
}
|
|
|
|
if (CGSetDisplayTransferByTable(
|
|
_cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE,
|
|
redTable, greenTable, blueTable
|
|
) != CGDisplayNoErr) {
|
|
CGDisplayRestoreColorSyncSettings();
|
|
return 1;
|
|
}
|
|
|
|
CSleep(10);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Fade the display from black to normal
|
|
* Restore previously saved gamma values
|
|
*/
|
|
static uint32 QZ_FadeGammaIn(const OTTD_QuartzGammaTable* table)
|
|
{
|
|
CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE];
|
|
CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE];
|
|
CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE];
|
|
float percent;
|
|
int j;
|
|
|
|
memset(redTable, 0, sizeof(redTable));
|
|
memset(greenTable, 0, sizeof(greenTable));
|
|
memset(blueTable, 0, sizeof(greenTable));
|
|
|
|
for (percent = 0.0; percent <= 1.0; percent += 0.01) {
|
|
for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) {
|
|
redTable[j] = table->red[j] * percent;
|
|
greenTable[j] = table->green[j] * percent;
|
|
blueTable[j] = table->blue[j] * percent;
|
|
}
|
|
|
|
if (CGSetDisplayTransferByTable(
|
|
_cocoa_video_data.display_id, QZ_GAMMA_TABLE_SIZE,
|
|
redTable, greenTable, blueTable
|
|
) != CGDisplayNoErr) {
|
|
CGDisplayRestoreColorSyncSettings();
|
|
return 1;
|
|
}
|
|
|
|
CSleep(10);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char* QZ_SetVideoFullScreen(int width, int height)
|
|
{
|
|
const char* errstr = "QZ_SetVideoFullScreen error";
|
|
int exact_match;
|
|
CFNumberRef number;
|
|
int bpp;
|
|
int gamma_error;
|
|
OTTD_QuartzGammaTable gamma_table;
|
|
NSRect screen_rect;
|
|
CGError error;
|
|
NSPoint pt;
|
|
|
|
/* Destroy any previous mode */
|
|
if (_cocoa_video_data.isset) QZ_UnsetVideoMode();
|
|
|
|
/* See if requested mode exists */
|
|
_cocoa_video_data.mode = CGDisplayBestModeForParameters(_cocoa_video_data.display_id, 8, width, height, &exact_match);
|
|
|
|
/* If the mode wasn't an exact match, check if it has the right bpp, and update width and height */
|
|
if (!exact_match) {
|
|
number = CFDictionaryGetValue (_cocoa_video_data.mode, kCGDisplayBitsPerPixel);
|
|
CFNumberGetValue(number, kCFNumberSInt32Type, &bpp);
|
|
if (bpp != 8) {
|
|
errstr = "Failed to find display resolution";
|
|
goto ERR_NO_MATCH;
|
|
}
|
|
|
|
number = CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayWidth);
|
|
CFNumberGetValue(number, kCFNumberSInt32Type, &width);
|
|
|
|
number = CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayHeight);
|
|
CFNumberGetValue(number, kCFNumberSInt32Type, &height);
|
|
}
|
|
|
|
/* Fade display to zero gamma */
|
|
gamma_error = QZ_FadeGammaOut(&gamma_table);
|
|
|
|
/* Put up the blanking window (a window above all other windows) */
|
|
error = CGDisplayCapture(_cocoa_video_data.display_id);
|
|
|
|
if (CGDisplayNoErr != error) {
|
|
errstr = "Failed capturing display";
|
|
goto ERR_NO_CAPTURE;
|
|
}
|
|
|
|
/* Do the physical switch */
|
|
if (CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.mode) != CGDisplayNoErr) {
|
|
errstr = "Failed switching display resolution";
|
|
goto ERR_NO_SWITCH;
|
|
}
|
|
|
|
_cocoa_video_data.realpixels = (uint8*)CGDisplayBaseAddress(_cocoa_video_data.display_id);
|
|
_cocoa_video_data.pitch = CGDisplayBytesPerRow(_cocoa_video_data.display_id);
|
|
|
|
_cocoa_video_data.width = CGDisplayPixelsWide(_cocoa_video_data.display_id);
|
|
_cocoa_video_data.height = CGDisplayPixelsHigh(_cocoa_video_data.display_id);
|
|
_cocoa_video_data.fullscreen = true;
|
|
|
|
/* Setup double-buffer emulation */
|
|
_cocoa_video_data.pixels = (uint8*)malloc(width * height);
|
|
if (_cocoa_video_data.pixels == NULL) {
|
|
errstr = "Failed to allocate memory for double buffering";
|
|
goto ERR_DOUBLEBUF;
|
|
}
|
|
|
|
if (!CGDisplayCanSetPalette(_cocoa_video_data.display_id)) {
|
|
errstr = "Not an indexed display mode.";
|
|
goto ERR_NOT_INDEXED;
|
|
}
|
|
|
|
/* If we don't hide menu bar, it will get events and interrupt the program */
|
|
HideMenuBar();
|
|
|
|
/* Fade the display to original gamma */
|
|
if (!gamma_error) QZ_FadeGammaIn(&gamma_table);
|
|
|
|
/* There is a bug in Cocoa where NSScreen doesn't synchronize
|
|
* with CGDirectDisplay, so the main screen's frame is wrong.
|
|
* As a result, coordinate translation produces incorrect results.
|
|
* We can hack around this bug by setting the screen rect ourselves.
|
|
* This hack should be removed if/when the bug is fixed.
|
|
*/
|
|
screen_rect = NSMakeRect(0, 0, width, height);
|
|
[ [ NSScreen mainScreen ] setFrame:screen_rect ];
|
|
|
|
/* we're fullscreen, so flag all input states... */
|
|
_cocoa_video_data.active = true;
|
|
|
|
|
|
pt = [ NSEvent mouseLocation ];
|
|
pt.y = CGDisplayPixelsHigh(_cocoa_video_data.display_id) - pt.y;
|
|
if (QZ_MouseIsInsideView(&pt)) QZ_HideMouse();
|
|
|
|
return NULL;
|
|
|
|
/* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
|
|
ERR_NOT_INDEXED:
|
|
free(_cocoa_video_data.pixels);
|
|
_cocoa_video_data.pixels = NULL;
|
|
ERR_DOUBLEBUF:
|
|
CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.save_mode);
|
|
ERR_NO_SWITCH:
|
|
CGReleaseAllDisplays();
|
|
ERR_NO_CAPTURE:
|
|
if (!gamma_error) QZ_FadeGammaIn(&gamma_table);
|
|
ERR_NO_MATCH:
|
|
return errstr;
|
|
}
|
|
|
|
|
|
static void QZ_UpdateFullscreenPalette(uint first_color, uint num_colors)
|
|
{
|
|
CGTableCount index;
|
|
CGDeviceColor color;
|
|
|
|
for (index = first_color; index < first_color+num_colors; index++) {
|
|
/* Clamp colors between 0.0 and 1.0 */
|
|
color.red = _cur_palette[index].r / 255.0;
|
|
color.blue = _cur_palette[index].b / 255.0;
|
|
color.green = _cur_palette[index].g / 255.0;
|
|
|
|
CGPaletteSetColorAtIndex(_cocoa_video_data.palette, color, index);
|
|
}
|
|
|
|
CGDisplaySetPalette(_cocoa_video_data.display_id, _cocoa_video_data.palette);
|
|
}
|
|
|
|
/* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */
|
|
static void QZ_WaitForVerticalBlank(void)
|
|
{
|
|
/* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */
|
|
double refreshRate;
|
|
double linesPerSecond;
|
|
double target;
|
|
double position;
|
|
double adjustment;
|
|
CFNumberRef refreshRateCFNumber;
|
|
|
|
refreshRateCFNumber = CFDictionaryGetValue(_cocoa_video_data.mode, kCGDisplayRefreshRate);
|
|
if (refreshRateCFNumber == NULL) return;
|
|
|
|
if (CFNumberGetValue(refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) == 0)
|
|
return;
|
|
|
|
if (refreshRate == 0) return;
|
|
|
|
linesPerSecond = refreshRate * _cocoa_video_data.height;
|
|
target = _cocoa_video_data.height;
|
|
|
|
/* Figure out the first delay so we start off about right */
|
|
position = CGDisplayBeamPosition(_cocoa_video_data.display_id);
|
|
if (position > target) position = 0;
|
|
|
|
adjustment = (target - position) / linesPerSecond;
|
|
|
|
CSleep((uint32)(adjustment * 1000));
|
|
}
|
|
|
|
|
|
static void QZ_DrawScreen(void)
|
|
{
|
|
const uint8* src = _cocoa_video_data.pixels;
|
|
uint8* dst = (uint8*)_cocoa_video_data.realpixels;
|
|
uint pitch = _cocoa_video_data.pitch;
|
|
uint width = _cocoa_video_data.width;
|
|
uint num_dirty = _cocoa_video_data.num_dirty_rects;
|
|
uint i;
|
|
|
|
/* Check if we need to do anything */
|
|
if (num_dirty == 0) return;
|
|
|
|
if (num_dirty >= MAX_DIRTY_RECTS) {
|
|
num_dirty = 1;
|
|
_cocoa_video_data.dirty_rects[0].left = 0;
|
|
_cocoa_video_data.dirty_rects[0].top = 0;
|
|
_cocoa_video_data.dirty_rects[0].right = _cocoa_video_data.width;
|
|
_cocoa_video_data.dirty_rects[0].bottom = _cocoa_video_data.height;
|
|
}
|
|
|
|
QZ_WaitForVerticalBlank();
|
|
/* Build the region of dirty rectangles */
|
|
for (i = 0; i < num_dirty; i++) {
|
|
uint y = _cocoa_video_data.dirty_rects[i].top;
|
|
uint left = _cocoa_video_data.dirty_rects[i].left;
|
|
uint length = _cocoa_video_data.dirty_rects[i].right - left;
|
|
uint bottom = _cocoa_video_data.dirty_rects[i].bottom;
|
|
|
|
for (; y < bottom; y++) {
|
|
memcpy(dst + y * pitch + left, src + y * width + left, length);
|
|
}
|
|
}
|
|
|
|
_cocoa_video_data.num_dirty_rects = 0;
|
|
}
|
|
|
|
|
|
static int QZ_ListFullscreenModes(OTTDPoint* mode_list, int max_modes)
|
|
{
|
|
CFIndex num_modes;
|
|
CFIndex i;
|
|
int list_size = 0;
|
|
|
|
num_modes = CFArrayGetCount(_cocoa_video_data.mode_list);
|
|
|
|
/* Build list of modes with the requested bpp */
|
|
for (i = 0; i < num_modes && list_size < max_modes; i++) {
|
|
CFDictionaryRef onemode;
|
|
CFNumberRef number;
|
|
int bpp;
|
|
int intvalue;
|
|
bool hasMode;
|
|
uint16 width, height;
|
|
|
|
onemode = CFArrayGetValueAtIndex(_cocoa_video_data.mode_list, i);
|
|
number = CFDictionaryGetValue(onemode, kCGDisplayBitsPerPixel);
|
|
CFNumberGetValue (number, kCFNumberSInt32Type, &bpp);
|
|
|
|
if (bpp != 8) continue;
|
|
|
|
number = CFDictionaryGetValue(onemode, kCGDisplayWidth);
|
|
CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue);
|
|
width = (uint16)intvalue;
|
|
|
|
number = CFDictionaryGetValue(onemode, kCGDisplayHeight);
|
|
CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue);
|
|
height = (uint16)intvalue;
|
|
|
|
/* Check if mode is already in the list */
|
|
{
|
|
int i;
|
|
hasMode = false;
|
|
for (i = 0; i < list_size; i++) {
|
|
if (mode_list[i].x == width && mode_list[i].y == height) {
|
|
hasMode = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasMode) continue;
|
|
|
|
/* Add mode to the list */
|
|
mode_list[list_size].x = width;
|
|
mode_list[list_size].y = height;
|
|
list_size++;
|
|
}
|
|
|
|
/* Sort list smallest to largest */
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < list_size; i++) {
|
|
for (j = 0; j < list_size-1; j++) {
|
|
if (mode_list[j].x > mode_list[j + 1].x || (
|
|
mode_list[j].x == mode_list[j + 1].x &&
|
|
mode_list[j].y > mode_list[j + 1].y
|
|
)) {
|
|
uint tmpw = mode_list[j].x;
|
|
uint tmph = mode_list[j].y;
|
|
|
|
mode_list[j].x = mode_list[j + 1].x;
|
|
mode_list[j].y = mode_list[j + 1].y;
|
|
|
|
mode_list[j + 1].x = tmpw;
|
|
mode_list[j + 1].y = tmph;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return list_size;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Windowed and fullscreen common code *
|
|
******************************************************************************/
|
|
|
|
static void QZ_UpdatePalette(uint start, uint count)
|
|
{
|
|
if (_cocoa_video_data.fullscreen) {
|
|
QZ_UpdateFullscreenPalette(start, count);
|
|
} else {
|
|
QZ_UpdateWindowPalette(start, count);
|
|
}
|
|
}
|
|
|
|
static void QZ_InitPalette(void)
|
|
{
|
|
QZ_UpdatePalette(0, 256);
|
|
}
|
|
|
|
static void QZ_Draw(void)
|
|
{
|
|
if (_cocoa_video_data.fullscreen) {
|
|
QZ_DrawScreen();
|
|
} else {
|
|
QZ_DrawWindow();
|
|
}
|
|
}
|
|
|
|
|
|
static const OTTDPoint _default_resolutions[] = {
|
|
{ 640, 480},
|
|
{ 800, 600},
|
|
{1024, 768},
|
|
{1152, 864},
|
|
{1280, 800},
|
|
{1280, 960},
|
|
{1280, 1024},
|
|
{1400, 1050},
|
|
{1600, 1200},
|
|
{1680, 1050},
|
|
{1920, 1200}
|
|
};
|
|
|
|
static void QZ_UpdateVideoModes(void)
|
|
{
|
|
uint i, j, count;
|
|
OTTDPoint modes[32];
|
|
const OTTDPoint *current_modes;
|
|
|
|
if (_cocoa_video_data.fullscreen) {
|
|
count = QZ_ListFullscreenModes(modes, 32);
|
|
current_modes = modes;
|
|
} else {
|
|
count = lengthof(_default_resolutions);
|
|
current_modes = _default_resolutions;
|
|
}
|
|
|
|
for (i = 0, j = 0; j < lengthof(_resolutions) && i < count; i++) {
|
|
if (_cocoa_video_data.fullscreen || (
|
|
(uint)current_modes[i].x < _cocoa_video_data.device_width &&
|
|
(uint)current_modes[i].y < _cocoa_video_data.device_height)
|
|
) {
|
|
_resolutions[j][0] = current_modes[i].x;
|
|
_resolutions[j][1] = current_modes[i].y;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
_num_resolutions = j;
|
|
}
|
|
|
|
static void QZ_UnsetVideoMode(void)
|
|
{
|
|
if (_cocoa_video_data.fullscreen) {
|
|
/* Release fullscreen resources */
|
|
OTTD_QuartzGammaTable gamma_table;
|
|
int gamma_error;
|
|
NSRect screen_rect;
|
|
|
|
gamma_error = QZ_FadeGammaOut(&gamma_table);
|
|
|
|
/* Restore original screen resolution/bpp */
|
|
CGDisplaySwitchToMode(_cocoa_video_data.display_id, _cocoa_video_data.save_mode);
|
|
CGReleaseAllDisplays();
|
|
ShowMenuBar();
|
|
/* Reset the main screen's rectangle
|
|
* See comment in QZ_SetVideoFullscreen for why we do this
|
|
*/
|
|
screen_rect = NSMakeRect(0,0,_cocoa_video_data.device_width,_cocoa_video_data.device_height);
|
|
[ [ NSScreen mainScreen ] setFrame:screen_rect ];
|
|
|
|
if (!gamma_error) QZ_FadeGammaIn(&gamma_table);
|
|
} else {
|
|
/* Release window mode resources */
|
|
[ _cocoa_video_data.window close ];
|
|
_cocoa_video_data.window = nil;
|
|
_cocoa_video_data.qdview = nil;
|
|
}
|
|
|
|
free(_cocoa_video_data.pixels);
|
|
_cocoa_video_data.pixels = NULL;
|
|
|
|
/* Signal successful teardown */
|
|
_cocoa_video_data.isset = false;
|
|
|
|
QZ_ShowMouse();
|
|
}
|
|
|
|
|
|
static const char* QZ_SetVideoMode(uint width, uint height, bool fullscreen)
|
|
{
|
|
const char *ret;
|
|
|
|
_cocoa_video_data.issetting = true;
|
|
if (fullscreen) {
|
|
/* Setup full screen video */
|
|
ret = QZ_SetVideoFullScreen(width, height);
|
|
} else {
|
|
/* Setup windowed video */
|
|
ret = QZ_SetVideoWindowed(width, height);
|
|
}
|
|
_cocoa_video_data.issetting = false;
|
|
if (ret != NULL) return ret;
|
|
|
|
/* Signal successful completion (used internally) */
|
|
_cocoa_video_data.isset = true;
|
|
|
|
/* Tell the game that the resolution has changed */
|
|
_screen.width = _cocoa_video_data.width;
|
|
_screen.height = _cocoa_video_data.height;
|
|
_screen.pitch = _cocoa_video_data.width;
|
|
|
|
QZ_UpdateVideoModes();
|
|
GameSizeChanged();
|
|
|
|
QZ_InitPalette();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char* QZ_SetVideoModeAndRestoreOnFailure(uint width, uint height, bool fullscreen)
|
|
{
|
|
bool wasset = _cocoa_video_data.isset;
|
|
uint32 oldwidth = _cocoa_video_data.width;
|
|
uint32 oldheight = _cocoa_video_data.height;
|
|
bool oldfullscreen = _cocoa_video_data.fullscreen;
|
|
const char *ret;
|
|
|
|
ret = QZ_SetVideoMode(width, height, fullscreen);
|
|
if (ret != NULL && wasset) QZ_SetVideoMode(oldwidth, oldheight, oldfullscreen);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void QZ_VideoInit(void)
|
|
{
|
|
memset(&_cocoa_video_data, 0, sizeof(_cocoa_video_data));
|
|
|
|
/* Initialize the video settings; this data persists between mode switches */
|
|
_cocoa_video_data.display_id = kCGDirectMainDisplay;
|
|
_cocoa_video_data.save_mode = CGDisplayCurrentMode(_cocoa_video_data.display_id);
|
|
_cocoa_video_data.mode_list = CGDisplayAvailableModes(_cocoa_video_data.display_id);
|
|
_cocoa_video_data.palette = CGPaletteCreateDefaultColorPalette();
|
|
|
|
/* Gather some information that is useful to know about the display */
|
|
/* Maybe this should be moved to QZ_SetVideoMode, in case this is changed after startup */
|
|
CFNumberGetValue(
|
|
CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayBitsPerPixel),
|
|
kCFNumberSInt32Type, &_cocoa_video_data.device_bpp
|
|
);
|
|
|
|
CFNumberGetValue(
|
|
CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayWidth),
|
|
kCFNumberSInt32Type, &_cocoa_video_data.device_width
|
|
);
|
|
|
|
CFNumberGetValue(
|
|
CFDictionaryGetValue(_cocoa_video_data.save_mode, kCGDisplayHeight),
|
|
kCFNumberSInt32Type, &_cocoa_video_data.device_height
|
|
);
|
|
|
|
_cocoa_video_data.cursor_visible = true;
|
|
|
|
/* register for sleep notifications so wake from sleep generates SDL_VIDEOEXPOSE */
|
|
// QZ_RegisterForSleepNotifications();
|
|
}
|
|
|
|
|
|
/* Convert local coordinate to window server (CoreGraphics) coordinate */
|
|
static CGPoint QZ_PrivateLocalToCG(NSPoint* p)
|
|
{
|
|
CGPoint cgp;
|
|
|
|
if (!_cocoa_video_data.fullscreen) {
|
|
*p = [ _cocoa_video_data.qdview convertPoint:*p toView: nil ];
|
|
*p = [ _cocoa_video_data.window convertBaseToScreen:*p ];
|
|
p->y = _cocoa_video_data.device_height - p->y;
|
|
}
|
|
|
|
cgp.x = p->x;
|
|
cgp.y = p->y;
|
|
|
|
return cgp;
|
|
}
|
|
|
|
static void QZ_WarpCursor(int x, int y)
|
|
{
|
|
NSPoint p;
|
|
CGPoint cgp;
|
|
|
|
/* Only allow warping when in foreground */
|
|
if (![ NSApp isActive ]) return;
|
|
|
|
p = NSMakePoint(x, y);
|
|
cgp = QZ_PrivateLocalToCG(&p);
|
|
|
|
/* this is the magic call that fixes cursor "freezing" after warp */
|
|
CGSetLocalEventsSuppressionInterval(0.0);
|
|
/* Do the actual warp */
|
|
CGWarpMouseCursorPosition(cgp);
|
|
|
|
/* Generate the mouse moved event */
|
|
}
|
|
|
|
static void QZ_ShowMouse(void)
|
|
{
|
|
if (!_cocoa_video_data.cursor_visible) {
|
|
[ NSCursor unhide ];
|
|
_cocoa_video_data.cursor_visible = true;
|
|
|
|
// Hide the openttd cursor when leaving the window
|
|
if (_cocoa_video_data.isset)
|
|
UndrawMouseCursor();
|
|
_cursor.in_window = false;
|
|
}
|
|
}
|
|
|
|
static void QZ_HideMouse(void)
|
|
{
|
|
if (_cocoa_video_data.cursor_visible) {
|
|
#ifndef _DEBUG
|
|
[ NSCursor hide ];
|
|
#endif
|
|
_cocoa_video_data.cursor_visible = false;
|
|
|
|
// Show the openttd cursor again
|
|
_cursor.in_window = true;
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* OS X application creation *
|
|
******************************************************************************/
|
|
|
|
/* The main class of the application, the application's delegate */
|
|
@implementation OTTDMain
|
|
/* Called when the internal event loop has just started running */
|
|
- (void) applicationDidFinishLaunching: (NSNotification*) note
|
|
{
|
|
/* Hand off to main application code */
|
|
QZ_GameLoop();
|
|
|
|
/* We're done, thank you for playing */
|
|
[ NSApp stop:_ottd_main ];
|
|
}
|
|
|
|
/* Display the in game quit confirmation dialog */
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*) sender
|
|
{
|
|
|
|
HandleExitGameRequest();
|
|
|
|
return NSTerminateCancel; // NSTerminateLater ?
|
|
}
|
|
@end
|
|
|
|
static void setApplicationMenu(void)
|
|
{
|
|
/* warning: this code is very odd */
|
|
NSMenu *appleMenu;
|
|
NSMenuItem *menuItem;
|
|
NSString *title;
|
|
NSString *appName;
|
|
|
|
appName = @"OTTD";
|
|
appleMenu = [[NSMenu alloc] initWithTitle:appName];
|
|
|
|
/* Add menu items */
|
|
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"];
|
|
|
|
menuItem = (NSMenuItem*)[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 */
|
|
[NSApp setAppleMenu:appleMenu];
|
|
|
|
/* Finally give up our references to the objects */
|
|
[appleMenu release];
|
|
[menuItem release];
|
|
}
|
|
|
|
/* Create a window menu */
|
|
static void setupWindowMenu(void)
|
|
{
|
|
NSMenu* windowMenu;
|
|
NSMenuItem* windowMenuItem;
|
|
NSMenuItem* menuItem;
|
|
|
|
windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
|
|
|
|
/* "Minimize" item */
|
|
menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
|
|
[windowMenu addItem:menuItem];
|
|
[menuItem release];
|
|
|
|
/* Put menu into the menubar */
|
|
windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
|
|
[windowMenuItem setSubmenu:windowMenu];
|
|
[[NSApp mainMenu] addItem:windowMenuItem];
|
|
|
|
/* Tell the application object that this is now the window menu */
|
|
[NSApp setWindowsMenu:windowMenu];
|
|
|
|
/* Finally give up our references to the objects */
|
|
[windowMenu release];
|
|
[windowMenuItem release];
|
|
}
|
|
|
|
static void setupApplication(void)
|
|
{
|
|
CPSProcessSerNum PSN;
|
|
|
|
/* Ensure the application object is initialised */
|
|
[NSApplication sharedApplication];
|
|
|
|
/* Tell the dock about us */
|
|
if (!CPSGetCurrentProcess(&PSN) &&
|
|
!CPSEnableForegroundOperation(&PSN, 0x03, 0x3C, 0x2C, 0x1103) &&
|
|
!CPSSetFrontProcess(&PSN)) {
|
|
[NSApplication sharedApplication];
|
|
}
|
|
|
|
/* 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];
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Video driver interface *
|
|
******************************************************************************/
|
|
|
|
static void CocoaVideoStop(void)
|
|
{
|
|
DEBUG(driver, 1)("cocoa_v: CocoaVideoStop");
|
|
|
|
if (!_cocoa_video_started) return;
|
|
|
|
if (_cocoa_video_data.isset) QZ_UnsetVideoMode();
|
|
|
|
[_ottd_main release];
|
|
|
|
_cocoa_video_started = false;
|
|
}
|
|
|
|
static const char *CocoaVideoStart(const char * const *parm)
|
|
{
|
|
const char *ret;
|
|
|
|
DEBUG(driver, 1)("cocoa_v: CocoaVideoStart");
|
|
|
|
if (_cocoa_video_started) return "Already started";
|
|
_cocoa_video_started = true;
|
|
|
|
memset(&_cocoa_video_data, 0, sizeof(_cocoa_video_data));
|
|
|
|
setupApplication();
|
|
|
|
/* Don't create a window or enter fullscreen if we're just going to show a dialog. */
|
|
if (_cocoa_video_dialog) return NULL;
|
|
|
|
QZ_VideoInit();
|
|
|
|
ret = QZ_SetVideoMode(_cur_resolution[0], _cur_resolution[1], _fullscreen);
|
|
if (ret != NULL) CocoaVideoStop();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void CocoaVideoMakeDirty(int left, int top, int width, int height)
|
|
{
|
|
if (_cocoa_video_data.num_dirty_rects < MAX_DIRTY_RECTS) {
|
|
_cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].left = left;
|
|
_cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].top = top;
|
|
_cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].right = left + width;
|
|
_cocoa_video_data.dirty_rects[_cocoa_video_data.num_dirty_rects].bottom = top + height;
|
|
}
|
|
_cocoa_video_data.num_dirty_rects++;
|
|
}
|
|
|
|
static void CocoaVideoMainLoop(void)
|
|
{
|
|
DEBUG(driver, 1)("cocoa_v: CocoaVideoMainLoop");
|
|
|
|
/* Start the main event loop */
|
|
[NSApp run];
|
|
}
|
|
|
|
static bool CocoaVideoChangeRes(int w, int h)
|
|
{
|
|
const char *ret;
|
|
DEBUG(driver, 1)("cocoa_v: CocoaVideoChangeRes");
|
|
|
|
ret = QZ_SetVideoModeAndRestoreOnFailure((uint)w, (uint)h, _cocoa_video_data.fullscreen);
|
|
if (ret != NULL) {
|
|
DEBUG(driver, 1)("cocoa_v: failed with message: %s", ret);
|
|
}
|
|
|
|
return ret == NULL;
|
|
}
|
|
|
|
static void CocoaVideoFullScreen(bool full_screen)
|
|
{
|
|
const char *ret;
|
|
|
|
DEBUG(driver, 1)("cocoa_v: CocoaVideoFullScreen");
|
|
|
|
ret = QZ_SetVideoModeAndRestoreOnFailure(_cocoa_video_data.width, _cocoa_video_data.height, full_screen);
|
|
if (ret != NULL) {
|
|
DEBUG(driver, 1)("cocoa_v: failed with message: %s", ret);
|
|
}
|
|
|
|
_fullscreen = _cocoa_video_data.fullscreen;
|
|
}
|
|
|
|
const HalVideoDriver _cocoa_video_driver = {
|
|
CocoaVideoStart,
|
|
CocoaVideoStop,
|
|
CocoaVideoMakeDirty,
|
|
CocoaVideoMainLoop,
|
|
CocoaVideoChangeRes,
|
|
CocoaVideoFullScreen,
|
|
};
|
|
|
|
|
|
/* This is needed since sometimes assert is called before the videodriver is initialized */
|
|
void CocoaDialog(const char* title, const char* message, const char* buttonLabel)
|
|
{
|
|
bool wasstarted;
|
|
|
|
_cocoa_video_dialog = true;
|
|
|
|
wasstarted = _cocoa_video_started;
|
|
if (!_cocoa_video_started && CocoaVideoStart(NULL) != NULL) {
|
|
fprintf(stderr, "%s: %s\n", title, message);
|
|
return;
|
|
}
|
|
|
|
NSRunAlertPanel([NSString stringWithCString: title], [NSString stringWithCString: message], [NSString stringWithCString: buttonLabel], nil, nil);
|
|
|
|
if (!wasstarted) CocoaVideoStop();
|
|
|
|
_cocoa_video_dialog = false;
|
|
}
|
|
|
|
|
|
/* This is needed since OS X applications are started with the working dir set to / when double-clicked */
|
|
void cocoaSetWorkingDirectory(void)
|
|
{
|
|
char parentdir[MAXPATHLEN];
|
|
int chdir_ret;
|
|
CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
|
CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url);
|
|
if (CFURLGetFileSystemRepresentation(url2, true, (unsigned char*)parentdir, MAXPATHLEN)) {
|
|
chdir_ret = chdir(parentdir); /* chdir to the binary app's parent */
|
|
assert(chdir_ret == 0);
|
|
}
|
|
CFRelease(url);
|
|
CFRelease(url2);
|
|
}
|
|
|
|
/* These are called from main() to prevent a _NSAutoreleaseNoPool error when
|
|
* exiting before the cocoa video driver has been loaded
|
|
*/
|
|
void cocoaSetupAutoreleasePool(void)
|
|
{
|
|
_ottd_autorelease_pool = [[NSAutoreleasePool alloc] init];
|
|
}
|
|
|
|
void cocoaReleaseAutoreleasePool(void)
|
|
{
|
|
[_ottd_autorelease_pool release];
|
|
}
|
|
|
|
#endif /* WITH_COCOA */
|