| // Copyright 2016 The Chromium Authors |
| // 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_caption_button.h" |
| #include <memory> |
| |
| #include "base/numerics/safe_conversions.h" |
| #include "base/win/windows_version.h" |
| #include "chrome/browser/themes/theme_properties.h" |
| #include "chrome/browser/ui/color/chrome_color_id.h" |
| #include "chrome/browser/ui/frame/window_frame_util.h" |
| #include "chrome/browser/ui/views/frame/browser_frame_view_win.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/grit/theme_resources.h" |
| #include "ui/base/metadata/metadata_impl_macros.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/geometry/skia_conversions.h" |
| #include "ui/gfx/scoped_canvas.h" |
| |
| WindowsCaptionButton::WindowsCaptionButton( |
| PressedCallback callback, |
| BrowserFrameViewWin* frame_view, |
| ViewID button_type, |
| const std::u16string& accessible_name) |
| : views::Button(std::move(callback)), |
| frame_view_(frame_view), |
| icon_painter_(CreateIconPainter()), |
| button_type_(button_type) { |
| SetAnimateOnStateChange(true); |
| // Not focusable by default, only for accessibility. |
| SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); |
| SetAccessibleName(accessible_name); |
| SetID(button_type); |
| } |
| |
| WindowsCaptionButton::~WindowsCaptionButton() = default; |
| |
| std::unique_ptr<Windows10IconPainter> |
| WindowsCaptionButton::CreateIconPainter() { |
| if (base::win::GetVersion() >= base::win::Version::WIN11) { |
| return std::make_unique<Windows11IconPainter>(); |
| } |
| return std::make_unique<Windows10IconPainter>(); |
| } |
| |
| gfx::Size WindowsCaptionButton::CalculatePreferredSize() const { |
| // TODO(bsep): The sizes in this function are for 1x device scale and don't |
| // match Windows button sizes at hidpi. |
| int height = WindowFrameUtil::kWindows10GlassCaptionButtonHeightRestored; |
| if (!frame_view_->browser_view()->webui_tab_strip() && |
| frame_view_->IsMaximized()) { |
| int maximized_height = |
| frame_view_->browser_view()->GetTabStripVisible() |
| ? frame_view_->browser_view()->GetTabStripHeight() |
| : frame_view_->TitlebarMaximizedVisualHeight(); |
| constexpr int kMaximizedBottomMargin = 2; |
| maximized_height -= kMaximizedBottomMargin; |
| height = std::min(height, maximized_height); |
| } |
| int base_width = WindowFrameUtil::kWindows10GlassCaptionButtonWidth; |
| return gfx::Size(base_width + GetBetweenButtonSpacing(), height); |
| } |
| |
| SkColor WindowsCaptionButton::GetBaseForegroundColor() const { |
| return GetColorProvider()->GetColor( |
| frame_view_->ShouldPaintAsActive() |
| ? kColorCaptionButtonForegroundActive |
| : kColorCaptionButtonForegroundInactive); |
| } |
| |
| void WindowsCaptionButton::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 = |
| GetColorProvider()->GetColor(kColorCaptionButtonBackground); |
| const SkAlpha theme_alpha = SkColorGetA(bg_color); |
| gfx::Rect bounds = GetContentsBounds(); |
| bounds.Inset(gfx::Insets::TLBR(0, GetBetweenButtonSpacing(), 0, 0)); |
| |
| if (theme_alpha > 0) { |
| canvas->FillRect( |
| bounds, |
| SkColorSetA(bg_color, |
| WindowFrameUtil:: |
| CalculateWindows10GlassCaptionButtonBackgroundAlpha( |
| theme_alpha))); |
| } |
| if (theme_provider->HasCustomImage(IDR_THEME_WINDOW_CONTROL_BACKGROUND)) { |
| // Figure out what portion of the background image to display |
| const int button_display_order = GetButtonDisplayOrderIndex(); |
| const int base_button_width = |
| WindowFrameUtil::kWindows10GlassCaptionButtonWidth; |
| const int base_visual_spacing = |
| WindowFrameUtil::kWindows10GlassCaptionButtonVisualSpacing; |
| const int src_x = |
| button_display_order * (base_button_width + base_visual_spacing); |
| const int src_y = 0; |
| |
| canvas->TileImageInt( |
| *theme_provider->GetImageSkiaNamed(IDR_THEME_WINDOW_CONTROL_BACKGROUND), |
| src_x, src_y, bounds.x(), bounds.y(), bounds.width(), bounds.height()); |
| } |
| |
| SkColor base_color; |
| SkAlpha hovered_alpha, pressed_alpha; |
| if (button_type_ == VIEW_ID_CLOSE_BUTTON) { |
| base_color = |
| GetColorProvider()->GetColor(kColorCaptionCloseButtonBackgroundHovered); |
| hovered_alpha = SK_AlphaOPAQUE; |
| pressed_alpha = 0x98; |
| } else { |
| // Match the native buttons. |
| base_color = GetBaseForegroundColor(); |
| 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 = base::ClampRound<SkAlpha>(hovered_alpha * kAlphaScale); |
| pressed_alpha = base::ClampRound<SkAlpha>(pressed_alpha * kAlphaScale); |
| } |
| } |
| |
| SkAlpha alpha; |
| if (GetState() == STATE_PRESSED) |
| alpha = pressed_alpha; |
| else |
| alpha = gfx::Tween::IntValueBetween(hover_animation().GetCurrentValue(), |
| SK_AlphaTRANSPARENT, hovered_alpha); |
| canvas->FillRect(bounds, SkColorSetA(base_color, alpha)); |
| } |
| |
| void WindowsCaptionButton::PaintButtonContents(gfx::Canvas* canvas) { |
| PaintSymbol(canvas); |
| } |
| |
| int WindowsCaptionButton::GetBetweenButtonSpacing() const { |
| const int display_order_index = GetButtonDisplayOrderIndex(); |
| return display_order_index == 0 |
| ? 0 |
| : WindowFrameUtil::kWindows10GlassCaptionButtonVisualSpacing; |
| } |
| |
| int WindowsCaptionButton::GetButtonDisplayOrderIndex() const { |
| int button_display_order = 0; |
| const bool tab_search_enabled = |
| WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled( |
| frame_view_->browser_view()->browser()); |
| switch (button_type_) { |
| case VIEW_ID_TAB_SEARCH_BUTTON: |
| button_display_order = 0; |
| break; |
| case VIEW_ID_MINIMIZE_BUTTON: |
| button_display_order = 0 + (tab_search_enabled ? 1 : 0); |
| break; |
| case VIEW_ID_MAXIMIZE_BUTTON: |
| case VIEW_ID_RESTORE_BUTTON: |
| button_display_order = 1 + (tab_search_enabled ? 1 : 0); |
| break; |
| case VIEW_ID_CLOSE_BUTTON: |
| button_display_order = 2 + (tab_search_enabled ? 1 : 0); |
| break; |
| default: |
| NOTREACHED_NORETURN(); |
| } |
| |
| // Reverse the ordering if we're in RTL mode |
| if (base::i18n::IsRTL()) { |
| const int max_index = tab_search_enabled ? 3 : 2; |
| button_display_order = max_index - button_display_order; |
| } |
| |
| return button_display_order; |
| } |
| |
| void WindowsCaptionButton::PaintSymbol(gfx::Canvas* canvas) { |
| SkColor symbol_color = GetBaseForegroundColor(); |
| const SkColor hovered_color = |
| GetColorProvider()->GetColor(kColorCaptionCloseButtonForegroundHovered); |
| if (!GetEnabled() || |
| (!frame_view_->ShouldPaintAsActive() && GetState() != STATE_HOVERED && |
| GetState() != STATE_PRESSED)) { |
| symbol_color = |
| SkColorSetA(symbol_color, SkColorGetA(GetColorProvider()->GetColor( |
| kColorCaptionForegroundInactive))); |
| } else if (button_type_ == VIEW_ID_CLOSE_BUTTON && |
| hover_animation().is_animating()) { |
| symbol_color = gfx::Tween::ColorValueBetween( |
| hover_animation().GetCurrentValue(), symbol_color, hovered_color); |
| } else if (button_type_ == VIEW_ID_CLOSE_BUTTON && |
| (GetState() == STATE_HOVERED || GetState() == STATE_PRESSED)) { |
| symbol_color = hovered_color; |
| } |
| |
| 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: |
| icon_painter_->PaintMinimizeIcon(canvas, symbol_rect, flags); |
| return; |
| |
| case VIEW_ID_MAXIMIZE_BUTTON: |
| icon_painter_->PaintMaximizeIcon(canvas, symbol_rect, flags); |
| return; |
| |
| case VIEW_ID_RESTORE_BUTTON: |
| icon_painter_->PaintRestoreIcon(canvas, symbol_rect, flags); |
| return; |
| |
| case VIEW_ID_CLOSE_BUTTON: { |
| // 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 == hovered_color ? 0.1f : 0.05f); |
| flags.setStrokeWidth(stroke_width + stroke_halo); |
| |
| icon_painter_->PaintCloseIcon(canvas, symbol_rect, flags); |
| return; |
| } |
| |
| case VIEW_ID_TAB_SEARCH_BUTTON: |
| icon_painter_->PaintTabSearchIcon(canvas, symbol_rect, flags); |
| return; |
| |
| default: |
| NOTREACHED_NORETURN(); |
| } |
| } |
| |
| BEGIN_METADATA(WindowsCaptionButton, views::Button) |
| ADD_READONLY_PROPERTY_METADATA(int, BetweenButtonSpacing) |
| ADD_READONLY_PROPERTY_METADATA(int, ButtonDisplayOrderIndex) |
| ADD_READONLY_PROPERTY_METADATA(SkColor, |
| BaseForegroundColor, |
| ui::metadata::SkColorConverter) |
| END_METADATA |