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
This commit is contained in:
Manolo Gouy 2018-10-20 21:58:04 +00:00
parent a2ff8ee535
commit 4c8b7c99ea

View File

@ -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 <NSTextInput
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 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 <NSDraggingInfo>)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 <NSTextInput
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 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 <NSDraggingInfo>)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__