Browse Source

fix: update Windows caption buttons to match Win11 style (#34790)

Shelley Vohr 2 years ago
parent
commit
95019f0454

+ 2 - 0
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",

+ 20 - 42
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<WinIconPainter> WinCaptionButton::CreateIconPainter() {
+  if (base::win::GetVersion() >= base::win::Version::WIN11) {
+    return std::make_unique<Win11IconPainter>();
+  }
+  return std::make_unique<WinIconPainter>();
+}
+
 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;
     }
 

+ 7 - 0
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 <memory>
+
 #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<WinIconPainter> 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<WinIconPainter> icon_painter_;
   ViewID button_type_;
 
   int base_width_ = WindowFrameUtil::kWindows10GlassCaptionButtonWidth;

+ 143 - 0
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

+ 64 - 0
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_