diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 8b47fc108f..4731409d05 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3981,15 +3981,19 @@ A jump table for the options with a short description can be found at |Q_op|. of columns fitting on the screen in fullscreen mode. If unset, 'columns' will be unchanged when entering fullscreen mode. background:color - When entering fullscreen, 'color' defines the color of the part - of the screen that is not occupied by the Vim control. If - 'color' is an 8-digit hexadecimal number preceded by '#', - it is interpreted as an explicit color value '#aarrggbb', with - one byte each for the alpha, red, green, and blue values. - Otherwise, 'color' is interpreted as a highlight group name, + When entering fullscreen, "color" defines the color of the part + of the screen that is not occupied by the Vim control, + including the "notch" area for MacBook laptops. + If "color" is a 6 or 8-digit hexadecimal number preceded by + '#', it is interpreted as an explicit color value '#rrggbb' / + '#aarrggbb', with one byte each for the alpha, red, green, and + blue values. The alpha value is ignored (use 'transparency' + instead for a translucent window). + Otherwise, "color" is interpreted as a highlight group name, and the fullscreen background is filled with that highlight group's background color, as defined by the current color scheme. + If unset, the default value is black (#FF000000). Examples: Don't change size when entering fullscreen: > diff --git a/src/MacVim/MMBackend.m b/src/MacVim/MMBackend.m index 523cd5d372..569d4c7c98 100644 --- a/src/MacVim/MMBackend.m +++ b/src/MacVim/MMBackend.m @@ -1188,7 +1188,7 @@ - (void)leaveFullScreen - (void)setFullScreenBackgroundColor:(int)color { NSMutableData *data = [NSMutableData data]; - color = MM_COLOR(color); + color = MM_COLOR_WITH_TRANSP(color,p_transp); [data appendBytes:&color length:sizeof(int)]; [self queueMessage:SetFullScreenColorMsgID data:data]; diff --git a/src/MacVim/MMCoreTextView.m b/src/MacVim/MMCoreTextView.m index c687cb8480..81b21d2750 100644 --- a/src/MacVim/MMCoreTextView.m +++ b/src/MacVim/MMCoreTextView.m @@ -831,7 +831,7 @@ - (void)drawRect:(NSRect)rect // Function to draw all rows void (^drawAllRows)(void (^)(CGContextRef,CGRect,int)) = ^(void (^drawFunc)(CGContextRef,CGRect,int)){ - for (size_t r = 0; r < grid.rows; r++) { + for (int r = 0; r < grid.rows; r++) { const CGRect rowRect = [self rectForRow:(int)r column:0 numRows:1 @@ -1276,6 +1276,9 @@ - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size if (fh < 1.0f) fh = 1.0f; desiredRows = floor((size.height - ih)/fh); + // Sanity checking in case unusual window sizes lead to degenerate results + if (desiredRows < 1) + desiredRows = 1; desiredSize.height = fh*desiredRows + ih; } @@ -1285,6 +1288,9 @@ - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size if (fw < 1.0f) fw = 1.0f; desiredCols = floor((size.width - iw)/fw); + // Sanity checking in case unusual window sizes lead to degenerate results + if (desiredCols < 1) + desiredCols = 1; desiredSize.width = fw*desiredCols + iw; } diff --git a/src/MacVim/MMFullScreenWindow.m b/src/MacVim/MMFullScreenWindow.m index 415c1fbd00..3d532b0d7c 100644 --- a/src/MacVim/MMFullScreenWindow.m +++ b/src/MacVim/MMFullScreenWindow.m @@ -136,6 +136,15 @@ - (void)enterFullScreen { ASLogDebug(@"Enter full-screen now"); + // Detach the window delegate right now to prevent any stray window + // messages (e.g. it may get resized when setting presentationOptions + // below) being sent to the window controller while we are in the middle of + // setting up the full screen window. + NSWindowController *winController = [target windowController]; + id delegate = [target delegate]; + [winController setWindow:nil]; + [target setDelegate:nil]; + // Hide Dock and menu bar when going to full screen. Only do so if the current screen // has a menu bar and dock. if ([self screenHasDockAndMenu]) { @@ -162,13 +171,6 @@ - (void)enterFullScreen // this call so set the frame again just in case. [self setFrame:[[target screen] frame] display:NO]; - // fool delegate - id delegate = [target delegate]; - [target setDelegate:nil]; - - // make target's window controller believe that it's now controlling us - [[target windowController] setWindow:self]; - oldTabBarStyle = [[view tabBarControl] styleName]; NSString *style = @@ -181,7 +183,7 @@ - (void)enterFullScreen [view removeFromSuperviewWithoutNeedingDisplay]; [[self contentView] addSubview:view]; [self setInitialFirstResponder:[view textView]]; - + // NOTE: Calling setTitle:nil causes an exception to be raised (and it is // possible that 'target' has no title when we get here). if ([target title]) { @@ -196,8 +198,10 @@ - (void)enterFullScreen [self setOpaque:[target isOpaque]]; + // reassign target's window controller to believe that it's now controlling us // don't set this sooner, so we don't get an additional - // focus gained message + // focus gained message + [winController setWindow:self]; [self setDelegate:delegate]; // Store view dimension used before entering full-screen, then resize the diff --git a/src/MacVim/MMTextStorage.m b/src/MacVim/MMTextStorage.m index b7bea86908..a8e8c39121 100644 --- a/src/MacVim/MMTextStorage.m +++ b/src/MacVim/MMTextStorage.m @@ -895,6 +895,9 @@ - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns if (fh < 1.0f) fh = 1.0f; fitRows = floor(size.height/fh); + // Sanity checking in case unusual window sizes lead to degenerate results + if (fitRows < 1) + fitRows = 1; fitSize.height = fh*fitRows; } @@ -903,6 +906,9 @@ - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns if (fw < 1.0f) fw = 1.0f; fitCols = floor(size.width/fw); + // Sanity checking in case unusual window sizes lead to degenerate results + if (fitCols < 1) + fitCols = 1; fitSize.width = fw*fitCols; } diff --git a/src/MacVim/MMVimController.m b/src/MacVim/MMVimController.m index ef23eec78b..a1ffcc2897 100644 --- a/src/MacVim/MMVimController.m +++ b/src/MacVim/MMVimController.m @@ -1129,7 +1129,7 @@ - (void)handleMessage:(int)msgid data:(NSData *)data case SetFullScreenColorMsgID: { const int *bg = (const int*)[data bytes]; - NSColor *color = [NSColor colorWithRgbInt:*bg]; + NSColor *color = [NSColor colorWithArgbInt:*bg]; [windowController setFullScreenBackgroundColor:color]; } diff --git a/src/MacVim/MMWindowController.m b/src/MacVim/MMWindowController.m index 0e389bd6c5..c3f2d8bd16 100644 --- a/src/MacVim/MMWindowController.m +++ b/src/MacVim/MMWindowController.m @@ -699,25 +699,32 @@ - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore // window, so we need to set a transparency color here to make the // transparency show through. if ([back alphaComponent] == 1) { - // Here, any solid color would do, but setting it with "back" has an - // interesting effect where the title bar gets subtly tinted by it - // as well, so do that. (Note that this won't play well in <=10.12 - // since we are using the deprecated - // NSWindowStyleMaskTexturedBackground which makes the titlebars - // transparent in those. Consider not using textured background.) + // The window's background color affects the title bar tint and + // if we are using a transparent title bar this color will show + // up as well. + // (Note that this won't play well in <=10.12 since we are using + // the deprecated NSWindowStyleMaskTexturedBackground which makes + // the titlebars transparent in those. Consider not using textured + // background.) [decoratedWindow setBackgroundColor:back]; + + // Note: We leave the full screen window's background color alone + // because it is affected by 'fuoptions' instead. We just change the + // alpha back to 1 in case it was changed previously because transparency + // was set. if (fullScreenWindow) { - [fullScreenWindow setBackgroundColor:back]; + [fullScreenWindow setBackgroundColor: + [fullScreenWindow.backgroundColor colorWithAlphaComponent:1]]; } } else { // HACK! We really want a transparent background color to avoid // double blending the transparency, but setting alpha=0 leads to // the window border disappearing and also drag-to-resize becomes a // lot slower. So hack around it by making it virtually transparent. - NSColor *clearColor = [back colorWithAlphaComponent:0.001]; - [decoratedWindow setBackgroundColor:clearColor]; + [decoratedWindow setBackgroundColor:[back colorWithAlphaComponent:0.001]]; if (fullScreenWindow) { - [fullScreenWindow setBackgroundColor:clearColor]; + [fullScreenWindow setBackgroundColor: + [fullScreenWindow.backgroundColor colorWithAlphaComponent:0.001]]; } } } @@ -1046,9 +1053,17 @@ - (void)leaveFullScreen } } +/// Called when the window is in non-native full-screen mode and the user has +/// updated the background color. - (void)setFullScreenBackgroundColor:(NSColor *)back { if (fullScreenWindow) + // See setDefaultColorsBackground: for why set a transparent + // background color, and why 0.001 instead of 0. + if ([back alphaComponent] != 1) { + back = [back colorWithAlphaComponent:0.001]; + } + [fullScreenWindow setBackgroundColor:back]; } @@ -1307,9 +1322,7 @@ - (void)windowDidResize:(id)sender // Calling setFrameSizeKeepGUISize: instead of setFrameSize: prevents a // degenerate case where frameSizeMayHaveChanged: ends up resizing the window // *again* causing windowDidResize: to be called. - if (fullScreenWindow == nil) { - [vimView setFrameSizeKeepGUISize:[self contentSize]]; - } else { + if (fullScreenEnabled && fullScreenWindow != nil) { // Non-native full screen mode is more complicated and needs to // re-layout the Vim view to properly account for the menu bar / notch, // and misc fuopt configuration. @@ -1318,6 +1331,9 @@ - (void)windowDidResize:(id)sender [vimView setFrameOrigin:desiredFrame.origin]; [vimView setFrameSizeKeepGUISize:desiredFrame.size]; } + else { + [vimView setFrameSizeKeepGUISize:[self contentSize]]; + } } - (void)windowDidChangeBackingProperties:(NSNotification *)notification diff --git a/src/MacVim/MacVimTests/MacVimTests.m b/src/MacVim/MacVimTests/MacVimTests.m index b87978726f..91cc3da78a 100644 --- a/src/MacVim/MacVimTests/MacVimTests.m +++ b/src/MacVim/MacVimTests/MacVimTests.m @@ -15,6 +15,8 @@ #import "Miscellaneous.h" #import "MMAppController.h" #import "MMApplication.h" +#import "MMFullScreenWindow.h" +#import "MMWindow.h" #import "MMTextView.h" #import "MMWindowController.h" #import "MMVimController.h" @@ -30,10 +32,6 @@ - (void)handleMessage:(int)msgid data:(NSData *)data; @end // Test harness -@interface MMAppController (Tests) -- (NSMutableArray*)vimControllers; -@end - @implementation MMAppController (Tests) - (NSMutableArray*)vimControllers { return vimControllers; @@ -50,6 +48,12 @@ - (BOOL)inLiveResize { } @end +@implementation MMWindowController (Tests) +- (BOOL)fullScreenEnabled { + return fullScreenEnabled; +} +@end + @interface MacVimTests : XCTestCase @end @@ -733,4 +737,241 @@ - (void) testWindowResize { [self waitForVimClose]; } +#pragma mark Full screen tests + +- (void)waitForNativeFullscreenEnter { + XCTestExpectation *expectation = [self expectationWithDescription:@"NativeFullscreenEnter"]; + + SEL sel = @selector(windowDidEnterFullScreen:); + Method method = class_getInstanceMethod([MMWindowController class], sel); + + IMP origIMP = method_getImplementation(method); + IMP newIMP = imp_implementationWithBlock(^(id self, id notification) { + typedef void (*fn)(id,SEL,NSNotification*); + ((fn)origIMP)(self, sel, notification); + [expectation fulfill]; + }); + + method_setImplementation(method, newIMP); + [self waitForExpectations:@[expectation] timeout:10]; + method_setImplementation(method, origIMP); +} + +- (void)waitForNativeFullscreenExit { + XCTestExpectation *expectation = [self expectationWithDescription:@"NativeFullscreenExit"]; + + SEL sel = @selector(windowDidExitFullScreen:); + Method method = class_getInstanceMethod([MMWindowController class], sel); + + IMP origIMP = method_getImplementation(method); + IMP newIMP = imp_implementationWithBlock(^(id self, id notification) { + typedef void (*fn)(id,SEL,NSNotification*); + ((fn)origIMP)(self, sel, notification); + [expectation fulfill]; + }); + + method_setImplementation(method, newIMP); + [self waitForExpectations:@[expectation] timeout:10]; + method_setImplementation(method, origIMP); +} + +/// Utility to test full screen functionality in both non-native/native full +/// screen. +- (void) fullScreenTestWithNative:(BOOL)native { + MMAppController *app = MMAppController.sharedInstance; + + // Cache test defaults + NSUserDefaults *ud = NSUserDefaults.standardUserDefaults; + NSDictionary *defaults = [ud volatileDomainForName:NSArgumentDomain]; + NSMutableDictionary *newDefaults = [defaults mutableCopy]; + + // Change native full screen setting + newDefaults[MMNativeFullScreenKey] = [NSNumber numberWithBool:native]; + [ud setVolatileDomain:newDefaults forName:NSArgumentDomain]; + + [app openNewWindow:NewWindowClean activate:YES]; + [self waitForVimOpenAndMessages]; + + MMWindowController *winController = app.keyVimController.windowController; + + // Enter full screen and check that the states are properly changed. + [self sendStringToVim:@":set fu\n" withMods:0]; + if (native) { + [self waitForNativeFullscreenEnter]; + } else { + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; // wait one more cycle to make sure we finished the transition + } + + XCTAssertTrue([winController fullScreenEnabled]); + if (native) { + XCTAssertTrue([winController.window isKindOfClass:[MMWindow class]]); + } else { + XCTAssertTrue([winController.window isKindOfClass:[MMFullScreenWindow class]]); + } + + // Exit full screen + [self sendStringToVim:@":set nofu\n" withMods:0]; + if (native) { + [self waitForNativeFullscreenExit]; + } else { + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; // wait one more cycle to make sure we finished the transition + } + + XCTAssertFalse([winController fullScreenEnabled]); + XCTAssertTrue([winController.window isKindOfClass:[MMWindow class]]); + + // Enter full screen again + [self sendStringToVim:@":set fu\n" withMods:0]; + if (native) { + [self waitForNativeFullscreenEnter]; + } else { + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; // wait one more cycle to make sure we finished the transition + } + + XCTAssertTrue([winController fullScreenEnabled]); + + // Test that resizing the vim view does not work when in full screen as we fix the window size instead + MMTextView *textView = [[[[app keyVimController] windowController] vimView] textView]; + const int fuRows = textView.maxRows; + const int fuCols = textView.maxColumns; + XCTAssertNotEqual(10, fuRows); // just some basic assumptions as full screen should have more rows/cols than this + XCTAssertNotEqual(30, fuCols); + [self sendStringToVim:@":set lines=10\n" withMods:0]; + [self sendStringToVim:@":set columns=30\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; // need to wait twice to allow full screen to force it back + XCTAssertEqual(fuRows, textView.maxRows); + XCTAssertEqual(fuCols, textView.maxColumns); + + // Clean up + [[app keyVimController] sendMessage:VimShouldCloseMsgID data:nil]; + [self waitForVimClose]; + + XCTAssertEqual(0, [app vimControllers].count); + + // Restore settings to test defaults + [ud setVolatileDomain:defaults forName:NSArgumentDomain]; +} + +- (void) testFullScreenNonNative { + [self fullScreenTestWithNative:NO]; +} + +- (void) testFullScreenNative { + [self fullScreenTestWithNative:YES]; +} + +- (void) testFullScreenNonNativeOptions { + MMAppController *app = MMAppController.sharedInstance; + + // Cache test defaults + NSUserDefaults *ud = NSUserDefaults.standardUserDefaults; + NSDictionary *defaults = [ud volatileDomainForName:NSArgumentDomain]; + NSMutableDictionary *newDefaults = [defaults mutableCopy]; + + // Change native full screen setting + newDefaults[MMNativeFullScreenKey] = @NO; + [ud setVolatileDomain:newDefaults forName:NSArgumentDomain]; + + [app openNewWindow:NewWindowClean activate:YES]; + [self waitForVimOpenAndMessages]; + + MMWindowController *winController = app.keyVimController.windowController; + MMTextView *textView = [[winController vimView] textView]; + + // Test maxvert/maxhorz + [self sendStringToVim:@":set lines=10\n" withMods:0]; + [self sendStringToVim:@":set columns=30\n" withMods:0]; + [self sendStringToVim:@":set fuoptions=\n" withMods:0]; + [self waitForVimProcess]; + + [self sendStringToVim:@":set fu\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqual(textView.maxRows, 10); + XCTAssertEqual(textView.maxColumns, 30); + [self sendStringToVim:@":set nofu\n" withMods:0]; + [self sendStringToVim:@":set fuoptions=maxvert\n" withMods:0]; + [self sendStringToVim:@":set fu\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertGreaterThan(textView.maxRows, 10); + XCTAssertEqual(textView.maxColumns, 30); + [self sendStringToVim:@":set nofu\n" withMods:0]; + [self sendStringToVim:@":set fuoptions=maxhorz\n" withMods:0]; + [self sendStringToVim:@":set fu\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqual(textView.maxRows, 10); + XCTAssertGreaterThan(textView.maxColumns, 30); + [self sendStringToVim:@":set nofu\n" withMods:0]; + [self sendStringToVim:@":set fuoptions=maxhorz,maxvert\n" withMods:0]; + [self sendStringToVim:@":set fu\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertGreaterThan(textView.maxRows, 10); + XCTAssertGreaterThan(textView.maxColumns, 30); + + // Test background color + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithArgbInt:0xff000000]); // default is black + + // Make sure changing colorscheme doesn't override the background color unlike in non-full screen mode + [self sendStringToVim:@":color desert\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithArgbInt:0xff000000]); + + // Changing fuoptions should update the background color immediately + [self sendStringToVim:@":set fuoptions=background:Normal\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithArgbInt:0xff333333]); + + // And switching colorscheme should also update the color as well + [self sendStringToVim:@":color blue\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithArgbInt:0xff000087]); + + // Test parsing manual colors in both 8-digit mode (alpha is ignored) and 6-digit mode + [self sendStringToVim:@":set fuoptions=background:#11234567\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithArgbInt:0xff234567]); + [self sendStringToVim:@":set fuoptions=background:#abcdef\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithArgbInt:0xffabcdef]); + + // Test setting transparency while in full screen. We always set the alpha of the background color to 0.001 when transparency is set. + [self sendStringToVim:@":set fuoptions=background:#ffff00\n:set transparency=50\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithRed:1 green:1 blue:0 alpha:0.001]); + + [self sendStringToVim:@":set fuoptions=background:#00ff00\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithRed:0 green:1 blue:0 alpha:0.001]); + + [self sendStringToVim:@":set transparency=0\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithRed:0 green:1 blue:0 alpha:1]); + + // Test setting transparency outside of full screen and make sure it still works + [self sendStringToVim:@":set nofu\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; + [self sendStringToVim:@":set transparency=50 fuoptions=background:#0000ff\n" withMods:0]; + [self sendStringToVim:@":set fu\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + [self waitForEventHandlingAndVimProcess]; + XCTAssertEqualObjects(winController.window.backgroundColor, [NSColor colorWithRed:0 green:0 blue:1 alpha:0.001]); + + // Clean up + [[app keyVimController] sendMessage:VimShouldCloseMsgID data:nil]; + [self waitForVimClose]; + + XCTAssertEqual(0, [app vimControllers].count); + + // Restore settings to test defaults + [ud setVolatileDomain:defaults forName:NSArgumentDomain]; +} + @end diff --git a/src/MacVim/gui_macvim.m b/src/MacVim/gui_macvim.m index 7704ccb978..3538acbbae 100644 --- a/src/MacVim/gui_macvim.m +++ b/src/MacVim/gui_macvim.m @@ -633,6 +633,14 @@ ASLogDebug(@"back=%ld norm=%ld", gui.def_back_pixel, gui.def_norm_pixel); + // If using a highlight group for fullscreen background color we need to + // update the app when a new color scheme has been picked. This function + // technically wouldn't be called if a user manually set the relevant + // highlight group to another color but works in most use cases when they + // just change the color scheme. + if (fuoptions_flags & FUOPT_BGCOLOR_HLGROUP) + gui_mch_fuopt_update(); + [[MMBackend sharedInstance] setDefaultColorsBackground:gui.def_back_pixel foreground:gui.def_norm_pixel]; diff --git a/src/optionstr.c b/src/optionstr.c index c5b1511f6d..b3cfd3a5f9 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -4839,16 +4839,16 @@ check_fuoptions(void) return FAIL; if (p[i] == '#') { - // explicit color (#aarrggbb) + // explicit color (#aarrggbb or #rrggbb) i++; for (j = i; j < i + 8 && vim_isxdigit(p[j]); ++j) ; - if (j < i + 8) - return FAIL; // less than 8 digits + if (j != (i + 8) && j != (i + 6)) + return FAIL; // has to be either 6 or 8 digits if (p[j] != NUL && p[j] != ',') return FAIL; new_fuoptions_bgcolor = 0; - for (k = 0; k < 8; ++k) + for (k = 0; k < (j - i); ++k) new_fuoptions_bgcolor = new_fuoptions_bgcolor * 16 + hex2nr(p[i + k]); i = j;