Add: Overlay cargo icon in vehicle/depot list when holding shift+ctrl. (#12938)
[openttd-github.git] / src / video / cocoa / cocoa_v.mm
blob7b08367cdc20ad874872724d64af744d5a30364d
1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
8 /** @file cocoa_v.mm Code related to the cocoa video driver(s). */
10 /******************************************************************************
11  *                             Cocoa video driver                             *
12  * Known things left to do:                                                   *
13  *  Nothing at the moment.                                                    *
14  ******************************************************************************/
16 #ifdef WITH_COCOA
18 #include "../../stdafx.h"
19 #include "../../os/macosx/macos.h"
21 #define Rect  OTTDRect
22 #define Point OTTDPoint
23 #import <Cocoa/Cocoa.h>
24 #import <QuartzCore/QuartzCore.h>
25 #undef Rect
26 #undef Point
28 #include "../../openttd.h"
29 #include "../../debug.h"
30 #include "../../error_func.h"
31 #include "../../core/geometry_func.hpp"
32 #include "../../core/math_func.hpp"
33 #include "cocoa_v.h"
34 #include "cocoa_wnd.h"
35 #include "../../blitter/factory.hpp"
36 #include "../../framerate_type.h"
37 #include "../../gfx_func.h"
38 #include "../../thread.h"
39 #include "../../core/random_func.hpp"
40 #include "../../progress.h"
41 #include "../../settings_type.h"
42 #include "../../window_func.h"
43 #include "../../window_gui.h"
45 #import <sys/param.h> /* for MAXPATHLEN */
46 #import <sys/time.h> /* gettimeofday */
48 /* The 10.12 SDK added new names for some enum constants and
49  * deprecated the old ones. As there's no functional change in any
50  * way, just use a define for older SDKs to the old names. */
51 #ifndef HAVE_OSX_1012_SDK
52 #       define NSEventModifierFlagCommand NSCommandKeyMask
53 #       define NSEventModifierFlagControl NSControlKeyMask
54 #       define NSEventModifierFlagOption NSAlternateKeyMask
55 #       define NSEventModifierFlagShift NSShiftKeyMask
56 #       define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
57 #endif
59 /**
60  * Important notice regarding all modifications!!!!!!!
61  * There are certain limitations because the file is objective C++.
62  * gdb has limitations.
63  * C++ and objective C code can't be joined in all cases (classes stuff).
64  * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
65  */
67 bool _cocoa_video_started = false;
68 static Palette _local_palette; ///< Current palette to use for drawing.
70 extern bool _tab_is_down;
73 /** List of common display/window sizes. */
74 static const Dimension _default_resolutions[] = {
75         {  640,  480 },
76         {  800,  600 },
77         { 1024,  768 },
78         { 1152,  864 },
79         { 1280,  800 },
80         { 1280,  960 },
81         { 1280, 1024 },
82         { 1400, 1050 },
83         { 1600, 1200 },
84         { 1680, 1050 },
85         { 1920, 1200 },
86         { 2560, 1440 }
90 VideoDriver_Cocoa::VideoDriver_Cocoa(bool uses_hardware_acceleration)
91         : VideoDriver(uses_hardware_acceleration)
93         this->setup         = false;
94         this->buffer_locked = false;
96         this->refresh_sys_sprites = true;
98         this->window    = nil;
99         this->cocoaview = nil;
100         this->delegate  = nil;
102         this->color_space = nullptr;
104         this->dirty_rect = {};
107 /** Stop Cocoa video driver. */
108 void VideoDriver_Cocoa::Stop()
110         if (!_cocoa_video_started) return;
112         CocoaExitApplication();
114         /* Release window mode resources */
115         if (this->window != nil) [ this->window close ];
116         [ this->cocoaview release ];
117         [ this->delegate release ];
119         CGColorSpaceRelease(this->color_space);
121         _cocoa_video_started = false;
124 /** Common driver initialization. */
125 std::optional<std::string_view> VideoDriver_Cocoa::Initialize()
127         if (!MacOSVersionIsAtLeast(10, 7, 0)) return "The Cocoa video driver requires Mac OS X 10.7 or later.";
129         if (_cocoa_video_started) return "Already started";
130         _cocoa_video_started = true;
132         /* Don't create a window or enter fullscreen if we're just going to show a dialog. */
133         if (!CocoaSetupApplication()) return std::nullopt;
135         this->UpdateAutoResolution();
136         this->orig_res = _cur_resolution;
138         return std::nullopt;
142  * Set dirty a rectangle managed by a cocoa video subdriver.
143  * @param left Left x cooordinate of the dirty rectangle.
144  * @param top Uppder y coordinate of the dirty rectangle.
145  * @param width Width of the dirty rectangle.
146  * @param height Height of the dirty rectangle.
147  */
148 void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height)
150         Rect r = {left, top, left + width, top + height};
151         this->dirty_rect = BoundingRect(this->dirty_rect, r);
155  * Start the main programme loop when using a cocoa video driver.
156  */
157 void VideoDriver_Cocoa::MainLoop()
159         /* Restart game loop if it was already running (e.g. after bootstrapping),
160          * otherwise this call is a no-op. */
161         [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ];
163         /* Start the main event loop. */
164         [ NSApp run ];
168  * Change the resolution when using a cocoa video driver.
169  * @param w New window width.
170  * @param h New window height.
171  * @return Whether the video driver was successfully updated.
172  */
173 bool VideoDriver_Cocoa::ChangeResolution(int w, int h)
175         NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size;
176         w = std::min(w, (int)screen_size.width);
177         h = std::min(h, (int)screen_size.height);
179         NSRect contentRect = NSMakeRect(0, 0, w, h);
180         [ this->window setContentSize:contentRect.size ];
182         /* Ensure frame height - title bar height >= view height */
183         float content_height = [ this->window contentRectForFrameRect:[ this->window frame ] ].size.height;
184         contentRect.size.height = Clamp(h, 0, (int)content_height);
186         if (this->cocoaview != nil) {
187                 h = (int)contentRect.size.height;
188                 [ this->cocoaview setFrameSize:contentRect.size ];
189         }
191         [ (OTTD_CocoaWindow *)this->window center ];
192         this->AllocateBackingStore();
194         return true;
198  * Toggle between windowed and full screen mode for cocoa display driver.
199  * @param full_screen Whether to switch to full screen or not.
200  * @return Whether the mode switch was successful.
201  */
202 bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen)
204         if (this->IsFullscreen() == full_screen) return true;
206         if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) {
207                 [ this->window performSelector:@selector(toggleFullScreen:) withObject:this->window ];
209                 /* Hide the menu bar and the dock */
210                 [ NSMenu setMenuBarVisible:!full_screen ];
212                 this->UpdateVideoModes();
213                 InvalidateWindowClassesData(WC_GAME_OPTIONS, 3);
214                 return true;
215         }
217         return false;
220 void VideoDriver_Cocoa::ClearSystemSprites()
222         this->refresh_sys_sprites = true;
225 void VideoDriver_Cocoa::PopulateSystemSprites()
227         if (this->refresh_sys_sprites && this->window != nil) {
228                 [ this->window refreshSystemSprites ];
229                 this->refresh_sys_sprites = false;
230         }
234  * Callback invoked after the blitter was changed.
235  * @return True if no error.
236  */
237 bool VideoDriver_Cocoa::AfterBlitterChange()
239         this->AllocateBackingStore(true);
240         return true;
244  * An edit box lost the input focus. Abort character compositing if necessary.
245  */
246 void VideoDriver_Cocoa::EditBoxLostFocus()
248         [ [ this->cocoaview inputContext ] performSelectorOnMainThread:@selector(discardMarkedText) withObject:nil waitUntilDone:[ NSThread isMainThread ] ];
249         /* Clear any marked string from the current edit box. */
250         HandleTextInput(nullptr, true);
254  * Get refresh rates of all connected monitors.
255  */
256 std::vector<int> VideoDriver_Cocoa::GetListOfMonitorRefreshRates()
258         std::vector<int> rates{};
260         if (MacOSVersionIsAtLeast(10, 6, 0)) {
261                 std::array<CGDirectDisplayID, 16> displays;
263                 uint32_t count = 0;
264                 CGGetActiveDisplayList(displays.size(), displays.data(), &count);
266                 for (uint32_t i = 0; i < count; i++) {
267                         CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
268                         int rate = (int)CGDisplayModeGetRefreshRate(mode);
269                         if (rate > 0) rates.push_back(rate);
270                         CGDisplayModeRelease(mode);
271                 }
272         }
274         return rates;
278  * Get the resolution of the main screen.
279  */
280 Dimension VideoDriver_Cocoa::GetScreenSize() const
282         NSRect frame = [ [ NSScreen mainScreen ] frame ];
283         return { static_cast<uint>(NSWidth(frame)), static_cast<uint>(NSHeight(frame)) };
286 /** Get DPI scale of our window. */
287 float VideoDriver_Cocoa::GetDPIScale()
289         return this->cocoaview != nil ? [ this->cocoaview getContentsScale ] : 1.0f;
292 /** Lock video buffer for drawing if it isn't already mapped. */
293 bool VideoDriver_Cocoa::LockVideoBuffer()
295         if (this->buffer_locked) return false;
296         this->buffer_locked = true;
298         _screen.dst_ptr = this->GetVideoPointer();
299         assert(_screen.dst_ptr != nullptr);
301         return true;
304 /** Unlock video buffer. */
305 void VideoDriver_Cocoa::UnlockVideoBuffer()
307         if (_screen.dst_ptr != nullptr) {
308                 /* Hand video buffer back to the drawing backend. */
309                 this->ReleaseVideoPointer();
310                 _screen.dst_ptr = nullptr;
311         }
313         this->buffer_locked = false;
317  * Are we in fullscreen mode?
318  * @return whether fullscreen mode is currently used
319  */
320 bool VideoDriver_Cocoa::IsFullscreen()
322         return this->window != nil && ([ this->window styleMask ] & NSWindowStyleMaskFullScreen) != 0;
326  * Handle a change of the display area.
327  */
328 void VideoDriver_Cocoa::GameSizeChanged()
330         /* Store old window size if we entered fullscreen mode. */
331         bool fullscreen = this->IsFullscreen();
332         if (fullscreen && !_fullscreen) this->orig_res = _cur_resolution;
333         _fullscreen = fullscreen;
335         BlitterFactory::GetCurrentBlitter()->PostResize();
337         ::GameSizeChanged();
339         /* We need to store the window size as non-Retina size in
340          * the config file to get same windows size on next start. */
341         _cur_resolution.width = [ this->cocoaview frame ].size.width;
342         _cur_resolution.height = [ this->cocoaview frame ].size.height;
346  * Update the video mode.
347  */
348 void VideoDriver_Cocoa::UpdateVideoModes()
350         _resolutions.clear();
352         if (this->IsFullscreen()) {
353                 /* Full screen, there is only one possible resolution. */
354                 NSSize screen = [ [ this->window screen ] frame ].size;
355                 _resolutions.emplace_back((uint)screen.width, (uint)screen.height);
356         } else {
357                 /* Windowed; offer a selection of common window sizes up until the
358                  * maximum usable screen space. This excludes the menu and dock areas. */
359                 NSSize maxSize = [ [ NSScreen mainScreen] visibleFrame ].size;
360                 for (const auto &d : _default_resolutions) {
361                         if (d.width < maxSize.width && d.height < maxSize.height) _resolutions.push_back(d);
362                 }
363                 _resolutions.emplace_back((uint)maxSize.width, (uint)maxSize.height);
364         }
368  * Build window and view with a given size.
369  * @param width Window width.
370  * @param height Window height.
371  */
372 bool VideoDriver_Cocoa::MakeWindow(int width, int height)
374         this->setup = true;
376         /* Limit window size to screen frame. */
377         NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size;
378         if (width > screen_size.width) width = screen_size.width;
379         if (height > screen_size.height) height = screen_size.height;
381         NSRect contentRect = NSMakeRect(0, 0, width, height);
383         /* Create main window. */
384 #ifdef HAVE_OSX_1012_SDK
385         unsigned int style = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable;
386 #else
387         unsigned int style = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSClosableWindowMask;
388 #endif
389         this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect styleMask:style backing:NSBackingStoreBuffered defer:NO driver:this ];
390         if (this->window == nil) {
391                 Debug(driver, 0, "Could not create the Cocoa window.");
392                 this->setup = false;
393                 return false;
394         }
396         /* Add built in full-screen support when available (OS X 10.7 and higher)
397          * This code actually compiles for 10.5 and later, but only makes sense in conjunction
398          * with the quartz fullscreen support as found only in 10.7 and later. */
399         if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) {
400                 NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ];
401                 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
402                 [ this->window setCollectionBehavior:behavior ];
404                 NSButton *fullscreenButton = [ this->window standardWindowButton:NSWindowZoomButton ];
405                 [ fullscreenButton setAction:@selector(toggleFullScreen:) ];
406                 [ fullscreenButton setTarget:this->window ];
407         }
409         this->delegate = [ [ OTTD_CocoaWindowDelegate alloc ] initWithDriver:this ];
410         [ this->window setDelegate:this->delegate ];
412         [ this->window center ];
413         [ this->window makeKeyAndOrderFront:nil ];
415         /* Create wrapper view for input and event handling. */
416         NSRect view_frame = [ this->window contentRectForFrameRect:[ this->window frame ] ];
417         this->cocoaview = [ [ OTTD_CocoaView alloc ] initWithFrame:view_frame ];
418         if (this->cocoaview == nil) {
419                 Debug(driver, 0, "Could not create the event wrapper view.");
420                 this->setup = false;
421                 return false;
422         }
423         [ this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
425         /* Create content view. */
426         NSView *draw_view = this->AllocateDrawView();
427         if (draw_view == nil) {
428                 Debug(driver, 0, "Could not create the drawing view.");
429                 this->setup = false;
430                 return false;
431         }
432         [ draw_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
434         /* Create view chain: window -> input wrapper view -> content view. */
435         [ this->window setContentView:this->cocoaview ];
436         [ this->cocoaview addSubview:draw_view ];
437         [ this->window makeFirstResponder:this->cocoaview ];
438         [ draw_view release ];
440         [ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ];
441         CGColorSpaceRelease(this->color_space);
442         this->color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
443         if (this->color_space == nullptr) this->color_space = CGColorSpaceCreateDeviceRGB();
444         if (this->color_space == nullptr) FatalError("Could not get a valid colour space for drawing.");
446         this->setup = false;
448         return true;
453  * Poll and handle a single event from the OS.
454  * @return True if there was an event to handle.
455  */
456 bool VideoDriver_Cocoa::PollEvent()
458 #ifdef HAVE_OSX_1012_SDK
459         NSEventMask mask = NSEventMaskAny;
460 #else
461         NSEventMask mask = NSAnyEventMask;
462 #endif
463         NSEvent *event = [ NSApp nextEventMatchingMask:mask untilDate:[ NSDate distantPast ] inMode:NSDefaultRunLoopMode dequeue:YES ];
465         if (event == nil) return false;
467         [ NSApp sendEvent:event ];
469         return true;
472 void VideoDriver_Cocoa::InputLoop()
474         NSUInteger cur_mods = [ NSEvent modifierFlags ];
476         bool old_ctrl_pressed = _ctrl_pressed;
478         _ctrl_pressed = (cur_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSEventModifierFlagControl : NSEventModifierFlagCommand)) != 0;
479         _shift_pressed = (cur_mods & NSEventModifierFlagShift) != 0;
481         this->fast_forward_key_pressed = _tab_is_down;
483         if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
486 /** Main game loop. */
487 void VideoDriver_Cocoa::MainLoopReal()
489         this->StartGameThread();
491         for (;;) {
492                 @autoreleasepool {
493                         if (_exit_game) {
494                                 /* Restore saved resolution if in fullscreen mode. */
495                                 if (this->IsFullscreen()) _cur_resolution = this->orig_res;
496                                 break;
497                         }
499                         this->Tick();
500                         this->SleepTillNextTick();
501                 }
502         }
504         this->StopGameThread();
508 /* Subclass of OTTD_CocoaView to fix Quartz rendering */
509 @interface OTTD_QuartzView : NSView {
510         VideoDriver_CocoaQuartz *driver;
512 - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_CocoaQuartz *)drv;
513 @end
515 @implementation OTTD_QuartzView
517 - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_CocoaQuartz *)drv
519         if (self = [ super initWithFrame:frameRect ]) {
520                 self->driver = drv;
522                 /* We manage our content updates ourselves. */
523                 self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
524                 self.wantsLayer = YES;
526                 self.layer.magnificationFilter = kCAFilterNearest;
527                 self.layer.opaque = YES;
528         }
529         return self;
532 - (BOOL)acceptsFirstResponder
534         return NO;
537 - (BOOL)isOpaque
539         return YES;
542 - (BOOL)wantsUpdateLayer
544         return YES;
547 - (void)updateLayer
549         if (driver->cgcontext == nullptr) return;
551         /* Set layer contents to our backing buffer, which avoids needless copying. */
552         CGImageRef fullImage = CGBitmapContextCreateImage(driver->cgcontext);
553         self.layer.contents = (__bridge id)fullImage;
554         CGImageRelease(fullImage);
557 - (void)viewDidChangeBackingProperties
559         [ super viewDidChangeBackingProperties ];
561         self.layer.contentsScale = [ driver->cocoaview getContentsScale ];
564 @end
567 static FVideoDriver_CocoaQuartz iFVideoDriver_CocoaQuartz;
569 /** Clear buffer to opaque black. */
570 static void ClearWindowBuffer(uint32_t *buffer, uint32_t pitch, uint32_t height)
572         uint32_t fill = Colour(0, 0, 0).data;
573         for (uint32_t y = 0; y < height; y++) {
574                 for (uint32_t x = 0; x < pitch; x++) {
575                         buffer[y * pitch + x] = fill;
576                 }
577         }
580 VideoDriver_CocoaQuartz::VideoDriver_CocoaQuartz()
582         this->window_width  = 0;
583         this->window_height = 0;
584         this->window_pitch  = 0;
585         this->buffer_depth  = 0;
586         this->window_buffer = nullptr;
587         this->pixel_buffer  = nullptr;
589         this->cgcontext     = nullptr;
592 std::optional<std::string_view> VideoDriver_CocoaQuartz::Start(const StringList &param)
594         auto err = this->Initialize();
595         if (err) return err;
597         int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
598         if (bpp != 8 && bpp != 32) {
599                 Stop();
600                 return "The cocoa quartz subdriver only supports 8 and 32 bpp.";
601         }
603         bool fullscreen = _fullscreen;
604         if (!this->MakeWindow(_cur_resolution.width, _cur_resolution.height)) {
605                 Stop();
606                 return "Could not create window";
607         }
609         this->AllocateBackingStore(true);
611         if (fullscreen) this->ToggleFullscreen(fullscreen);
613         this->GameSizeChanged();
614         this->UpdateVideoModes();
616         this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
618         return std::nullopt;
622 void VideoDriver_CocoaQuartz::Stop()
624         this->VideoDriver_Cocoa::Stop();
626         CGContextRelease(this->cgcontext);
628         free(this->window_buffer);
629         free(this->pixel_buffer);
632 NSView *VideoDriver_CocoaQuartz::AllocateDrawView()
634         return [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->cocoaview bounds ] andDriver:this ];
637 /** Resize the window. */
638 void VideoDriver_CocoaQuartz::AllocateBackingStore(bool)
640         if (this->window == nil || this->cocoaview == nil || this->setup) return;
642         this->UpdatePalette(0, 256);
644         NSRect newframe = [ this->cocoaview getRealRect:[ this->cocoaview frame ] ];
646         this->window_width = (int)newframe.size.width;
647         this->window_height = (int)newframe.size.height;
648         this->window_pitch = Align(this->window_width, 16 / sizeof(uint32_t)); // Quartz likes lines that are multiple of 16-byte.
649         this->buffer_depth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
651         /* Create Core Graphics Context */
652         free(this->window_buffer);
653         this->window_buffer = malloc(this->window_pitch * this->window_height * sizeof(uint32_t));
654         /* Initialize with opaque black. */
655         ClearWindowBuffer((uint32_t *)this->window_buffer, this->window_pitch, this->window_height);
657         CGContextRelease(this->cgcontext);
658         this->cgcontext = CGBitmapContextCreate(
659                 this->window_buffer,       // data
660                 this->window_width,        // width
661                 this->window_height,       // height
662                 8,                         // bits per component
663                 this->window_pitch * 4,    // bytes per row
664                 this->color_space,         // color space
665                 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host
666         );
668         assert(this->cgcontext != nullptr);
669         CGContextSetShouldAntialias(this->cgcontext, FALSE);
670         CGContextSetAllowsAntialiasing(this->cgcontext, FALSE);
671         CGContextSetInterpolationQuality(this->cgcontext, kCGInterpolationNone);
673         if (this->buffer_depth == 8) {
674                 free(this->pixel_buffer);
675                 this->pixel_buffer = malloc(this->window_width * this->window_height);
676                 if (this->pixel_buffer == nullptr) UserError("Out of memory allocating pixel buffer");
677         } else {
678                 free(this->pixel_buffer);
679                 this->pixel_buffer = nullptr;
680         }
682         /* Tell the game that the resolution has changed */
683         _screen.width   = this->window_width;
684         _screen.height  = this->window_height;
685         _screen.pitch   = this->buffer_depth == 8 ? this->window_width : this->window_pitch;
686         _screen.dst_ptr = this->GetVideoPointer();
688         /* Redraw screen */
689         this->MakeDirty(0, 0, _screen.width, _screen.height);
690         this->GameSizeChanged();
694  * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode.
696  * @param left The x coord for the left edge of the box to blit.
697  * @param top The y coord for the top edge of the box to blit.
698  * @param right The x coord for the right edge of the box to blit.
699  * @param bottom The y coord for the bottom edge of the box to blit.
700  */
701 void VideoDriver_CocoaQuartz::BlitIndexedToView32(int left, int top, int right, int bottom)
703         const uint32_t *pal   = this->palette;
704         const uint8_t  *src   = (uint8_t*)this->pixel_buffer;
705         uint32_t       *dst   = (uint32_t*)this->window_buffer;
706         uint          width = this->window_width;
707         uint          pitch = this->window_pitch;
709         for (int y = top; y < bottom; y++) {
710                 for (int x = left; x < right; x++) {
711                         dst[y * pitch + x] = pal[src[y * width + x]];
712                 }
713         }
716 /** Update the palette */
717 void VideoDriver_CocoaQuartz::UpdatePalette(uint first_color, uint num_colors)
719         if (this->buffer_depth != 8) return;
721         for (uint i = first_color; i < first_color + num_colors; i++) {
722                 uint32_t clr = 0xff000000;
723                 clr |= (uint32_t)_local_palette.palette[i].r << 16;
724                 clr |= (uint32_t)_local_palette.palette[i].g << 8;
725                 clr |= (uint32_t)_local_palette.palette[i].b;
726                 this->palette[i] = clr;
727         }
729         this->MakeDirty(0, 0, _screen.width, _screen.height);
732 void VideoDriver_CocoaQuartz::CheckPaletteAnim()
734         if (!CopyPalette(_local_palette)) return;
736         Blitter *blitter = BlitterFactory::GetCurrentBlitter();
738         switch (blitter->UsePaletteAnimation()) {
739                 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
740                         this->UpdatePalette(_local_palette.first_dirty, _local_palette.count_dirty);
741                         break;
743                 case Blitter::PALETTE_ANIMATION_BLITTER:
744                         blitter->PaletteAnimate(_local_palette);
745                         break;
747                 case Blitter::PALETTE_ANIMATION_NONE:
748                         break;
750                 default:
751                         NOT_REACHED();
752         }
755 /** Draw window */
756 void VideoDriver_CocoaQuartz::Paint()
758         PerformanceMeasurer framerate(PFE_VIDEO);
760         /* Check if we need to do anything */
761         if (IsEmptyRect(this->dirty_rect) || [ this->window isMiniaturized ]) return;
763         /* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */
764         if (this->buffer_depth == 8) {
765                 BlitIndexedToView32(
766                         this->dirty_rect.left,
767                         this->dirty_rect.top,
768                         this->dirty_rect.right,
769                         this->dirty_rect.bottom
770                 );
771         }
773         NSRect dirtyrect;
774         dirtyrect.origin.x = this->dirty_rect.left;
775         dirtyrect.origin.y = this->window_height - this->dirty_rect.bottom;
776         dirtyrect.size.width = this->dirty_rect.right - this->dirty_rect.left;
777         dirtyrect.size.height = this->dirty_rect.bottom - this->dirty_rect.top;
779         /* Notify OS X that we have new content to show. */
780         [ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ];
782         /* Tell the OS to get our contents to screen as soon as possible. */
783         [ CATransaction flush ];
785         this->dirty_rect = {};
788 #endif /* WITH_COCOA */