Chromium Code Reviews| 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) |