1 /* $Id: wnd_quickdraw.mm 26108 2013-11-25 14:30:22Z rubidium $ */
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /******************************************************************************
11 * Cocoa video driver *
12 * Known things left to do: *
13 * List available resolutions. *
14 ******************************************************************************/
17 #ifdef ENABLE_COCOA_QUICKDRAW
19 #define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_3
20 #include "../../stdafx.h"
21 #include "../../os/macosx/macos.h"
24 #define Point OTTDPoint
25 #import <Cocoa/Cocoa.h>
29 #include "../../debug.h"
30 #include "../../rev.h"
31 #include "../../core/geometry_type.hpp"
33 #include "../../core/math_func.hpp"
34 #include "../../gfx_func.h"
37 * Important notice regarding all modifications!!!!!!!
38 * There are certain limitations because the file is objective C++.
39 * gdb has limitations.
40 * C++ and objective C code can't be joined in all cases (classes stuff).
41 * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
45 class WindowQuickdrawSubdriver;
48 class WindowQuickdrawSubdriver : public CocoaSubdriver {
51 * This function copies 32bpp pixels from the screen buffer in 16bpp windowed mode.
53 * @param left The x coord for the left edge of the box to blit.
54 * @param top The y coord for the top edge of the box to blit.
55 * @param right The x coord for the right edge of the box to blit.
56 * @param bottom The y coord for the bottom edge of the box to blit.
58 void Blit32ToView32(int left, int top, int right, int bottom);
61 * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode.
63 * @param left The x coord for the left edge of the box to blit.
64 * @param top The y coord for the top edge of the box to blit.
65 * @param right The x coord for the right edge of the box to blit.
66 * @param bottom The y coord for the bottom edge of the box to blit.
68 void BlitIndexedToView32(int left, int top, int right, int bottom);
71 * This function copies 8bpp pixels from the screen buffer in 16bpp windowed mode.
73 * @param left The x coord for the left edge of the box to blit.
74 * @param top The y coord for the top edge of the box to blit.
75 * @param right The x coord for the right edge of the box to blit.
76 * @param bottom The y coord for the bottom edge of the box to blit.
78 void BlitIndexedToView16(int left, int top, int right, int bottom);
80 inline void BlitToView(int left, int top, int right, int bottom);
81 void DrawResizeIcon();
83 virtual void GetDeviceInfo();
84 virtual bool SetVideoMode(int width, int height, int bpp);
87 WindowQuickdrawSubdriver();
88 virtual ~WindowQuickdrawSubdriver();
90 virtual void Draw(bool force_update);
91 virtual void MakeDirty(int left, int top, int width, int height);
92 virtual void UpdatePalette(uint first_color, uint num_colors);
94 virtual uint ListModes(OTTD_Point *modes, uint max_modes);
96 virtual bool ChangeResolution(int w, int h, int bpp);
98 virtual bool IsFullscreen() { return false; }
100 virtual int GetWidth() { return window_width; }
101 virtual int GetHeight() { return window_height; }
102 virtual void *GetPixelBuffer() { return pixel_buffer; }
104 /* Convert local coordinate to window server (CoreGraphics) coordinate */
105 virtual CGPoint PrivateLocalToCG(NSPoint *p);
107 virtual NSPoint GetMouseLocation(NSEvent *event);
108 virtual bool MouseIsInsideView(NSPoint *pt);
110 virtual bool IsActive() { return active; }
112 void SetPortAlphaOpaque();
113 bool WindowResized();
116 static const int _resize_icon_width = 16;
117 static const int _resize_icon_height = 16;
119 static bool _resize_icon[] = {
120 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
121 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
122 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
123 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
124 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1,
125 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,
126 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0,
127 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
128 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
129 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
130 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,
131 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
132 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
133 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
134 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,
135 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0
139 void WindowQuickdrawSubdriver::GetDeviceInfo()
141 /* Initialize the video settings; this data persists between mode switches */
142 CFDictionaryRef cur_mode = CGDisplayCurrentMode(kCGDirectMainDisplay);
144 /* Gather some information that is useful to know about the display */
145 CFNumberGetValue((const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayBitsPerPixel),
146 kCFNumberSInt32Type, &this->device_depth);
148 CFNumberGetValue((const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayWidth),
149 kCFNumberSInt32Type, &this->device_width);
151 CFNumberGetValue((const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayHeight),
152 kCFNumberSInt32Type, &this->device_height);
155 bool WindowQuickdrawSubdriver::SetVideoMode(int width, int height, int bpp)
158 this->GetDeviceInfo();
160 if (bpp > this->device_depth) {
161 DEBUG(driver, 0, "Cannot use a blitter with a higher screen depth than the display when running in windowed mode.");
166 if (width > this->device_width) width = this->device_width;
167 if (height > this->device_height) height = this->device_height;
169 NSRect contentRect = NSMakeRect(0, 0, width, height);
171 /* Check if we should recreate the window */
172 if (this->window == nil) {
173 OTTD_CocoaWindowDelegate *delegate;
175 /* Set the window style */
176 unsigned int style = NSTitledWindowMask;
177 style |= (NSMiniaturizableWindowMask | NSClosableWindowMask);
178 style |= NSResizableWindowMask;
180 /* Manually create a window, avoids having a nib file resource */
181 this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect
182 styleMask:style backing:NSBackingStoreBuffered defer:NO ];
184 if (this->window == nil) {
185 DEBUG(driver, 0, "Could not create the Cocoa window.");
190 [ this->window setDriver:this ];
193 snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision);
194 NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption ];
195 [ this->window setTitle:nsscaption ];
196 [ this->window setMiniwindowTitle:nsscaption ];
197 [ nsscaption release ];
199 [ this->window setContentMinSize:NSMakeSize(64.0f, 64.0f) ];
201 [ this->window setAcceptsMouseMovedEvents:YES ];
202 [ this->window setViewsNeedDisplay:NO ];
204 delegate = [ [ OTTD_CocoaWindowDelegate alloc ] init ];
205 [ delegate setDriver:this ];
206 [ this->window setDelegate: [ delegate autorelease ] ];
208 /* We already have a window, just change its size */
209 [ this->window setContentSize:contentRect.size ];
210 /* Ensure frame height - title bar height >= view height
211 * The height of title bar of the window is 22 pixels */
212 contentRect.size.height = Clamp(height, 0, [ this->window frame ].size.height - 22);
213 height = contentRect.size.height;
214 [ this->cocoaview setFrameSize:contentRect.size ];
218 this->window_width = width;
219 this->window_height = height;
220 this->buffer_depth = bpp;
222 [ this->window center ];
224 /* Only recreate the view if it doesn't already exist */
225 if (this->cocoaview == nil) {
226 this->cocoaview = [ [ NSQuickDrawView alloc ] initWithFrame:contentRect ];
227 if (this->cocoaview == nil) {
228 DEBUG(driver, 0, "Could not create the Quickdraw view.");
233 [ this->cocoaview setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
234 [ [ this->window contentView ] addSubview:this->cocoaview ];
235 [ this->cocoaview release ];
236 [ this->window makeKeyAndOrderFront:nil ];
239 bool ret = this->WindowResized();
240 this->UpdatePalette(0, 256);
246 void WindowQuickdrawSubdriver::Blit32ToView32(int left, int top, int right, int bottom)
248 const uint32 *src = (uint32*)this->pixel_buffer;
249 uint32 *dst = (uint32*)this->window_buffer;
250 uint width = this->window_width;
251 uint pitch = this->window_pitch / 4;
253 dst += top * pitch + left;
254 src += top * width + left;
256 for (int y = top; y < bottom; y++, dst+= pitch, src+= width) {
257 memcpy(dst, src, (right - left) * 4);
261 void WindowQuickdrawSubdriver::BlitIndexedToView32(int left, int top, int right, int bottom)
263 const uint32 *pal = this->palette;
264 const uint8 *src = (uint8*)this->pixel_buffer;
265 uint32 *dst = (uint32*)this->window_buffer;
266 uint width = this->window_width;
267 uint pitch = this->window_pitch / 4;
269 for (int y = top; y < bottom; y++) {
270 for (int x = left; x < right; x++) {
271 dst[y * pitch + x] = pal[src[y * width + x]];
276 void WindowQuickdrawSubdriver::BlitIndexedToView16(int left, int top, int right, int bottom)
278 const uint32 *pal = this->palette;
279 const uint8 *src = (uint8*)this->pixel_buffer;
280 uint16 *dst = (uint16*)this->window_buffer;
281 uint width = this->window_width;
282 uint pitch = this->window_pitch / 2;
284 for (int y = top; y < bottom; y++) {
285 for (int x = left; x < right; x++) {
286 dst[y * pitch + x] = pal[src[y * width + x]];
292 inline void WindowQuickdrawSubdriver::BlitToView(int left, int top, int right, int bottom)
294 switch (this->device_depth) {
296 switch (this->buffer_depth) {
298 this->Blit32ToView32(left, top, right, bottom);
301 this->BlitIndexedToView32(left, top, right, bottom);
306 this->BlitIndexedToView16(left, top, right, bottom);
311 void WindowQuickdrawSubdriver::DrawResizeIcon()
313 int xoff = this->window_width - _resize_icon_width;
314 int yoff = this->window_height - _resize_icon_height;
316 switch (this->device_depth) {
318 for (int y = 0; y < _resize_icon_height; y++) {
319 uint32 *trg = (uint32*)this->window_buffer + (yoff + y) * this->window_pitch / 4 + xoff;
321 for (int x = 0; x < _resize_icon_width; x++, trg++) {
322 if (_resize_icon[y * _resize_icon_width + x]) *trg = 0xff000000;
327 for (int y = 0; y < _resize_icon_height; y++) {
328 uint16 *trg = (uint16*)this->window_buffer + (yoff + y) * this->window_pitch / 2 + xoff;
330 for (int x = 0; x < _resize_icon_width; x++, trg++) {
331 if (_resize_icon[y * _resize_icon_width + x]) *trg = 0x0000;
339 WindowQuickdrawSubdriver::WindowQuickdrawSubdriver()
341 this->window_width = 0;
342 this->window_height = 0;
343 this->buffer_depth = 0;
344 this->pixel_buffer = NULL;
345 this->active = false;
349 this->cocoaview = nil;
351 this->num_dirty_rects = MAX_DIRTY_RECTS;
354 WindowQuickdrawSubdriver::~WindowQuickdrawSubdriver()
356 /* Release window mode resources */
357 if (this->window != nil) [ this->window close ];
359 free(this->pixel_buffer);
362 void WindowQuickdrawSubdriver::Draw(bool force_update)
364 /* Check if we need to do anything */
365 if (this->num_dirty_rects == 0 || [ this->window isMiniaturized ]) return;
367 if (this->num_dirty_rects >= MAX_DIRTY_RECTS) {
368 this->num_dirty_rects = 1;
369 this->dirty_rects[0].left = 0;
370 this->dirty_rects[0].top = 0;
371 this->dirty_rects[0].right = this->window_width;
372 this->dirty_rects[0].bottom = this->window_height;
375 RgnHandle dirty = NewRgn();
376 RgnHandle temp = NewRgn();
380 /* Build the region of dirty rectangles */
381 for (int i = 0; i < this->num_dirty_rects; i++) {
382 this->BlitToView(this->dirty_rects[i].left, this->dirty_rects[i].top,
383 this->dirty_rects[i].right, this->dirty_rects[i].bottom);
385 MacSetRectRgn(temp, this->dirty_rects[i].left, this->dirty_rects[i].top,
386 this->dirty_rects[i].right, this->dirty_rects[i].bottom);
387 MacUnionRgn(dirty, temp, dirty);
390 this->DrawResizeIcon();
392 /* Flush the dirty region */
393 QDFlushPortBuffer( (OpaqueGrafPtr*) [ this->cocoaview qdPort ], dirty);
397 this->num_dirty_rects = 0;
400 void WindowQuickdrawSubdriver::MakeDirty(int left, int top, int width, int height)
402 if (this->num_dirty_rects < MAX_DIRTY_RECTS) {
403 this->dirty_rects[this->num_dirty_rects].left = left;
404 this->dirty_rects[this->num_dirty_rects].top = top;
405 this->dirty_rects[this->num_dirty_rects].right = left + width;
406 this->dirty_rects[this->num_dirty_rects].bottom = top + height;
408 this->num_dirty_rects++;
411 void WindowQuickdrawSubdriver::UpdatePalette(uint first_color, uint num_colors)
413 if (this->buffer_depth != 8) return;
415 switch (this->device_depth) {
417 for (uint i = first_color; i < first_color + num_colors; i++) {
418 uint32 clr32 = 0xff000000;
419 clr32 |= (uint32)_cur_palette.palette[i].r << 16;
420 clr32 |= (uint32)_cur_palette.palette[i].g << 8;
421 clr32 |= (uint32)_cur_palette.palette[i].b;
422 this->palette[i] = clr32;
426 for (uint i = first_color; i < first_color + num_colors; i++) {
427 uint16 clr16 = 0x0000;
428 clr16 |= (uint16)((_cur_palette.palette[i].r >> 3) & 0x1f) << 10;
429 clr16 |= (uint16)((_cur_palette.palette[i].g >> 3) & 0x1f) << 5;
430 clr16 |= (uint16)((_cur_palette.palette[i].b >> 3) & 0x1f);
431 this->palette[i] = clr16;
436 this->num_dirty_rects = MAX_DIRTY_RECTS;
439 uint WindowQuickdrawSubdriver::ListModes(OTTD_Point *modes, uint max_modes)
441 return QZ_ListModes(modes, max_modes, kCGDirectMainDisplay, this->buffer_depth);
444 bool WindowQuickdrawSubdriver::ChangeResolution(int w, int h, int bpp)
446 int old_width = this->window_width;
447 int old_height = this->window_height;
448 int old_bpp = this->buffer_depth;
450 if (this->SetVideoMode(w, h, bpp)) return true;
452 if (old_width != 0 && old_height != 0) this->SetVideoMode(old_width, old_height, old_bpp);
457 /* Convert local coordinate to window server (CoreGraphics) coordinate */
458 CGPoint WindowQuickdrawSubdriver::PrivateLocalToCG(NSPoint *p)
460 *p = [ this->cocoaview convertPoint:*p toView: nil ];
461 *p = [ this->window convertBaseToScreen:*p ];
462 p->y = this->device_height - p->y;
464 return CGPointMake(p->x, p->y);
467 NSPoint WindowQuickdrawSubdriver::GetMouseLocation(NSEvent *event)
469 NSPoint pt = [ event locationInWindow ];
470 pt = [ this->cocoaview convertPoint:pt fromView:nil ];
475 bool WindowQuickdrawSubdriver::MouseIsInsideView(NSPoint *pt)
477 return [ this->cocoaview mouse:*pt inRect:[ this->cocoaview bounds ] ];
481 /* This function makes the *game region* of the window 100% opaque.
482 * The genie effect uses the alpha component. Otherwise,
483 * it doesn't seem to matter what value it has.
485 void WindowQuickdrawSubdriver::SetPortAlphaOpaque()
487 if (this->device_depth != 32) return;
489 uint32 *pixels = (uint32*)this->window_buffer;
490 uint32 pitch = this->window_pitch / 4;
492 for (int y = 0; y < this->window_height; y++)
493 for (int x = 0; x < this->window_width; x++) {
494 pixels[y * pitch + x] |= 0xFF000000;
498 bool WindowQuickdrawSubdriver::WindowResized()
500 if (this->window == nil || this->cocoaview == nil) return true;
502 NSRect newframe = [ this->cocoaview frame ];
503 CGrafPtr thePort = (OpaqueGrafPtr*) [ this->cocoaview qdPort ];
505 LockPortBits(thePort);
506 this->window_buffer = GetPixBaseAddr(GetPortPixMap(thePort));
507 this->window_pitch = GetPixRowBytes(GetPortPixMap(thePort));
508 UnlockPortBits(thePort);
510 /* _cocoa_video_data.realpixels now points to the window's pixels
511 * We want it to point to the *view's* pixels
513 int voff = [ this->window frame ].size.height - newframe.size.height - newframe.origin.y;
514 int hoff = [ this->cocoaview frame ].origin.x;
515 this->window_buffer = (uint8*)this->window_buffer + (voff * this->window_pitch) + hoff * (this->device_depth / 8);
517 this->window_width = newframe.size.width;
518 this->window_height = newframe.size.height;
520 free(this->pixel_buffer);
521 this->pixel_buffer = malloc(this->window_width * this->window_height * this->buffer_depth / 8);
522 if (this->pixel_buffer == NULL) {
523 DEBUG(driver, 0, "Failed to allocate pixel buffer");
527 QZ_GameSizeChanged();
530 this->num_dirty_rects = MAX_DIRTY_RECTS;
536 CocoaSubdriver *QZ_CreateWindowQuickdrawSubdriver(int width, int height, int bpp)
538 WindowQuickdrawSubdriver *ret;
540 if (MacOSVersionIsAtLeast(10, 5, 0)) {
541 DEBUG(driver, 0, "The cocoa quickdraw subdriver is not recommended for Mac OS X 10.5 or later.");
544 if (bpp != 8 && bpp != 32) {
545 DEBUG(driver, 0, "The cocoa quickdraw subdriver only supports 8 and 32 bpp.");
549 ret = new WindowQuickdrawSubdriver();
551 if (!ret->ChangeResolution(width, height, bpp)) {
559 #endif /* ENABLE_COCOA_QUICKDRAW */
560 #endif /* WITH_COCOA */