diff --git a/ChangeLog b/ChangeLog index 35e134257..203f098ee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,31 @@ +2013-10-15 Eric Wasylishen + + * Headers/Additions/GNUstepGUI/GSTheme.h: Remove GSScrollViewBottomCorner + part name, instead themes should just provide a part called NSScrollView. + + Add -scrollViewScrollersOverlapBorders method. + + * Source/GSTheme.m: Remove GSScrollViewBottomCorner part name. + * Source/GSThemeDrawing.m: Add -scrollViewScrollersOverlapBorders. + * Source/GSThemeDrawing.m (-drawBrowserRect:...): If + -scrollViewScrollersOverlapBorders is enabled, fill the browser background + with the NSScrollView tile. + * Source/GSThemeDrawing.m (-drawScrollViewRect:...): If + -scrollViewScrollersOverlapBorders is enabled, fill the scroll view background + with the NSScrollView tile. + * Source/NSScroller.m (-rectForPart:): Change the meaning of the + GSScrollerKnobOvershoot default so the knob only overlaps the buttons + by this much (rather than both ends of the track). Turns out this is more + useful for themes. + * Source/NSScrollView.m (-tile): Add support for + -[GSTheme scrollViewScrollersOverlapBorders] + * Source/NSBrowser.m (-tile): Add support for + -[GSTheme scrollViewScrollersOverlapBorders] and + -[GSTheme scrollViewUseBottomCorner] + + The overall point of these additions is to support NSScrollView and + NSBrowser looking like: http://jesseross.com/clients/gnustep/ui/concepts/ + 2013-10-15 Eric Wasylishen * Source/NSTableView.m (-tile): Check the GSScrollViewNoInnerBorder diff --git a/Headers/Additions/GNUstepGUI/GSTheme.h b/Headers/Additions/GNUstepGUI/GSTheme.h index 20ad98bdb..07c275ee4 100644 --- a/Headers/Additions/GNUstepGUI/GSTheme.h +++ b/Headers/Additions/GNUstepGUI/GSTheme.h @@ -268,9 +268,6 @@ APPKIT_EXPORT NSString *GSScrollerUpArrow; APPKIT_EXPORT NSString *GSScrollerVerticalKnob; APPKIT_EXPORT NSString *GSScrollerVerticalSlot; -/* Scroll view parts */ -APPKIT_EXPORT NSString *GSScrollViewBottomCorner; - /* Names for table view parts */ APPKIT_EXPORT NSString *GSTableHeader; APPKIT_EXPORT NSString *GSTableCorner; @@ -891,7 +888,22 @@ APPKIT_EXPORT NSString *GSThemeWillDeactivateNotification; */ - (float) defaultScrollerWidth; -- (BOOL) scrolViewUseBottomCorner; +/** + * If YES, instructs NSScrollView to leave an empty square space where + * the horizontal and vertical scrollers meet. + * + * Controlled by user default GSScrollViewUseBottomCorner; default YES. + */ +- (BOOL) scrollViewUseBottomCorner; + +/** + * If YES, instructs NSScrollView to make the scrollers overlap the border. + * The scroll view border is drawn using the NSScrollView part, which + * must be provided by the theme if this method returns YES. + * + * Controlled by user default GSScrollViewScrollersOverlapBorders; default NO; + */ +- (BOOL) scrollViewScrollersOverlapBorders; /** * Method for toolbar theming. diff --git a/Source/GSTheme.m b/Source/GSTheme.m index 38cf259dc..70b7d3a59 100644 --- a/Source/GSTheme.m +++ b/Source/GSTheme.m @@ -77,9 +77,6 @@ NSString *GSScrollerUpArrow = @"GSScrollerUpArrow"; NSString *GSScrollerVerticalKnob = @"GSScrollerVerticalKnob"; NSString *GSScrollerVerticalSlot = @"GSScrollerVerticalSlot"; -// Scroll view parts -NSString *GSScrollViewBottomCorner = @"GSScrollViewBottomCorner"; - // Table view part names NSString *GSTableHeader = @"GSTableHeader"; NSString *GSTableCorner = @"GSTableCorner"; diff --git a/Source/GSThemeDrawing.m b/Source/GSThemeDrawing.m index 560e8806a..5b8da5fcf 100644 --- a/Source/GSThemeDrawing.m +++ b/Source/GSThemeDrawing.m @@ -611,7 +611,7 @@ return defaultScrollerWidth; } -- (BOOL) scrolViewUseBottomCorner +- (BOOL) scrollViewUseBottomCorner { NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; if ([defs objectForKey: @"GSScrollViewUseBottomCorner"] != nil) @@ -621,6 +621,16 @@ return YES; } +- (BOOL) scrollViewScrollersOverlapBorders +{ + NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; + if ([defs objectForKey: @"GSScrollViewScrollersOverlapBorders"] != nil) + { + return [defs boolForKey: @"GSScrollViewScrollersOverlapBorders"]; + } + return NO; +} + - (NSColor *) toolbarBackgroundColor { NSColor *color; @@ -2435,6 +2445,21 @@ typedef enum { } } } + + if (![self browserUseBezels] + && [self scrollViewScrollersOverlapBorders]) + { + NSRect baseRect = NSMakeRect(0, 0, bounds.size.width, 1); + NSRect colFrame = [browser frameOfColumn: [browser firstVisibleColumn]]; + NSRect scrollViewRect = NSUnionRect(baseRect, colFrame); + + GSDrawTiles *tiles = [self tilesNamed: @"NSScrollView" + state: GSThemeNormalState]; + + [self fillRect: scrollViewRect + withTiles: tiles + background: [NSColor clearColor]]; + } } - (CGFloat) browserColumnSeparation @@ -2521,6 +2546,7 @@ typedef enum { NSRect bounds = [view bounds]; BOOL hasInnerBorder = ![[NSUserDefaults standardUserDefaults] boolForKey: @"GSScrollViewNoInnerBorder"]; + GSDrawTiles *tiles = nil; name = [theme nameForElement: self]; if (name == nil) @@ -2528,28 +2554,38 @@ typedef enum { name = @"NSScrollView"; } color = [theme colorNamed: name state: GSThemeNormalState]; + tiles = [theme tilesNamed: name state: GSThemeNormalState]; if (color == nil) { color = [NSColor controlDarkShadowColor]; } - - switch (borderType) + + if (tiles == nil) { - case NSNoBorder: - break; - - case NSLineBorder: - [color set]; - NSFrameRect(bounds); - break; - - case NSBezelBorder: - [theme drawGrayBezel: bounds withClip: rect]; - break; - - case NSGrooveBorder: - [theme drawGroove: bounds withClip: rect]; - break; + switch (borderType) + { + case NSNoBorder: + break; + + case NSLineBorder: + [color set]; + NSFrameRect(bounds); + break; + + case NSBezelBorder: + [theme drawGrayBezel: bounds withClip: rect]; + break; + + case NSGrooveBorder: + [theme drawGroove: bounds withClip: rect]; + break; + } + } + else + { + [self fillRect: bounds + withTiles: tiles + background: [NSColor clearColor]]; } if (hasInnerBorder) @@ -2600,26 +2636,6 @@ typedef enum { DPSstroke(ctxt); } } - - if (![self scrolViewUseBottomCorner] - && [scrollView hasHorizontalScroller] - && [scrollView hasVerticalScroller]) - { - NSScroller *vertScroller = [scrollView verticalScroller]; - NSScroller *horizScroller = [scrollView horizontalScroller]; - - NSRect bottomCornerRect = NSMakeRect([vertScroller frame].origin.x, - [horizScroller frame].origin.y, - NSWidth([vertScroller frame]), - NSHeight([horizScroller frame])); - - GSDrawTiles *tiles = [self tilesNamed: GSScrollViewBottomCorner - state: GSThemeNormalState]; - - [self fillRect: bottomCornerRect - withTiles: tiles - background: [NSColor clearColor]]; - } } - (void) drawSliderBorderAndBackground: (NSBorderType)aType diff --git a/Source/NSBrowser.m b/Source/NSBrowser.m index 2cbd0370f..efbc2d08e 100644 --- a/Source/NSBrowser.m +++ b/Source/NSBrowser.m @@ -1811,6 +1811,12 @@ static BOOL browserUseBezels; else rect.size.width = _frame.size.width - (rect.origin.x + bezelBorderSize.width); + + // FIXME: Assumes left-side scrollers + if ([[GSTheme theme] scrollViewScrollersOverlapBorders]) + { + rect.size.width -= 1; + } } if (rect.size.width < 0) @@ -1863,6 +1869,8 @@ static BOOL browserUseBezels; NSSize bezelBorderSize = NSZeroSize; NSInteger i, num, columnCount, delta; CGFloat frameWidth; + const BOOL overlapBorders = [[GSTheme theme] scrollViewScrollersOverlapBorders]; + const BOOL useBottomCorner = [[GSTheme theme] scrollViewUseBottomCorner]; if (browserUseBezels) bezelBorderSize = [[GSTheme theme] sizeForBorderType: NSBezelBorder]; @@ -1892,6 +1900,21 @@ static BOOL browserUseBezels; else _columnSize.height -= scrollerWidth + (2 * bezelBorderSize.height); + // "Bottom corner" box + if (!browserUseBezels && !useBottomCorner) + { + _scrollerRect.origin.x += scrollerWidth; + _scrollerRect.size.width -= scrollerWidth; + } + + /** Horizontall expand the scroller by GSScrollerKnobOvershoot on the left */ + if (overlapBorders) + { + // FIXME: Assumes left scroller + _scrollerRect.origin.x -= 1; + _scrollerRect.size.width += 1; + } + if (!NSEqualRects(_scrollerRect, [_horizontalScroller frame])) { [_horizontalScroller setFrame: _scrollerRect]; diff --git a/Source/NSScrollView.m b/Source/NSScrollView.m index 2e08d99d7..fa749e32f 100644 --- a/Source/NSScrollView.m +++ b/Source/NSScrollView.m @@ -1064,6 +1064,12 @@ static CGFloat scrollerWidth; [self tile]; } +static NSRectEdge +GSOppositeEdge(NSRectEdge edge) +{ + return (edge == NSMinXEdge) ? NSMaxXEdge : NSMinXEdge; +} + - (void) tile { NSRect headerRect, contentRect; @@ -1075,8 +1081,9 @@ static CGFloat scrollerWidth; CGFloat innerBorderWidth = [[NSUserDefaults standardUserDefaults] boolForKey: @"GSScrollViewNoInnerBorder"] ? 0.0 : 1.0; - BOOL useBottomCorner = [[GSTheme theme] scrolViewUseBottomCorner]; - + const BOOL useBottomCorner = [[GSTheme theme] scrollViewUseBottomCorner]; + const BOOL overlapBorders = [[GSTheme theme] scrollViewScrollersOverlapBorders]; + style = NSInterfaceStyleForKey(@"NSScrollViewInterfaceStyle", nil); if (style == NSMacintoshInterfaceStyle @@ -1098,7 +1105,11 @@ static CGFloat scrollerWidth; } /* Prepare the contentRect by insetting the borders. */ - contentRect = NSInsetRect(_bounds, border.width, border.height); + contentRect = _bounds; + + if (!overlapBorders) + contentRect = NSInsetRect(contentRect, border.width, border.height); + if (contentRect.size.width < 0 || contentRect.size.height < 0) { /* FIXME ... should we do something else when given @@ -1108,6 +1119,32 @@ static CGFloat scrollerWidth; [self _synchronizeHeaderAndCornerView]; + if (overlapBorders) + { + if (_borderType != NSNoBorder) + { + if (!(_hasHeaderView || _hasCornerView)) + { + // Inset 1px on the top + NSDivideRect(contentRect, NULL, &contentRect, 1, topEdge); + } + if (!_hasVertScroller) + { + // Inset 1px on the edge where the vertical scroller would be + NSDivideRect(contentRect, NULL, &contentRect, 1, verticalScrollerEdge); + } + if (!_hasHorizScroller) + { + NSDivideRect(contentRect, NULL, &contentRect, 1, bottomEdge); + } + // The vertical edge without a scroller + { + NSDivideRect(contentRect, NULL, &contentRect, 1, + GSOppositeEdge(verticalScrollerEdge)); + } + } + } + /* First, allocate vertical space for the headerView / cornerView (but - NB - the headerView needs to be placed above the clipview later on, we can't place it now). */ @@ -1151,6 +1188,13 @@ static CGFloat scrollerWidth; scrollerWidth, bottomEdge); } + /** Vertically expand the scroller by 1pt on each end */ + if (overlapBorders) + { + vertScrollerRect.origin.y -= 1; + vertScrollerRect.size.height += 2; + } + [_vertScroller setFrame: vertScrollerRect]; /* Substract 1 for the line that separates the vertical scroller @@ -1167,6 +1211,13 @@ static CGFloat scrollerWidth; NSDivideRect (contentRect, &horizScrollerRect, &contentRect, scrollerWidth, bottomEdge); + /** Horizontall expand the scroller by 1pt on each end */ + if (overlapBorders) + { + horizScrollerRect.origin.x -= 1; + horizScrollerRect.size.width += 2; + } + [_horizScroller setFrame: horizScrollerRect]; /* Substract 1 for the width for the line that separates the @@ -1300,10 +1351,10 @@ static CGFloat scrollerWidth; - (BOOL) isOpaque { // FIXME: Only needs to be NO in a corner case, - // when [[GSTheme theme] scrolViewUseBottomCorner] is NO + // when [[GSTheme theme] scrollViewUseBottomCorner] is NO // and the theme tile for the bottom corner is transparent. // So maybe cache the value of - // [[GSTheme theme] scrolViewUseBottomCorner] and check it here. + // [[GSTheme theme] scrollViewUseBottomCorner] and check it here. return NO; } @@ -1739,6 +1790,13 @@ static CGFloat scrollerWidth; - (void) _themeDidActivate: (NSNotification*)notification { + // N.B. Reload cached [NSScroller scrollerWidth] since the + // new theme may have a different scroller width. + // + // Since scrollerWidth is a static, it will get overwritten + // several times; doesn't matter though. + scrollerWidth = [NSScroller scrollerWidth]; + [self tile]; } diff --git a/Source/NSScroller.m b/Source/NSScroller.m index d8cafd622..4b6c9eeed 100644 --- a/Source/NSScroller.m +++ b/Source/NSScroller.m @@ -68,8 +68,8 @@ static NSCell *horizontalKnobSlotCell = nil; static NSCell *verticalKnobSlotCell = nil; static CGFloat scrollerWidth = 0.0; /** - * This is the amount (in userspace points) by which the knob slides beyond - * either end of the track. Typical use would be to set it to 1 when both + * This is the amount (in userspace points) by which the knob slides over the + * button ends of the track. Typical use would be to set it to 1 when both * the knob and the buttons have a 1-point border, so that when the knob is * at its maximum, it overlaps the button by 1 point giving a resulting * 1-point wide border. @@ -1229,8 +1229,29 @@ static float buttonsOffset = 1.0; // buttonsWidth = sw - 2*buttonsOffset knobHeight = buttonsWidth; /* calc knob's position */ - knobPosition = floor((float)_doubleValue * (slotHeight - knobHeight + (2 * scrollerKnobOvershoot))) - - scrollerKnobOvershoot; + + { + CGFloat knobOvershootAbove = scrollerKnobOvershoot; + CGFloat knobOvershootBelow = scrollerKnobOvershoot; + if (arrowsSameEnd + && _arrowsPosition == NSScrollerArrowsMinEnd) + { + knobOvershootBelow = 0; + } + else if (arrowsSameEnd + && _arrowsPosition == NSScrollerArrowsMaxEnd) + { + knobOvershootAbove = 0; + } + else if (_arrowsPosition == NSScrollerArrowsNone) + { + knobOvershootAbove = 0; + knobOvershootBelow = 0; + } + + knobPosition = floor((float)_doubleValue * (slotHeight - knobHeight + knobOvershootAbove + knobOvershootBelow)) + - knobOvershootAbove; + } if (arrowsSameEnd) {