Commit 65181316 authored by Peter Kasting's avatar Peter Kasting Committed by Commit Bot

Cleanup pre-refresh functionality in tab.cc, part 1.

This removes support for pre-refresh modes.  It does not change the design of
path computations/drawing or other deeper refactors.

Also does some other minor cleanup, and fixes one clear bug with high DPI in
PaintChildren().

Bug: 873855
Change-Id: I3496ea60ed670d5f1c30abf33b989162c558fa16
Reviewed-on: https://chromium-review.googlesource.com/1231898Reviewed-by: default avatarAllen Bauer <kylixrd@chromium.org>
Commit-Queue: Peter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/master@{#592527}
parent c1e2f785
......@@ -322,64 +322,12 @@ IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewAshTest,
manager->ShowWindowForUser(window, account_id2);
EXPECT_TRUE(MultiUserWindowManager::ShouldShowAvatar(window));
if (GetParam() != switches::kTopChromeMDMaterialRefresh) {
// An icon should show on the top left corner of the teleported browser
// window.
EXPECT_TRUE(frame_view->profile_indicator_icon());
}
// Teleport the window back to owner desktop.
manager->ShowWindowForUser(window, account_id1);
EXPECT_FALSE(MultiUserWindowManager::ShouldShowAvatar(window));
EXPECT_FALSE(frame_view->profile_indicator_icon());
}
// Hit Test for Avatar Menu Button on ChromeOS.
IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewAshTest,
AvatarMenuButtonHitTestOnChromeOS) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
BrowserNonClientFrameViewAsh* frame_view = GetFrameViewAsh(browser_view);
gfx::Point avatar_center(profiles::kAvatarIconWidth / 2,
profiles::kAvatarIconHeight / 2);
// The increased header height in the touch-optimized UI affects the expected
// result.
int expected_value =
GetParam() == switches::kTopChromeMDMaterialTouchOptimized ? HTCAPTION
: HTCLIENT;
EXPECT_EQ(expected_value, frame_view->NonClientHitTest(avatar_center));
EXPECT_FALSE(frame_view->profile_indicator_icon());
const AccountId current_user =
multi_user_util::GetAccountIdFromProfile(browser()->profile());
TestMultiUserWindowManager* manager =
new TestMultiUserWindowManager(browser(), current_user);
// Teleport the window to another desktop.
const AccountId account_id2(AccountId::FromUserEmail("user2"));
manager->ShowWindowForUser(browser()->window()->GetNativeWindow(),
account_id2);
if (GetParam() != switches::kTopChromeMDMaterialRefresh) {
// Clicking on the avatar icon should have same behaviour like clicking on
// the caption area, i.e., allow the user to drag the browser window around.
EXPECT_EQ(HTCAPTION, frame_view->NonClientHitTest(avatar_center));
EXPECT_TRUE(frame_view->profile_indicator_icon());
}
}
// Tests that for an incognito browser, there is an avatar icon view, unless in
// touch-optimized mode.
IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewAshTest, IncognitoAvatar) {
Browser* incognito_browser = CreateIncognitoBrowser();
BrowserView* browser_view =
BrowserView::GetBrowserViewForBrowser(incognito_browser);
BrowserNonClientFrameViewAsh* frame_view = GetFrameViewAsh(browser_view);
const bool should_have_avatar = GetParam() == switches::kTopChromeMDMaterial;
const bool has_avatar = !!frame_view->profile_indicator_icon();
EXPECT_EQ(should_have_avatar, has_avatar);
}
IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewAshTest,
IncognitoMarkedAsAssistantBlocked) {
Browser* incognito_browser = CreateIncognitoBrowser();
......@@ -1448,12 +1396,11 @@ IN_PROC_BROWSER_TEST_P(NonHomeLauncherBrowserNonClientFrameViewAshTest,
EXPECT_EQ(expected_height, frame_view->frame_header_->GetHeaderHeight());
}
#define INSTANTIATE_TEST_CASE(name) \
INSTANTIATE_TEST_CASE_P( \
, name, \
::testing::Values(switches::kTopChromeMDMaterial, \
switches::kTopChromeMDMaterialTouchOptimized, \
switches::kTopChromeMDMaterialRefresh), \
#define INSTANTIATE_TEST_CASE(name) \
INSTANTIATE_TEST_CASE_P( \
, name, \
::testing::Values(switches::kTopChromeMDMaterialRefresh, \
switches::kTopChromeMDMaterialRefreshTouchOptimized), \
&TopChromeMdParamToString)
INSTANTIATE_TEST_CASE(BrowserNonClientFrameViewAshTest);
......
......@@ -78,12 +78,6 @@ using MD = ui::MaterialDesignController;
namespace {
constexpr int kExtraLeftPaddingToBalanceCloseButtonPadding = 2;
constexpr int kRefreshExtraLeftPaddingToBalanceCloseButtonPadding = 4;
constexpr int kRefreshAlertIndicatorCloseButtonPadding = 6;
constexpr int kTouchableRefreshAlertIndicatorCloseButtonPadding = 8;
// When a non-pinned tab becomes a pinned tab the width of the tab animates. If
// the width of a pinned tab is at least kPinnedTabExtraWidthToRenderAsNormal
// larger than the desired pinned tab width then the tab is rendered as a normal
......@@ -94,14 +88,6 @@ constexpr int kPinnedTabExtraWidthToRenderAsNormal = 30;
// Opacity of the active tab background painted over inactive selected tabs.
constexpr float kSelectedTabOpacity = 0.75f;
// Inactive selected tabs have their throb value scaled by this.
constexpr float kSelectedTabThrobScale = 0.95f - kSelectedTabOpacity;
// Height of the separator painted on the left edge of the tab for the material
// refresh mode.
constexpr int kTabSeparatorHeight = 20;
constexpr int kTabSeparatorTouchHeight = 24;
// Helper functions ------------------------------------------------------------
// Returns the coordinate for an object of size |item_size| centered in a region
......@@ -119,27 +105,6 @@ int Center(int size, int item_size) {
return extra_space / 2;
}
// For non-material-refresh mode, returns the width of the tab endcap in DIP.
// More precisely, this is the width of the curve making up either the outer or
// inner edge of the stroke.
//
// These two curves are horizontally offset by 1 px (regardless of scale); the
// total width of the endcap from tab outer edge to the inside end of the stroke
// inner edge is (GetTabEndcapWidthForLayout() * scale) + 1.
int GetTabEndcapWidthForLayout() {
const int mode = MD::GetMode();
DCHECK_LE(mode, 2);
constexpr int kEndcapWidth[] = {16, 18, 24};
return kEndcapWidth[mode];
}
// For painting the endcaps, the top corners are actually shifted outwards 0.5
// DIP from the grid.
float GetTabEndcapWidthForPainting() {
return GetTabEndcapWidthForLayout() - 0.5f;
}
void DrawHighlight(gfx::Canvas* canvas,
const SkPoint& p,
SkScalar radius,
......@@ -217,12 +182,15 @@ float GetTopCornerRadiusForWidth(int width) {
return base::ClampToRange<float>(radius, 0, ideal_radius);
}
// The refresh-specific implementation of GetInteriorPath() (see below).
gfx::Path GetRefreshInteriorPath(float scale,
float stroke_thickness,
float bottom_offset,
const gfx::Rect& bounds,
const gfx::InsetsF& insets) {
// Returns a path corresponding to the tab's content region inside the outer
// stroke. The sides of the path will be inset by |insets|; this is useful when
// trying to clip favicons to match the overall tab shape but be inset from the
// edge.
gfx::Path GetInteriorPath(float scale,
float stroke_thickness,
float bottom_offset,
const gfx::Rect& bounds,
const gfx::InsetsF& insets = gfx::InsetsF()) {
const gfx::RectF aligned_bounds =
ScaleAndAlignBounds(bounds, scale, stroke_thickness);
......@@ -304,61 +272,18 @@ gfx::Path GetRefreshInteriorPath(float scale,
return OffsetAndIntersectPaths(left_path, right_path, insets.Scale(scale));
}
// Returns a path corresponding to the tab's content region inside the outer
// stroke. The sides of the path will be inset by |insets|; this is useful when
// trying to clip favicons to match the overall tab shape but be inset from the
// edge.
gfx::Path GetInteriorPath(float scale,
float stroke_thickness,
float bottom_offset,
const gfx::Rect& bounds,
const gfx::InsetsF& insets = gfx::InsetsF()) {
if (MD::IsRefreshUi())
return GetRefreshInteriorPath(scale, stroke_thickness, bottom_offset,
bounds, insets);
const float right = bounds.width() * scale;
// The bottom of the tab needs to be pixel-aligned or else when we call
// ClipPath with anti-aliasing enabled it can cause artifacts.
const float bottom = std::ceil(bounds.height() * scale);
const float endcap_width = GetTabEndcapWidthForPainting();
// Construct the interior path by intersecting paths representing the left
// and right halves of the tab. Compared to computing the full path at once,
// this makes it easier to avoid overdraw in the top center near minimum
// width, and to implement cases where !insets.IsEmpty().
gfx::Path right_path;
right_path.moveTo(right - 1, bottom);
right_path.rCubicTo(-0.75 * scale, 0, -1.625 * scale, -0.5 * scale,
-2 * scale, -1.5 * scale);
right_path.lineTo(right - 1 - (endcap_width - 2) * scale, 2.5 * scale);
right_path.rCubicTo(-0.375 * scale, -1 * scale, -1.25 * scale, -1.5 * scale,
-2 * scale, -1.5 * scale);
right_path.lineTo(0, scale);
right_path.lineTo(0, bottom);
right_path.close();
gfx::Path left_path;
left_path.moveTo(1 + endcap_width * scale, scale);
left_path.rCubicTo(-0.75 * scale, 0, -1.625 * scale, 0.5 * scale, -2 * scale,
1.5 * scale);
left_path.lineTo(1 + 2 * scale, bottom - 1.5 * scale);
left_path.rCubicTo(-0.375 * scale, scale, -1.25 * scale, 1.5 * scale,
-2 * scale, 1.5 * scale);
left_path.lineTo(right, bottom);
left_path.lineTo(right, scale);
left_path.close();
return OffsetAndIntersectPaths(left_path, right_path, insets.Scale(scale));
}
// The refresh-specific implementation of GetBorderPath() (see below).
gfx::Path GetRefreshBorderPath(const gfx::Rect& bounds,
bool extend_to_top,
float scale,
float stroke_thickness,
float bottom_offset) {
// Returns a path corresponding to the tab's outer border for a given tab
// |scale| and |bounds|. If |unscale_at_end| is true, this path will be
// normalized to a 1x scale by scaling by 1/scale before returning. If
// |extend_to_top| is true, the path is extended vertically to the top of the
// tab bounds. The caller uses this for Fitts' Law purposes in
// maximized/fullscreen mode.
gfx::Path GetBorderPath(float scale,
float stroke_thickness,
float bottom_offset,
bool unscale_at_end,
bool extend_to_top,
const gfx::Rect& bounds) {
const gfx::RectF aligned_bounds =
ScaleAndAlignBounds(bounds, scale, stroke_thickness);
......@@ -369,7 +294,7 @@ gfx::Path GetRefreshBorderPath(const gfx::Rect& bounds,
const float bottom_radius =
std::max(top_radius - stroke_thickness, 0.f) - bottom_offset;
// See comments in GetRefreshInteriorPath().
// See comments in GetInteriorPath().
const float extension = Tab::GetCornerRadius() * scale;
const float corner_gap = extension - bottom_radius;
......@@ -432,83 +357,6 @@ gfx::Path GetRefreshBorderPath(const gfx::Rect& bounds,
origin.Scale(scale);
path.offset(-origin.x(), -origin.y());
return path;
}
// Returns the inverse of the slope of the diagonal portion of the tab outer
// border. (This is a positive value, so it's specifically for the slope of the
// leading edge.)
//
// This returns the inverse (dx/dy instead of dy/dx) because we use exact values
// for the vertical distances between points and then compute the horizontal
// deltas from those.
float GetInverseDiagonalSlope() {
// In refresh, tab sides do not have slopes, so no one should call this.
DCHECK(!MD::IsRefreshUi());
// This is computed from the border path as follows:
// * The endcap width is enough for the whole stroke outer curve, i.e. the
// side diagonal plus the curves on both its ends.
// * The bottom and top curve together are 4 DIP wide, so the diagonal is
// (endcap width - 4) DIP wide.
// * The bottom and top curve are each 1.5 px high. Additionally, there is an
// extra 1 px below the bottom curve and (scale - 1) px above the top curve,
// so the diagonal is ((height - 1.5 - 1.5) * scale - 1 - (scale - 1)) px
// high. Simplifying this gives (height - 4) * scale px, or (height - 4)
// DIP.
return (GetTabEndcapWidthForPainting() - 4) /
(GetLayoutConstant(TAB_HEIGHT) - 4);
}
// Returns a path corresponding to the tab's outer border for a given tab
// |scale| and |bounds|. If |unscale_at_end| is true, this path will be
// normalized to a 1x scale by scaling by 1/scale before returning. If
// |extend_to_top| is true, the path is extended vertically to the top of the
// tab bounds. The caller uses this for Fitts' Law purposes in
// maximized/fullscreen mode.
gfx::Path GetBorderPath(float scale,
float stroke_thickness,
float bottom_offset,
bool unscale_at_end,
bool extend_to_top,
const gfx::Rect& bounds) {
gfx::Path path;
if (MD::IsRefreshUi()) {
path = GetRefreshBorderPath(bounds, extend_to_top, scale, stroke_thickness,
bottom_offset);
} else {
const float top = scale - stroke_thickness;
const float right = bounds.width() * scale;
const float bottom = bounds.height() * scale;
const float endcap_width = GetTabEndcapWidthForPainting();
path.moveTo(0, bottom);
path.rLineTo(0, -stroke_thickness);
path.rCubicTo(0.75 * scale, 0, 1.625 * scale, -0.5 * scale, 2 * scale,
-1.5 * scale);
path.lineTo((endcap_width - 2) * scale, top + 1.5 * scale);
if (extend_to_top) {
// Create the vertical extension by extending the side diagonals until
// they reach the top of the bounds.
const float dy = 2.5 * scale - stroke_thickness;
const float dx = GetInverseDiagonalSlope() * dy;
path.rLineTo(dx, -dy);
path.lineTo(right - (endcap_width - 2) * scale - dx, 0);
path.rLineTo(dx, dy);
} else {
path.rCubicTo(0.375 * scale, -scale, 1.25 * scale, -1.5 * scale,
2 * scale, -1.5 * scale);
path.lineTo(right - endcap_width * scale, top);
path.rCubicTo(0.75 * scale, 0, 1.625 * scale, 0.5 * scale, 2 * scale,
1.5 * scale);
}
path.lineTo(right - 2 * scale, bottom - stroke_thickness - 1.5 * scale);
path.rCubicTo(0.375 * scale, scale, 1.25 * scale, 1.5 * scale, 2 * scale,
1.5 * scale);
path.rLineTo(0, stroke_thickness);
path.close();
}
if (unscale_at_end && (scale != 1))
path.transform(SkMatrix::MakeScale(1.f / scale));
......@@ -640,7 +488,7 @@ bool Tab::GetHitTestMask(gfx::Path* mask) const {
const views::Widget* widget = GetWidget();
*mask = GetBorderPath(
GetWidget()->GetCompositor()->device_scale_factor(), GetStrokeThickness(),
IsActive() ? 0 : controller_->GetStrokeThickness(), true,
GetBottomStrokeThickness(), true,
widget && (widget->IsMaximized() || widget->IsFullscreen()), bounds());
return true;
}
......@@ -651,15 +499,12 @@ void Tab::Layout() {
const bool was_showing_icon = showing_icon_;
UpdateIconVisibility();
int extra_left_padding = 0;
int start = contents_rect.x();
if (extra_padding_before_content_) {
extra_left_padding =
MD::IsRefreshUi() ? kRefreshExtraLeftPaddingToBalanceCloseButtonPadding
: kExtraLeftPaddingToBalanceCloseButtonPadding;
constexpr int kExtraLeftPaddingToBalanceCloseButtonPadding = 4;
start += kExtraLeftPaddingToBalanceCloseButtonPadding;
}
const int start = contents_rect.x() + extra_left_padding;
// The bounds for the favicon will include extra width for the attention
// indicator, but visually it will be smaller at kFaviconSize wide.
gfx::Rect favicon_bounds(start, contents_rect.y(), 0, 0);
......@@ -723,18 +568,18 @@ void Tab::Layout() {
close_button_->SetVisible(showing_close_button_);
if (showing_alert_indicator_) {
const bool is_touch_optimized = MD::IsTouchOptimizedUiEnabled();
const gfx::Size image_size(alert_indicator_button_->GetPreferredSize());
int alert_to_close_spacing = 0;
if (extra_alert_indicator_padding_) {
alert_to_close_spacing =
is_touch_optimized ? kTouchableRefreshAlertIndicatorCloseButtonPadding
: kRefreshAlertIndicatorCloseButtonPadding;
} else if (!MD::IsRefreshUi() && is_touch_optimized) {
alert_to_close_spacing = after_title_padding;
int right = contents_rect.right();
if (showing_close_button_) {
right = close_x;
if (extra_alert_indicator_padding_) {
constexpr int kTouchableAlertIndicatorCloseButtonPadding = 8;
constexpr int kAlertIndicatorCloseButtonPadding = 6;
right -= MD::IsTouchOptimizedUiEnabled()
? kTouchableAlertIndicatorCloseButtonPadding
: kAlertIndicatorCloseButtonPadding;
}
}
const int right = showing_close_button_ ? (close_x - alert_to_close_spacing)
: contents_rect.right();
const gfx::Size image_size = alert_indicator_button_->GetPreferredSize();
gfx::Rect bounds(
std::max(contents_rect.x(), right - image_size.width()),
contents_rect.y() + Center(contents_rect.height(), image_size.height()),
......@@ -900,14 +745,14 @@ void Tab::OnMouseEntered(const ui::MouseEvent& event) {
hover_controller_.SetSubtleOpacityScale(
controller_->GetHoverOpacityForRadialHighlight());
hover_controller_.Show(GlowHoverController::SUBTLE);
OnButtonColorMaybeChanged();
UpdateForegroundColors();
Layout();
}
void Tab::OnMouseExited(const ui::MouseEvent& event) {
mouse_hovered_ = false;
hover_controller_.Hide();
OnButtonColorMaybeChanged();
UpdateForegroundColors();
Layout();
}
......@@ -987,24 +832,19 @@ void Tab::PaintChildren(const views::PaintInfo& info) {
constexpr float kChildClipPadding = 2.5f;
const gfx::InsetsF padding(0, kChildClipPadding + opacities.left, 0,
kChildClipPadding + opacities.right);
clip_recorder.ClipPathWithAntiAliasing(GetInteriorPath(
paint_recording_scale, paint_recording_scale * GetStrokeThickness(),
IsActive() ? 0 : controller_->GetStrokeThickness(), bounds(), padding));
clip_recorder.ClipPathWithAntiAliasing(
GetInteriorPath(paint_recording_scale, GetStrokeThickness(),
GetBottomStrokeThickness(), bounds(), padding));
View::PaintChildren(info);
}
void Tab::OnPaint(gfx::Canvas* canvas) {
// Don't paint if we're narrower than we can render correctly. (This should
// only happen during animations).
if (!MD::IsRefreshUi() && (width() < GetMinimumInactiveWidth()))
return;
gfx::Path clip;
if (!controller_->ShouldPaintTab(
this,
base::BindRepeating(
&GetBorderPath, canvas->image_scale(), GetStrokeThickness(),
IsActive() ? 0 : controller_->GetStrokeThickness(), true, false),
base::BindRepeating(&GetBorderPath, canvas->image_scale(),
GetStrokeThickness(), GetBottomStrokeThickness(),
true, false),
&clip))
return;
......@@ -1012,11 +852,11 @@ void Tab::OnPaint(gfx::Canvas* canvas) {
}
void Tab::AddedToWidget() {
OnButtonColorMaybeChanged();
UpdateForegroundColors();
}
void Tab::OnThemeChanged() {
OnButtonColorMaybeChanged();
UpdateForegroundColors();
}
void Tab::SetClosing(bool closing) {
......@@ -1059,36 +899,13 @@ SkColor Tab::GetAlertIndicatorColor(TabAlertState state) const {
}
}
SkColor Tab::GetCloseTabButtonColor(
views::Button::ButtonState button_state) const {
// The theme provider may be null if we're not currently in a widget
// hierarchy.
const ui::ThemeProvider* theme_provider = GetThemeProvider();
if (!theme_provider)
return gfx::kPlaceholderColor;
int color_id;
switch (button_state) {
case views::Button::STATE_HOVERED:
color_id = ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_HOVER;
break;
case views::Button::STATE_PRESSED:
color_id = ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_PRESSED;
break;
default:
color_id = IsActive() ? ThemeProperties::COLOR_TAB_CLOSE_BUTTON_ACTIVE
: ThemeProperties::COLOR_TAB_CLOSE_BUTTON_INACTIVE;
}
return theme_provider->GetColor(color_id);
}
bool Tab::IsActive() const {
return controller_->IsActiveTab(this);
}
void Tab::ActiveStateChanged() {
UpdateTabIconNeedsAttentionBlocked();
OnButtonColorMaybeChanged();
UpdateForegroundColors();
alert_indicator_button_->UpdateEnabledForMuteToggle();
Layout();
}
......@@ -1098,12 +915,12 @@ void Tab::AlertStateChanged() {
}
void Tab::FrameColorsChanged() {
OnButtonColorMaybeChanged();
UpdateForegroundColors();
SchedulePaint();
}
void Tab::SelectedStateChanged() {
OnButtonColorMaybeChanged();
UpdateForegroundColors();
}
bool Tab::IsSelected() const {
......@@ -1178,11 +995,17 @@ void Tab::SetTabNeedsAttention(bool attention) {
}
float Tab::GetStrokeThickness(bool should_paint_as_active) const {
return !MD::IsRefreshUi() || IsActive() || should_paint_as_active
return (IsActive() || should_paint_as_active)
? controller_->GetStrokeThickness()
: 0;
}
float Tab::GetBottomStrokeThickness(bool should_paint_as_active) const {
return (IsActive() || should_paint_as_active)
? 0
: controller_->GetStrokeThickness();
}
int Tab::GetWidthOfLargestSelectableRegion() const {
// Assume the entire region to the left of the alert indicator and/or close
// buttons is available for click-to-select. If neither are visible, the
......@@ -1205,15 +1028,11 @@ gfx::Insets Tab::GetContentsInsets() const {
// static
gfx::Insets Tab::GetContentsHorizontalInsets() {
return gfx::Insets(0, MD::IsRefreshUi() ? (GetCornerRadius() * 2)
: GetTabEndcapWidthForLayout());
return gfx::Insets(0, GetCornerRadius() * 2);
}
// static
int Tab::GetMinimumInactiveWidth() {
if (!MD::IsRefreshUi())
return GetContentsHorizontalInsets().width();
// Allow tabs to shrink until they appear to be 16 DIP wide excluding outer
// corners.
constexpr int kInteriorWidth = 16;
......@@ -1229,10 +1048,10 @@ int Tab::GetMinimumActiveWidth() {
// static
int Tab::GetStandardWidth() {
constexpr int kRefreshTabWidth = 240 - kSeparatorThickness;
constexpr int kLayoutWidth[] = {193, 193, 245, kRefreshTabWidth,
kRefreshTabWidth};
return GetOverlap() + kLayoutWidth[MD::GetMode()];
// The standard tab width is 240 DIP including both separators.
constexpr int kTabWidth = 240;
// The overlap includes one separator, so subtract it here.
return kTabWidth + GetOverlap() - kSeparatorThickness;
}
// static
......@@ -1249,18 +1068,19 @@ int Tab::GetCornerRadius() {
// static
int Tab::GetDragInset() {
return MD::IsRefreshUi() ? GetCornerRadius() : GetTabEndcapWidthForLayout();
return GetCornerRadius();
}
// static
int Tab::GetOverlap() {
// For refresh, overlap the separators.
return MD::IsRefreshUi() ? (GetCornerRadius() * 2 + kSeparatorThickness)
: GetTabEndcapWidthForLayout();
// Overlap the separators.
return GetCornerRadius() * 2 + kSeparatorThickness;
}
// static
int Tab::GetTabSeparatorHeight() {
constexpr int kTabSeparatorHeight = 20;
constexpr int kTabSeparatorTouchHeight = 24;
return MD::IsTouchOptimizedUiEnabled() ? kTabSeparatorTouchHeight
: kTabSeparatorHeight;
}
......@@ -1334,6 +1154,28 @@ void Tab::PaintTabBackground(gfx::Canvas* canvas,
: SK_ColorTRANSPARENT;
const SkColor stroke_color = controller_->GetToolbarTopSeparatorColor();
const bool paint_hover_effect = !active && hover_controller_.ShouldDraw();
const float scale = canvas->image_scale();
const float stroke_thickness = GetStrokeThickness(active);
const float bottom_offset = GetBottomStrokeThickness(active);
const auto paint_fill = [&](gfx::Canvas* canvas) {
// When there's a border, we want the stroke to cover up the edge of the
// fill path (https://crbug.com/873003), so set the fill path halfway
// between the inner path and the border paths. When there's no stroke,
// |stroke_thickness| is 0 and the fill, inner, and stroke paths are all
// identical.
gfx::Path fill_path =
GetInteriorPath(scale, stroke_thickness / 2, bottom_offset, bounds());
PaintTabBackgroundFill(canvas, fill_path, active, paint_hover_effect,
active_color, inactive_color, fill_id, y_inset);
};
const auto paint_stroke = [&](gfx::Canvas* canvas) {
gfx::Path interior_path =
GetInteriorPath(scale, stroke_thickness, bottom_offset, bounds());
gfx::Path outer_path = GetBorderPath(scale, stroke_thickness, bottom_offset,
false, false, bounds());
PaintTabBackgroundStroke(canvas, interior_path, outer_path, active,
stroke_color);
};
// If there is a |fill_id| we don't try to cache. This could be improved
// but would require knowing then the image from the ThemeProvider had been
......@@ -1344,65 +1186,33 @@ void Tab::PaintTabBackground(gfx::Canvas* canvas,
// on every invalidation and we would need to invalidate the cache based on
// the hover states.
//
// Finally, in refresh, we don't cache for non-integral scale factors, since
// tabs draw with slightly different offsets so as to pixel-align the layout
// rect (see ScaleAndAlignBounds()).
const float scale = canvas->image_scale();
const float stroke_thickness = GetStrokeThickness(active);
const float bottom_offset = active ? 0 : controller_->GetStrokeThickness();
if (fill_id || paint_hover_effect ||
(MD::IsRefreshUi() && (std::trunc(scale) != scale))) {
// When there's a border, we want the stroke to cover up the edge of the
// fill path (https://crbug.com/873003), so set the fill path halfway
// between the inner path and the border paths. When there's no stroke,
// |stroke_thickness| is 0 and the fill, inner, and stroke paths are all
// identical. Avoid doing this on pre-refresh since strokes may be
// transparent.
gfx::Path fill_path = GetInteriorPath(
scale, MD::IsRefreshUi() ? stroke_thickness / 2 : stroke_thickness,
bottom_offset, bounds());
PaintTabBackgroundFill(canvas, fill_path, active, paint_hover_effect,
active_color, inactive_color, fill_id, y_inset);
// Finally, we don't cache for non-integral scale factors, since tabs draw
// with slightly different offsets so as to pixel-align the layout rect (see
// ScaleAndAlignBounds()).
if (fill_id || paint_hover_effect || (std::trunc(scale) != scale)) {
paint_fill(canvas);
if (stroke_thickness > 0) {
gfx::Path interior_path =
GetInteriorPath(scale, stroke_thickness, bottom_offset, bounds());
gfx::Path outer_path = GetBorderPath(
scale, stroke_thickness, bottom_offset, false, false, bounds());
gfx::ScopedCanvas scoped_canvas(clip ? canvas : nullptr);
if (clip)
canvas->sk_canvas()->clipPath(*clip, SkClipOp::kDifference, true);
PaintTabBackgroundStroke(canvas, interior_path, outer_path, active,
stroke_color);
paint_stroke(canvas);
}
} else {
BackgroundCache& cache =
active ? background_active_cache_ : background_inactive_cache_;
if (!cache.CacheKeyMatches(scale, size(), active_color, inactive_color,
stroke_color, stroke_thickness)) {
// See the comment in the non-caching case above for why we divide
// |stroke_thickness| by 2 on refresh.
gfx::Path fill_path = GetInteriorPath(
scale, MD::IsRefreshUi() ? stroke_thickness / 2 : stroke_thickness,
bottom_offset, bounds());
cc::PaintRecorder recorder;
{
gfx::Canvas cache_canvas(
recorder.beginRecording(size().width(), size().height()), scale);
PaintTabBackgroundFill(&cache_canvas, fill_path, active,
paint_hover_effect, active_color, inactive_color,
fill_id, y_inset);
paint_fill(&cache_canvas);
cache.fill_record = recorder.finishRecordingAsPicture();
}
if (stroke_thickness > 0) {
gfx::Path interior_path =
GetInteriorPath(scale, stroke_thickness, bottom_offset, bounds());
gfx::Path border_path = GetBorderPath(
scale, stroke_thickness, bottom_offset, false, false, bounds());
gfx::Canvas cache_canvas(
recorder.beginRecording(size().width(), size().height()), scale);
PaintTabBackgroundStroke(&cache_canvas, interior_path, border_path,
active, stroke_color);
paint_stroke(&cache_canvas);
cache.stroke_record = recorder.finishRecordingAsPicture();
}
......@@ -1627,33 +1437,11 @@ void Tab::UpdateIconVisibility() {
// We also check this for active tabs so that the extra padding doesn't pop
// in and out as you switch tabs.
extra_padding_before_content_ = large_enough_for_close_button;
if (DCHECK_IS_ON()) {
const int extra_left_padding =
MD::IsRefreshUi()
? kRefreshExtraLeftPaddingToBalanceCloseButtonPadding
: kExtraLeftPaddingToBalanceCloseButtonPadding;
DCHECK(!extra_padding_before_content_ ||
extra_left_padding <= available_width);
if (extra_padding_before_content_)
available_width -= extra_left_padding;
}
}
if (MD::IsRefreshUi()) {
extra_alert_indicator_padding_ = showing_alert_indicator_ &&
showing_close_button_ &&
large_enough_for_close_button;
if (DCHECK_IS_ON()) {
const int extra_alert_padding =
MD::IsTouchOptimizedUiEnabled()
? kTouchableRefreshAlertIndicatorCloseButtonPadding
: kRefreshAlertIndicatorCloseButtonPadding;
DCHECK(!extra_alert_indicator_padding_ ||
extra_alert_padding <= available_width);
}
}
extra_alert_indicator_padding_ = showing_alert_indicator_ &&
showing_close_button_ &&
large_enough_for_close_button;
}
bool Tab::ShouldRenderAsNormalTab() const {
......@@ -1662,9 +1450,6 @@ bool Tab::ShouldRenderAsNormalTab() const {
}
Tab::SeparatorOpacities Tab::GetSeparatorOpacities(bool for_layout) const {
if (!MD::IsRefreshUi())
return SeparatorOpacities();
// Something should visually separate tabs from each other and any adjacent
// new tab button. Normally, active and hovered tabs draw distinct shapes
// (via different background colors) and thus need no separators, while
......@@ -1763,6 +1548,7 @@ float Tab::GetThrobValue() const {
// Wrapping in closure to only compute offset when needed (animate or hover).
const auto offset = [=] {
constexpr float kSelectedTabThrobScale = 0.95f - kSelectedTabOpacity;
const float opacity = GetHoverOpacity();
return is_selected ? (kSelectedTabThrobScale * opacity) : opacity;
};
......@@ -1775,16 +1561,6 @@ float Tab::GetThrobValue() const {
return val;
}
void Tab::OnButtonColorMaybeChanged() {
// The theme provider may be null if we're not currently in a widget
// hierarchy.
const ui::ThemeProvider* theme_provider = GetThemeProvider();
if (!theme_provider)
return;
UpdateForegroundColors();
}
void Tab::UpdateTabIconNeedsAttentionBlocked() {
// Only show the blocked attention indicator on non-active tabs. For active
// tabs, the user sees the dialog blocking the tab, so there's no point to it
......@@ -1798,10 +1574,16 @@ void Tab::UpdateTabIconNeedsAttentionBlocked() {
}
void Tab::UpdateForegroundColors() {
// These ratios are calculated from the default colors specified in the
// Material Refresh design document. Active/inactive are the contrast ratios
// of the close X against the tab background. Hovered/pressed are the contrast
// ratios of the highlight circle against the tab background.
// The theme provider may be null if we're not currently in a widget
// hierarchy.
const ui::ThemeProvider* theme_provider = GetThemeProvider();
if (!theme_provider)
return;
// These ratios are calculated from the default Chrome theme colors.
// Active/inactive are the contrast ratios of the close X against the tab
// background. Hovered/pressed are the contrast ratios of the highlight circle
// against the tab background.
constexpr float kMinimumActiveContrastRatio = 6.05f;
constexpr float kMinimumInactiveContrastRatio = 4.61f;
constexpr float kMinimumHoveredContrastRatio = 5.02f;
......@@ -1831,16 +1613,10 @@ void Tab::UpdateForegroundColors() {
title_->SetEnabledColor(tab_title_color);
const SkColor base_icon_color =
MD::GetMode() == ui::MaterialDesignController::MATERIAL_TOUCH_OPTIMIZED
? GetCloseTabButtonColor(views::Button::STATE_NORMAL)
: tab_title_color;
const SkColor base_hovered_pressed_icon_color =
MD::IsNewerMaterialUi() ? base_icon_color : SK_ColorWHITE;
const SkColor base_hovered_color =
GetCloseTabButtonColor(views::Button::STATE_HOVERED);
const SkColor base_pressed_color =
GetCloseTabButtonColor(views::Button::STATE_PRESSED);
const SkColor base_hovered_color = theme_provider->GetColor(
ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_HOVER);
const SkColor base_pressed_color = theme_provider->GetColor(
ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_PRESSED);
const auto get_color_for_contrast_ratio = [](SkColor fg_color,
SkColor bg_color,
......@@ -1851,7 +1627,7 @@ void Tab::UpdateForegroundColors() {
};
const SkColor generated_icon_color = get_color_for_contrast_ratio(
base_icon_color, tab_bg_color,
tab_title_color, tab_bg_color,
IsActive() ? kMinimumActiveContrastRatio : kMinimumInactiveContrastRatio);
const SkColor generated_hovered_color = get_color_for_contrast_ratio(
base_hovered_color, tab_bg_color, kMinimumHoveredContrastRatio);
......@@ -1859,10 +1635,10 @@ void Tab::UpdateForegroundColors() {
base_pressed_color, tab_bg_color, kMinimumPressedContrastRatio);
const SkColor generated_hovered_icon_color =
color_utils::GetColorWithMinimumContrast(base_hovered_pressed_icon_color,
color_utils::GetColorWithMinimumContrast(tab_title_color,
generated_hovered_color);
const SkColor generated_pressed_icon_color =
color_utils::GetColorWithMinimumContrast(base_hovered_pressed_icon_color,
color_utils::GetColorWithMinimumContrast(tab_title_color,
generated_pressed_color);
close_button_->SetIconColors(
generated_icon_color, generated_hovered_icon_color,
......
......@@ -54,8 +54,8 @@ class Tab : public gfx::AnimationDelegate,
// The Tab's class name.
static const char kViewClassName[];
// Under refresh, thickness in DIPs of the separator painted on the left and
// right edges of the tab.
// Thickness in DIPs of the separator painted on the left and right edges of
// the tab.
static constexpr int kSeparatorThickness = 1;
// When the content's width of the tab shrinks to below this size we should
......@@ -123,9 +123,6 @@ class Tab : public gfx::AnimationDelegate,
// Returns the color used for the alert indicator icon.
SkColor GetAlertIndicatorColor(TabAlertState state) const;
// Returns the color to be used for the tab close button.
SkColor GetCloseTabButtonColor(views::Button::ButtonState button_state) const;
// Returns true if this tab is the active tab.
bool IsActive() const;
......@@ -177,12 +174,15 @@ class Tab : public gfx::AnimationDelegate,
bool mouse_hovered() const { return mouse_hovered_; }
// Returns the thickness of the stroke drawn around the tab. If
// |should_paint_as_active| is true, the tab is treated as an active tab
// regardless of its true current state; this affects Refresh, which never
// paints strokes on inactive tabs.
// Returns the thickness of the stroke drawn around the top and sides of the
// tab. Only active tabs may have a stroke, and not in all cases. If there
// is no stroke, returns 0. If |should_paint_as_active| is true, the tab is
// treated as an active tab regardless of its true current state.
float GetStrokeThickness(bool should_paint_as_active = false) const;
// Returns the thickness of the stroke drawn below the tab.
float GetBottomStrokeThickness(bool should_paint_as_active = false) const;
// Returns the width of the largest part of the tab that is available for the
// user to click to select/activate the tab.
int GetWidthOfLargestSelectableRegion() const;
......@@ -274,8 +274,7 @@ class Tab : public gfx::AnimationDelegate,
bool active,
SkColor color);
// Paints the separator lines on the left and right edge of the tab if in
// material refresh mode.
// Paints the separator lines on the left and right edge of the tab.
void PaintSeparators(gfx::Canvas* canvas);
// Computes which icons are visible in the tab. Should be called everytime
......@@ -299,11 +298,6 @@ class Tab : public gfx::AnimationDelegate,
// tab title change and pulsing.
float GetThrobValue() const;
// Recalculates the correct |button_color_| and resets the title, alert
// indicator, and close button colors if necessary. This should be called any
// time the theme or active state may have changed.
void OnButtonColorMaybeChanged();
// Updates the blocked attention state of the |icon_|. This only updates
// state; it is the responsibility of the caller to request a paint.
void UpdateTabIconNeedsAttentionBlocked();
......
......@@ -65,8 +65,8 @@ class TabCloseButton : public views::ImageButton,
// Draw the close "X" glyph.
void DrawCloseGlyph(gfx::Canvas* canvas, ButtonState state);
// In material refresh mode, calculates opacity based on the current state of
// the hover animation on the parent tab.
// Calculates opacity based on the current state of the hover animation on the
// parent tab.
SkAlpha GetOpacity();
MouseEventCallback mouse_event_callback_;
......
......@@ -36,9 +36,9 @@ class TabController {
// Returns true if multiple selection is supported.
virtual bool SupportsMultipleSelection() = 0;
// Under Refresh, returns where the new tab button should be placed. This is
// needed to determine which tab separators need to be faded in/out while
// animating into position.
// Returns where the new tab button should be placed. This is needed to
// determine which tab separators need to be faded in/out while animating into
// position.
virtual NewTabButtonPosition GetNewTabButtonPosition() const = 0;
// Returns true if the close button for the given tab is forced to be hidden.
......@@ -130,8 +130,8 @@ class TabController {
border_callback,
gfx::Path* clip) = 0;
// Returns the thickness of the stroke around all tabs (for pre-refresh) or
// the active tab (for refresh) in DIP. Returns 0 if there is no stroke.
// Returns the thickness of the stroke around the active tab in DIP. Returns
// 0 if there is no stroke.
virtual int GetStrokeThickness() const = 0;
// Returns true if tab loading throbbers can be painted to a composited layer.
......@@ -147,7 +147,7 @@ class TabController {
// state of the window.
virtual SkColor GetToolbarTopSeparatorColor() const = 0;
// Under Refresh, returns the color of the separator between the tabs.
// Returns the color of the separator between the tabs.
virtual SkColor GetTabSeparatorColor() const = 0;
// Returns the tab background color based on both the |state| of the tab and
......
......@@ -186,15 +186,6 @@ class TabStripTest : public ChromeViewsTestBase,
return delegate;
}
bool IsTabShowingCloseButton(Tab* tab) {
ui::MouseEvent event(ui::ET_MOUSE_ENTERED, gfx::Point(0, 0),
gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);
// In Refresh, close buttons on inactive tabs are never visible until the
// tab is hovered. It's harmless to do this in other cases.
tab->OnMouseEntered(event);
return tab->showing_close_button_;
}
bool IsShowingAttentionIndicator(Tab* tab) {
return tab->icon_->ShowingAttentionIndicator();
}
......@@ -443,9 +434,9 @@ TEST_P(TabStripTest, TabCloseButtonVisibilityWhenStacked) {
Tab* tab2 = tab_strip_->tab_at(2);
// Ensure that all tab close buttons are initially visible.
EXPECT_TRUE(IsTabShowingCloseButton(tab0));
EXPECT_TRUE(IsTabShowingCloseButton(tab1));
EXPECT_TRUE(IsTabShowingCloseButton(tab2));
EXPECT_TRUE(tab0->showing_close_button_);
EXPECT_TRUE(tab1->showing_close_button_);
EXPECT_TRUE(tab2->showing_close_button_);
// Enter stacked layout mode and verify this sets |touch_layout_|.
ASSERT_FALSE(touch_layout());
......@@ -454,18 +445,18 @@ TEST_P(TabStripTest, TabCloseButtonVisibilityWhenStacked) {
// Only the close button of the active tab should be visible in stacked
// layout mode.
EXPECT_FALSE(IsTabShowingCloseButton(tab0));
EXPECT_TRUE(IsTabShowingCloseButton(tab1));
EXPECT_FALSE(IsTabShowingCloseButton(tab2));
EXPECT_FALSE(tab0->showing_close_button_);
EXPECT_TRUE(tab1->showing_close_button_);
EXPECT_FALSE(tab2->showing_close_button_);
// An inactive tab added to the tabstrip should not show
// its tab close button.
controller_->AddTab(3, false);
Tab* tab3 = tab_strip_->tab_at(3);
EXPECT_FALSE(IsTabShowingCloseButton(tab0));
EXPECT_TRUE(IsTabShowingCloseButton(tab1));
EXPECT_FALSE(IsTabShowingCloseButton(tab2));
EXPECT_FALSE(IsTabShowingCloseButton(tab3));
EXPECT_FALSE(tab0->showing_close_button_);
EXPECT_TRUE(tab1->showing_close_button_);
EXPECT_FALSE(tab2->showing_close_button_);
EXPECT_FALSE(tab3->showing_close_button_);
// After switching tabs, the previously-active tab should have its
// tab close button hidden and the newly-active tab should show
......@@ -473,26 +464,26 @@ TEST_P(TabStripTest, TabCloseButtonVisibilityWhenStacked) {
tab_strip_->SelectTab(tab2);
ASSERT_FALSE(tab1->IsActive());
ASSERT_TRUE(tab2->IsActive());
EXPECT_FALSE(IsTabShowingCloseButton(tab0));
EXPECT_FALSE(IsTabShowingCloseButton(tab1));
EXPECT_TRUE(IsTabShowingCloseButton(tab2));
EXPECT_FALSE(IsTabShowingCloseButton(tab3));
EXPECT_FALSE(tab0->showing_close_button_);
EXPECT_FALSE(tab1->showing_close_button_);
EXPECT_TRUE(tab2->showing_close_button_);
EXPECT_FALSE(tab3->showing_close_button_);
// After closing the active tab, the tab which becomes active should
// show its tab close button.
tab_strip_->CloseTab(tab1, CLOSE_TAB_FROM_TOUCH);
tab1 = nullptr;
ASSERT_TRUE(tab2->IsActive());
EXPECT_FALSE(IsTabShowingCloseButton(tab0));
EXPECT_TRUE(IsTabShowingCloseButton(tab2));
EXPECT_FALSE(IsTabShowingCloseButton(tab3));
EXPECT_FALSE(tab0->showing_close_button_);
EXPECT_TRUE(tab2->showing_close_button_);
EXPECT_FALSE(tab3->showing_close_button_);
// All tab close buttons should be shown when disengaging stacked tab mode.
tab_strip_->SetStackedLayout(false);
ASSERT_FALSE(touch_layout());
EXPECT_TRUE(IsTabShowingCloseButton(tab0));
EXPECT_TRUE(IsTabShowingCloseButton(tab2));
EXPECT_TRUE(IsTabShowingCloseButton(tab3));
EXPECT_TRUE(tab0->showing_close_button_);
EXPECT_TRUE(tab2->showing_close_button_);
EXPECT_TRUE(tab3->showing_close_button_);
}
// Tests that the tab close buttons of non-active tabs are hidden when
......@@ -524,9 +515,9 @@ TEST_P(TabStripTest, TabCloseButtonVisibilityWhenNotStacked) {
ASSERT_FALSE(touch_layout());
// Ensure that all tab close buttons are initially visible.
EXPECT_TRUE(IsTabShowingCloseButton(tab0));
EXPECT_TRUE(IsTabShowingCloseButton(tab1));
EXPECT_TRUE(IsTabShowingCloseButton(tab2));
EXPECT_TRUE(tab0->showing_close_button_);
EXPECT_TRUE(tab1->showing_close_button_);
EXPECT_TRUE(tab2->showing_close_button_);
// Shrink the tab sizes by adding more tabs.
// An inactive tab added to the tabstrip, now each tab size is not
......@@ -534,7 +525,7 @@ TEST_P(TabStripTest, TabCloseButtonVisibilityWhenNotStacked) {
// tab close button.
controller_->AddTab(3, false);
Tab* tab3 = tab_strip_->tab_at(3);
EXPECT_FALSE(IsTabShowingCloseButton(tab3));
EXPECT_FALSE(tab3->showing_close_button_);
// This inactive tab doesn't have alert button, but its favicon and
// title would be shown.
......@@ -543,18 +534,18 @@ TEST_P(TabStripTest, TabCloseButtonVisibilityWhenNotStacked) {
EXPECT_TRUE(tab3->title_->visible());
// The active tab's close button still shows.
EXPECT_TRUE(IsTabShowingCloseButton(tab1));
EXPECT_TRUE(tab1->showing_close_button_);
// An active tab added to the tabstrip should show its tab close
// button.
controller_->AddTab(4, true);
Tab* tab4 = tab_strip_->tab_at(4);
ASSERT_TRUE(tab4->IsActive());
EXPECT_TRUE(IsTabShowingCloseButton(tab4));
EXPECT_TRUE(tab4->showing_close_button_);
// The previous active button is now inactive so its close
// button should not show.
EXPECT_FALSE(IsTabShowingCloseButton(tab1));
EXPECT_FALSE(tab1->showing_close_button_);
// After switching tabs, the previously-active tab should have its
// tab close button hidden and the newly-active tab should show
......@@ -562,11 +553,11 @@ TEST_P(TabStripTest, TabCloseButtonVisibilityWhenNotStacked) {
tab_strip_->SelectTab(tab2);
ASSERT_FALSE(tab4->IsActive());
ASSERT_TRUE(tab2->IsActive());
EXPECT_FALSE(IsTabShowingCloseButton(tab0));
EXPECT_FALSE(IsTabShowingCloseButton(tab1));
EXPECT_TRUE(IsTabShowingCloseButton(tab2));
EXPECT_FALSE(IsTabShowingCloseButton(tab3));
EXPECT_FALSE(IsTabShowingCloseButton(tab4));
EXPECT_FALSE(tab0->showing_close_button_);
EXPECT_FALSE(tab1->showing_close_button_);
EXPECT_TRUE(tab2->showing_close_button_);
EXPECT_FALSE(tab3->showing_close_button_);
EXPECT_FALSE(tab4->showing_close_button_);
// After closing the active tab, the tab which becomes active should
// show its tab close button.
......@@ -574,10 +565,10 @@ TEST_P(TabStripTest, TabCloseButtonVisibilityWhenNotStacked) {
tab2 = nullptr;
ASSERT_TRUE(tab3->IsActive());
DoLayout();
EXPECT_FALSE(IsTabShowingCloseButton(tab0));
EXPECT_FALSE(IsTabShowingCloseButton(tab1));
EXPECT_TRUE(IsTabShowingCloseButton(tab3));
EXPECT_FALSE(IsTabShowingCloseButton(tab4));
EXPECT_FALSE(tab0->showing_close_button_);
EXPECT_FALSE(tab1->showing_close_button_);
EXPECT_TRUE(tab3->showing_close_button_);
EXPECT_FALSE(tab4->showing_close_button_);
}
TEST_P(TabStripTest, GetEventHandlerForOverlappingArea) {
......
......@@ -719,8 +719,7 @@ TEST_F(TabTest, TitleTextHasSufficientContrast) {
SkColor fg_active;
SkColor bg_inactive;
SkColor fg_inactive;
};
ColorScheme color_schemes[] = {
} color_schemes[] = {
{
SK_ColorBLACK, SK_ColorWHITE, SK_ColorBLACK, SK_ColorWHITE,
},
......@@ -731,8 +730,15 @@ TEST_F(TabTest, TitleTextHasSufficientContrast) {
kDarkGray, kLightGray, kDarkGray, kLightGray,
},
};
// Create a tab inside a Widget, so it has a theme provider, so the call to
// UpdateForegroundColors() below doesn't no-op.
Widget widget;
InitWidget(&widget);
FakeTabController controller;
Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
for (const auto& colors : color_schemes) {
controller.SetTabColors(colors.bg_active, colors.fg_active,
colors.bg_inactive, colors.fg_inactive);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment