diff --git a/WAYWindow/WAYWindow.h b/WAYWindow/WAYWindow.h index 5989d10..5b5468e 100644 --- a/WAYWindow/WAYWindow.h +++ b/WAYWindow/WAYWindow.h @@ -38,9 +38,15 @@ //// Returns the titlebar view of the window, which you can add arbitrary subviews to. @property (strong,readonly) IBOutlet NSView *titleBarView; +/// If set to YES, the title of the window is vertically centered. Default: YES. +@property (nonatomic) IBInspectable BOOL verticallyCenterTitle; + /// If set to YES, the standard window button will be vertically centered. Default: YES. @property (nonatomic) IBInspectable BOOL centerTrafficLightButtons; +/// If set to YES, the traffic light buttons are displayed in vertical orientation. Default: NO. +@property (nonatomic) IBInspectable BOOL verticalTrafficLightButtons; + /// Defines the left margin of the standard window buttons. Defaut: OS X default value. @property (nonatomic) IBInspectable CGFloat trafficLightButtonsLeftMargin; @@ -50,6 +56,9 @@ /// If set to YES, the title of the window will be hidden. Default: YES. @property (nonatomic) IBInspectable BOOL hidesTitle; +/// If set to YES, the title of the window is shown. Default: NO. +@property (nonatomic) IBInspectable BOOL showsTitle; + /// Replaces the window's content view with an instance of NSVisualEffectView and applies the Vibrant Dark look. Transfers all subviews to the new content view. - (void) setContentViewAppearanceVibrantDark; diff --git a/WAYWindow/WAYWindow.m b/WAYWindow/WAYWindow.m index 8421030..84c751d 100644 --- a/WAYWindow/WAYWindow.m +++ b/WAYWindow/WAYWindow.m @@ -19,6 +19,7 @@ // #import "WAYWindow.h" +#import /** Convenience methods to make NSPointerArray act like a stack of selectors. It would be a subclass, but NSPointerArray @@ -199,41 +200,58 @@ - (void) orderFront:(id)sender { #pragma mark - Public - (NSView *) titleBarView { - return [_standardButtons[0] superview]; + return [_standardButtons[0] superview]; } - (void) setCenterTrafficLightButtons:(BOOL)centerTrafficLightButtons { - _centerTrafficLightButtons = centerTrafficLightButtons; - [self _setNeedsLayout]; + _centerTrafficLightButtons = centerTrafficLightButtons; + [self _setNeedsLayout]; } -- (void) setTitleBarHeight:(CGFloat)titleBarHeight { +- (void) setVerticalTrafficLightButtons:(BOOL)verticalTrafficLightButtons { + _verticalTrafficLightButtons = verticalTrafficLightButtons; + [self _setNeedsLayout]; +} - titleBarHeight = MAX(titleBarHeight,[[self class] defaultTitleBarHeight]); - CGFloat delta = titleBarHeight - _titleBarHeight; - _titleBarHeight = titleBarHeight; - - if (_dummyTitlebarAccessoryViewController) { - [self removeTitlebarAccessoryViewControllerAtIndex:0]; - } - - NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 10, titleBarHeight-[WAYWindow defaultTitleBarHeight])]; - _dummyTitlebarAccessoryViewController = [NSTitlebarAccessoryViewController new]; - _dummyTitlebarAccessoryViewController.view = view; - _dummyTitlebarAccessoryViewController.fullScreenMinHeight = titleBarHeight; - [self addTitlebarAccessoryViewController:_dummyTitlebarAccessoryViewController]; - - NSRect frame = self.frame; - frame.size.height += delta; - frame.origin.y -= delta; - - [self _setNeedsLayout]; - [self setFrame:frame display:NO]; // NO is important. +- (void) setVerticallyCenterTitle:(BOOL)verticallyCenterTitle { + _verticallyCenterTitle = verticallyCenterTitle; + [self _setNeedsLayout]; +} + +- (void) setTitleBarHeight:(CGFloat)titleBarHeight { + + titleBarHeight = MAX(titleBarHeight,[[self class] defaultTitleBarHeight]); + CGFloat delta = titleBarHeight - _titleBarHeight; + _titleBarHeight = titleBarHeight; + + if (_dummyTitlebarAccessoryViewController) { + [self removeTitlebarAccessoryViewControllerAtIndex:0]; + } + + NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 10, titleBarHeight-[WAYWindow defaultTitleBarHeight])]; + _dummyTitlebarAccessoryViewController = [NSTitlebarAccessoryViewController new]; + _dummyTitlebarAccessoryViewController.view = view; + _dummyTitlebarAccessoryViewController.fullScreenMinHeight = titleBarHeight; + [self addTitlebarAccessoryViewController:_dummyTitlebarAccessoryViewController]; + + NSRect frame = self.frame; + frame.size.height += delta; + frame.origin.y -= delta; + + [self _setNeedsLayout]; + [self setFrame:frame display:NO]; // NO is important. } - (void) setHidesTitle:(BOOL)hidesTitle { - _hidesTitle = hidesTitle; - [self setTitleVisibility:hidesTitle ? NSWindowTitleHidden : NSWindowTitleVisible]; + _hidesTitle = hidesTitle; + _showsTitle = !hidesTitle; + [self setTitleVisibility:hidesTitle ? NSWindowTitleHidden : NSWindowTitleVisible]; +} + +- (void) setShowsTitle:(BOOL)showsTitle { + _showsTitle = showsTitle; + _hidesTitle = !showsTitle; + [self setTitleVisibility:showsTitle ? NSWindowTitleVisible : NSWindowTitleHidden]; } - (void) setContentViewAppearanceVibrantDark { @@ -305,40 +323,118 @@ - (NSView *) replaceSubview:(NSView *)aView withViewOfClass:(Class)newViewClass #pragma mark - Private - (void) _setUp { - _delegateProxy = [[WAYWindowDelegateProxy alloc] init]; - _delegateProxy.firstDelegate = self; - super.delegate = _delegateProxy; - - _standardButtons = @[[self standardWindowButton:NSWindowCloseButton], - [self standardWindowButton:NSWindowMiniaturizeButton], - [self standardWindowButton:NSWindowZoomButton]]; - _centerTrafficLightButtons = YES; - - NSButton *closeButton = [self standardWindowButton:NSWindowCloseButton]; - kWAYWindowDefaultTrafficLightButtonsLeftMargin = NSMinX(closeButton.frame); - kWAYWindowDefaultTrafficLightButtonsTopMargin = NSHeight(closeButton.superview.frame)-NSMaxY(closeButton.frame); - - self.styleMask |= NSFullSizeContentViewWindowMask; - _trafficLightButtonsLeftMargin = kWAYWindowDefaultTrafficLightButtonsLeftMargin; - _trafficLightButtonsTopMargin = kWAYWindowDefaultTrafficLightButtonsTopMargin; - - self.hidesTitle = YES; - - [super setDelegate:self]; - [self _setNeedsLayout]; + _delegateProxy = [[WAYWindowDelegateProxy alloc] init]; + _delegateProxy.firstDelegate = self; + super.delegate = _delegateProxy; + + _standardButtons = @[[self standardWindowButton:NSWindowCloseButton], + [self standardWindowButton:NSWindowMiniaturizeButton], + [self standardWindowButton:NSWindowZoomButton]]; + _centerTrafficLightButtons = YES; + + NSButton *closeButton = [self standardWindowButton:NSWindowCloseButton]; + kWAYWindowDefaultTrafficLightButtonsLeftMargin = NSMinX(closeButton.frame); + kWAYWindowDefaultTrafficLightButtonsTopMargin = NSHeight(closeButton.superview.frame)-NSMaxY(closeButton.frame); + + self.styleMask |= NSFullSizeContentViewWindowMask; + _trafficLightButtonsLeftMargin = kWAYWindowDefaultTrafficLightButtonsLeftMargin; + _trafficLightButtonsTopMargin = kWAYWindowDefaultTrafficLightButtonsTopMargin; + + self.hidesTitle = YES; + self.showsTitle = NO; + self.verticallyCenterTitle = YES; + + [super setDelegate:self]; + [self _setNeedsLayout]; } - (void) _setNeedsLayout { - [_standardButtons enumerateObjectsUsingBlock:^(NSButton *standardButton, NSUInteger idx, BOOL *stop) { - NSRect frame = standardButton.frame; - if (_centerTrafficLightButtons) - frame.origin.y = NSHeight(standardButton.superview.frame)/2-NSHeight(standardButton.frame)/2; - else - frame.origin.y = NSHeight(standardButton.superview.frame)-NSHeight(standardButton.frame)-_trafficLightButtonsTopMargin; - - frame.origin.x = _trafficLightButtonsLeftMargin +idx*(NSWidth(frame) + 6); - [standardButton setFrame:frame]; - }]; + [_standardButtons enumerateObjectsUsingBlock:^(NSButton *standardButton, NSUInteger idx, BOOL *stop) { + NSRect frame = standardButton.frame; + CGFloat trafficLightSeparation = 5.0; // db: modified from the original + if (_verticalTrafficLightButtons) + { + if (_centerTrafficLightButtons) + { + CGFloat trafficLightsHeight = 3.0*NSHeight(frame) + 2.0*trafficLightSeparation; + CGFloat offset = (_titleBarHeight - trafficLightsHeight)/2.0; + frame.origin.y = _titleBarHeight - NSHeight(frame) - idx*(NSHeight(frame) + trafficLightSeparation) - offset; + } + else + { + frame.origin.y = (_titleBarHeight - NSHeight(frame) - _trafficLightButtonsTopMargin) - idx*(NSHeight(frame) + trafficLightSeparation); + } + + frame.origin.x = _trafficLightButtonsLeftMargin; + [standardButton setFrame:frame]; + } + else + { + if (_centerTrafficLightButtons) + frame.origin.y = NSHeight(standardButton.superview.frame)/2-NSHeight(standardButton.frame)/2; + else + frame.origin.y = NSHeight(standardButton.superview.frame)-NSHeight(standardButton.frame)-_trafficLightButtonsTopMargin; + + frame.origin.x = _trafficLightButtonsLeftMargin +idx*(NSWidth(frame) + 6.0); + [standardButton setFrame:frame]; + } + }]; + + // Vertically center window title if necessary. + if (self.verticallyCenterTitle) { + [self layoutVerticallyCenterTitle]; + } +} + +// Position the title so it is centered vertically in the parent's view. This is done by traversing +// the view hierarchy and vertically centering the title name, title edit state, document icon, and +// autosave button according to class names. Naturally, centering won't work if class names change. +- (void) layoutVerticallyCenterTitle +{ + NSRect drawingRect = self.titleBarView.frame; + if (self.showsTitle) + { + NSRect titleTextRect; + NSDictionary *titleTextStyles = nil; + [self getTitleFrame:&titleTextRect textAttributes:&titleTextStyles forWindow:self]; + titleTextRect.origin.y = floor(NSMidY(drawingRect) - (NSHeight(titleTextRect) / 2.f) + 1); + for (NSView * view in self.titleBarView.subviews) + { + if ([[view className] isEqualToString:[NSTextField className]] || [[view className] isEqualToString:@"NSThemeDocumentButton"] || [[view className] isEqualToString:@"NSThemeAutosaveButton"]) + { + [view setFrameOrigin:NSMakePoint(view.frame.origin.x, titleTextRect.origin.y)]; + } + } + } +} + +- (void) getNativeTitleTextInfo:(out HIThemeTextInfo *)titleTextInfo +{ + BOOL drawsAsMainWindow = (self.isMainWindow && [NSApplication sharedApplication].isActive); + titleTextInfo->version = 1; + titleTextInfo->state = drawsAsMainWindow ? kThemeStateActive : kThemeStateUnavailableInactive; + titleTextInfo->fontID = kThemeWindowTitleFont; + titleTextInfo->horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + titleTextInfo->verticalFlushness = kHIThemeTextVerticalFlushCenter; + titleTextInfo->options = kHIThemeTextBoxOptionEngraved; + titleTextInfo->truncationPosition = kHIThemeTextTruncationDefault; + titleTextInfo->truncationMaxLines = 1; +} + +- (void) getTitleFrame:(out NSRect *)frame textAttributes:(out NSDictionary **)attributes forWindow:(in WAYWindow *)window +{ + NSSize titleSize; + NSRect titleTextRect; + + HIThemeTextInfo titleTextInfo; + [self getNativeTitleTextInfo:&titleTextInfo]; + HIThemeGetTextDimensions((__bridge CFTypeRef)(window.title), 0, &titleTextInfo, &titleSize.width, &titleSize.height, NULL); + + titleTextRect.size = titleSize; + + if (frame) { + *frame = titleTextRect; + } } #pragma mark - NSWindow Delegate diff --git a/WAYWindowDemo/AppDelegate.m b/WAYWindowDemo/AppDelegate.m index d0371e9..d7c1b8c 100644 --- a/WAYWindowDemo/AppDelegate.m +++ b/WAYWindowDemo/AppDelegate.m @@ -21,7 +21,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application [_window setVibrantDarkAppearance]; [_window setContentViewAppearanceVibrantDark]; - [_window.titleBarView addSubview:_titlebar]; + [_window.titleBarView addSubview:_titlebar]; } - (void)applicationWillTerminate:(NSNotification *)aNotification {