Skip to content

Commit

Permalink
feat: enable windows control overlay on Windows (#30678)
Browse files Browse the repository at this point in the history
cherry-picked from 41646d1

Co-Authored-By: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com>

Co-authored-by: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com>
  • Loading branch information
jkleinsc and mlaurencin committed Aug 30, 2021
1 parent d73539b commit 9cc2d3f
Show file tree
Hide file tree
Showing 24 changed files with 950 additions and 67 deletions.
3 changes: 3 additions & 0 deletions chromium_src/BUILD.gn
Expand Up @@ -64,8 +64,11 @@ static_library("chrome") {
"//chrome/browser/extensions/global_shortcut_listener_win.cc",
"//chrome/browser/extensions/global_shortcut_listener_win.h",
"//chrome/browser/icon_loader_win.cc",
"//chrome/browser/ui/frame/window_frame_util.h",
"//chrome/browser/ui/view_ids.h",
"//chrome/browser/win/chrome_process_finder.cc",
"//chrome/browser/win/chrome_process_finder.h",
"//chrome/browser/win/titlebar_config.h",
"//chrome/child/v8_crashpad_support_win.cc",
"//chrome/child/v8_crashpad_support_win.h",
]
Expand Down
18 changes: 6 additions & 12 deletions docs/api/browser-window.md
Expand Up @@ -213,16 +213,13 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
* `followWindow` - The backdrop should automatically appear active when the window is active, and inactive when it is not. This is the default.
* `active` - The backdrop should always appear active.
* `inactive` - The backdrop should always appear inactive.
* `titleBarStyle` String (optional) - The style of window title bar.
* `titleBarStyle` String (optional) _macOS_ _Windows_ - The style of window title bar.
Default is `default`. Possible values are:
* `default` - Results in the standard gray opaque Mac title
bar.
* `hidden` - Results in a hidden title bar and a full size content window, yet
the title bar still has the standard window controls ("traffic lights") in
the top left.
* `hiddenInset` - Results in a hidden title bar with an alternative look
* `default` - Results in the standard title bar for macOS or Windows respectively.
* `hidden` - Results in a hidden title bar and a full size content window. On macOS, the window still has the standard window controls (“traffic lights”) in the top left. On Windows, when combined with `titleBarOverlay: true` it will activate the Window Controls Overlay (see `titleBarOverlay` for more information), otherwise no window controls will be shown.
* `hiddenInset` - Only on macOS, results in a hidden title bar with an alternative look
where the traffic light buttons are slightly more inset from the window edge.
* `customButtonsOnHover` - Results in a hidden title bar and a full size
* `customButtonsOnHover` - Only on macOS, results in a hidden title bar and a full size
content window, the traffic light buttons will display when being hovered
over in the top left of the window. **Note:** This option is currently
experimental.
Expand Down Expand Up @@ -392,10 +389,7 @@ 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` Boolean (optional) - On macOS, when using a frameless window in conjunction with
`win.setWindowButtonVisibility(true)` or using a `titleBarStyle` so that the traffic lights are visible,
this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and
[CSS Environment Variables][overlay-css-env-vars]. Default is `false`.
* `titleBarOverlay` [OverlayOptions](structures/overlay-options.md) | 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`. On Windows, the [OverlayOptions](structures/overlay-options.md) can be used instead of a boolean to specify colors for the overlay.

When setting minimum or maximum window size with `minWidth`/`maxWidth`/
`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from
Expand Down
32 changes: 24 additions & 8 deletions docs/api/frameless-window.md
Expand Up @@ -18,24 +18,26 @@ const win = new BrowserWindow({ width: 800, height: 600, frame: false })
win.show()
```

### Alternatives on macOS
### Alternatives

There's an alternative way to specify a chromeless window.
There's an alternative way to specify a chromeless window on macOS and Windows.
Instead of setting `frame` to `false` which disables both the titlebar and window controls,
you may want to have the title bar hidden and your content extend to the full window size,
yet still preserve the window controls ("traffic lights") for standard window actions.
yet still preserve the window controls ("traffic lights" on macOS) for standard window actions.
You can do so by specifying the `titleBarStyle` option:

#### `hidden`

Results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls (“traffic lights”) in the top left.
Results in a hidden title bar and a full size content window. On macOS, the title bar still has the standard window controls (“traffic lights”) in the top left.

```javascript
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ titleBarStyle: 'hidden' })
win.show()
```

### Alternatives on macOS

#### `hiddenInset`

Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge.
Expand Down Expand Up @@ -63,19 +65,33 @@ win.show()

## Windows Control Overlay

On macOS, when using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` or using one of the `titleBarStyle`s described above so
that the traffic lights are visible, you can access the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and
[CSS Environment Variables][overlay-css-env-vars] by setting the `titleBarOverlay` option to true:
When using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` on macOS, using one of the `titleBarStyle`s as described above so
that the traffic lights are visible, or using `titleBarStyle: hidden` on Windows, you can access the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and
[CSS Environment Variables][overlay-css-env-vars] by setting the `titleBarOverlay` option to true. Specifying `true` will result in an overlay with default system colors.

On Windows, you can also specify the color of the overlay and its symbols by setting `titleBarOverlay` to an object with the options `color` and `symbolColor`. If an option is not specified, the color will default to its system color for the window control buttons:

```javascript
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
titleBarStyle: 'hiddenInset',
titleBarStyle: 'hidden',
titleBarOverlay: true
})
win.show()
```

```javascript
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
titleBarStyle: 'hidden',
titleBarOverlay: {
color: '#2f3241',
symbolColor: '#74b1be'
}
})
win.show()
```

## Transparent window

By setting the `transparent` option to `true`, you can also make the frameless
Expand Down
4 changes: 4 additions & 0 deletions docs/api/structures/overlay-options.md
@@ -0,0 +1,4 @@
# OverlayOptions Object

* `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.
14 changes: 14 additions & 0 deletions electron_strings.grdp
@@ -1,5 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<grit-part>
<!-- Windows Caption Buttons -->
<message name="IDS_APP_ACCNAME_CLOSE" desc="The accessible name for the Close button.">
Close
</message>
<message name="IDS_APP_ACCNAME_MINIMIZE" desc="The accessible name for the Minimize button.">
Minimize
</message>
<message name="IDS_APP_ACCNAME_MAXIMIZE" desc="The accessible name for the Maximize button.">
Maximize
</message>
<message name="IDS_APP_ACCNAME_RESTORE" desc="The accessible name for the Restore button.">
Restore
</message>

<!-- Printing Service -->
<message name="IDS_UTILITY_PROCESS_PRINTING_SERVICE_NAME" desc="The name of the utility process used for printing conversions.">
Printing Service
Expand Down
1 change: 1 addition & 0 deletions filenames.auto.gni
Expand Up @@ -100,6 +100,7 @@ auto_filenames = {
"docs/api/structures/new-window-web-contents-event.md",
"docs/api/structures/notification-action.md",
"docs/api/structures/notification-response.md",
"docs/api/structures/overlay-options.md",
"docs/api/structures/point.md",
"docs/api/structures/post-body.md",
"docs/api/structures/printer-info.md",
Expand Down
4 changes: 4 additions & 0 deletions filenames.gni
Expand Up @@ -90,6 +90,10 @@ filenames = {
"shell/browser/ui/views/electron_views_delegate_win.cc",
"shell/browser/ui/views/win_frame_view.cc",
"shell/browser/ui/views/win_frame_view.h",
"shell/browser/ui/views/win_caption_button.cc",
"shell/browser/ui/views/win_caption_button.h",
"shell/browser/ui/views/win_caption_button_container.cc",
"shell/browser/ui/views/win_caption_button_container.h",
"shell/browser/ui/win/dialog_thread.cc",
"shell/browser/ui/win/dialog_thread.h",
"shell/browser/ui/win/electron_desktop_native_widget_aura.cc",
Expand Down
42 changes: 41 additions & 1 deletion shell/browser/native_window.cc
Expand Up @@ -24,6 +24,34 @@
#include "ui/display/win/screen_win.h"
#endif

namespace gin {

template <>
struct Converter<electron::NativeWindow::TitleBarStyle> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
electron::NativeWindow::TitleBarStyle* out) {
using TitleBarStyle = electron::NativeWindow::TitleBarStyle;
std::string title_bar_style;
if (!ConvertFromV8(isolate, val, &title_bar_style))
return false;
if (title_bar_style == "hidden") {
*out = TitleBarStyle::kHidden;
#if defined(OS_MAC)
} else if (title_bar_style == "hiddenInset") {
*out = TitleBarStyle::kHiddenInset;
} else if (title_bar_style == "customButtonsOnHover") {
*out = TitleBarStyle::kCustomButtonsOnHover;
#endif
} else {
return false;
}
return true;
}
};

} // namespace gin

namespace electron {

namespace {
Expand Down Expand Up @@ -54,7 +82,19 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
options.Get(options::kFrame, &has_frame_);
options.Get(options::kTransparent, &transparent_);
options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_);
options.Get(options::ktitleBarOverlay, &titlebar_overlay_);
options.Get(options::kTitleBarStyle, &title_bar_style_);

v8::Local<v8::Value> titlebar_overlay;
if (options.Get(options::ktitleBarOverlay, &titlebar_overlay)) {
if (titlebar_overlay->IsBoolean()) {
options.Get(options::ktitleBarOverlay, &titlebar_overlay_);
} else if (titlebar_overlay->IsObject()) {
titlebar_overlay_ = true;
#if !defined(OS_WIN)
DCHECK(false);
#endif
}
}

if (parent)
options.Get("modal", &is_modal_);
Expand Down
12 changes: 12 additions & 0 deletions shell/browser/native_window.h
Expand Up @@ -316,6 +316,14 @@ class NativeWindow : public base::SupportsUserData,
views::Widget* widget() const { return widget_.get(); }
views::View* content_view() const { return content_view_; }

enum class TitleBarStyle {
kNormal,
kHidden,
kHiddenInset,
kCustomButtonsOnHover,
};
TitleBarStyle title_bar_style() const { return title_bar_style_; }

bool has_frame() const { return has_frame_; }
void set_has_frame(bool has_frame) { has_frame_ = has_frame; }

Expand Down Expand Up @@ -347,8 +355,12 @@ class NativeWindow : public base::SupportsUserData,
[&browser_view](NativeBrowserView* n) { return (n == browser_view); });
}

// The boolean parsing of the "titleBarOverlay" option
bool titlebar_overlay_ = false;

// The "titleBarStyle" option.
TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal;

private:
std::unique_ptr<views::Widget> widget_;

Expand Down
11 changes: 0 additions & 11 deletions shell/browser/native_window_mac.h
Expand Up @@ -184,14 +184,6 @@ class NativeWindowMac : public NativeWindow,
kInactive,
};

enum class TitleBarStyle {
kNormal,
kHidden,
kHiddenInset,
kCustomButtonsOnHover,
};
TitleBarStyle title_bar_style() const { return title_bar_style_; }

ElectronPreviewItem* preview_item() const { return preview_item_.get(); }
ElectronTouchBar* touch_bar() const { return touch_bar_.get(); }
bool zoom_to_page_width() const { return zoom_to_page_width_; }
Expand Down Expand Up @@ -249,9 +241,6 @@ class NativeWindowMac : public NativeWindow,
// The presentation options before entering kiosk mode.
NSApplicationPresentationOptions kiosk_options_;

// The "titleBarStyle" option.
TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal;

// The "visualEffectState" option.
VisualEffectState visual_effect_state_ = VisualEffectState::kFollowWindow;

Expand Down
23 changes: 0 additions & 23 deletions shell/browser/native_window_mac.mm
Expand Up @@ -163,28 +163,6 @@ - (void)drawRect:(NSRect)dirtyRect {

namespace gin {

template <>
struct Converter<electron::NativeWindowMac::TitleBarStyle> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
electron::NativeWindowMac::TitleBarStyle* out) {
using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
std::string title_bar_style;
if (!ConvertFromV8(isolate, val, &title_bar_style))
return false;
if (title_bar_style == "hidden") {
*out = TitleBarStyle::kHidden;
} else if (title_bar_style == "hiddenInset") {
*out = TitleBarStyle::kHiddenInset;
} else if (title_bar_style == "customButtonsOnHover") {
*out = TitleBarStyle::kCustomButtonsOnHover;
} else {
return false;
}
return true;
}
};

template <>
struct Converter<electron::NativeWindowMac::VisualEffectState> {
static bool FromV8(v8::Isolate* isolate,
Expand Down Expand Up @@ -274,7 +252,6 @@ void ViewDidMoveToSuperview(NSView* self, SEL _cmd) {

bool resizable = true;
options.Get(options::kResizable, &resizable);
options.Get(options::kTitleBarStyle, &title_bar_style_);
options.Get(options::kZoomToPageWidth, &zoom_to_page_width_);
options.Get(options::kSimpleFullScreen, &always_simple_fullscreen_);
options.GetOptional(options::kTrafficLightPosition, &traffic_light_position_);
Expand Down
33 changes: 33 additions & 0 deletions shell/browser/native_window_views.cc
Expand Up @@ -71,12 +71,14 @@

#elif defined(OS_WIN)
#include "base/win/win_util.h"
#include "extensions/common/image_util.h"
#include "shell/browser/ui/views/win_frame_view.h"
#include "shell/browser/ui/win/electron_desktop_native_widget_aura.h"
#include "skia/ext/skia_utils_win.h"
#include "ui/base/win/shell.h"
#include "ui/display/screen.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/color_utils.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif

Expand Down Expand Up @@ -165,6 +167,37 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
options.Get("thickFrame", &thick_frame_);
if (transparent())
thick_frame_ = false;

overlay_button_color_ = color_utils::GetSysSkColor(COLOR_BTNFACE);
overlay_symbol_color_ = color_utils::GetSysSkColor(COLOR_BTNTEXT);

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);
options.Get(options::ktitleBarOverlay, &titlebar_overlay_obj);

std::string overlay_color_string;
if (titlebar_overlay_obj.Get(options::kOverlayButtonColor,
&overlay_color_string)) {
bool success = extensions::image_util::ParseCssColorString(
overlay_color_string, &overlay_button_color_);
DCHECK(success);
}

std::string overlay_symbol_color_string;
if (titlebar_overlay_obj.Get(options::kOverlaySymbolColor,
&overlay_symbol_color_string)) {
bool success = extensions::image_util::ParseCssColorString(
overlay_symbol_color_string, &overlay_symbol_color_);
DCHECK(success);
}
}

if (title_bar_style_ != TitleBarStyle::kNormal)
set_has_frame(false);
#endif

if (enable_larger_than_screen())
Expand Down
15 changes: 15 additions & 0 deletions shell/browser/native_window_views.h
Expand Up @@ -19,6 +19,7 @@
#if defined(OS_WIN)
#include "base/win/scoped_gdi_object.h"
#include "shell/browser/ui/win/taskbar_host.h"

#endif

namespace views {
Expand Down Expand Up @@ -175,6 +176,15 @@ class NativeWindowViews : public NativeWindow,
TaskbarHost& taskbar_host() { return taskbar_host_; }
#endif

#if defined(OS_WIN)
bool IsWindowControlsOverlayEnabled() const {
return (title_bar_style_ == NativeWindowViews::TitleBarStyle::kHidden) &&
titlebar_overlay_;
}
SkColor overlay_button_color() const { return overlay_button_color_; }
SkColor overlay_symbol_color() const { return overlay_symbol_color_; }
#endif

private:
// views::WidgetObserver:
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
Expand Down Expand Up @@ -293,6 +303,11 @@ class NativeWindowViews : public NativeWindow,

// Whether the window is currently being moved.
bool is_moving_ = false;

// The color to use as the theme and symbol colors respectively for Window
// Controls Overlay if enabled on Windows.
SkColor overlay_button_color_;
SkColor overlay_symbol_color_;
#endif

// Handles unhandled keyboard messages coming back from the renderer process.
Expand Down

0 comments on commit 9cc2d3f

Please sign in to comment.