From 4c8b7c99ea5a5d082265320d2d5e834a4da5ac00 Mon Sep 17 00:00:00 2001 From: Manolo Gouy Date: Sat, 20 Oct 2018 21:58:04 +0000 Subject: [PATCH] Add support of MacOS 10.14 Mojave when app is linked with SDK 10.14 Mojave support requires to draw windows using so-called "layer-backed views". git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@13077 ea41ed52-d2ee-0310-a9c1-e6b18d33e121 --- src/Fl_cocoa.mm | 343 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 273 insertions(+), 70 deletions(-) diff --git a/src/Fl_cocoa.mm b/src/Fl_cocoa.mm index eb770735f..73113ac94 100644 --- a/src/Fl_cocoa.mm +++ b/src/Fl_cocoa.mm @@ -100,6 +100,7 @@ static int main_screen_height; // height of menubar-containing screen used to co static BOOL through_drawRect = NO; // through_Fl_X_flush = YES means Fl_X::flush() was called static BOOL through_Fl_X_flush = NO; +static BOOL views_use_CA = NO; // YES means views are layer-backed, as on macOS 10.14 when linked with SDK 10.14 static int im_enabled = -1; // OS version-dependent pasteboard type names #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 @@ -660,6 +661,65 @@ void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data) #endif @end + +@interface FLView : NSView = MAC_OS_X_VERSION_10_5 +, NSTextInputClient +#endif +> { + BOOL in_key_event; // YES means keypress is being processed by handleEvent + BOOL need_handle; // YES means Fl::handle(FL_KEYBOARD,) is needed after handleEvent processing + NSInteger identifier; + NSRange selectedRange; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 +@public + CGContextRef layer_gc; +#endif +} ++ (void)prepareEtext:(NSString*)aString; ++ (void)concatEtext:(NSString*)aString; +- (BOOL)process_keydown:(NSEvent*)theEvent; +- (id)initWithFrame:(NSRect)frameRect; +- (void)drawRect:(NSRect)rect; +- (BOOL)acceptsFirstResponder; +- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent; +- (void)resetCursorRects; +- (BOOL)performKeyEquivalent:(NSEvent*)theEvent; +- (void)mouseUp:(NSEvent *)theEvent; +- (void)rightMouseUp:(NSEvent *)theEvent; +- (void)otherMouseUp:(NSEvent *)theEvent; +- (void)mouseDown:(NSEvent *)theEvent; +- (void)rightMouseDown:(NSEvent *)theEvent; +- (void)otherMouseDown:(NSEvent *)theEvent; +- (void)mouseMoved:(NSEvent *)theEvent; +- (void)mouseDragged:(NSEvent *)theEvent; +- (void)rightMouseDragged:(NSEvent *)theEvent; +- (void)otherMouseDragged:(NSEvent *)theEvent; +- (void)scrollWheel:(NSEvent *)theEvent; +- (void)magnifyWithEvent:(NSEvent *)theEvent; +- (void)keyDown:(NSEvent *)theEvent; +- (void)keyUp:(NSEvent *)theEvent; +- (void)flagsChanged:(NSEvent *)theEvent; +- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender; +- (NSDragOperation)draggingUpdated:(id < NSDraggingInfo >)sender; +- (BOOL)performDragOperation:(id )sender; +- (void)draggingExited:(id < NSDraggingInfo >)sender; +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal; +#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange; +- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection replacementRange:(NSRange)replacementRange; +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange; +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange; +- (NSInteger)windowLevel; +#endif +- (BOOL)did_view_resolution_change; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 +- (BOOL)wantsLayer; +- (void)displayLayer:(CALayer *)layer; +- (void)viewFrameDidChange; +#endif +@end + @implementation FLWindow #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 - (NSPoint)convertBaseToScreen:(NSPoint)aPoint @@ -889,9 +949,9 @@ double fl_mac_flush_and_wait(double time_to_wait) { // the idle function may turn off idle, we can then wait: if (Fl::idle) time_to_wait = 0.0; } - NSDisableScreenUpdates(); // 10.3 Makes updates to all windows appear as a single event + if (fl_mac_os_version < 1011) NSDisableScreenUpdates(); // 10.3 Makes updates to all windows appear as a single event Fl::flush(); - NSEnableScreenUpdates(); // 10.3 + if (fl_mac_os_version < 1011) NSEnableScreenUpdates(); // 10.3 if (Fl::idle && !in_idle) // 'idle' may have been set within flush() time_to_wait = 0.0; double retval = fl_wait(time_to_wait); @@ -1216,6 +1276,9 @@ static FLTextView *fltextview_instance = nil; - (void)windowDidMiniaturize:(NSNotification *)notif; - (BOOL)windowShouldClose:(id)fl; - (void)anyWindowWillClose:(NSNotification *)notif; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 +- (void)viewFrameDidChangeNotification:(NSNotification *)notif; +#endif - (void)doNothing:(id)unused; @end @@ -1378,6 +1441,18 @@ static FLWindowDelegate *flwindowdelegate_instance = nil; // FLTK sets position of parent and children. setSubwindowFrame is no longer necessary. if (fl_mac_os_version < 101000) [nsw recursivelySendToSubwindows:@selector(setSubwindowFrame)]; [nsw checkSubwindowFrame]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + if (views_use_CA && [(FLView*)[nsw contentView] did_view_resolution_change]) { + if (window->as_gl_window()) { // move layered GL window to different resolution + NSView *view = [nsw contentView]; + [view display]; [view display]; // 2 necessary for toplevel GL windows + } else [(FLView*)[nsw contentView] viewFrameDidChange]; + if (window->parent()) { + [nsw setSubwindowFrame]; + [[nsw contentView] display]; + } + } +#endif fl_unlock_function(); } - (void)windowDidResize:(NSNotification *)notif @@ -1501,6 +1576,13 @@ static FLWindowDelegate *flwindowdelegate_instance = nil; } fl_unlock_function(); } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 +-(void)viewFrameDidChangeNotification:(NSNotification *)notif +{ + NSView *view = (NSView*)[notif object]; + if ([view layer] && [view isMemberOfClass:[FLView class]]) [(FLView*)view viewFrameDidChange]; +} +#endif - (void)doNothing:(id)unused { return; @@ -1831,6 +1913,12 @@ void fl_open_display() { selector:@selector(anyWindowWillClose:) name:NSWindowWillCloseNotification object:nil]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + [[NSNotificationCenter defaultCenter] addObserver:[FLWindowDelegate singleInstance] + selector:@selector(viewFrameDidChangeNotification:) + name:NSViewFrameDidChangeNotification + object:nil]; +#endif if (![NSThread isMultiThreaded]) { // With old OS X versions, it is necessary to create one thread for secondary pthreads to be // allowed to use cocoa, especially to create an NSAutoreleasePool. @@ -1981,18 +2069,8 @@ static void handleUpdateEvent( Fl_Window *window ) { if ( !window ) return; Fl_X *i = Fl_X::i( window ); - if (fl_mac_os_version >= 100700) { // determine whether window is mapped to a retina display - bool previous = i->mapped_to_retina(); - // rewrite next call that requires 10.7 and therefore triggers a compiler warning on old SDKs - //NSSize s = [[i->xid contentView] convertSizeToBacking:NSMakeSize(10, 10)]; - typedef NSSize (*convertSizeIMP)(id, SEL, NSSize); - static convertSizeIMP addr = (convertSizeIMP)[NSView instanceMethodForSelector:@selector(convertSizeToBacking:)]; - NSSize s = addr([i->xid contentView], @selector(convertSizeToBacking:), NSMakeSize(10, 10)); - i->mapped_to_retina( int(s.width + 0.5) > 10 ); - if (i->wait_for_expose == 0 && previous != i->mapped_to_retina()) i->changed_resolution(true); - } - i->wait_for_expose = 0; - + [(FLView*)[fl_xid(window) contentView] did_view_resolution_change]; + if (!views_use_CA) i->wait_for_expose = 0; if ( i->region ) { XDestroyRegion(i->region); i->region = 0; @@ -2258,55 +2336,127 @@ static FLTextInputContext* fltextinputcontext_instance = nil; } @end -@interface FLView : NSView = MAC_OS_X_VERSION_10_5 -, NSTextInputClient -#endif -> { - BOOL in_key_event; // YES means keypress is being processed by handleEvent - BOOL need_handle; // YES means Fl::handle(FL_KEYBOARD,) is needed after handleEvent processing - NSInteger identifier; - NSRange selectedRange; +/* Implementation note for the support of layer-backed views. + MacOS 10.14 Mojave changes the way all drawing to displays is performed: + all NSView objects become layer-backed, that is, the drawing is done by + Core Animation to a CALayer object whose content is then displayed by the NSView. + The global variable views_use_CA is set to YES when such change applies, + that is, for apps running under 10.14 and linked to SDK 10.14. + When views_use_CA is NO, views are not supposed to be layer-backed. + + Each layer-backed non-OpenGL window has a single FLView object which itself has an associated CALayer. + FLView implements displayLayer:. Consequently, FLView objects are drawn + by the displayLayer: method. An FLView manages also a member variable + CGContextRef layer_gc, a bitmap context the size of the view (double on Retina). + All Quartz drawings go to this bitmap. updateLayer finishes by using an image copy + of the bitmap as the layer's contents. That step fills the window. + FLView implements viewFrameDidChange which deletes the bitmap and zeros layer_gc. + This ensures the bitmap is recreated when the window is resized. + viewFrameDidChange is also called when the window flips between low/high resolution displays. + + Each layer-backed OpenGL window has an associated FLViewGL object, derived from FLView. + FLViewGL objects are drawn by the displayLayer: method which calls drawRect: + which draws the GL scene. + */ + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 +static CGContextRef prepare_bitmap_for_layer(int w, int h ) { + CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB(); + CGContextRef gc = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, cspace, kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(cspace); + CGContextClearRect(gc, CGRectMake(0,0,w,h)); + return gc; } -+ (void)prepareEtext:(NSString*)aString; -+ (void)concatEtext:(NSString*)aString; -- (BOOL)process_keydown:(NSEvent*)theEvent; -- (id)initWithFrame:(NSRect)frameRect; -- (void)drawRect:(NSRect)rect; -- (BOOL)acceptsFirstResponder; -- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent; -- (void)resetCursorRects; -- (BOOL)performKeyEquivalent:(NSEvent*)theEvent; -- (void)mouseUp:(NSEvent *)theEvent; -- (void)rightMouseUp:(NSEvent *)theEvent; -- (void)otherMouseUp:(NSEvent *)theEvent; -- (void)mouseDown:(NSEvent *)theEvent; -- (void)rightMouseDown:(NSEvent *)theEvent; -- (void)otherMouseDown:(NSEvent *)theEvent; -- (void)mouseMoved:(NSEvent *)theEvent; -- (void)mouseDragged:(NSEvent *)theEvent; -- (void)rightMouseDragged:(NSEvent *)theEvent; -- (void)otherMouseDragged:(NSEvent *)theEvent; -- (void)scrollWheel:(NSEvent *)theEvent; -- (void)magnifyWithEvent:(NSEvent *)theEvent; -- (void)keyDown:(NSEvent *)theEvent; -- (void)keyUp:(NSEvent *)theEvent; -- (void)flagsChanged:(NSEvent *)theEvent; -- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender; -- (NSDragOperation)draggingUpdated:(id < NSDraggingInfo >)sender; -- (BOOL)performDragOperation:(id )sender; -- (void)draggingExited:(id < NSDraggingInfo >)sender; -- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal; -#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 -- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange; -- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection replacementRange:(NSRange)replacementRange; -- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange; -- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange; -- (NSInteger)windowLevel; -#endif + +@interface FLViewGL : FLView // only for layered GL windows +- (void)displayLayer:(CALayer *)layer; @end +@implementation FLViewGL +- (void)displayLayer:(CALayer *)layer { + if (!Fl::use_high_res_GL()) layer.contentsScale = 1.; + [self drawRect:[self frame]]; + Fl_Window *window = [(FLWindow*)[self window] getFl_Window]; + if (window->parent()) window->redraw(); // useful during resize of GL subwindow + Fl_X *i = Fl_X::i( window ); + if (i->wait_for_expose) { + // 1st drawing of GL window + NSRect r = [[self window] frame]; + r.size.width -= 1; + [[self window] setFrame:r display:NO]; // very dirty but works. Should find something better. + r.size.width += 1; + [[self window] setFrame:r display:YES]; + i->wait_for_expose = 0; + } +} +@end +#endif //>= MAC_OS_X_VERSION_10_8 + + @implementation FLView +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 +- (BOOL)wantsLayer { + return views_use_CA; +} +- (void)displayLayer:(CALayer *)layer { + // called if views are layered (but not for GL) : all drawing to window goes through this + Fl_Window *window = [(FLWindow*)[self window] getFl_Window]; + Fl_X *i = Fl_X::i( window ); + if (!layer_gc) { // runs when window is created, resized, changed screen resolution + NSRect r = [self frame]; + layer.bounds = NSRectToCGRect(r); + i->wait_for_expose = 0; + [self did_view_resolution_change]; + if (i->mapped_to_retina()) { + r.size.width *= 2; r.size.height *= 2; + layer.contentsScale = 2.; + } else layer.contentsScale = 1.; + layer_gc = prepare_bitmap_for_layer(r.size.width, r.size.height); + Fl_X *i = Fl_X::i(window); + if ( i->region ) { + XDestroyRegion(i->region); + i->region = 0; + } + window->clear_damage(FL_DAMAGE_ALL); + } + if (window->damage()) { + through_drawRect = YES; + i->flush(); + Fl_X::q_release_context(); + through_drawRect = NO; + window->clear_damage(); + CGImageRef cgimg = CGBitmapContextCreateImage(layer_gc); // requires 10.4 + layer.contents = (id)cgimg; + CGImageRelease(cgimg); + } +} +-(void)viewFrameDidChange +{ + CGContextRelease(layer_gc); + layer_gc = NULL; +} +-(void)dealloc { + CGContextRelease(layer_gc); + [super dealloc]; +} +#endif //>= MAC_OS_X_VERSION_10_8 + +- (BOOL)did_view_resolution_change { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (fl_mac_os_version >= 100700) { // determine whether window is mapped to a retina display + Fl_Window *window = [(FLWindow*)[self window] getFl_Window]; + Fl_X *i = Fl_X::i( window ); + bool previous = i->mapped_to_retina(); + NSSize s = [self convertSizeToBacking:NSMakeSize(10, 10)]; // 10.7 + i->mapped_to_retina( int(s.width + 0.5) > 10 ); + BOOL retval = (i->wait_for_expose == 0 && previous != i->mapped_to_retina()); + if (retval) i->changed_resolution(true); + return retval; + } +#endif + return NO; +} + - (BOOL)process_keydown:(NSEvent*)theEvent { id o = fl_mac_os_version >= 100600 ? [self performSelector:@selector(inputContext)] : [FLTextInputContext singleInstance]; @@ -2959,6 +3109,14 @@ void Fl_X::flush() { if (w->as_gl_window()) { w->flush(); +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + } else if (views_use_CA) { + if (!through_drawRect) { + FLView *view = (FLView*)[fl_xid(w) contentView]; + [view displayLayer:[view layer]]; + } else + w->flush(); +#endif } else { make_current_counts = 1; if (!through_drawRect) [[xid contentView] lockFocus]; @@ -3112,7 +3270,12 @@ void Fl_X::make(Fl_Window* w) x->next = NULL; Fl_X::first = x; } - FLView *myview = [[FLView alloc] initWithFrame:crect]; + FLView *myview = +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + views_use_CA && w->as_gl_window() ? [FLViewGL alloc] : +#endif + [FLView alloc]; + myview = [myview initWithFrame:crect]; [cw setContentView:myview]; [myview release]; [cw setLevel:winlevel]; @@ -3359,23 +3522,36 @@ void Fl_Window::resize(int X,int Y,int W,int H) { */ void Fl_Window::make_current() { - if (make_current_counts > 1) return; + if (make_current_counts > 1 && !views_use_CA) return; if (make_current_counts) make_current_counts++; + if (views_use_CA && !through_drawRect) { // detect direct calls from the app + damage(FL_DAMAGE_CHILD); // make next draws to this window displayed at next event loop + } Fl_X::q_release_context(); fl_window = i->xid; Fl_X::set_high_resolution( i->mapped_to_retina() ); current_ = this; - - NSGraphicsContext *nsgc; -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 - if (fl_mac_os_version >= 100400) - nsgc = [fl_window graphicsContext]; // 10.4 - else +//NSLog(@"region-count=%d damage=%u",i->region?i->region->count:0, damage()); + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + if (views_use_CA) { + i->gc = ((FLView*)[fl_window contentView])->layer_gc; + } else #endif - nsgc = through_Fl_X_flush ? [NSGraphicsContext currentContext] : [NSGraphicsContext graphicsContextWithWindow:fl_window]; - i->gc = (CGContextRef)[nsgc graphicsPort]; + { + NSGraphicsContext *nsgc; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 + if (fl_mac_os_version >= 100400) + nsgc = [fl_window graphicsContext]; // 10.4 + else +#endif + nsgc = through_Fl_X_flush ? [NSGraphicsContext currentContext] : [NSGraphicsContext graphicsContextWithWindow:fl_window]; + i->gc = (CGContextRef)[nsgc graphicsPort]; + } + fl_gc = i->gc; CGContextSaveGState(fl_gc); // native context + if (views_use_CA && i->mapped_to_retina()) CGContextScaleCTM(fl_gc, 2,2); // antialiasing must be deactivated because it applies to rectangles too // and escapes even clipping!!! // it gets activated when needed (e.g., draw text) @@ -4281,6 +4457,26 @@ static NSBitmapImageRep* GL_rect_to_nsbitmap(Fl_Window *win, int x, int y, int w return bitmap; } +static NSBitmapImageRep* rect_to_NSBitmapImageRep_layer(Fl_Window *win, int x, int y, int w, int h) +{ // capture window data for layer-based views because initWithFocusedViewRect: does not work for them + NSBitmapImageRep *bitmap = nil; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + CGContextRef gc = ((FLView*)[fl_xid(win) contentView])->layer_gc; + CGImageRef cgimg = CGBitmapContextCreateImage(gc); // requires 10.4 + Fl_X *i = Fl_X::i( win ); + int resolution = i->mapped_to_retina() ? 2 : 1; + if (x || y || w != win->w() || h != win->h()) { + CGRect rect = CGRectMake(x * resolution, y * resolution, w * resolution, h * resolution); + CGImageRef cgimg2 = CGImageCreateWithImageInRect(cgimg, rect); + CGImageRelease(cgimg); + cgimg = cgimg2; + } + bitmap = [[NSBitmapImageRep alloc] initWithCGImage:cgimg];//10.5 + CGImageRelease(cgimg); +#endif + return bitmap; +} + static NSBitmapImageRep* rect_to_NSBitmapImageRep(Fl_Window *win, int x, int y, int w, int h) /* Captures a rectangle from a mapped window. On retina displays, the resulting bitmap has 2 pixels per screen unit. @@ -4291,6 +4487,8 @@ static NSBitmapImageRep* rect_to_NSBitmapImageRep(Fl_Window *win, int x, int y, NSRect rect; if (win->as_gl_window() && y >= 0) { bitmap = GL_rect_to_nsbitmap(win, x, y, w, h); + } else if (views_use_CA) { + bitmap = rect_to_NSBitmapImageRep_layer(win, x, y, w, h); } else { NSView *winview = nil; if ( through_Fl_X_flush && Fl_Window::current() == win ) { @@ -4642,7 +4840,12 @@ int Fl_X::calc_mac_os_version() { sscanf(s, "%d.%d.%d", &M, &m, &b); } [localPool release]; - return M*10000 + m*100 + b; + fl_mac_os_version = M*10000 + m*100 + b; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14 + if (fl_mac_os_version >= 101400) views_use_CA = YES; +#endif + //if (fl_mac_os_version >= 101300) views_use_CA = YES; // to get as with mojave + return fl_mac_os_version; } #endif // __APPLE__