blob: 3760704d95dcc3f62362b1a9200fd0c6f00b6453 [file] [log] [blame]
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/glass_browser_frame_view.h"
#include "chrome/grit/theme_resources.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/scoped_canvas.h"
Windows10CaptionButton::Windows10CaptionButton(
GlassBrowserFrameView* frame_view,
ViewID button_type)
: views::Button(frame_view),
frame_view_(frame_view),
button_type_(button_type) {
set_animate_on_state_change(true);
}
gfx::Size Windows10CaptionButton::CalculatePreferredSize() const {
// TODO(bsep): The sizes in this function are for 1x device scale and don't
// match Windows button sizes at hidpi.
constexpr int kButtonHeightRestored = 29;
int h = kButtonHeightRestored;
if (frame_view_->IsMaximized()) {
h = frame_view_->browser_view()->IsTabStripVisible()
? frame_view_->browser_view()->GetTabStripHeight()
: frame_view_->TitlebarMaximizedVisualHeight();
constexpr int kMaximizedBottomMargin = 2;
h -= kMaximizedBottomMargin;
}
constexpr int kButtonWidth = 45;
return gfx::Size(kButtonWidth, h);
}
namespace {
SkAlpha ButtonBackgroundAlpha(SkAlpha theme_alpha) {
return theme_alpha == SK_AlphaOPAQUE ? 0xCC : theme_alpha;
}
}
SkColor Windows10CaptionButton::GetBaseColor() const {
const SkColor titlebar_color = frame_view_->GetTitlebarColor();
const SkColor bg_color =
GetThemeProvider()->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND);
const SkAlpha theme_alpha = SkColorGetA(bg_color);
const SkColor blend_color =
theme_alpha > 0
? color_utils::AlphaBlend(bg_color, titlebar_color,
ButtonBackgroundAlpha(theme_alpha))
: titlebar_color;
return color_utils::IsDark(blend_color) ? SK_ColorWHITE : SK_ColorBLACK;
}
void Windows10CaptionButton::OnPaintBackground(gfx::Canvas* canvas) {
// Paint the background of the button (the semi-transparent rectangle that
// appears when you hover or press the button).
const ui::ThemeProvider* theme_provider = GetThemeProvider();
const SkColor bg_color =
theme_provider->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND);
const SkAlpha theme_alpha = SkColorGetA(bg_color);
if (theme_alpha > 0) {
canvas->FillRect(GetContentsBounds(),
SkColorSetA(bg_color, ButtonBackgroundAlpha(theme_alpha)));
}
if (theme_provider->HasCustomImage(IDR_THEME_WINDOW_CONTROL_BACKGROUND)) {
const gfx::Rect bounds = GetContentsBounds();
canvas->TileImageInt(
*theme_provider->GetImageSkiaNamed(IDR_THEME_WINDOW_CONTROL_BACKGROUND),
0, 0, bounds.width(), bounds.height());
}
SkColor base_color;
SkAlpha hovered_alpha, pressed_alpha;
if (button_type_ == VIEW_ID_CLOSE_BUTTON) {
base_color = SkColorSetRGB(0xE8, 0x11, 0x23);
hovered_alpha = SK_AlphaOPAQUE;
pressed_alpha = 0x98;
} else {
// Match the native buttons.
base_color = GetBaseColor();
hovered_alpha = 0x1A;
pressed_alpha = 0x33;
if (theme_alpha > 0) {
// Theme buttons have slightly increased opacity to make them stand out
// against a visually-busy frame image.
constexpr float kAlphaScale = 1.3f;
hovered_alpha = gfx::ToRoundedInt(hovered_alpha * kAlphaScale);
pressed_alpha = gfx::ToRoundedInt(pressed_alpha * kAlphaScale);
}
}
SkAlpha alpha;
if (state() == STATE_PRESSED)
alpha = pressed_alpha;
else
alpha = gfx::Tween::IntValueBetween(hover_animation().GetCurrentValue(),
SK_AlphaTRANSPARENT, hovered_alpha);
canvas->FillRect(GetContentsBounds(), SkColorSetA(base_color, alpha));
}
void Windows10CaptionButton::PaintButtonContents(gfx::Canvas* canvas) {
PaintSymbol(canvas);
}
namespace {
// Canvas::DrawRect's stroke can bleed out of |rect|'s bounds, so this draws a
// rectangle inset such that the result is constrained to |rect|'s size.
void DrawRect(gfx::Canvas* canvas,
const gfx::Rect& rect,
const cc::PaintFlags& flags) {
gfx::RectF rect_f(rect);
float stroke_half_width = flags.getStrokeWidth() / 2;
rect_f.Inset(stroke_half_width, stroke_half_width);
canvas->DrawRect(rect_f, flags);
}
} // namespace
void Windows10CaptionButton::PaintSymbol(gfx::Canvas* canvas) {
SkColor symbol_color = GetBaseColor();
if (!frame_view_->ShouldPaintAsActive() && state() != STATE_HOVERED &&
state() != STATE_PRESSED) {
symbol_color = SkColorSetA(symbol_color, 0x65);
} else if (button_type_ == VIEW_ID_CLOSE_BUTTON &&
hover_animation().is_animating()) {
symbol_color = gfx::Tween::ColorValueBetween(
hover_animation().GetCurrentValue(), symbol_color, SK_ColorWHITE);
} else if (button_type_ == VIEW_ID_CLOSE_BUTTON &&
(state() == STATE_HOVERED || state() == STATE_PRESSED)) {
symbol_color = SK_ColorWHITE;
}
gfx::ScopedCanvas scoped_canvas(canvas);
const float scale = canvas->UndoDeviceScaleFactor();
const int symbol_size_pixels = std::round(10 * scale);
gfx::RectF bounds_rect(GetContentsBounds());
bounds_rect.Scale(scale);
gfx::Rect symbol_rect(gfx::ToEnclosingRect(bounds_rect));
symbol_rect.ClampToCenteredSize(
gfx::Size(symbol_size_pixels, symbol_size_pixels));
cc::PaintFlags flags;
flags.setAntiAlias(false);
flags.setColor(symbol_color);
flags.setStyle(cc::PaintFlags::kStroke_Style);
// Stroke width jumps up a pixel every time we reach a new integral scale.
const int stroke_width = std::floor(scale);
flags.setStrokeWidth(stroke_width);
switch (button_type_) {
case VIEW_ID_MINIMIZE_BUTTON: {
const int y = symbol_rect.CenterPoint().y();
const gfx::Point p1 = gfx::Point(symbol_rect.x(), y);
const gfx::Point p2 = gfx::Point(symbol_rect.right(), y);
canvas->DrawLine(p1, p2, flags);
return;
}
case VIEW_ID_MAXIMIZE_BUTTON:
DrawRect(canvas, symbol_rect, flags);
return;
case VIEW_ID_RESTORE_BUTTON: {
// Bottom left ("in front") square.
const int separation = std::floor(2 * scale);
symbol_rect.Inset(0, separation, separation, 0);
DrawRect(canvas, symbol_rect, flags);
// Top right ("behind") square.
canvas->ClipRect(symbol_rect, SkClipOp::kDifference);
symbol_rect.Offset(separation, -separation);
DrawRect(canvas, symbol_rect, flags);
return;
}
case VIEW_ID_CLOSE_BUTTON: {
flags.setAntiAlias(true);
// The close button's X is surrounded by a "halo" of transparent pixels.
// When the X is white, the transparent pixels need to be a bit brighter
// to be visible.
const float stroke_halo =
stroke_width * (symbol_color == SK_ColorWHITE ? 0.1f : 0.05f);
flags.setStrokeWidth(stroke_width + stroke_halo);
// TODO(bsep): This sometimes draws misaligned at fractional device scales
// because the button's origin isn't necessarily aligned to pixels.
canvas->ClipRect(symbol_rect);
SkPath path;
path.moveTo(symbol_rect.x(), symbol_rect.y());
path.lineTo(symbol_rect.right(), symbol_rect.bottom());
path.moveTo(symbol_rect.right(), symbol_rect.y());
path.lineTo(symbol_rect.x(), symbol_rect.bottom());
canvas->DrawPath(path, flags);
return;
}
default:
NOTREACHED();
return;
}
}