Index: chrome/browser/ui/views/tabs/tab_strip.cc |
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc |
index 564405dfbb3d268910180d3b9847176667414c3b..34f422f8b1236189025e3e56a860ac2d22e5997e 100644 |
--- a/chrome/browser/ui/views/tabs/tab_strip.cc |
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc |
@@ -19,6 +19,7 @@ |
#include "chrome/browser/ui/layout_constants.h" |
#include "chrome/browser/ui/tabs/tab_strip_model.h" |
#include "chrome/browser/ui/view_ids.h" |
+#include "chrome/browser/ui/views/frame/browser_view.h" |
#include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h" |
#include "chrome/browser/ui/views/tabs/tab.h" |
#include "chrome/browser/ui/views/tabs/tab_drag_controller.h" |
@@ -31,11 +32,16 @@ |
#include "content/public/browser/user_metrics.h" |
#include "content/public/common/content_switches.h" |
#include "grit/theme_resources.h" |
+#include "third_party/skia/include/core/SkColorFilter.h" |
+#include "third_party/skia/include/effects/SkBlurMaskFilter.h" |
+#include "third_party/skia/include/effects/SkLayerDrawLooper.h" |
+#include "third_party/skia/include/pathops/SkPathOps.h" |
#include "ui/accessibility/ax_view_state.h" |
#include "ui/base/default_theme_provider.h" |
#include "ui/base/dragdrop/drag_drop_types.h" |
#include "ui/base/l10n/l10n_util.h" |
#include "ui/base/models/list_selection_model.h" |
+#include "ui/base/resource/material_design/material_design_controller.h" |
#include "ui/base/resource/resource_bundle.h" |
#include "ui/compositor/compositing_recorder.h" |
#include "ui/compositor/paint_recorder.h" |
@@ -126,6 +132,28 @@ int GetNewTabButtonWidth() { |
GetLayoutConstant(TABSTRIP_NEW_TAB_BUTTON_OVERLAP); |
} |
+skia::RefPtr<SkDrawLooper> CreateShadowDrawLooper(SkAlpha alpha) { |
+ SkLayerDrawLooper::Builder looper_builder; |
+ looper_builder.addLayer(); |
+ |
+ SkLayerDrawLooper::LayerInfo layer_info; |
+ layer_info.fPaintBits |= SkLayerDrawLooper::kMaskFilter_Bit; |
+ layer_info.fPaintBits |= SkLayerDrawLooper::kColorFilter_Bit; |
+ layer_info.fColorMode = SkXfermode::kDst_Mode; |
+ layer_info.fOffset.set(0, 1); |
+ skia::RefPtr<SkMaskFilter> blur_mask = |
+ skia::AdoptRef(SkBlurMaskFilter::Create( |
+ kNormal_SkBlurStyle, 0.5, SkBlurMaskFilter::kHighQuality_BlurFlag)); |
+ skia::RefPtr<SkColorFilter> color_filter = |
+ skia::AdoptRef(SkColorFilter::CreateModeFilter( |
+ SkColorSetA(SK_ColorBLACK, alpha), SkXfermode::kSrcIn_Mode)); |
+ SkPaint* layer_paint = looper_builder.addLayer(layer_info); |
+ layer_paint->setMaskFilter(blur_mask.get()); |
+ layer_paint->setColorFilter(color_filter.get()); |
+ |
+ return skia::AdoptRef(looper_builder.detachLooper()); |
+} |
+ |
// Animation delegate used for any automatic tab movement. Hides the tab if it |
// is not fully visible within the tabstrip area, to prevent overflow clipping. |
class TabAnimationDelegate : public gfx::AnimationDelegate { |
@@ -267,11 +295,22 @@ class NewTabButton : public views::ImageButton, |
// views::MaskedTargeterDelegate: |
bool GetHitTestMask(gfx::Path* mask) const override; |
+ // Computes a path corresponding to the button's outer border for a given |
+ // |scale| and stores it in |path|. |button_y| is used as the y-coordinate |
+ // for the top of the button. If |extend_to_top| is true, the path is |
+ // extended vertically to y = 0. The caller uses this for Fitts' Law purposes |
+ // in maximized/fullscreen mode. |
+ void GetBorderPath(float button_y, |
+ float scale, |
+ bool extend_to_top, |
+ SkPath* path) const; |
+ |
// Paints the fill region of the button into |canvas|, according to the |
- // supplied values from GetImage(). |
+ // supplied values from GetImage() and the given |fill| path. |
void PaintFill(bool pressed, |
double hover_value, |
float scale, |
+ const SkPath& fill, |
gfx::Canvas* canvas) const; |
// Tab strip that contains this button. |
@@ -339,32 +378,77 @@ void NewTabButton::OnPaint(gfx::Canvas* canvas) { |
hover_value = hover_animation_->GetCurrentValue(); |
const float scale = canvas->image_scale(); |
- // Fill. |
- gfx::ImageSkia* mask = |
- GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK); |
- // The canvas and mask have to use the same scale factor. |
- const float fill_canvas_scale = mask->HasRepresentation(scale) ? |
- scale : ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P); |
- gfx::Canvas fill_canvas(GetNewTabButtonSize(), fill_canvas_scale, false); |
- PaintFill(pressed, hover_value, fill_canvas_scale, &fill_canvas); |
- gfx::ImageSkia image(fill_canvas.ExtractImageRep()); |
- canvas->DrawImageInt( |
- gfx::ImageSkiaOperations::CreateMaskedImage(image, *mask), 0, 0); |
- |
- // Stroke. Draw the button border with a slight alpha. |
- static const SkAlpha kGlassFrameOverlayAlpha = 178; |
- static const SkAlpha kOpaqueFrameOverlayAlpha = 230; |
- const SkAlpha alpha = GetWidget()->ShouldWindowContentsBeTransparent() ? |
- kGlassFrameOverlayAlpha : kOpaqueFrameOverlayAlpha; |
- const int overlay_id = pressed ? IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON; |
- canvas->DrawImageInt(*GetThemeProvider()->GetImageSkiaNamed(overlay_id), 0, 0, |
- alpha); |
+ SkPath fill; |
+ if (ui::MaterialDesignController::IsModeMaterial()) { |
+ // Fill. |
+ fill.moveTo(9.75 * scale, 16 * scale); |
+ fill.rCubicTo(-0.75 * scale, 0, -1.625 * scale, -0.5 * scale, -2 * scale, |
+ -1.5 * scale); |
+ fill.rLineTo(-5.75 * scale, -12.5 * scale); |
+ fill.rCubicTo(0, -0.5 * scale, 0.25 * scale, -scale, scale, -scale); |
+ fill.rLineTo(23.25 * scale, 0); |
+ fill.rCubicTo(0.75 * scale, 0, 1.625 * scale, 0.5 * scale, 2 * scale, |
+ 1.5 * scale); |
+ fill.rLineTo(5.75 * scale, 12.5 * scale); |
+ fill.rCubicTo(0, 0.5 * scale, -0.25 * scale, scale, -scale, scale); |
+ fill.close(); |
+ PaintFill(pressed, hover_value, scale, fill, canvas); |
+ |
+ // Stroke. |
+ gfx::ScopedCanvas scoped_canvas(canvas); |
+ canvas->UndoDeviceScaleFactor(); |
+ SkPath stroke; |
+ GetBorderPath(0, scale, false, &stroke); |
+ // We want to draw a drop shadow either inside or outside the stroke, |
+ // depending on whether we're pressed; so, either clip out what's outside |
+ // the stroke, or clip out the fill inside it. |
+ if (pressed) |
+ canvas->ClipPath(stroke, true); |
+ Op(stroke, fill, kDifference_SkPathOp, &stroke); |
+ if (!pressed) |
+ canvas->sk_canvas()->clipPath(fill, SkRegion::kDifference_Op, true); |
+ // Now draw the stroke and shadow; the stroke will always be visible, while |
+ // the shadow will be affected by the clip we set above. |
+ SkPaint paint; |
+ paint.setAntiAlias(true); |
+ skia::RefPtr<SkDrawLooper> stroke_looper = CreateShadowDrawLooper(0x8C); |
+ paint.setLooper(stroke_looper.get()); |
+ paint.setColor(SkColorSetA(SK_ColorBLACK, pressed ? 0x38 : 0x27)); |
+ canvas->DrawPath(stroke, paint); |
+ } else { |
+ // Fill. |
+ gfx::ImageSkia* mask = |
+ GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK); |
+ // The canvas and mask have to use the same scale factor. |
+ const float fill_canvas_scale = mask->HasRepresentation(scale) ? |
+ scale : ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P); |
+ gfx::Canvas fill_canvas(GetNewTabButtonSize(), fill_canvas_scale, false); |
+ PaintFill(pressed, hover_value, fill_canvas_scale, fill, &fill_canvas); |
+ gfx::ImageSkia image(fill_canvas.ExtractImageRep()); |
+ canvas->DrawImageInt( |
+ gfx::ImageSkiaOperations::CreateMaskedImage(image, *mask), 0, 0); |
+ |
+ // Stroke. Draw the button border with a slight alpha. |
+ static const SkAlpha kGlassFrameOverlayAlpha = 178; |
+ static const SkAlpha kOpaqueFrameOverlayAlpha = 230; |
+ const SkAlpha alpha = GetWidget()->ShouldWindowContentsBeTransparent() ? |
+ kGlassFrameOverlayAlpha : kOpaqueFrameOverlayAlpha; |
+ const int overlay_id = pressed ? IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON; |
+ canvas->DrawImageInt(*GetThemeProvider()->GetImageSkiaNamed(overlay_id), 0, |
+ 0, alpha); |
+ } |
} |
bool NewTabButton::GetHitTestMask(gfx::Path* mask) const { |
DCHECK(mask); |
- if (tab_strip_->SizeTabButtonToTopOfTabStrip()) { |
+ if (ui::MaterialDesignController::IsModeMaterial()) { |
+ SkPath border; |
+ const float scale = GetWidget()->GetCompositor()->device_scale_factor(); |
+ GetBorderPath(TabStrip::kNewTabButtonVerticalOffset * scale, scale, |
+ tab_strip_->SizeTabButtonToTopOfTabStrip(), &border); |
+ mask->addPath(border, SkMatrix::MakeScale(1 / scale)); |
+ } else if (tab_strip_->SizeTabButtonToTopOfTabStrip()) { |
// When the button is sized to the top of the tab strip, we want the hit |
// test mask to be defined as the complete (rectangular) bounds of the |
// button. |
@@ -393,56 +477,134 @@ bool NewTabButton::GetHitTestMask(gfx::Path* mask) const { |
return true; |
} |
+void NewTabButton::GetBorderPath(float button_y, |
+ float scale, |
+ bool extend_to_top, |
+ SkPath* path) const { |
+ path->moveTo(9.75 * scale - 1, button_y + 16 * scale + 1); |
+ path->rCubicTo(-0.75 * scale, 0, -1.625 * scale, -0.5 * scale, -2 * scale, |
+ -1.5 * scale); |
+ path->rLineTo(-5.75 * scale, -12.5 * scale); |
+ if (extend_to_top) { |
+ // Create the vertical extension by extending the side diagonals at the |
+ // upper left and lower right corners until they reach the top and bottom of |
+ // the border, respectively (in other words, "un-round-off" those corners |
+ // and turn them into sharp points). Then extend upward from the corner |
+ // points to the top of the bounds. |
+ const float dy = scale + 2; |
+ const float dx = 11.5 / 25 * dy; |
+ path->rLineTo(-dx, -dy); |
+ path->rLineTo(0, -button_y - scale + 1); |
+ path->lineTo(34 * scale + 1 + dx, 0); |
+ path->rLineTo(0, button_y + 16 * scale + 1); |
+ } else { |
+ path->rCubicTo(-0.5 * scale, -1.125 * scale, 0.5 * scale, -scale - 2, scale, |
+ -scale - 2); |
+ path->rLineTo(23.25 * scale + 2, 0); |
+ path->rCubicTo(0.75 * scale, 0, 1.625 * scale, 0.5 * scale, 2 * scale, |
+ 1.5 * scale); |
+ path->rLineTo(5.75 * scale, 12.5 * scale); |
+ path->rCubicTo(0.5 * scale, 1.125 * scale, -0.5 * scale, scale + 2, -scale, |
+ scale + 2); |
+ } |
+ path->close(); |
+} |
+ |
void NewTabButton::PaintFill(bool pressed, |
double hover_value, |
float scale, |
+ const SkPath& fill, |
gfx::Canvas* canvas) const { |
bool custom_image; |
const int bg_id = tab_strip_->GetBackgroundResourceId(&custom_image); |
+ gfx::ScopedCanvas scoped_canvas(canvas); |
+ |
+ const bool md = ui::MaterialDesignController::IsModeMaterial(); |
+ if (md) { |
+ canvas->UndoDeviceScaleFactor(); |
+ |
+ // For unpressed buttons, draw the fill and its shadow. |
+ if (!pressed) { |
+ gfx::ScopedCanvas scoped_canvas(canvas); |
+ |
+ // For custom themes, clip out the fill path itself so only the shadow |
+ // around it is drawn. We'll draw the fill image below. |
+ if (custom_image) |
+ canvas->sk_canvas()->clipPath(fill, SkRegion::kDifference_Op, true); |
+ |
+ SkPaint paint; |
+ paint.setAntiAlias(true); |
+ skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(0x26); |
+ paint.setLooper(looper.get()); |
+ paint.setColor(Tab::kInactiveTabColor); |
+ canvas->DrawPath(fill, paint); |
+ } |
+ |
+ // Clip to just the fill region for any theme drawing and hover/pressed |
+ // state overlay drawing below. In non-MD mode, this is done on the caller |
+ // side using image masking operations. |
+ canvas->ClipPath(fill, true); |
+ canvas->sk_canvas()->scale(scale, scale); |
+ } |
+ |
// Draw the fill background image. |
const gfx::Size size(GetNewTabButtonSize()); |
- const ui::ThemeProvider* theme_provider = GetThemeProvider(); |
- gfx::ImageSkia* background = theme_provider->GetImageSkiaNamed(bg_id); |
- // For custom tab backgrounds the background starts at the top of the tab |
- // strip. Otherwise the background starts at the top of the frame. |
- const int offset_y = theme_provider->HasCustomImage(bg_id) ? |
- -GetLayoutConstant(TAB_TOP_EXCLUSION_HEIGHT) : background_offset_.y(); |
- |
- // The new tab background is mirrored in RTL mode, but the theme background |
- // should never be mirrored. Mirror it here to compensate. |
- float x_scale = 1.0f; |
- int x = GetMirroredX() + background_offset_.x(); |
- if (base::i18n::IsRTL()) { |
- x_scale = -1.0f; |
- // Offset by |width| such that the same region is painted as if there was |
- // no flip. |
- x += size.width(); |
- } |
- canvas->TileImageInt(*background, x, |
- TabStrip::kNewTabButtonVerticalOffset + offset_y, |
- x_scale, 1.0f, 0, 0, size.width(), size.height()); |
- |
- // Adjust the alpha of the fill to match that of inactive tabs (except for |
- // pressed buttons, which get a different value). |
- static const SkAlpha kPressedAlpha = 145; |
- const SkAlpha alpha = |
- pressed ? kPressedAlpha : tab_strip_->GetInactiveAlpha(true); |
- if (alpha != 255) { |
- SkPaint paint; |
- paint.setAlpha(alpha); |
- paint.setXfermodeMode(SkXfermode::kDstIn_Mode); |
- paint.setStyle(SkPaint::kFill_Style); |
- canvas->DrawRect(gfx::Rect(size), paint); |
+ if (custom_image || !md) { |
+ const ui::ThemeProvider* theme_provider = GetThemeProvider(); |
+ gfx::ImageSkia* background = theme_provider->GetImageSkiaNamed(bg_id); |
+ // For custom tab backgrounds the background starts at the top of the tab |
+ // strip. Otherwise the background starts at the top of the frame. |
+ const int offset_y = theme_provider->HasCustomImage(bg_id) ? |
+ -GetLayoutConstant(TAB_TOP_EXCLUSION_HEIGHT) : background_offset_.y(); |
+ |
+ // The new tab background is mirrored in RTL mode, but the theme background |
+ // should never be mirrored. Mirror it here to compensate. |
+ float x_scale = 1.0f; |
+ int x = GetMirroredX() + background_offset_.x(); |
+ if (base::i18n::IsRTL()) { |
+ x_scale = -1.0f; |
+ // Offset by |width| such that the same region is painted as if there was |
+ // no flip. |
+ x += size.width(); |
+ } |
+ canvas->TileImageInt(*background, x, |
+ TabStrip::kNewTabButtonVerticalOffset + offset_y, |
+ x_scale, 1.0f, 0, 0, size.width(), size.height()); |
+ |
+ // For non-MD, adjust the alpha of the fill to match that of inactive tabs |
+ // (except for pressed buttons, which get a different value). For MD, we do |
+ // this with an opacity recorder in TabStrip::PaintChildren() so the fill |
+ // and stroke are both affected, to better match how tabs are handled, but |
+ // in non-MD, the button stroke is already lighter than the tab stroke, and |
+ // using the opacity recorder washes it out too much. |
+ static const SkAlpha kPressedAlpha = 145; |
+ const SkAlpha alpha = |
+ pressed ? kPressedAlpha : tab_strip_->GetInactiveAlpha(true); |
+ if (alpha != 255 && !md) { |
+ SkPaint paint; |
+ paint.setAlpha(alpha); |
+ paint.setXfermodeMode(SkXfermode::kDstIn_Mode); |
+ paint.setStyle(SkPaint::kFill_Style); |
+ canvas->DrawRect(gfx::Rect(size), paint); |
+ } |
} |
// White highlight on hover. |
if (hover_value) { |
const int alpha = |
- gfx::Tween::LinearIntValueBetween(hover_value, 0x00, 0x40); |
+ gfx::Tween::LinearIntValueBetween(hover_value, 0x00, md ? 0x4D : 0x40); |
canvas->FillRect(GetLocalBounds(), |
SkColorSetA(SK_ColorWHITE, static_cast<SkAlpha>(alpha))); |
} |
+ |
+ // For MD, most states' opacities are adjusted using an opacity recorder in |
+ // TabStrip::PaintChildren(), but the pressed state is excluded there and |
+ // instead rendered using a dark overlay here. This produces a different |
+ // effect than for non-MD, and avoiding the use of the opacity recorder keeps |
+ // the stroke more visible in this state. |
+ if (md && pressed) |
+ canvas->FillRect(GetLocalBounds(), SkColorSetA(SK_ColorBLACK, 0x14)); |
} |
/////////////////////////////////////////////////////////////////////////////// |
@@ -1270,13 +1432,11 @@ void TabStrip::PaintChildren(const ui::PaintContext& context) { |
Tabs selected_tabs; |
{ |
- const chrome::HostDesktopType host_desktop_type = |
- chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView()); |
- const uint8_t inactive_tab_alpha = |
- (host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH) ? |
- GetInactiveAlpha(false) : 255; |
+ // TODO(pkasting): This results in greyscale AA for the tab titles. Work |
+ // with Mike Reed to fix, or else convert to passing the tabs a memory |
+ // canvas to paint on and then manually compositing. |
ui::CompositingRecorder opacity_recorder(context, size(), |
- inactive_tab_alpha); |
+ GetInactiveAlpha(false)); |
PaintClosingTabs(tab_count(), context); |
@@ -1319,28 +1479,6 @@ void TabStrip::PaintChildren(const ui::PaintContext& context) { |
} |
} |
- if (GetWidget()->ShouldWindowContentsBeTransparent()) { |
- ui::PaintRecorder recorder(context, size()); |
- // Make sure non-active tabs are somewhat transparent. |
- SkPaint paint; |
- // If there are multiple tabs selected, fade non-selected tabs more to make |
- // the selected tabs more noticable. |
- uint8_t alpha = GetInactiveAlpha(false); |
- paint.setColor(SkColorSetA(SK_ColorWHITE, alpha)); |
- paint.setXfermodeMode(SkXfermode::kDstIn_Mode); |
- paint.setStyle(SkPaint::kFill_Style); |
- |
- gfx::Rect bounds(GetLocalBounds()); |
- // The tab graphics include some shadows at the top, plus a 1 pixel top |
- // stroke. Exclude this region when trying to make tabs transparent as it's |
- // transparent enough already, and drawing in this region can overlap the |
- // avatar button, leading to visual artifacts. Also exclude the toolbar |
- // overlap region at the bottom. |
- gfx::Insets tab_insets(GetLayoutInsets(TAB)); |
- bounds.Inset(0, tab_insets.top(), 0, tab_insets.bottom()); |
- recorder.canvas()->DrawRect(bounds, paint); |
- } |
- |
// Now selected but not active. We don't want these dimmed if using native |
// frame, so they're painted after initial pass. |
for (size_t i = 0; i < selected_tabs.size(); ++i) |
@@ -1351,7 +1489,16 @@ void TabStrip::PaintChildren(const ui::PaintContext& context) { |
active_tab->Paint(context); |
// Paint the New Tab button. |
- newtab_button_->Paint(context); |
+ const bool md = ui::MaterialDesignController::IsModeMaterial(); |
+ if (md && (newtab_button_->state() != views::CustomButton::STATE_PRESSED)) { |
+ // Match the inactive tab opacity for non-pressed states. See comments in |
+ // NewTabButton::PaintFill() for why we don't do this for the pressed state. |
+ ui::CompositingRecorder opacity_recorder(context, size(), |
+ GetInactiveAlpha(true)); |
+ newtab_button_->Paint(context); |
+ } else { |
+ newtab_button_->Paint(context); |
+ } |
// And the dragged tabs. |
for (size_t i = 0; i < tabs_dragging.size(); ++i) |
@@ -1360,6 +1507,18 @@ void TabStrip::PaintChildren(const ui::PaintContext& context) { |
// If the active tab is being dragged, it goes last. |
if (active_tab && is_dragging) |
active_tab->Paint(context); |
+ |
+ if (md) { |
+ ui::PaintRecorder recorder(context, size()); |
+ gfx::Canvas* canvas = recorder.canvas(); |
+ if (active_tab) { |
+ canvas->sk_canvas()->clipRect( |
+ gfx::RectToSkRect(active_tab->GetMirroredBounds()), |
+ SkRegion::kDifference_Op); |
+ } |
+ BrowserView::Paint1pxHorizontalLine( |
+ canvas, SkColorSetA(SK_ColorBLACK, 0x40), GetLocalBounds(), true); |
+ } |
} |
const char* TabStrip::GetClassName() const { |