Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3904)

Unified Diff: chrome/browser/ui/cocoa/tabs/tab_view.mm

Issue 918533005: Mac: Optimize TabView drawing. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/ui/cocoa/tabs/tab_view.h ('k') | chrome/browser/ui/cocoa/tabs/tab_view_unittest.mm » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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)
« no previous file with comments | « chrome/browser/ui/cocoa/tabs/tab_view.h ('k') | chrome/browser/ui/cocoa/tabs/tab_view_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698