diff --git a/runtime/doc/gui_mac.txt b/runtime/doc/gui_mac.txt index 68963f4bf1..df2644f4e2 100644 --- a/runtime/doc/gui_mac.txt +++ b/runtime/doc/gui_mac.txt @@ -290,6 +290,7 @@ KEY VALUE ~ *MMDialogsTrackPwd* open/save dialogs track the Vim pwd [bool] *MMDisableLaunchAnimation* disable launch animation when opening a new MacVim window [bool] +*MMDisableTablineAnimation* disable animation in GUI tabs [bool] *MMFontPreserveLineSpacing* use the line-spacing as specified by font [bool] *MMLoginShell* use login shell for launching Vim [bool] *MMLoginShellArgument* login shell parameter [string] @@ -573,8 +574,11 @@ _cycleWindows: Select next window (similar to ) _cycleWindowsBackwards: Select previous window (similar to ) _removeWindowFromStageManagerSet Remove window from a Stage Manager Set. Same as the "Remove Window from Set" menu item. -joinAllStageManagerSets Window will float among all Stage Manager sets -unjoinAllStageManagerSets Window will only show up in its own set +joinAllStageManagerSets: Window will float among all Stage Manager sets +unjoinAllStageManagerSets: Window will only show up in its own set +scrollToCurrentTab: Scroll to the selected tab in the GUI tab bar +scrollBackwardOneTab: Scroll backward by one tab in the tab bar +scrollForwardOneTab: Scroll forward by one tab in the tab bar ============================================================================== 7. Toolbar *macvim-toolbar* diff --git a/runtime/doc/tags b/runtime/doc/tags index b1b23149db..d8b5f59472 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -5648,6 +5648,7 @@ MMCmdLineAlignBottom gui_mac.txt /*MMCmdLineAlignBottom* MMDefaultTablineColors gui_mac.txt /*MMDefaultTablineColors* MMDialogsTrackPwd gui_mac.txt /*MMDialogsTrackPwd* MMDisableLaunchAnimation gui_mac.txt /*MMDisableLaunchAnimation* +MMDisableTablineAnimation gui_mac.txt /*MMDisableTablineAnimation* MMFontPreserveLineSpacing gui_mac.txt /*MMFontPreserveLineSpacing* MMFullScreenFadeTime gui_mac.txt /*MMFullScreenFadeTime* MMLoginShell gui_mac.txt /*MMLoginShell* diff --git a/src/MacVim/Actions.plist b/src/MacVim/Actions.plist index 99d32b8def..6e468c5ac9 100644 --- a/src/MacVim/Actions.plist +++ b/src/MacVim/Actions.plist @@ -8,6 +8,12 @@ addNewTab: + scrollToCurrentTab: + + scrollBackwardOneTab: + + scrollForwardOneTab: + arrangeInFront: clearRecentDocuments: diff --git a/src/MacVim/Base.lproj/Preferences.xib b/src/MacVim/Base.lproj/Preferences.xib index 0dbab227b2..1aa2e8057d 100644 --- a/src/MacVim/Base.lproj/Preferences.xib +++ b/src/MacVim/Base.lproj/Preferences.xib @@ -280,11 +280,11 @@ - + - + @@ -345,7 +345,7 @@ - + @@ -407,6 +407,33 @@ + + + + + + + + + + + + + + + + @@ -508,7 +535,7 @@ - + @@ -595,7 +622,7 @@ - + diff --git a/src/MacVim/MMAppController.h b/src/MacVim/MMAppController.h index e9f9631556..f97c028c76 100644 --- a/src/MacVim/MMAppController.h +++ b/src/MacVim/MMAppController.h @@ -84,6 +84,7 @@ - (BOOL)openFiles:(NSArray *)filenames withArguments:(NSDictionary *)args; - (void)refreshAllAppearances; +- (void)refreshAllTabProperties; - (void)refreshAllFonts; - (void)refreshAllResizeConstraints; - (void)refreshAllTextViews; diff --git a/src/MacVim/MMAppController.m b/src/MacVim/MMAppController.m index e1c1f4ffee..116ee74c27 100644 --- a/src/MacVim/MMAppController.m +++ b/src/MacVim/MMAppController.m @@ -196,6 +196,7 @@ + (void)registerDefaults MMUntitledWindowKey, [NSNumber numberWithBool:NO], MMNoWindowShadowKey, [NSNumber numberWithBool:NO], MMDisableLaunchAnimationKey, + [NSNumber numberWithBool:NO], MMDisableTablineAnimationKey, [NSNumber numberWithInt:0], MMAppearanceModeSelectionKey, [NSNumber numberWithBool:NO], MMNoTitleBarWindowKey, [NSNumber numberWithBool:NO], MMTitlebarAppearsTransparentKey, @@ -1229,19 +1230,22 @@ - (BOOL)openFiles:(NSArray *)filenames withArguments:(NSDictionary *)args - (void)refreshAllAppearances { - const NSUInteger count = [vimControllers count]; - for (unsigned i = 0; i < count; ++i) { - MMVimController *vc = [vimControllers objectAtIndex:i]; + for (MMVimController *vc in vimControllers) { [vc.windowController refreshApperanceMode]; } } +- (void)refreshAllTabProperties +{ + for (MMVimController *vc in vimControllers) { + [vc.windowController.vimView refreshTabProperties]; + } +} + /// Refresh all Vim text views' fonts. - (void)refreshAllFonts { - const NSUInteger count = [vimControllers count]; - for (unsigned i = 0; i < count; ++i) { - MMVimController *vc = [vimControllers objectAtIndex:i]; + for (MMVimController *vc in vimControllers) { [vc.windowController refreshFonts]; } } @@ -1250,9 +1254,7 @@ - (void)refreshAllFonts /// and resize the windows to match the constraints. - (void)refreshAllResizeConstraints { - const NSUInteger count = [vimControllers count]; - for (unsigned i = 0; i < count; ++i) { - MMVimController *vc = [vimControllers objectAtIndex:i]; + for (MMVimController *vc in vimControllers) { [vc.windowController updateResizeConstraints:YES]; } } @@ -1261,9 +1263,7 @@ - (void)refreshAllResizeConstraints /// cmdline alignment properties to make sure they are pinned properly. - (void)refreshAllTextViews { - const NSUInteger count = [vimControllers count]; - for (unsigned i = 0; i < count; ++i) { - MMVimController *vc = [vimControllers objectAtIndex:i]; + for (MMVimController *vc in vimControllers) { [vc.windowController.vimView.textView updateCmdlineRow]; vc.windowController.vimView.textView.needsDisplay = YES; } diff --git a/src/MacVim/MMPreferenceController.h b/src/MacVim/MMPreferenceController.h index 3021e1be13..e705f4e3a2 100644 --- a/src/MacVim/MMPreferenceController.h +++ b/src/MacVim/MMPreferenceController.h @@ -39,5 +39,6 @@ // Appearance pane - (IBAction)fontPropertiesChanged:(id)sender; +- (IBAction)tabsPropertiesChanged:(id)sender; @end diff --git a/src/MacVim/MMPreferenceController.m b/src/MacVim/MMPreferenceController.m index a556626496..4a615da45b 100644 --- a/src/MacVim/MMPreferenceController.m +++ b/src/MacVim/MMPreferenceController.m @@ -167,6 +167,11 @@ - (IBAction)fontPropertiesChanged:(id)sender [[MMAppController sharedInstance] refreshAllFonts]; } +- (IBAction)tabsPropertiesChanged:(id)sender +{ + [[MMAppController sharedInstance] refreshAllTabProperties]; +} + - (IBAction)smoothResizeChanged:(id)sender { [[MMAppController sharedInstance] refreshAllResizeConstraints]; diff --git a/src/MacVim/MMTabline/MMHoverButton.h b/src/MacVim/MMTabline/MMHoverButton.h index df3322b8a9..43351a9b73 100644 --- a/src/MacVim/MMTabline/MMHoverButton.h +++ b/src/MacVim/MMTabline/MMHoverButton.h @@ -6,6 +6,14 @@ @property (nonatomic, retain) NSColor *fgColor; -+ (NSImage *)imageNamed:(NSString *)name; +typedef enum : NSUInteger { + MMHoverButtonImageAddTab = 0, + MMHoverButtonImageCloseTab, + MMHoverButtonImageScrollLeft, + MMHoverButtonImageScrollRight, + MMHoverButtonImageCount +} MMHoverButtonImage; + ++ (NSImage *)imageFromType:(MMHoverButtonImage)imageType; @end diff --git a/src/MacVim/MMTabline/MMHoverButton.m b/src/MacVim/MMTabline/MMHoverButton.m index e51c4d8c30..0b9b076f85 100644 --- a/src/MacVim/MMTabline/MMHoverButton.m +++ b/src/MacVim/MMTabline/MMHoverButton.m @@ -6,41 +6,64 @@ @implementation MMHoverButton NSBox *_circle; } -+ (NSImage *)imageNamed:(NSString *)name ++ (NSImage *)imageFromType:(MMHoverButtonImage)imageType { - CGFloat size = [name isEqualToString:@"CloseTabButton"] ? 15 : 17; - return [NSImage imageWithSize:NSMakeSize(size, size) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { - NSBezierPath *p = [NSBezierPath new]; - if ([name isEqualToString:@"AddTabButton"]) { + if (imageType >= MMHoverButtonImageCount) + return nil; + + CGFloat size = imageType == MMHoverButtonImageCloseTab ? 15 : 17; + + static __weak NSImage *imageCache[MMHoverButtonImageCount] = { nil }; + if (imageCache[imageType] != nil) + return imageCache[imageType]; + + BOOL (^drawFuncs[MMHoverButtonImageCount])(NSRect) = { + // AddTab + ^BOOL(NSRect dstRect) { + NSBezierPath *p = [NSBezierPath new]; [p moveToPoint:NSMakePoint( 8.5, 4.5)]; [p lineToPoint:NSMakePoint( 8.5, 12.5)]; [p moveToPoint:NSMakePoint( 4.5, 8.5)]; [p lineToPoint:NSMakePoint(12.5, 8.5)]; [p setLineWidth:1.2]; [p stroke]; - } - else if ([name isEqualToString:@"CloseTabButton"]) { + return YES; + }, + // CloseTab + ^BOOL(NSRect dstRect) { + NSBezierPath *p = [NSBezierPath new]; [p moveToPoint:NSMakePoint( 4.5, 4.5)]; [p lineToPoint:NSMakePoint(10.5, 10.5)]; [p moveToPoint:NSMakePoint( 4.5, 10.5)]; [p lineToPoint:NSMakePoint(10.5, 4.5)]; [p setLineWidth:1.2]; [p stroke]; - } - else if ([name isEqualToString:@"ScrollLeftButton"]) { + return YES; + }, + // ScrollLeft + ^BOOL(NSRect dstRect) { + NSBezierPath *p = [NSBezierPath new]; [p moveToPoint:NSMakePoint( 5.0, 8.5)]; [p lineToPoint:NSMakePoint(10.0, 4.5)]; [p lineToPoint:NSMakePoint(10.0, 12.5)]; [p fill]; - } - else if ([name isEqualToString:@"ScrollRightButton"]) { + return YES; + }, + // ScrollRight + ^BOOL(NSRect dstRect) { + NSBezierPath *p = [NSBezierPath new]; [p moveToPoint:NSMakePoint(12.0, 8.5)]; [p lineToPoint:NSMakePoint( 7.0, 4.5)]; [p lineToPoint:NSMakePoint( 7.0, 12.5)]; [p fill]; + return YES; } - return YES; - }]; + }; + NSImage *img = [NSImage imageWithSize:NSMakeSize(size, size) + flipped:NO + drawingHandler:drawFuncs[imageType]]; + imageCache[imageType] = img; + return img; } - (instancetype)initWithFrame:(NSRect)frameRect @@ -70,22 +93,28 @@ - (void)setFgColor:(NSColor *)color self.image = super.image; } -- (void)setImage:(NSImage *)image +- (void)setImage:(NSImage *)imageTemplate { - _circle.cornerRadius = image.size.width / 2.0; + _circle.cornerRadius = imageTemplate.size.width / 2.0; NSColor *fillColor = self.fgColor ?: NSColor.controlTextColor; - super.image = [NSImage imageWithSize:image.size flipped:NO drawingHandler:^BOOL(NSRect dstRect) { - [image drawInRect:dstRect]; + NSImage *image = [NSImage imageWithSize:imageTemplate.size + flipped:NO + drawingHandler:^BOOL(NSRect dstRect) { + [imageTemplate drawInRect:dstRect]; [fillColor set]; NSRectFillUsingOperation(dstRect, NSCompositingOperationSourceAtop); return YES; }]; - self.alternateImage = [NSImage imageWithSize:image.size flipped:NO drawingHandler:^BOOL(NSRect dstRect) { + NSImage *alternateImage = [NSImage imageWithSize:imageTemplate.size + flipped:NO + drawingHandler:^BOOL(NSRect dstRect) { [[fillColor colorWithAlphaComponent:0.2] set]; [[NSBezierPath bezierPathWithOvalInRect:dstRect] fill]; - [super.image drawInRect:dstRect]; + [image drawInRect:dstRect]; return YES; }]; + super.image = image; + self.alternateImage = alternateImage; } - (void)setEnabled:(BOOL)enabled diff --git a/src/MacVim/MMTabline/MMTab.m b/src/MacVim/MMTabline/MMTab.m index 5b3fe16ce4..1b6de407a9 100644 --- a/src/MacVim/MMTabline/MMTab.m +++ b/src/MacVim/MMTabline/MMTab.m @@ -39,7 +39,7 @@ - (instancetype)initWithFrame:(NSRect)frameRect tabline:(MMTabline *)tabline _tabline = tabline; _closeButton = [MMHoverButton new]; - _closeButton.image = [MMHoverButton imageNamed:@"CloseTabButton"]; + _closeButton.image = [MMHoverButton imageFromType:MMHoverButtonImageCloseTab]; _closeButton.target = self; _closeButton.action = @selector(closeTab:); _closeButton.translatesAutoresizingMaskIntoConstraints = NO; diff --git a/src/MacVim/MMTabline/MMTabline.h b/src/MacVim/MMTabline/MMTabline.h index 462619d2f0..eb59034990 100644 --- a/src/MacVim/MMTabline/MMTabline.h +++ b/src/MacVim/MMTabline/MMTabline.h @@ -16,6 +16,7 @@ @property (nonatomic) NSInteger minimumTabWidth; @property (nonatomic) BOOL showsAddTabButton; @property (nonatomic) BOOL showsTabScrollButtons; +@property (nonatomic) BOOL useAnimation; @property (nonatomic, readonly) NSInteger numberOfTabs; @property (nonatomic, retain, readonly) MMHoverButton *addTabButton; @property (nonatomic, retain) NSColor *tablineBgColor; @@ -33,6 +34,7 @@ - (NSInteger)addTabAtIndex:(NSInteger)index; - (void)closeTab:(MMTab *)tab force:(BOOL)force layoutImmediately:(BOOL)layoutImmediately; +- (void)closeAllTabs; /// Batch update all the tabs using tab tags as unique IDs. Tab line will handle /// creating / removing tabs as necessary, and moving tabs to their new @@ -53,6 +55,8 @@ - (void)selectTabAtIndex:(NSInteger)index; - (MMTab *)tabAtIndex:(NSInteger)index; - (void)scrollTabToVisibleAtIndex:(NSInteger)index; +- (void)scrollLeftOneTab; +- (void)scrollRightOneTab; - (void)setTablineSelBackground:(NSColor *)back foreground:(NSColor *)fore; @end diff --git a/src/MacVim/MMTabline/MMTabline.m b/src/MacVim/MMTabline/MMTabline.m index 6f58ccbbfd..eb37b9f072 100644 --- a/src/MacVim/MMTabline/MMTabline.m +++ b/src/MacVim/MMTabline/MMTabline.m @@ -8,13 +8,14 @@ CGFloat remainder; } TabWidth; -const CGFloat OptimumTabWidth = 200; +const CGFloat OptimumTabWidth = 220; const CGFloat MinimumTabWidth = 100; const CGFloat TabOverlap = 6; +const CGFloat ScrollOneTabAllowance = 0.25; // If we are showing 75+% of the tab, consider it to be fully shown when deciding whether to scroll to next tab. -static MMHoverButton* MakeHoverButton(MMTabline *tabline, NSString *imageName, NSString *tooltip, SEL action, BOOL continuous) { +static MMHoverButton* MakeHoverButton(MMTabline *tabline, MMHoverButtonImage imageType, NSString *tooltip, SEL action, BOOL continuous) { MMHoverButton *button = [MMHoverButton new]; - button.image = [MMHoverButton imageNamed:imageName]; + button.image = [MMHoverButton imageFromType:imageType]; button.translatesAutoresizingMaskIntoConstraints = NO; button.target = tabline; button.action = action; @@ -63,6 +64,7 @@ - (instancetype)initWithFrame:(NSRect)frameRect _tabs = [NSMutableArray new]; _showsAddTabButton = YES; // get from NSUserDefaults _showsTabScrollButtons = YES; // get from NSUserDefaults + _useAnimation = YES; // get from NSUserDefaults _selectedTabIndex = -1; @@ -80,9 +82,9 @@ - (instancetype)initWithFrame:(NSRect)frameRect _scrollView.documentView = _tabsContainer; [self addSubview:_scrollView]; - _addTabButton = MakeHoverButton(self, @"AddTabButton", @"New Tab (⌘T)", @selector(addTabAtEnd), NO); - _leftScrollButton = MakeHoverButton(self, @"ScrollLeftButton", @"Scroll Tabs", @selector(scrollLeftOneTab), YES); - _rightScrollButton = MakeHoverButton(self, @"ScrollRightButton", @"Scroll Tabs", @selector(scrollRightOneTab), YES); + _addTabButton = MakeHoverButton(self, MMHoverButtonImageAddTab, @"New Tab (⌘T)", @selector(addTabAtEnd), NO); + _leftScrollButton = MakeHoverButton(self, MMHoverButtonImageScrollLeft, @"Scroll Tabs", @selector(scrollLeftOneTab), YES); + _rightScrollButton = MakeHoverButton(self, MMHoverButtonImageScrollRight, @"Scroll Tabs", @selector(scrollRightOneTab), YES); [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_leftScrollButton][_rightScrollButton]-5-[_scrollView]-5-[_addTabButton]" options:NSLayoutFormatAlignAllCenterY metrics:nil views:NSDictionaryOfVariableBindings(_scrollView, _leftScrollButton, _rightScrollButton, _addTabButton)]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_scrollView]|" options:0 metrics:nil views:@{@"_scrollView":_scrollView}]]; @@ -95,29 +97,8 @@ - (instancetype)initWithFrame:(NSRect)frameRect [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didScroll:) name:NSViewBoundsDidChangeNotification object:_scrollView.contentView]; - // Monitor for scroll wheel events so we can scroll the tabline - // horizontally without the user having to hold down SHIFT. - _scrollWheelEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { - NSPoint location = [_scrollView convertPoint:event.locationInWindow fromView:nil]; - // We want events: - // where the mouse is over the _scrollView - // and where the user is not modifying it with the SHIFT key - // and initiated by the scroll wheel and not the trackpad - if ([_scrollView mouse:location inRect:_scrollView.bounds] - && !event.modifierFlags - && !event.hasPreciseScrollingDeltas) - { - // Create a new scroll wheel event based on the original, - // but set the new deltaX to the original's deltaY. - // stackoverflow.com/a/38991946/111418 - CGEventRef cgEvent = CGEventCreateCopy(event.CGEvent); - CGEventSetIntegerValueField(cgEvent, kCGScrollWheelEventDeltaAxis2, event.scrollingDeltaY); - NSEvent *newEvent = [NSEvent eventWithCGEvent:cgEvent]; - CFRelease(cgEvent); - return newEvent; - } - return event; - }]; + [self addScrollWheelMonitor]; + } return self; } @@ -140,6 +121,32 @@ - (void)viewDidChangeEffectiveAppearance for (MMTab *tab in _tabs) tab.state = tab.state; } +- (void)viewDidHide +{ + if (_scrollWheelEventMonitor != nil) { + [NSEvent removeMonitor:_scrollWheelEventMonitor]; + _scrollWheelEventMonitor = nil; + } + [super viewDidHide]; +} + +- (void)viewDidUnhide +{ + [self addScrollWheelMonitor]; + [super viewDidUnhide]; +} + +- (void)dealloc +{ + if (_scrollWheelEventMonitor != nil) { + [NSEvent removeMonitor:_scrollWheelEventMonitor]; + _scrollWheelEventMonitor = nil; + } + + // This is not necessary after macOS 10.11, but there's no harm in doing so + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + #pragma mark - Accessors - (NSInteger)numberOfTabs @@ -313,6 +320,18 @@ - (void)closeTab:(MMTab *)tab force:(BOOL)force layoutImmediately:(BOOL)layoutIm } } +- (void)closeAllTabs +{ + _selectedTabIndex = -1; + _draggedTab = nil; + _initialDraggedTabIndex = _finalDraggedTabIndex = NSNotFound; + for (MMTab *tab in _tabs) { + [tab removeFromSuperview]; + } + [_tabs removeAllObjects]; + [self fixupLayoutWithAnimation:NO]; +} + - (void)updateTabsByTags:(NSInteger *)tags len:(NSUInteger)len delayTabResize:(BOOL)delayTabResize { BOOL needUpdate = NO; @@ -545,6 +564,50 @@ - (TabWidth)tabWidthForTabs:(NSInteger)numTabs return (TabWidth){tabWidth, availableWidthForTabs - tabWidth * numTabs}; } +/// Install a scroll wheel event monitor so that we can convert vertical scroll +/// wheel events to horizontal ones, so that the user doesn't have to hold down +/// SHIFT key while scrolling. +/// +/// Caller *has* to call `removeMonitor:` on `_scrollWheelEventMonitor` +/// afterwards. +- (void)addScrollWheelMonitor +{ + // We have to use a local event monitor because we are not allowed to + // override NSScrollView's scrollWheel: method. If we do so we will lose + // macOS responsive scrolling. See: + // https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKitOlderNotes/index.html#10_9Scrolling + if (_scrollWheelEventMonitor != nil) + return; + __weak NSScrollView *scrollView_weak = _scrollView; + __weak __typeof__(self) self_weak = self; + _scrollWheelEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { + // We want an event: + // - that actually belongs to this window + // - initiated by the scroll wheel and not the trackpad + // - is a vertical scroll event (if this is a horizontal scroll event + // either via holding SHIFT or third-party software we just let it + // through) + // - where the mouse is over the scroll view + if (event.window == self_weak.window + && !event.hasPreciseScrollingDeltas + && (event.scrollingDeltaX == 0 && event.scrollingDeltaY != 0) + && [scrollView_weak mouse:[scrollView_weak convertPoint:event.locationInWindow fromView:nil] + inRect:scrollView_weak.bounds]) + { + // Create a new scroll wheel event based on the original, + // but set the new deltaX to the original's deltaY. + // stackoverflow.com/a/38991946/111418 + CGEventRef cgEvent = CGEventCreateCopy(event.CGEvent); + CGEventSetIntegerValueField(cgEvent, kCGScrollWheelEventDeltaAxis1, 0); + CGEventSetIntegerValueField(cgEvent, kCGScrollWheelEventDeltaAxis2, event.scrollingDeltaY); + NSEvent *newEvent = [NSEvent eventWithCGEvent:cgEvent]; + CFRelease(cgEvent); + return newEvent; + } + return event; + }]; +} + - (void)fixupCloseButtons { if (_tabs.count == 1) { @@ -562,7 +625,16 @@ - (void)fixupTabZOrder - (void)fixupLayoutWithAnimation:(BOOL)shouldAnimate delayResize:(BOOL)delayResize { - if (_tabs.count == 0) return; + if (!self.useAnimation) + shouldAnimate = NO; + + if (_tabs.count == 0) { + NSRect frame = _tabsContainer.frame; + frame.size.width = 0; + _tabsContainer.frame = frame; + [self updateTabScrollButtonsEnabledState]; + return; + } if (delayResize) { // The pending delayed resize is trigged by mouse exit, but if we are @@ -759,7 +831,7 @@ - (void)updateTabScrollButtonsEnabledState - (void)scrollTabToVisibleAtIndex:(NSInteger)index { if (_tabs.count == 0) return; - if (index < 0 || index >= _tabs.count) return; + index = index < 0 ? 0 : (index >= _tabs.count ? _tabs.count - 1 : index); // Get the amount of time elapsed between the previous invocation // of this method and now. Use this elapsed time to set the animation @@ -780,8 +852,8 @@ - (void)scrollTabToVisibleAtIndex:(NSInteger)index NSTimeInterval elapsedTime = 0.1; #endif - NSRect tabFrame = _tabs[index].frame; - NSRect clipBounds = _scrollView.contentView.bounds; + NSRect tabFrame = _tabs[index].animator.frame; + NSRect clipBounds =_scrollView.contentView.animator.bounds; // One side or the other of the selected tab is clipped. if (!NSContainsRect(clipBounds, tabFrame)) { if (NSMinX(tabFrame) > NSMinX(clipBounds)) { @@ -791,20 +863,25 @@ - (void)scrollTabToVisibleAtIndex:(NSInteger)index // Left side of the selected tab is clipped. clipBounds.origin.x = tabFrame.origin.x; } - [NSAnimationContext beginGrouping]; - [NSAnimationContext.currentContext setDuration:elapsedTime < 0.2 ? 0.05 : 0.2]; - _scrollView.contentView.animator.bounds = clipBounds; - [NSAnimationContext endGrouping]; + if (_useAnimation) { + [NSAnimationContext beginGrouping]; + [NSAnimationContext.currentContext setDuration:elapsedTime < 0.2 ? 0.05 : 0.2]; + [_scrollView.contentView.animator setBoundsOrigin:clipBounds.origin]; + [NSAnimationContext endGrouping]; + } else { + [_scrollView.contentView setBoundsOrigin:clipBounds.origin]; + } } } - (void)scrollLeftOneTab { - NSRect clipBounds = _scrollView.contentView.bounds; + NSRect clipBounds = _scrollView.contentView.animator.bounds; for (NSInteger i = _tabs.count - 1; i >= 0; i--) { NSRect tabFrame = _tabs[i].frame; if (!NSContainsRect(clipBounds, tabFrame)) { - if (NSMinX(tabFrame) < NSMinX(clipBounds)) { + CGFloat allowance = i == 0 ? 0 : NSWidth(tabFrame) * ScrollOneTabAllowance; + if (NSMinX(tabFrame) + allowance < NSMinX(clipBounds)) { [self scrollTabToVisibleAtIndex:i]; break; } @@ -814,11 +891,12 @@ - (void)scrollLeftOneTab - (void)scrollRightOneTab { - NSRect clipBounds = _scrollView.contentView.bounds; + NSRect clipBounds = _scrollView.contentView.animator.bounds; for (NSInteger i = 0; i < _tabs.count; i++) { NSRect tabFrame = _tabs[i].frame; if (!NSContainsRect(clipBounds, tabFrame)) { - if (NSMaxX(tabFrame) > NSMaxX(clipBounds)) { + CGFloat allowance = i == _tabs.count - 1 ? 0 : NSWidth(tabFrame) * ScrollOneTabAllowance; + if (NSMaxX(tabFrame) - allowance > NSMaxX(clipBounds)) { [self scrollTabToVisibleAtIndex:i]; break; } diff --git a/src/MacVim/MMVimView.h b/src/MacVim/MMVimView.h index 8f7da31a2a..d29b75c63c 100644 --- a/src/MacVim/MMVimView.h +++ b/src/MacVim/MMVimView.h @@ -44,7 +44,12 @@ - (MMTabline *)tabline; - (IBAction)addNewTab:(id)sender; +- (IBAction)scrollToCurrentTab:(id)sender; +- (IBAction)scrollBackwardOneTab:(id)sender; +- (IBAction)scrollForwardOneTab:(id)sender; +- (void)showTabline:(BOOL)on; - (void)updateTabsWithData:(NSData *)data; +- (void)refreshTabProperties; - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type; - (BOOL)destroyScrollbarWithIdentifier:(int32_t)ident; diff --git a/src/MacVim/MMVimView.m b/src/MacVim/MMVimView.m index 35a55cbb30..b696ac32ee 100644 --- a/src/MacVim/MMVimView.m +++ b/src/MacVim/MMVimView.m @@ -115,6 +115,7 @@ - (MMVimView *)initWithFrame:(NSRect)frame tabline.hidden = YES; tabline.showsAddTabButton = [ud boolForKey:MMShowAddTabButtonKey]; tabline.showsTabScrollButtons = [ud boolForKey:MMShowTabScrollButtonsKey]; + tabline.useAnimation = ![ud boolForKey:MMDisableTablineAnimationKey]; tabline.optimumTabWidth = [ud integerForKey:MMTabOptimumWidthKey]; tabline.minimumTabWidth = [ud integerForKey:MMTabMinWidthKey]; tabline.addTabButton.target = self; @@ -251,9 +252,36 @@ - (void)setDesiredRows:(int)r columns:(int)c - (IBAction)addNewTab:(id)sender { + // Callback from the "Create a new tab button". We override this so we can + // send a message to Vim first and let it handle it before replying back. [vimController sendMessage:AddNewTabMsgID data:nil]; } +- (IBAction)scrollToCurrentTab:(id)sender +{ + [tabline scrollTabToVisibleAtIndex:tabline.selectedTabIndex]; +} + +- (IBAction)scrollBackwardOneTab:(id)sender +{ + [tabline scrollLeftOneTab]; +} + +- (IBAction)scrollForwardOneTab:(id)sender +{ + [tabline scrollRightOneTab]; +} + +- (void)showTabline:(BOOL)on +{ + [tabline setHidden:!on]; + if (!on) { + // When the tab is not shown we don't get tab updates from Vim. We just + // close all of them as otherwise we will be holding onto stale states. + [tabline closeAllTabs]; + } +} + /// Callback from Vim to update the tabline with new tab data - (void)updateTabsWithData:(NSData *)data { @@ -330,6 +358,12 @@ - (void)updateTabsWithData:(NSData *)data } } +- (void)refreshTabProperties +{ + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + tabline.showsTabScrollButtons = [ud boolForKey:MMShowTabScrollButtonsKey]; +} + - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type { MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident diff --git a/src/MacVim/MMWindowController.m b/src/MacVim/MMWindowController.m index 5ae435bf34..e062796052 100644 --- a/src/MacVim/MMWindowController.m +++ b/src/MacVim/MMWindowController.m @@ -868,7 +868,7 @@ - (void)processInputQueueDidFinish - (void)showTabline:(BOOL)on { - [[vimView tabline] setHidden:!on]; + [vimView showTabline:on]; [self updateTablineSeparator]; shouldMaximizeWindow = YES; } diff --git a/src/MacVim/Miscellaneous.h b/src/MacVim/Miscellaneous.h index 3b864602a8..0d297dd094 100644 --- a/src/MacVim/Miscellaneous.h +++ b/src/MacVim/Miscellaneous.h @@ -41,6 +41,7 @@ extern NSString *MMTitlebarAppearsTransparentKey; extern NSString *MMTitlebarShowsDocumentIconKey; extern NSString *MMNoWindowShadowKey; extern NSString *MMDisableLaunchAnimationKey; +extern NSString *MMDisableTablineAnimationKey; extern NSString *MMLoginShellKey; extern NSString *MMUntitledWindowKey; extern NSString *MMZoomBothKey; diff --git a/src/MacVim/Miscellaneous.m b/src/MacVim/Miscellaneous.m index 84b1bbec44..0b1e0523e2 100644 --- a/src/MacVim/Miscellaneous.m +++ b/src/MacVim/Miscellaneous.m @@ -37,6 +37,7 @@ NSString *MMTitlebarShowsDocumentIconKey = @"MMTitlebarShowsDocumentIcon"; NSString *MMNoWindowShadowKey = @"MMNoWindowShadow"; NSString *MMDisableLaunchAnimationKey = @"MMDisableLaunchAnimation"; +NSString *MMDisableTablineAnimationKey = @"MMDisableTablineAnimation"; NSString *MMLoginShellKey = @"MMLoginShell"; NSString *MMUntitledWindowKey = @"MMUntitledWindow"; NSString *MMZoomBothKey = @"MMZoomBoth";