Browse Source

feat: add WCO height option (#33064)

* feat: add WCO height option (#31222)

* feat: add WCO height option

* add docs and mac functionality

* add macOS functionality and height lowerbound

* Update docs/api/browser-window.md

Co-authored-by: John Kleinschmidt <[email protected]>

* update macOS functionality

* add chromium related notes

* add test and fix pixel under button bug and fix typo

* revert changes to docs/api/frameless-window.md

* modify `useCustomHeight` calls

* update `useCustomHeight` and `getCurrentMargin`

* modify margin calculation

* fix minimum custom height on macOS

* Update window_buttons_proxy.mm

* fix specified traffic light positions

Co-authored-by: John Kleinschmidt <[email protected]>

* Update win_caption_button.h

* Update win_caption_button_container.h

Co-authored-by: John Kleinschmidt <[email protected]>
Michaela Laurencin 3 years ago
parent
commit
bd31023314

+ 2 - 1
docs/api/browser-window.md

@@ -391,9 +391,10 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
       contain the layout of the document—without requiring scrolling. Enabling
       this will cause the `preferred-size-changed` event to be emitted on the
       `WebContents` when the preferred size changes. Default is `false`.
-  * `titleBarOverlay` Object | Boolean (optional) -  When using a frameless window in conjunction with `win.setWindowButtonVisibility(true)` on macOS or using a `titleBarStyle` so that the standard window controls ("traffic lights" on macOS) are visible, this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and [CSS Environment Variables][overlay-css-env-vars]. Specifying `true` will result in an overlay with default system colors. Default is `false`.
+  * `titleBarOverlay` Object | Boolean (optional) -  When using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` on macOS or using a `titleBarStyle` so that the standard window controls ("traffic lights" on macOS) are visible, this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and [CSS Environment Variables][overlay-css-env-vars]. Specifying `true` will result in an overlay with default system colors. Default is `false`.
     * `color` String (optional) _Windows_ - The CSS color of the Window Controls Overlay when enabled. Default is the system color.
     * `symbolColor` String (optional) _Windows_ - The CSS color of the symbols on the Window Controls Overlay when enabled. Default is the system color.
+    * `height` Integer (optional) _macOS_ _Windows_ - The height of the title bar and Window Controls Overlay in pixels. Default is system height.
 
 When setting minimum or maximum window size with `minWidth`/`maxWidth`/
 `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from

+ 9 - 1
shell/browser/native_window.cc

@@ -90,7 +90,15 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
       options.Get(options::ktitleBarOverlay, &titlebar_overlay_);
     } else if (titlebar_overlay->IsObject()) {
       titlebar_overlay_ = true;
-#if !defined(OS_WIN)
+
+      gin_helper::Dictionary titlebar_overlay =
+          gin::Dictionary::CreateEmpty(options.isolate());
+      options.Get(options::ktitleBarOverlay, &titlebar_overlay);
+      int height;
+      if (titlebar_overlay.Get(options::kOverlayHeight, &height))
+        titlebar_overlay_height_ = height;
+
+#if !(defined(OS_WIN) || defined(OS_MAC))
       DCHECK(false);
 #endif
     }

+ 5 - 0
shell/browser/native_window.h

@@ -323,6 +323,7 @@ class NativeWindow : public base::SupportsUserData,
     kCustomButtonsOnHover,
   };
   TitleBarStyle title_bar_style() const { return title_bar_style_; }
+  int titlebar_overlay_height() const { return titlebar_overlay_height_; }
 
   bool has_frame() const { return has_frame_; }
   void set_has_frame(bool has_frame) { has_frame_ = has_frame; }
@@ -358,6 +359,10 @@ class NativeWindow : public base::SupportsUserData,
   // The boolean parsing of the "titleBarOverlay" option
   bool titlebar_overlay_ = false;
 
+  // The custom height parsed from the "height" option in a Object
+  // "titleBarOverlay"
+  int titlebar_overlay_height_ = 0;
+
   // The "titleBarStyle" option.
   TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal;
 

+ 7 - 1
shell/browser/native_window_mac.mm

@@ -369,6 +369,7 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
       InternalSetWindowButtonVisibility(false);
     } else {
       buttons_proxy_.reset([[WindowButtonsProxy alloc] initWithWindow:window_]);
+      [buttons_proxy_ setHeight:titlebar_overlay_height()];
       if (traffic_light_position_) {
         [buttons_proxy_ setMargin:*traffic_light_position_];
       } else if (title_bar_style_ == TitleBarStyle::kHiddenInset) {
@@ -1839,7 +1840,12 @@ gfx::Rect NativeWindowMac::GetWindowControlsOverlayRect() {
     NSRect buttons = [buttons_proxy_ getButtonsContainerBounds];
     gfx::Rect overlay;
     overlay.set_width(GetContentSize().width() - NSWidth(buttons));
-    overlay.set_height(NSHeight(buttons));
+    if ([buttons_proxy_ useCustomHeight]) {
+      overlay.set_height(titlebar_overlay_height());
+    } else {
+      overlay.set_height(NSHeight(buttons));
+    }
+
     if (!base::i18n::IsRTL())
       overlay.set_x(NSMaxX(buttons));
     return overlay;

+ 1 - 2
shell/browser/native_window_views.cc

@@ -179,9 +179,8 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
   v8::Local<v8::Value> titlebar_overlay;
   if (options.Get(options::ktitleBarOverlay, &titlebar_overlay) &&
       titlebar_overlay->IsObject()) {
-    v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
     gin_helper::Dictionary titlebar_overlay_obj =
-        gin::Dictionary::CreateEmpty(isolate);
+        gin::Dictionary::CreateEmpty(options.isolate());
     options.Get(options::ktitleBarOverlay, &titlebar_overlay_obj);
 
     std::string overlay_color_string;

+ 6 - 0
shell/browser/ui/cocoa/window_buttons_proxy.h

@@ -30,6 +30,8 @@
   gfx::Point margin_;
   // The default left-top margin.
   gfx::Point default_margin_;
+  // Current height of the title bar container.
+  float height_;
 
   // Track mouse moves above window buttons.
   BOOL show_on_hover_;
@@ -49,6 +51,10 @@
 // Set left-top margin of the window buttons..
 - (void)setMargin:(const absl::optional<gfx::Point>&)margin;
 
+// Set height of button container
+- (void)setHeight:(const float)height;
+- (BOOL)useCustomHeight;
+
 // Return the bounds of all 3 buttons, with margin on all sides.
 - (NSRect)getButtonsContainerBounds;
 

+ 32 - 4
shell/browser/ui/cocoa/window_buttons_proxy.mm

@@ -36,6 +36,8 @@
 
   // Remember the default margin.
   margin_ = default_margin_ = [self getCurrentMargin];
+  // Custom height will be used if set larger than default
+  height_ = 0;
 
   return self;
 }
@@ -86,6 +88,17 @@
   [self redraw];
 }
 
+- (void)setHeight:(const float)height {
+  height_ = height;
+  [self redraw];
+}
+
+- (BOOL)useCustomHeight {
+  NSView* left = [self leftButton];
+  float button_height = NSHeight(left.frame);
+  return height_ > button_height + 2 * default_margin_.y();
+}
+
 - (NSRect)getButtonsContainerBounds {
   return NSInsetRect([self getButtonsBounds], -margin_.x(), -margin_.y());
 }
@@ -111,14 +124,18 @@
 
   NSRect cbounds = titleBarContainer.frame;
   cbounds.size.height = button_height + 2 * margin_.y();
+  // Custom height must be larger than the button height to use
+  if ([self useCustomHeight]) {
+    cbounds.size.height = height_;
+  }
   cbounds.origin.y = NSHeight(window_.frame) - NSHeight(cbounds);
   [titleBarContainer setFrame:cbounds];
 
-  [left setFrameOrigin:NSMakePoint(start, margin_.y())];
+  [left setFrameOrigin:NSMakePoint(start, [self getCurrentMargin].y())];
   start += button_width + padding;
-  [middle setFrameOrigin:NSMakePoint(start, margin_.y())];
+  [middle setFrameOrigin:NSMakePoint(start, [self getCurrentMargin].y())];
   start += button_width + padding;
-  [right setFrameOrigin:NSMakePoint(start, margin_.y())];
+  [right setFrameOrigin:NSMakePoint(start, [self getCurrentMargin].y())];
 
   if (hover_view_)
     [hover_view_ setFrame:[self getButtonsBounds]];
@@ -167,6 +184,7 @@
 - (NSRect)getButtonsBounds {
   NSView* left = [self leftButton];
   NSView* right = [self rightButton];
+
   return NSMakeRect(NSMinX(left.frame), NSMinY(left.frame),
                     NSMaxX(right.frame) - NSMinX(left.frame),
                     NSHeight(left.frame));
@@ -182,7 +200,17 @@
   NSView* left = [self leftButton];
   NSView* right = [self rightButton];
 
-  result.set_y((NSHeight(titleBarContainer.frame) - NSHeight(left.frame)) / 2);
+  if (height_ != 0) {
+    result.set_y((height_ - NSHeight(left.frame)) / 2);
+
+    // Do not center buttons if height and button position specified
+    if (margin_.y() != default_margin_.y())
+      result.set_y(height_ - NSHeight(left.frame) - margin_.y());
+
+  } else {
+    result.set_y((NSHeight(titleBarContainer.frame) - NSHeight(left.frame)) /
+                 2);
+  }
 
   if (base::i18n::IsRTL())
     result.set_x(NSWidth(window_.frame) - NSMaxX(right.frame));

+ 18 - 4
shell/browser/ui/views/win_caption_button.cc

@@ -2,13 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Modified from chrome/browser/ui/views/frame/windows_10_caption_button.cc
+
 #include "shell/browser/ui/views/win_caption_button.h"
 
 #include <utility>
 
 #include "base/i18n/rtl.h"
 #include "base/numerics/safe_conversions.h"
-#include "chrome/browser/ui/frame/window_frame_util.h"
 #include "chrome/grit/theme_resources.h"
 #include "shell/browser/ui/views/win_frame_view.h"
 #include "shell/common/color_util.h"
@@ -37,9 +38,8 @@ WinCaptionButton::WinCaptionButton(PressedCallback callback,
 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.
-  int height = WindowFrameUtil::kWindows10GlassCaptionButtonHeightRestored;
-  int base_width = WindowFrameUtil::kWindows10GlassCaptionButtonWidth;
-  return gfx::Size(base_width + GetBetweenButtonSpacing(), height);
+
+  return gfx::Size(base_width_ + GetBetweenButtonSpacing(), height_);
 }
 
 void WinCaptionButton::OnPaintBackground(gfx::Canvas* canvas) {
@@ -88,6 +88,20 @@ void WinCaptionButton::PaintButtonContents(gfx::Canvas* canvas) {
   PaintSymbol(canvas);
 }
 
+gfx::Size WinCaptionButton::GetSize() const {
+  return gfx::Size(base_width_, height_);
+}
+
+void WinCaptionButton::SetSize(gfx::Size size) {
+  int width = size.width();
+  int height = size.height();
+
+  if (width > 0)
+    base_width_ = width;
+  if (height > 0)
+    height_ = height;
+}
+
 int WinCaptionButton::GetBetweenButtonSpacing() const {
   const int display_order_index = GetButtonDisplayOrderIndex();
   return display_order_index == 0

+ 10 - 1
shell/browser/ui/views/win_caption_button.h

@@ -2,9 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Modified from chrome/browser/ui/views/frame/windows_10_caption_button.h
+
 #ifndef SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_
 #define SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_
 
+#include "chrome/browser/ui/frame/window_frame_util.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/gfx/canvas.h"
@@ -28,7 +31,10 @@ class WinCaptionButton : public views::Button {
   void OnPaintBackground(gfx::Canvas* canvas) override;
   void PaintButtonContents(gfx::Canvas* canvas) override;
 
-  //  private:
+  gfx::Size GetSize() const;
+  void SetSize(gfx::Size size);
+
+ private:
   // 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.
@@ -48,6 +54,9 @@ class WinCaptionButton : public views::Button {
 
   WinFrameView* frame_view_;
   ViewID button_type_;
+
+  int base_width_ = WindowFrameUtil::kWindows10GlassCaptionButtonWidth;
+  int height_ = WindowFrameUtil::kWindows10GlassCaptionButtonHeightRestored;
 };
 }  // namespace electron
 

+ 15 - 0
shell/browser/ui/views/win_caption_button_container.cc

@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Modified from
+// chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc
+
 #include "shell/browser/ui/views/win_caption_button_container.h"
 
 #include <memory>
@@ -96,6 +99,18 @@ int WinCaptionButtonContainer::NonClientHitTest(const gfx::Point& point) const {
   return HTCAPTION;
 }
 
+gfx::Size WinCaptionButtonContainer::GetButtonSize() const {
+  // Close button size is set the same as all the buttons
+  return close_button_->GetSize();
+}
+
+void WinCaptionButtonContainer::SetButtonSize(gfx::Size size) {
+  minimize_button_->SetSize(size);
+  maximize_button_->SetSize(size);
+  restore_button_->SetSize(size);
+  close_button_->SetSize(size);
+}
+
 void WinCaptionButtonContainer::ResetWindowControls() {
   minimize_button_->SetState(views::Button::STATE_NORMAL);
   maximize_button_->SetState(views::Button::STATE_NORMAL);

+ 6 - 0
shell/browser/ui/views/win_caption_button_container.h

@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Modified from
+// chrome/browser/ui/views/frame/glass_browser_caption_button_container.h
+
 #ifndef SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_CONTAINER_H_
 #define SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_CONTAINER_H_
 
@@ -35,6 +38,9 @@ class WinCaptionButtonContainer : public views::View,
   // See also ClientView::NonClientHitTest.
   int NonClientHitTest(const gfx::Point& point) const;
 
+  gfx::Size GetButtonSize() const;
+  void SetButtonSize(gfx::Size size);
+
  private:
   // views::View:
   void AddedToWidget() override;

+ 28 - 13
shell/browser/ui/views/win_frame_view.cc

@@ -193,13 +193,20 @@ int WinFrameView::TitlebarMaximizedVisualHeight() const {
   return maximized_height;
 }
 
-int WinFrameView::TitlebarHeight(bool restored) const {
-  if (frame()->IsFullscreen() && !restored)
+// NOTE(@mlaurencin): Usage of IsWebUITabStrip simplified out from Chromium
+int WinFrameView::TitlebarHeight(int custom_height) const {
+  if (frame()->IsFullscreen() && !IsMaximized())
     return 0;
 
-  return TitlebarMaximizedVisualHeight() + FrameTopBorderThickness(false);
+  int height = TitlebarMaximizedVisualHeight() +
+               FrameTopBorderThickness(false) - WindowTopY();
+  if (custom_height > TitlebarMaximizedVisualHeight())
+    height = custom_height - WindowTopY();
+
+  return height;
 }
 
+// NOTE(@mlaurencin): Usage of IsWebUITabStrip simplified out from Chromium
 int WinFrameView::WindowTopY() const {
   // The window top is SM_CYSIZEFRAME pixels when maximized (see the comment in
   // FrameTopBorderThickness()) and floor(system dsf) pixels when restored.
@@ -222,27 +229,35 @@ void WinFrameView::LayoutCaptionButtons() {
   }
 
   caption_button_container_->SetVisible(true);
-
   const gfx::Size preferred_size =
       caption_button_container_->GetPreferredSize();
-  int height = preferred_size.height();
 
-  height = IsMaximized() ? TitlebarMaximizedVisualHeight()
-                         : TitlebarHeight(false) - WindowTopY();
+  int custom_height = window()->titlebar_overlay_height();
+  int height = TitlebarHeight(custom_height);
 
-  // TODO(mlaurencin): This -1 creates a 1 pixel gap between the right
-  // edge of the overlay and the edge of the window, allowing for this edge
-  // portion to return the correct hit test and be manually resized properly.
-  // Alternatives can be explored, but the differences in view structures
-  // between Electron and Chromium may result in this as the best option.
+  // TODO(mlaurencin): This -1 creates a 1 pixel margin between the right
+  // edge of the button container and the edge of the window, allowing for this
+  // edge portion to return the correct hit test and be manually resized
+  // properly. Alternatives can be explored, but the differences in view
+  // structures between Electron and Chromium may result in this as the best
+  // option.
   int variable_width =
       IsMaximized() ? preferred_size.width() : preferred_size.width() - 1;
   caption_button_container_->SetBounds(width() - preferred_size.width(),
                                        WindowTopY(), variable_width, height);
+
+  // Needed for heights larger than default
+  caption_button_container_->SetButtonSize(gfx::Size(0, height));
 }
 
 void WinFrameView::LayoutWindowControlsOverlay() {
-  int overlay_height = caption_button_container_->size().height();
+  int overlay_height = window()->titlebar_overlay_height();
+  if (overlay_height == 0) {
+    // Accounting for the 1 pixel margin at the top of the button container
+    overlay_height = IsMaximized()
+                         ? caption_button_container_->size().height()
+                         : caption_button_container_->size().height() + 1;
+  }
   int overlay_width = caption_button_container_->size().width();
   int bounding_rect_width = width() - overlay_width;
   auto bounding_rect =

+ 1 - 1
shell/browser/ui/views/win_frame_view.h

@@ -67,7 +67,7 @@ class WinFrameView : public FramelessView {
 
   // Returns the height of the titlebar for popups or other browser types that
   // don't have tabs.
-  int TitlebarHeight(bool restored) const;
+  int TitlebarHeight(int custom_height) const;
 
   // Returns the y coordinate for the top of the frame, which in maximized mode
   // is the top of the screen and in restored mode is 1 pixel below the top of

+ 3 - 0
shell/common/options_switches.cc

@@ -36,6 +36,9 @@ const char kRoundedCorners[] = "roundedCorners";
 const char kOverlayButtonColor[] = "color";
 const char kOverlaySymbolColor[] = "symbolColor";
 
+// The custom height for Window Controls Overlay.
+const char kOverlayHeight[] = "height";
+
 // Whether the window should show in taskbar.
 const char kSkipTaskbar[] = "skipTaskbar";
 

+ 1 - 0
shell/common/options_switches.h

@@ -60,6 +60,7 @@ extern const char kRoundedCorners[];
 extern const char ktitleBarOverlay[];
 extern const char kOverlayButtonColor[];
 extern const char kOverlaySymbolColor[];
+extern const char kOverlayHeight[];
 
 // WebPreferences.
 extern const char kZoomFactor[];

+ 50 - 0
spec-main/api-browser-window-spec.ts

@@ -1988,6 +1988,56 @@ describe('BrowserWindow module', () => {
     });
   });
 
+  ifdescribe(process.platform === 'win32' || (process.platform === 'darwin' && semver.gte(os.release(), '14.0.0')))('"titleBarOverlay" option', () => {
+    const testWindowsOverlayHeight = async (size: any) => {
+      const w = new BrowserWindow({
+        show: false,
+        width: 400,
+        height: 400,
+        titleBarStyle: 'hidden',
+        webPreferences: {
+          nodeIntegration: true,
+          contextIsolation: false
+        },
+        titleBarOverlay: {
+          height: size
+        }
+      });
+      const overlayHTML = path.join(__dirname, 'fixtures', 'pages', 'overlay.html');
+      if (process.platform === 'darwin') {
+        await w.loadFile(overlayHTML);
+      } else {
+        const overlayReady = emittedOnce(ipcMain, 'geometrychange');
+        await w.loadFile(overlayHTML);
+        await overlayReady;
+      }
+      const overlayEnabled = await w.webContents.executeJavaScript('navigator.windowControlsOverlay.visible');
+      expect(overlayEnabled).to.be.true('overlayEnabled');
+      const overlayRectPreMax = await w.webContents.executeJavaScript('getJSOverlayProperties()');
+      await w.maximize();
+      const max = await w.isMaximized();
+      expect(max).to.equal(true);
+      const overlayRectPostMax = await w.webContents.executeJavaScript('getJSOverlayProperties()');
+
+      expect(overlayRectPreMax.y).to.equal(0);
+      if (process.platform === 'darwin') {
+        expect(overlayRectPreMax.x).to.be.greaterThan(0);
+      } else {
+        expect(overlayRectPreMax.x).to.equal(0);
+      }
+      expect(overlayRectPreMax.width).to.be.greaterThan(0);
+
+      expect(overlayRectPreMax.height).to.equal(size);
+      // Confirm that maximization only affected the height of the buttons and not the title bar
+      expect(overlayRectPostMax.height).to.equal(size);
+    };
+    afterEach(closeAllWindows);
+    afterEach(() => { ipcMain.removeAllListeners('geometrychange'); });
+    it('sets Window Control Overlay with title bar height of 40', async () => {
+      await testWindowsOverlayHeight(40);
+    });
+  });
+
   ifdescribe(process.platform === 'darwin')('"enableLargerThanScreen" option', () => {
     afterEach(closeAllWindows);
     it('can move the window out of screen', () => {