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 cad76744d3ea0f3e22403f3653185a39d494e10a..bd5b053e29234616eecb871ae3edf81d6198cd21 100644 |
| --- a/chrome/browser/ui/cocoa/tabs/tab_view.mm |
| +++ b/chrome/browser/ui/cocoa/tabs/tab_view.mm |
| @@ -19,12 +19,12 @@ |
| #import "third_party/google_toolbox_for_mac/src/AppKit/GTMFadeTruncatingTextFieldCell.h" |
| #import "ui/base/cocoa/nsgraphics_context_additions.h" |
| #import "ui/base/cocoa/nsview_additions.h" |
| +#include "ui/base/cocoa/three_part_image.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #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 +44,30 @@ const NSTimeInterval kGlowUpdateInterval = 0.025; |
| // has moved less than the threshold, we want to close the tab. |
| const CGFloat kRapidCloseDist = 2.5; |
| +namespace { |
| + |
| +ui::ThreePartImage* GetMaskImage() { |
| + static ui::ThreePartImage* mask = |
| + new ui::ThreePartImage(IDR_TAB_ALPHA_LEFT, 0, IDR_TAB_ALPHA_RIGHT); |
|
Nico
2015/02/13 04:17:27
nit: We have CR_DEFINE_STATIC_LOCAL for this (also
|
| + return mask; |
| +} |
| + |
| +ui::ThreePartImage* GetStrokeImage(bool active) { |
| + static ui::ThreePartImage* activeStroke = new ui::ThreePartImage( |
| + IDR_TAB_ACTIVE_LEFT, IDR_TAB_ACTIVE_CENTER, IDR_TAB_ACTIVE_RIGHT); |
| + static ui::ThreePartImage* inactiveStroke = new ui::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 +100,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 +177,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 +307,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]; |
| + |
| + 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); |
| - 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]; |
| + themeImagePositionForAlignment:THEME_IMAGE_ALIGN_WITH_TAB_STRIP]; |
| + [[NSGraphicsContext currentContext] cr_setPatternPhase:position forView:self]; |
| - CGImageRef mask([self tabClippingMask]); |
| - CGRect maskBounds = CGRectMake(0, 0, maskCacheWidth_, kMaskHeight); |
| - CGContextClipToMask(cgContext, maskBounds, mask); |
| + [[self backgroundColorForSelected:(state_ != NSOffState)] set]; |
| + NSRectFill(dirtyRect); |
| - // 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; |
| - } |
| - |
| - // 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; |
| CGContextBeginTransparencyLayer(cgContext, 0); |
| // The alert glow overlay is like the selected state but at most at most 80% |
| @@ -388,12 +357,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 +393,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]; |
| @@ -749,57 +697,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) |