From 42af13c141eb74f63e58827f8a33e7f66d7d829a Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 31 Jan 2021 18:58:23 +0100 Subject: [PATCH] Codechange: [OSX] Split drawing into its own subview. This allows the drawing backend code to be independent of any event or command handling. --- src/video/cocoa/cocoa_v.mm | 53 ++++++++++-- src/video/cocoa/cocoa_wnd.h | 19 +---- src/video/cocoa/cocoa_wnd.mm | 159 +++++++++++++++++------------------ 3 files changed, 126 insertions(+), 105 deletions(-) diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 7f1e10bfe4..ef30519ea6 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -77,7 +77,11 @@ static FVideoDriver_Cocoa iFVideoDriver_Cocoa; /* Subclass of OTTD_CocoaView to fix Quartz rendering */ -@interface OTTD_QuartzView : OTTD_CocoaView +@interface OTTD_QuartzView : NSView { + VideoDriver_Cocoa *driver; +} +- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv; + - (void)drawRect:(NSRect)invalidRect; @end @@ -311,6 +315,28 @@ void VideoDriver_Cocoa::GameSizeChanged() @implementation OTTD_QuartzView +- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv +{ + if (self = [ super initWithFrame:frameRect ]) { + self->driver = drv; + } + return self; +} + +- (BOOL)acceptsFirstResponder +{ + return NO; +} + +/** + * Define the opaqueness of the window / screen + * @return opaqueness of window / screen + */ +- (BOOL)isOpaque +{ + return YES; +} + - (void)drawRect:(NSRect)invalidRect { if (driver->cgcontext == NULL) return; @@ -429,13 +455,12 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) /* Create main window. */ unsigned int style = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSClosableWindowMask; - this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect styleMask:style backing:NSBackingStoreBuffered defer:NO ]; + this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect styleMask:style backing:NSBackingStoreBuffered defer:NO driver:this ]; if (this->window == nil) { DEBUG(driver, 0, "Could not create the Cocoa window."); this->setup = false; return false; } - [ this->window setDriver:this ]; /* Add built in full-screen support when available (OS X 10.7 and higher) * This code actually compiles for 10.5 and later, but only makes sense in conjunction @@ -468,16 +493,28 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) [ (OTTD_CocoaWindow *)this->window center ]; [ this->window makeKeyAndOrderFront:nil ]; - /* Create content view. */ - this->cocoaview = [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->window contentRectForFrameRect:[ this->window frame ] ] andDriver:this ]; + /* Create wrapper view for text input. */ + NSRect view_frame = [ this->window contentRectForFrameRect:[ this->window frame ] ]; + this->cocoaview = [ [ OTTD_CocoaView alloc ] initWithFrame:view_frame andDriver:this ]; if (this->cocoaview == nil) { - DEBUG(driver, 0, "Could not create the Quartz view."); + DEBUG(driver, 0, "Could not create the text wrapper view."); this->setup = false; return false; } + [ (NSView *)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; - [ (NSView*)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; - [ this->window setContentView:cocoaview ]; + /* Create content view. */ + NSView *draw_view = [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->cocoaview bounds ] andDriver:this ]; + if (draw_view == nil) { + DEBUG(driver, 0, "Could not create the drawing view."); + this->setup = false; + return false; + } + [ draw_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; + + [ this->window setContentView:this->cocoaview ]; + [ this->cocoaview addSubview:draw_view ]; + [ draw_view release ]; [ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ]; CGColorSpaceRelease(this->color_space); diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index 96834d9047..744a657861 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -22,31 +22,23 @@ extern NSString *OTTDMainLaunchGameEngine; @end /** Subclass of NSWindow to cater our special needs */ -@interface OTTD_CocoaWindow : NSWindow { - VideoDriver_Cocoa *driver; -} - -- (void)setDriver:(VideoDriver_Cocoa *)drv; +@interface OTTD_CocoaWindow : NSWindow +- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv; - (void)miniaturize:(id)sender; - (void)display; - (void)setFrame:(NSRect)frameRect display:(BOOL)flag; - (void)appDidHide:(NSNotification*)note; - (void)appDidUnhide:(NSNotification*)note; -- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; @end /** Subclass of NSView to fix Quartz rendering and mouse awareness */ -@interface OTTD_CocoaView : NSView -{ +@interface OTTD_CocoaView : NSView { VideoDriver_Cocoa *driver; - NSTrackingRectTag trackingtag; } - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv; -- (void)drawRect:(NSRect)rect; -- (BOOL)isOpaque; + - (BOOL)acceptsFirstResponder; -- (BOOL)becomeFirstResponder; - (void)setTrackingRect; - (void)clearTrackingRect; - (void)resetCursorRects; @@ -58,9 +50,6 @@ extern NSString *OTTDMainLaunchGameEngine; /** Delegate for our NSWindow to send ask for quit on close */ @interface OTTD_CocoaWindowDelegate : NSObject -{ - VideoDriver_Cocoa *driver; -} - (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv; - (BOOL)windowShouldClose:(id)sender; diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index e7c916443b..ac6d35dd21 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -48,6 +48,46 @@ NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine"; static bool _cocoa_video_dialog = false; static OTTDMain *_ottd_main; + +/** + * Count the number of UTF-16 code points in a range of an UTF-8 string. + * @param from Start of the range. + * @param to End of the range. + * @return Number of UTF-16 code points in the range. + */ +static NSUInteger CountUtf16Units(const char *from, const char *to) +{ + NSUInteger i = 0; + + while (from < to) { + WChar c; + size_t len = Utf8Decode(&c, from); + i += len < 4 ? 1 : 2; // Watch for surrogate pairs. + from += len; + } + + return i; +} + +/** + * Advance an UTF-8 string by a number of equivalent UTF-16 code points. + * @param str UTF-8 string. + * @param count Number of UTF-16 code points to advance the string by. + * @return Advanced string pointer. + */ +static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) +{ + for (NSUInteger i = 0; i < count && *str != '\0'; ) { + WChar c; + size_t len = Utf8Decode(&c, str); + i += len < 4 ? 1 : 2; // Watch for surrogates. + str += len; + } + + return str; +} + + /** * The main class of the application, the application's delegate. */ @@ -69,11 +109,13 @@ static OTTDMain *_ottd_main; */ - (void)launchGameEngine: (NSNotification*) note { + auto *drv = static_cast(VideoDriver::GetInstance()); + /* Setup cursor for the current _game_mode. */ - [ static_cast(VideoDriver::GetInstance())->cocoaview resetCursorRects ]; + [ drv->window invalidateCursorRectsForView:[ drv->window contentView ] ]; /* Hand off to main application code. */ - static_cast(VideoDriver::GetInstance())->GameLoop(); + drv->GameLoop(); /* We are done, thank you for playing. */ [ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ]; @@ -283,12 +325,10 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel } @end -@implementation OTTD_CocoaWindow - -- (void)setDriver:(VideoDriver_Cocoa *)drv -{ - driver = drv; +@implementation OTTD_CocoaWindow { + VideoDriver_Cocoa *driver; } + /** * Minimize the window */ @@ -346,62 +386,28 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel /** * Initialize event system for the application rectangle */ -- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag +- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv { - /* Make our window subclass receive these application notifications */ - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; + if (self = [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer: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(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; - return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; + self->driver = drv; + } + + return self; } @end - - -/** - * Count the number of UTF-16 code points in a range of an UTF-8 string. - * @param from Start of the range. - * @param to End of the range. - * @return Number of UTF-16 code points in the range. - */ -static NSUInteger CountUtf16Units(const char *from, const char *to) -{ - NSUInteger i = 0; - - while (from < to) { - WChar c; - size_t len = Utf8Decode(&c, from); - i += len < 4 ? 1 : 2; // Watch for surrogate pairs. - from += len; - } - - return i; +@implementation OTTD_CocoaView { + NSTrackingRectTag trackingtag; } -/** - * Advance an UTF-8 string by a number of equivalent UTF-16 code points. - * @param str UTF-8 string. - * @param count Number of UTF-16 code points to advance the string by. - * @return Advanced string pointer. - */ -static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) -{ - for (NSUInteger i = 0; i < count && *str != '\0'; ) { - WChar c; - size_t len = Utf8Decode(&c, str); - i += len < 4 ? 1 : 2; // Watch for surrogates. - str += len; - } - - return str; -} - -@implementation OTTD_CocoaView - - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv { if (self = [ super initWithFrame:frameRect ]) { @@ -409,22 +415,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) } return self; } -/** - * Define the opaqueness of the window / screen - * @return opaqueness of window / screen - */ -- (BOOL)isOpaque -{ - return YES; -} -/** - * Draws a rectangle on the screen. - * It's overwritten by the individual drivers but must be defined - */ -- (void)drawRect:(NSRect)invalidRect -{ - return; -} + /** * Allow to handle events */ @@ -432,13 +423,15 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) { return YES; } -/** - * Actually handle events - */ -- (BOOL)becomeFirstResponder + +- (void)setNeedsDisplayInRect:(NSRect)invalidRect { - return YES; + /* Drawing is handled by our sub-views. Just pass it along. */ + for ( NSView *v in [ self subviews ]) { + [ v setNeedsDisplayInRect:[ v convertRect:invalidRect fromView:self ] ]; + } } + /** * Define the rectangle where we draw our application window */ @@ -556,7 +549,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) /** Unmark the current marked text. */ - (void)unmarkText { - HandleTextInput(NULL, true); + HandleTextInput(nullptr, true); } /** Get the caret position. */ @@ -575,7 +568,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) size_t mark_len; const char *mark = _focused_window->GetMarkedText(&mark_len); - if (mark != NULL) { + if (mark != nullptr) { NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), mark); NSUInteger len = CountUtf16Units(mark, mark + mark_len); @@ -591,7 +584,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) if (!EditBoxInGlobalFocus()) return NO; size_t len; - return _focused_window->GetMarkedText(&len) != NULL; + return _focused_window->GetMarkedText(&len) != nullptr; } /** Get a string corresponding to the given range. */ @@ -602,7 +595,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) NSString *s = [ NSString stringWithUTF8String:_focused_window->GetFocusedText() ]; NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange); - if (actualRange != NULL) *actualRange = valid_range; + if (actualRange != nullptr) *actualRange = valid_range; if (valid_range.length == 0) return nil; return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ]; @@ -611,7 +604,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) /** Get a string corresponding to the given range. */ - (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange { - return [ self attributedSubstringForProposedRange:theRange actualRange:NULL ]; + return [ self attributedSubstringForProposedRange:theRange actualRange:nil ]; } /** Get the current edit box string. */ @@ -632,7 +625,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y }; const char *ch = _focused_window->GetTextCharacterAtPosition(pt); - if (ch == NULL) return NSNotFound; + if (ch == nullptr) return NSNotFound; return CountUtf16Units(_focused_window->GetFocusedText(), ch); } @@ -808,8 +801,10 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) @end +@implementation OTTD_CocoaWindowDelegate { + VideoDriver_Cocoa *driver; +} -@implementation OTTD_CocoaWindowDelegate /** Initialize the video driver */ - (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv {