Index: chrome/browser/ui/cocoa/tabs/tab_view.mm |
diff --git a/chrome/browser/ui/cocoa/tabs/tab_view.mm b/chrome/browser/ui/cocoa/tabs/tab_view.mm |
index 4c26e9a6bb9d363ada11fc5bf73fedf3b1fb7704..fe68c0ff472f009c987e76221c210062e8f705b7 100644 |
--- a/chrome/browser/ui/cocoa/tabs/tab_view.mm |
+++ b/chrome/browser/ui/cocoa/tabs/tab_view.mm |
@@ -24,7 +24,6 @@ |
#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" |
-const int kMaskHeight = 29; // Height of the mask bitmap. |
const int kFillHeight = 25; // Height of the "mask on" part of the mask bitmap. |
// The amount of time in seconds during which each type of glow increases, holds |
@@ -44,12 +43,109 @@ const NSTimeInterval kGlowUpdateInterval = 0.025; |
// has moved less than the threshold, we want to close the tab. |
const CGFloat kRapidCloseDist = 2.5; |
+namespace { |
+ |
+// A wrapper around NSDrawThreePartImage that caches the images. |
+// The middle image is optional. |
+class ThreePartImage { |
+ public: |
+ ThreePartImage(int left_id, int middle_id, int right_id) { |
+ DCHECK(left_id); |
+ DCHECK(right_id); |
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
+ leftImage_.reset(rb.GetNativeImageNamed(left_id).CopyNSImage()); |
+ rightImage_.reset(rb.GetNativeImageNamed(right_id).CopyNSImage()); |
+ leftSize_ = [leftImage_ size]; |
+ rightSize_ = [rightImage_ size]; |
+ |
+ if (middle_id) |
+ middleImage_.reset(rb.GetNativeImageNamed(middle_id).CopyNSImage()); |
+ } |
+ |
+ void DrawInRect(NSRect rect, NSCompositingOperation op, CGFloat alpha) { |
+ rect.size.height = leftSize_.height; |
+ NSDrawThreePartImage(rect, leftImage_, middleImage_, rightImage_, |
+ NO, op, alpha, NO); |
+ } |
+ |
+ NSRect GetLeftRect(NSRect bounds) { |
+ NSRect left, right; |
+ NSDivideRect(bounds, &left, &right, leftSize_.width, NSMinXEdge); |
+ return left; |
+ } |
+ |
+ NSRect GetMiddleRect(NSRect bounds) { |
+ NSRect left, middle, right; |
+ NSDivideRect(bounds, &left, &middle, leftSize_.width, NSMinXEdge); |
+ NSDivideRect(middle, &right, &middle, rightSize_.width, NSMaxXEdge); |
+ return middle; |
+ } |
+ |
+ NSRect GetRightRect(NSRect bounds) { |
+ NSRect left, right; |
+ NSDivideRect(bounds, &right, &left, rightSize_.width, NSMaxXEdge); |
+ return right; |
+ } |
+ |
+ // Returns YES if |point| is in a non-transparent part of the images. |
+ BOOL HitTest(NSPoint point, NSRect bounds) { |
+ NSRect middleRect = GetMiddleRect(bounds); |
+ if (NSPointInRect(point, middleRect)) |
+ return middleImage_ ? HitTestImage(point, middleImage_, middleRect) : YES; |
+ |
+ NSRect leftRect = GetLeftRect(bounds); |
+ if (NSPointInRect(point, leftRect)) |
+ return HitTestImage(point, leftImage_, leftRect); |
+ |
+ NSRect rightRect = GetRightRect(bounds); |
+ if (NSPointInRect(point, rightRect)) |
+ return HitTestImage(point, rightImage_, rightRect); |
+ |
+ return NO; |
+ } |
+ |
+ private: |
+ // Returns YES if |point| is in a non-transparent part of |image|. |
+ BOOL HitTestImage(NSPoint point, NSImage* image, NSRect imageRect) { |
+ NSRect pointRect = NSMakeRect(point.x, point.y, 1, 1); |
+ return [image hitTestRect:pointRect |
+ withImageDestinationRect:imageRect |
+ context:nil |
+ hints:nil |
+ flipped:NO]; |
+ } |
+ |
+ base::scoped_nsobject<NSImage> leftImage_; |
+ base::scoped_nsobject<NSImage> middleImage_; |
+ base::scoped_nsobject<NSImage> rightImage_; |
+ NSSize leftSize_; |
+ NSSize rightSize_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ThreePartImage); |
+}; |
+ |
+ThreePartImage* GetMaskImage() { |
+ static ThreePartImage* mask = |
+ new ThreePartImage(IDR_TAB_ALPHA_LEFT, 0, IDR_TAB_ALPHA_RIGHT); |
+ return mask; |
+} |
+ |
+ThreePartImage* GetStrokeImage(bool active) { |
+ static ThreePartImage* activeStroke = new ThreePartImage( |
+ IDR_TAB_ACTIVE_LEFT, IDR_TAB_ACTIVE_CENTER, IDR_TAB_ACTIVE_RIGHT); |
+ static ThreePartImage* inactiveStroke = new ThreePartImage( |
+ IDR_TAB_INACTIVE_LEFT, IDR_TAB_INACTIVE_CENTER, IDR_TAB_INACTIVE_RIGHT); |
+ |
+ return active ? activeStroke : inactiveStroke; |
+} |
+ |
+} // namespace |
+ |
@interface TabView(Private) |
- (void)resetLastGlowUpdateTime; |
- (NSTimeInterval)timeElapsedSinceLastGlowUpdate; |
- (void)adjustGlowValue; |
-- (CGImageRef)tabClippingMask; |
@end // TabView(Private) |
@@ -82,6 +178,8 @@ const CGFloat kRapidCloseDist = 2.5; |
[labelCell setFont:font]; |
[titleView_ setCell:labelCell]; |
titleViewCell_ = labelCell; |
+ |
+ [self setWantsLayer:YES]; // -drawFill: needs a layer. |
} |
return self; |
} |
@@ -157,34 +255,9 @@ const CGFloat kRapidCloseDist = 2.5; |
return defaultHitTestResult; |
NSPoint viewPoint = [self convertPoint:aPoint fromView:[self superview]]; |
- NSRect pointRect = NSMakeRect(viewPoint.x, viewPoint.y, 1, 1); |
- |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- NSImage* left = rb.GetNativeImageNamed(IDR_TAB_ALPHA_LEFT).ToNSImage(); |
- if (viewPoint.x < [left size].width) { |
- NSRect imageRect = NSMakeRect(0, 0, [left size].width, [left size].height); |
- if ([left hitTestRect:pointRect withImageDestinationRect:imageRect |
- context:nil hints:nil flipped:NO]) { |
- return self; |
- } |
- return nil; |
- } |
- |
- NSImage* right = rb.GetNativeImageNamed(IDR_TAB_ALPHA_RIGHT).ToNSImage(); |
- CGFloat rightX = NSWidth([self bounds]) - [right size].width; |
- if (viewPoint.x > rightX) { |
- NSRect imageRect = NSMakeRect( |
- rightX, 0, [right size].width, [right size].height); |
- if ([right hitTestRect:pointRect withImageDestinationRect:imageRect |
- context:nil hints:nil flipped:NO]) { |
- return self; |
- } |
- return nil; |
- } |
- |
- if (viewPoint.y < kFillHeight) |
- return self; |
- return nil; |
+ NSRect maskRect = [self bounds]; |
+ maskRect.size.height = kFillHeight; |
+ return GetMaskImage()->HitTest(viewPoint, maskRect) ? self : nil; |
} |
// Returns |YES| if this tab can be torn away into a new window. |
@@ -312,74 +385,48 @@ const CGFloat kRapidCloseDist = 2.5; |
return themeProvider->GetNSImageColorNamed(bitmapResources[active][selected]); |
} |
-// Draws the active tab background. |
-- (void)drawFillForActiveTab:(NSRect)dirtyRect { |
- NSColor* backgroundImageColor = [self backgroundColorForSelected:YES]; |
- [backgroundImageColor set]; |
- |
- // Themes can have partially transparent images. NSRectFill() is measurably |
- // faster though, so call it for the known-safe default theme. |
- ThemeService* themeProvider = |
- static_cast<ThemeService*>([[self window] themeProvider]); |
- if (themeProvider && themeProvider->UsingDefaultTheme()) |
- NSRectFill(dirtyRect); |
- else |
- NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver); |
-} |
- |
// Draws the tab background. |
- (void)drawFill:(NSRect)dirtyRect { |
gfx::ScopedNSGraphicsContextSaveGState scopedGState; |
- NSGraphicsContext* context = [NSGraphicsContext currentContext]; |
- CGContextRef cgContext = static_cast<CGContextRef>([context graphicsPort]); |
+ NSRect bounds = [self bounds]; |
- ThemeService* themeProvider = |
- static_cast<ThemeService*>([[self window] themeProvider]); |
- NSPoint position = [[self window] |
- themeImagePositionForAlignment: THEME_IMAGE_ALIGN_WITH_TAB_STRIP]; |
- [context cr_setPatternPhase:position forView:self]; |
+ NSRect clippingRect = bounds; |
+ clippingRect.size.height = kFillHeight; |
+ if (state_ != NSOnState) { |
+ // Background tabs should not paint over the tab strip separator, which is |
+ // two pixels high in both lodpi and hidpi. |
+ clippingRect.origin.y = 2 * [self cr_lineWidth]; |
+ clippingRect.size.height -= clippingRect.origin.y; |
+ } |
+ NSRectClip(clippingRect); |
- CGImageRef mask([self tabClippingMask]); |
- CGRect maskBounds = CGRectMake(0, 0, maskCacheWidth_, kMaskHeight); |
- CGContextClipToMask(cgContext, maskBounds, mask); |
+ NSPoint position = [[self window] |
+ themeImagePositionForAlignment:THEME_IMAGE_ALIGN_WITH_TAB_STRIP]; |
+ [[NSGraphicsContext currentContext] cr_setPatternPhase:position forView:self]; |
- // There is only 1 active tab at a time. |
- // It has a different fill color which draws over the separator line. |
- if (state_ == NSOnState) { |
- [self drawFillForActiveTab:dirtyRect]; |
- return; |
- } |
+ [[self backgroundColorForSelected:(state_ != NSOffState)] set]; |
+ NSRectFill(dirtyRect); |
- // Background tabs should not paint over the tab strip separator, which is |
- // two pixels high in both lodpi and hidpi. |
- if (dirtyRect.origin.y < 1) |
- dirtyRect.origin.y = 2 * [self cr_lineWidth]; |
+ if (state_ == NSOffState) |
+ [self drawGlow:dirtyRect]; |
- // There can be multiple selected tabs. |
- // They have the same fill color as the active tab, but do not draw over |
- // the separator. |
- if (state_ == NSMixedState) { |
- [self drawFillForActiveTab:dirtyRect]; |
- return; |
+ // If we filled outside the middle rect, we need to erase what we filled |
+ // outside the tab's shape. |
+ // This only works if we are drawing to our own backing layer. |
+ if (!NSContainsRect(GetMaskImage()->GetMiddleRect(bounds), dirtyRect)) { |
+ DCHECK([self layer]); |
+ GetMaskImage()->DrawInRect(bounds, NSCompositeDestinationIn, 1.0); |
} |
+} |
- // Draw the tab background. |
- NSColor* backgroundImageColor = [self backgroundColorForSelected:NO]; |
- [backgroundImageColor set]; |
- |
- // Themes can have partially transparent images. NSRectFill() is measurably |
- // faster though, so call it for the known-safe default theme. |
- bool usingDefaultTheme = themeProvider && themeProvider->UsingDefaultTheme(); |
- if (usingDefaultTheme) |
- NSRectFill(dirtyRect); |
- else |
- NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver); |
+// Draw the glow for hover and the overlay for alerts. |
+- (void)drawGlow:(NSRect)dirtyRect { |
+ NSGraphicsContext* context = [NSGraphicsContext currentContext]; |
+ CGContextRef cgContext = static_cast<CGContextRef>([context graphicsPort]); |
- // Draw the glow for hover and the overlay for alerts. |
CGFloat hoverAlpha = [self hoverAlpha]; |
CGFloat alertAlpha = [self alertAlpha]; |
if (hoverAlpha > 0 || alertAlpha > 0) { |
- gfx::ScopedNSGraphicsContextSaveGState contextSave; |
Robert Sesek
2015/02/12 17:16:47
Why remove this line?
Andre
2015/02/12 17:23:01
It seems unnecessary.
The documentation for CGCont
|
CGContextBeginTransparencyLayer(cgContext, 0); |
// The alert glow overlay is like the selected state but at most at most 80% |
@@ -388,12 +435,14 @@ const CGFloat kRapidCloseDist = 2.5; |
backgroundAlpha += (1 - backgroundAlpha) * 0.5 * hoverAlpha; |
CGContextSetAlpha(cgContext, backgroundAlpha); |
- [self drawFillForActiveTab:dirtyRect]; |
+ [[self backgroundColorForSelected:YES] set]; |
+ NSRectFill(dirtyRect); |
// ui::ThemeProvider::HasCustomImage is true only if the theme provides the |
// image. However, even if the theme doesn't provide a tab background, the |
// theme machinery will make one if given a frame image. See |
// BrowserThemePack::GenerateTabBackgroundImages for details. |
+ ui::ThemeProvider* themeProvider = [[self window] themeProvider]; |
BOOL hasCustomTheme = themeProvider && |
(themeProvider->HasCustomImage(IDR_THEME_TAB_BACKGROUND) || |
themeProvider->HasCustomImage(IDR_THEME_FRAME)); |
@@ -422,35 +471,12 @@ const CGFloat kRapidCloseDist = 2.5; |
// Draws the tab outline. |
- (void)drawStroke:(NSRect)dirtyRect { |
- BOOL focused = [[self window] isMainWindow]; |
- CGFloat alpha = focused ? 1.0 : tabs::kImageNoFocusAlpha; |
- |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- float height = |
- [rb.GetNativeImageNamed(IDR_TAB_ACTIVE_LEFT).ToNSImage() size].height; |
- if (state_ == NSOnState) { |
- NSDrawThreePartImage(NSMakeRect(0, 0, NSWidth([self bounds]), height), |
- rb.GetNativeImageNamed(IDR_TAB_ACTIVE_LEFT).ToNSImage(), |
- rb.GetNativeImageNamed(IDR_TAB_ACTIVE_CENTER).ToNSImage(), |
- rb.GetNativeImageNamed(IDR_TAB_ACTIVE_RIGHT).ToNSImage(), |
- /*vertical=*/NO, |
- NSCompositeSourceOver, |
- alpha, |
- /*flipped=*/NO); |
- } else { |
- NSDrawThreePartImage(NSMakeRect(0, 0, NSWidth([self bounds]), height), |
- rb.GetNativeImageNamed(IDR_TAB_INACTIVE_LEFT).ToNSImage(), |
- rb.GetNativeImageNamed(IDR_TAB_INACTIVE_CENTER).ToNSImage(), |
- rb.GetNativeImageNamed(IDR_TAB_INACTIVE_RIGHT).ToNSImage(), |
- /*vertical=*/NO, |
- NSCompositeSourceOver, |
- alpha, |
- /*flipped=*/NO); |
- } |
+ CGFloat alpha = [[self window] isMainWindow] ? 1.0 : tabs::kImageNoFocusAlpha; |
+ GetStrokeImage(state_ == NSOnState) |
+ ->DrawInRect([self bounds], NSCompositeSourceOver, alpha); |
} |
- (void)drawRect:(NSRect)dirtyRect { |
- // Close button and image are drawn by subviews. |
[self drawFill:dirtyRect]; |
[self drawStroke:dirtyRect]; |
@@ -493,6 +519,9 @@ const CGFloat kRapidCloseDist = 2.5; |
} |
- (void)setTitle:(NSString*)title { |
+ if ([title isEqualToString:[titleView_ stringValue]]) |
+ return; |
+ |
[titleView_ setStringValue:title]; |
base::string16 title16 = base::SysNSStringToUTF16(title); |
@@ -509,8 +538,11 @@ const CGFloat kRapidCloseDist = 2.5; |
} |
- (void)setTitleFrame:(NSRect)titleFrame { |
+ NSRect oldTitleFrame = [titleView_ frame]; |
+ if (NSEqualRects(titleFrame, oldTitleFrame)) |
+ return; |
[titleView_ setFrame:titleFrame]; |
- [self setNeedsDisplayInRect:titleFrame]; |
+ [self setNeedsDisplayInRect:NSUnionRect(titleFrame, oldTitleFrame)]; |
} |
- (NSColor*)titleColor { |
@@ -518,6 +550,8 @@ const CGFloat kRapidCloseDist = 2.5; |
} |
- (void)setTitleColor:(NSColor*)titleColor { |
+ if ([titleColor isEqual:[titleView_ textColor]]) |
+ return; |
[titleView_ setTextColor:titleColor]; |
[self setNeedsDisplayInRect:[titleView_ frame]]; |
} |
@@ -527,6 +561,8 @@ const CGFloat kRapidCloseDist = 2.5; |
} |
- (void)setTitleHidden:(BOOL)titleHidden { |
+ if (titleHidden == [titleView_ isHidden]) |
+ return; |
[titleView_ setHidden:titleHidden]; |
[self setNeedsDisplayInRect:[titleView_ frame]]; |
} |
@@ -739,57 +775,4 @@ const CGFloat kRapidCloseDist = 2.5; |
[self setNeedsDisplay:YES]; |
} |
-- (CGImageRef)tabClippingMask { |
- // NOTE: NSHeight([self bounds]) doesn't match the height of the bitmaps. |
- CGFloat scale = 1; |
- if ([[self window] respondsToSelector:@selector(backingScaleFactor)]) |
- scale = [[self window] backingScaleFactor]; |
- |
- NSRect bounds = [self bounds]; |
- CGFloat tabWidth = NSWidth(bounds); |
- if (tabWidth == maskCacheWidth_ && scale == maskCacheScale_) |
- return maskCache_.get(); |
- |
- maskCacheWidth_ = tabWidth; |
- maskCacheScale_ = scale; |
- |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- NSImage* leftMask = rb.GetNativeImageNamed(IDR_TAB_ALPHA_LEFT).ToNSImage(); |
- NSImage* rightMask = rb.GetNativeImageNamed(IDR_TAB_ALPHA_RIGHT).ToNSImage(); |
- |
- CGFloat leftWidth = leftMask.size.width; |
- CGFloat rightWidth = rightMask.size.width; |
- |
- // Image masks must be in the DeviceGray colorspace. Create a context and |
- // draw the mask into it. |
- base::ScopedCFTypeRef<CGColorSpaceRef> colorspace( |
- CGColorSpaceCreateDeviceGray()); |
- base::ScopedCFTypeRef<CGContextRef> maskContext( |
- CGBitmapContextCreate(NULL, tabWidth * scale, kMaskHeight * scale, |
- 8, tabWidth * scale, colorspace, 0)); |
- CGContextScaleCTM(maskContext, scale, scale); |
- NSGraphicsContext* maskGraphicsContext = |
- [NSGraphicsContext graphicsContextWithGraphicsPort:maskContext |
- flipped:NO]; |
- |
- gfx::ScopedNSGraphicsContextSaveGState scopedGState; |
- [NSGraphicsContext setCurrentContext:maskGraphicsContext]; |
- |
- // Draw mask image. |
- [[NSColor blackColor] setFill]; |
- CGContextFillRect(maskContext, CGRectMake(0, 0, tabWidth, kMaskHeight)); |
- |
- NSDrawThreePartImage(NSMakeRect(0, 0, tabWidth, kMaskHeight), |
- leftMask, nil, rightMask, /*vertical=*/NO, NSCompositeSourceOver, 1.0, |
- /*flipped=*/NO); |
- |
- CGFloat middleWidth = tabWidth - leftWidth - rightWidth; |
- NSRect middleRect = NSMakeRect(leftWidth, 0, middleWidth, kFillHeight); |
- [[NSColor whiteColor] setFill]; |
- NSRectFill(middleRect); |
- |
- maskCache_.reset(CGBitmapContextCreateImage(maskContext)); |
- return maskCache_; |
-} |
- |
@end // @implementation TabView(Private) |