From 2c0327c3948a157a0ab15248322bc2941407465a Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Wed, 13 Jul 2022 09:37:10 +0200 Subject: [PATCH] fix: update Windows caption buttons to match Win11 style (#34888) * fix: update Windows caption buttons to match Win11 style * chore: fix const issue Co-authored-by: Shelley Vohr --- filenames.gni | 2 + shell/browser/ui/views/win_caption_button.cc | 62 +++----- shell/browser/ui/views/win_caption_button.h | 7 + shell/browser/ui/views/win_icon_painter.cc | 143 +++++++++++++++++++ shell/browser/ui/views/win_icon_painter.h | 64 +++++++++ 5 files changed, 236 insertions(+), 42 deletions(-) create mode 100644 shell/browser/ui/views/win_icon_painter.cc create mode 100644 shell/browser/ui/views/win_icon_painter.h diff --git a/filenames.gni b/filenames.gni index 3ce306928a96f..3af6eb4e1d931 100644 --- a/filenames.gni +++ b/filenames.gni @@ -85,6 +85,8 @@ filenames = { "shell/browser/ui/message_box_win.cc", "shell/browser/ui/tray_icon_win.cc", "shell/browser/ui/views/electron_views_delegate_win.cc", + "shell/browser/ui/views/win_icon_painter.cc", + "shell/browser/ui/views/win_icon_painter.h", "shell/browser/ui/views/win_frame_view.cc", "shell/browser/ui/views/win_frame_view.h", "shell/browser/ui/views/win_caption_button.cc", diff --git a/shell/browser/ui/views/win_caption_button.cc b/shell/browser/ui/views/win_caption_button.cc index 46280ee4b1583..9903fecd3146d 100644 --- a/shell/browser/ui/views/win_caption_button.cc +++ b/shell/browser/ui/views/win_caption_button.cc @@ -10,6 +10,7 @@ #include "base/i18n/rtl.h" #include "base/numerics/safe_conversions.h" +#include "base/win/windows_version.h" #include "chrome/grit/theme_resources.h" #include "shell/browser/ui/views/win_frame_view.h" #include "shell/common/color_util.h" @@ -28,6 +29,7 @@ WinCaptionButton::WinCaptionButton(PressedCallback callback, 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. @@ -35,6 +37,15 @@ WinCaptionButton::WinCaptionButton(PressedCallback callback, SetAccessibleName(accessible_name); } +WinCaptionButton::~WinCaptionButton() = default; + +std::unique_ptr WinCaptionButton::CreateIconPainter() { + if (base::win::GetVersion() >= base::win::Version::WIN11) { + return std::make_unique(); + } + return std::make_unique(); +} + gfx::Size WinCaptionButton::CalculatePreferredSize() const { // TODO(bsep): The sizes in this function are for 1x device scale and don't // match Windows button sizes at hidpi. @@ -50,9 +61,10 @@ void WinCaptionButton::OnPaintBackground(gfx::Canvas* canvas) { const SkAlpha theme_alpha = SkColorGetA(bg_color); gfx::Rect bounds = GetContentsBounds(); - bounds.Inset(gfx::Insets::TLBR(0, 0, 0, 0)); + bounds.Inset(gfx::Insets::TLBR(0, GetBetweenButtonSpacing(), 0, 0)); - canvas->FillRect(bounds, SkColorSetA(bg_color, theme_alpha)); + if (theme_alpha > 0) + canvas->FillRect(bounds, SkColorSetA(bg_color, theme_alpha)); SkColor base_color; SkAlpha hovered_alpha, pressed_alpha; @@ -136,21 +148,6 @@ int WinCaptionButton::GetButtonDisplayOrderIndex() const { return button_display_order; } -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(gfx::InsetsF::VH(stroke_half_width, stroke_half_width)); - canvas->DrawRect(rect_f, flags); -} - -} // namespace - void WinCaptionButton::PaintSymbol(gfx::Canvas* canvas) { SkColor symbol_color = frame_view_->window()->overlay_symbol_color(); @@ -183,32 +180,21 @@ void WinCaptionButton::PaintSymbol(gfx::Canvas* canvas) { 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); + icon_painter_->PaintMinimizeIcon(canvas, symbol_rect, flags); return; } - case VIEW_ID_MAXIMIZE_BUTTON: - DrawRect(canvas, symbol_rect, flags); + case VIEW_ID_MAXIMIZE_BUTTON: { + icon_painter_->PaintMaximizeIcon(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(gfx::Insets::TLBR(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); + icon_painter_->PaintRestoreIcon(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. @@ -216,15 +202,7 @@ void WinCaptionButton::PaintSymbol(gfx::Canvas* canvas) { 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); + icon_painter_->PaintCloseIcon(canvas, symbol_rect, flags); return; } diff --git a/shell/browser/ui/views/win_caption_button.h b/shell/browser/ui/views/win_caption_button.h index fafa3e414bcff..801474b5e3f4b 100644 --- a/shell/browser/ui/views/win_caption_button.h +++ b/shell/browser/ui/views/win_caption_button.h @@ -7,8 +7,11 @@ #ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_ #define ELECTRON_SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_ +#include + #include "chrome/browser/ui/frame/window_frame_util.h" #include "chrome/browser/ui/view_ids.h" +#include "shell/browser/ui/views/win_icon_painter.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/gfx/canvas.h" #include "ui/views/controls/button/button.h" @@ -23,6 +26,8 @@ class WinCaptionButton : public views::Button { WinFrameView* frame_view, ViewID button_type, const std::u16string& accessible_name); + ~WinCaptionButton() override; + WinCaptionButton(const WinCaptionButton&) = delete; WinCaptionButton& operator=(const WinCaptionButton&) = delete; @@ -35,6 +40,7 @@ class WinCaptionButton : public views::Button { void SetSize(gfx::Size size); private: + std::unique_ptr CreateIconPainter(); // Returns the amount we should visually reserve on the left (right in RTL) // for spacing between buttons. We do this instead of repositioning the // buttons to avoid the sliver of deadspace that would result. @@ -53,6 +59,7 @@ class WinCaptionButton : public views::Button { void PaintSymbol(gfx::Canvas* canvas); WinFrameView* frame_view_; + std::unique_ptr icon_painter_; ViewID button_type_; int base_width_ = WindowFrameUtil::kWindows10GlassCaptionButtonWidth; diff --git a/shell/browser/ui/views/win_icon_painter.cc b/shell/browser/ui/views/win_icon_painter.cc new file mode 100644 index 0000000000000..d61a7970c6705 --- /dev/null +++ b/shell/browser/ui/views/win_icon_painter.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2022 Microsoft, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "shell/browser/ui/views/win_icon_painter.h" + +#include "base/numerics/safe_conversions.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" + +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); + canvas->DrawRect(rect_f, flags); +} + +void DrawRoundRect(gfx::Canvas* canvas, + const gfx::Rect& rect, + int radius, + const cc::PaintFlags& flags) { + gfx::RectF rect_f(rect); + float stroke_half_width = flags.getStrokeWidth() / 2; + rect_f.Inset(stroke_half_width); + canvas->DrawRoundRect(rect_f, radius, flags); +} + +// Draws a path containing the top and right edges of a rounded rectangle. +void DrawRoundRectEdges(gfx::Canvas* canvas, + const gfx::Rect& rect, + float radius, + const cc::PaintFlags& flags) { + gfx::RectF symbol_rect_f(rect); + float stroke_half_width = flags.getStrokeWidth() / 2; + symbol_rect_f.Inset(stroke_half_width); + SkPath path; + path.moveTo(symbol_rect_f.x(), symbol_rect_f.y()); + path.arcTo(symbol_rect_f.right(), symbol_rect_f.y(), symbol_rect_f.right(), + symbol_rect_f.y() + radius, radius); + path.lineTo(symbol_rect_f.right(), symbol_rect_f.bottom()); + canvas->DrawPath(path, flags); +} + +} // namespace + +namespace electron { + +WinIconPainter::WinIconPainter() = default; +WinIconPainter::~WinIconPainter() = default; + +void WinIconPainter::PaintMinimizeIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags) { + 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); +} + +void WinIconPainter::PaintMaximizeIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags) { + DrawRect(canvas, symbol_rect, flags); +} + +void WinIconPainter::PaintRestoreIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags) { + const int separation = std::floor(2 * canvas->image_scale()); + gfx::Rect icon_rect = symbol_rect; + icon_rect.Inset(gfx::Insets::TLBR(separation, 0, 0, separation)); + + // Bottom left ("in front") square. + DrawRect(canvas, icon_rect, flags); + + // Top right ("behind") square. + canvas->ClipRect(icon_rect, SkClipOp::kDifference); + icon_rect.Offset(separation, -separation); + DrawRect(canvas, icon_rect, flags); +} + +void WinIconPainter::PaintCloseIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags) { + // TODO(bsep): This sometimes draws misaligned at fractional device scales + // because the button's origin isn't necessarily aligned to pixels. + cc::PaintFlags paint_flags = flags; + paint_flags.setAntiAlias(true); + + 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); +} + +Win11IconPainter::Win11IconPainter() = default; +Win11IconPainter::~Win11IconPainter() = default; + +void Win11IconPainter::PaintMaximizeIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags) { + cc::PaintFlags paint_flags = flags; + paint_flags.setAntiAlias(true); + + const float corner_radius = 2 * canvas->image_scale(); + DrawRoundRect(canvas, symbol_rect, corner_radius, flags); +} + +void Win11IconPainter::PaintRestoreIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags) { + const int separation = std::floor(2 * canvas->image_scale()); + gfx::Rect icon_rect = symbol_rect; + icon_rect.Inset(gfx::Insets::TLBR(separation, 0, 0, separation)); + + cc::PaintFlags paint_flags = flags; + paint_flags.setAntiAlias(true); + + // Bottom left ("in front") rounded square. + const float bottom_rect_radius = 1 * canvas->image_scale(); + DrawRoundRect(canvas, icon_rect, bottom_rect_radius, flags); + + // Top right ("behind") top+right edges of rounded square (2.5x). + icon_rect.Offset(separation, -separation); + // Apply inset to left+bottom edges since we don't draw arcs for those edges + constexpr int top_rect_inset = 1; + icon_rect.Inset(gfx::Insets::TLBR(0, top_rect_inset, top_rect_inset, 0)); + + const float top_rect_radius = 2.5f * canvas->image_scale(); + DrawRoundRectEdges(canvas, icon_rect, top_rect_radius, flags); +} +} // namespace electron diff --git a/shell/browser/ui/views/win_icon_painter.h b/shell/browser/ui/views/win_icon_painter.h new file mode 100644 index 0000000000000..d3c85be296a0b --- /dev/null +++ b/shell/browser/ui/views/win_icon_painter.h @@ -0,0 +1,64 @@ +// Copyright (c) 2022 Microsoft, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_WIN_ICON_PAINTER_H_ +#define ELECTRON_SHELL_BROWSER_UI_VIEWS_WIN_ICON_PAINTER_H_ + +#include "base/memory/raw_ptr.h" +#include "ui/gfx/canvas.h" + +namespace electron { + +class WinIconPainter { + public: + WinIconPainter(); + virtual ~WinIconPainter(); + + WinIconPainter(const WinIconPainter&) = delete; + WinIconPainter& operator=(const WinIconPainter&) = delete; + + public: + // Paints the minimize icon for the button + virtual void PaintMinimizeIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags); + + // Paints the maximize icon for the button + virtual void PaintMaximizeIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags); + + // Paints the restore icon for the button + virtual void PaintRestoreIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags); + + // Paints the close icon for the button + virtual void PaintCloseIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags); +}; + +class Win11IconPainter : public WinIconPainter { + public: + Win11IconPainter(); + ~Win11IconPainter() override; + + Win11IconPainter(const Win11IconPainter&) = delete; + Win11IconPainter& operator=(const Win11IconPainter&) = delete; + + public: + // Paints the maximize icon for the button + void PaintMaximizeIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags) override; + + // Paints the restore icon for the button + void PaintRestoreIcon(gfx::Canvas* canvas, + const gfx::Rect& symbol_rect, + const cc::PaintFlags& flags) override; +}; +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_WIN_ICON_PAINTER_H_