Skip to content

Commit 0cd8c42

Browse files
authoredJul 22, 2021
feat(windows): Customizable title bar style (#3765)
__Issue:__ There are several quirks on Windows with the 'undecorated' Windows style - issues like: - #3730 - #3071 - #3063 __Fix:__ These can be deal-breakers for use as a daily editor, so until we have those fixed for the 'undecorated' Windows, change the default setting to use the native titlebar. This introduces a new setting - `window.titleBarStyle` that can be `"native"` or `"custom"`. The `"custom"` titlebar looks nicer, because it is themed and custom rendered, however it has the above quirks. On Windows, change the default to `"native"`. In addition, this pushes up the configuration loading sooner in the startup cycle, so we can pick up configuration settings like `"window.titleBarStyle"` prior to opening the window. __Todo:__ - [x] Fix margin on Windows, based on whether we are using the decorated window or not - [x] Test OSX - [x] Test Windows (both settings) - [x] Test on Linux (both settings)
1 parent 8e0298e commit 0cd8c42

File tree

12 files changed

+150
-42
lines changed

12 files changed

+150
-42
lines changed
 

‎CHANGES_CURRENT.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
- #3718 - Completion: Add `editor.suggest.itemsToShow` setting (fixes #3712)
99
- #3736 - Search: add default keys to go to next / previous search result (fixes #3713)
1010
- #3733 - Quick Open: Add bindings to open in splits, not current buffer.
11+
- #3765 - UX: Add `"window.titleBarStyle"` configuration setting
12+
13+
> __BREAKING:__ On Windows, the default setting is to use the `"native"` title bar.
14+
> Set `"window.titleBarStyle": "custom"` to keep the previous behavior.
1115
1216
### Bug Fixes
1317

‎bench/lib/Helpers.re

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ let simpleState = {
3939
~titlebarHeight=0.,
4040
~getZoom=() => 1.0,
4141
~setZoom=_zoom => (),
42+
~useNativeTitleBar=true,
4243
);
4344

4445
Reducer.reduce(

‎docs/docs/configuration/settings.md

+2
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ The configuration file, `configuration.json` is in the Oni2 directory, whose loc
181181

182182
- `window.menuBarVisibility` __(_"visible" | "hidden"_ default: `"visible"`)__ - Controls the visibility of the menu bar.
183183

184+
- `window.titleBarStyle` __(_"native" | "custom"_ default: `"native"` on Windows, `"custom"` otherwise)__ - Controls whether the titlebar is custom-rendered.
185+
184186
- `oni.layout.showLayoutTabs` __(_"always"|"smart"|"never"_ default: `"smart"`)__ - Controls the display of layout tabs. `"smart"` will only show the tabs if there's more than one.
185187

186188
- `oni.layout.layoutTabPosition` __(_"top"|"bottom"_ default: `"bottom"`)__ - Controls the position of the layout tabs.

‎integration_test/lib/Oni_IntegrationTestLib.re

+1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ let runTest =
190190
~titlebarHeight=0.,
191191
~setZoom,
192192
~getZoom,
193+
~useNativeTitleBar=true,
193194
),
194195
);
195196

‎src/Feature/Configuration/Feature_Configuration.rei

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module ConfigurationLoader: {
1111
let none: t;
1212

1313
let file: FpExp.t(FpExp.absolute) => t;
14+
15+
let loadImmediate: t => result(Config.Settings.t, string);
1416
};
1517

1618
let initial:

‎src/Feature/Configuration/GlobalConfiguration.re

+34
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ module Decoders = {
4646
),
4747
),
4848
]);
49+
50+
let titleBarStyle: decoder([ | `Native | `Custom]) =
51+
string
52+
|> map(String.lowercase_ascii)
53+
|> map(
54+
fun
55+
| "native" => `Native
56+
| "custom" => `Custom
57+
| _ => `Custom,
58+
);
4959
};
5060

5161
module Encoders = {
@@ -57,6 +67,11 @@ module Encoders = {
5767
| `None => string("none")
5868
| `Inline => string("inline")
5969
};
70+
71+
let titleBarStyle: encoder([ | `Native | `Custom]) =
72+
fun
73+
| `Native => string("native")
74+
| `Custom => string("custom");
6075
};
6176

6277
module Codecs = {
@@ -292,6 +307,24 @@ module Search = {
292307
let followSymlinks = setting("search.followSymlinks", bool, ~default=true);
293308
};
294309

310+
module Window = {
311+
// On Windows, default the titlebar style to native, due to various bugs around the custom-rendered window, like:
312+
// #3730 - Keyboard shortcuts for window movement broken
313+
// #3071 - Window has no shadow on Windows
314+
// #3063 - Part of onivim fullscreen is visible on second monitor
315+
let defaultTitleBarStyle =
316+
switch (Revery.Environment.os) {
317+
| Windows(_) => `Native
318+
| _ => `Custom
319+
};
320+
let titleBarStyle =
321+
setting(
322+
"window.titleBarStyle",
323+
custom(~decode=Decoders.titleBarStyle, ~encode=Encoders.titleBarStyle),
324+
~default=defaultTitleBarStyle,
325+
);
326+
};
327+
295328
module Workbench = {
296329
let activityBarVisible =
297330
setting("workbench.activityBar.visible", bool, ~default=true);
@@ -319,6 +352,7 @@ let contributions = [
319352
Editor.snippetSuggestions.spec,
320353
Files.exclude.spec,
321354
Explorer.autoReveal.spec,
355+
Window.titleBarStyle.spec,
322356
Workbench.activityBarVisible.spec,
323357
Workbench.editorShowTabs.spec,
324358
Workbench.editorEnablePreview.spec,

‎src/Feature/TitleBar/Feature_TitleBar.re

+20-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ type msg =
1515
| WindowCloseClicked
1616
| TitleDoubleClicked;
1717

18+
type model = {
19+
// We need to store this, as opposed to pulling it
20+
// from config, because we need to respect the value
21+
// used on initialization.
22+
useNativeTitleBar: bool,
23+
};
24+
25+
let isNative = ({useNativeTitleBar, _}) => useNativeTitleBar;
26+
27+
let initial = (~useNativeTitleBar) => {useNativeTitleBar: useNativeTitleBar};
28+
1829
module Log = (val Log.withNamespace("Oni2.Feature.TitleBar"));
1930

2031
module Internal = {
@@ -155,7 +166,7 @@ type outmsg =
155166
| Nothing
156167
| Effect(Isolinear.Effect.t(msg));
157168

158-
let update = (~maximize, ~minimize, ~restore, ~close, msg) => {
169+
let update = (~maximize, ~minimize, ~restore, ~close, msg, model) => {
159170
let internalDoubleClickEffect =
160171
Isolinear.Effect.create(~name="window.doubleClick", () => {
161172
switch (Internal.getTitleDoubleClickBehavior()) {
@@ -174,11 +185,11 @@ let update = (~maximize, ~minimize, ~restore, ~close, msg) => {
174185
Isolinear.Effect.create(~name="window.restore", () => restore());
175186

176187
switch (msg) {
177-
| TitleDoubleClicked => Effect(internalDoubleClickEffect)
178-
| WindowCloseClicked => Effect(internalWindowCloseEffect)
179-
| WindowMaximizeClicked => Effect(internalWindowMaximizeEffect)
180-
| WindowRestoreClicked => Effect(internalWindowRestoreEffect)
181-
| WindowMinimizeClicked => Effect(internalWindowMinimizeEffect)
188+
| TitleDoubleClicked => (model, Effect(internalDoubleClickEffect))
189+
| WindowCloseClicked => (model, Effect(internalWindowCloseEffect))
190+
| WindowMaximizeClicked => (model, Effect(internalWindowMaximizeEffect))
191+
| WindowRestoreClicked => (model, Effect(internalWindowRestoreEffect))
192+
| WindowMinimizeClicked => (model, Effect(internalWindowMinimizeEffect))
182193
};
183194
};
184195

@@ -537,11 +548,13 @@ module View = {
537548
~theme,
538549
~font: UiFont.t,
539550
~height,
551+
~model,
540552
(),
541553
) => {
542554
let title =
543555
title(~activeBuffer, ~workspaceRoot, ~workspaceDirectory, ~config);
544556
switch (Revery.Environment.os) {
557+
| Mac(_) when isNative(model) => React.empty
545558
| Mac({major, _}) =>
546559
<Mac
547560
isFocused
@@ -555,6 +568,7 @@ module View = {
555568
height
556569
majorVersion=major
557570
/>
571+
| Windows(_) when isNative(model) => menuBar
558572
| Windows(_) =>
559573
<Windows
560574
menuBar

‎src/Feature/TitleBar/Feature_TitleBar.rei

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ type windowDisplayMode =
88
| Maximized
99
| Fullscreen;
1010

11+
type model;
12+
13+
let initial: (~useNativeTitleBar: bool) => model;
14+
15+
let isNative: model => bool;
16+
1117
[@deriving show]
1218
type msg;
1319

@@ -32,9 +38,10 @@ let update:
3238
~minimize: unit => unit,
3339
~restore: unit => unit,
3440
~close: unit => unit,
35-
msg
41+
msg,
42+
model
3643
) =>
37-
outmsg;
44+
(model, outmsg);
3845

3946
// VIEW
4047

@@ -54,6 +61,7 @@ module View: {
5461
~theme: ColorTheme.Colors.t,
5562
~font: UiFont.t,
5663
~height: float,
64+
~model: model,
5765
unit
5866
) =>
5967
Revery.UI.element;

‎src/Model/State.re

+3
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ type t = {
493493
modal: option(Feature_Modals.model),
494494
snippets: Feature_Snippets.model,
495495
textContentProviders: list((int, string)),
496+
titleBar: Feature_TitleBar.model,
496497
vim: Feature_Vim.model,
497498
zoom: Feature_Zoom.model,
498499
autoUpdate: Feature_AutoUpdate.model,
@@ -516,6 +517,7 @@ let initial =
516517
~titlebarHeight,
517518
~getZoom,
518519
~setZoom,
520+
~useNativeTitleBar,
519521
) => {
520522
let config =
521523
Feature_Configuration.initial(
@@ -668,6 +670,7 @@ let initial =
668670
quickOpen: Feature_QuickOpen.initial,
669671
snippets: Feature_Snippets.initial,
670672
terminals: Feature_Terminal.initial,
673+
titleBar: Feature_TitleBar.initial(~useNativeTitleBar),
671674
textContentProviders: [],
672675
vim: Feature_Vim.initial,
673676
zoom: Feature_Zoom.initial(~getZoom, ~setZoom),

‎src/Store/Features.re

+14-10
Original file line numberDiff line numberDiff line change
@@ -2542,21 +2542,25 @@ let update =
25422542
);
25432543

25442544
| TitleBar(titleBarMsg) =>
2545+
let (titleBar', outmsg) =
2546+
Feature_TitleBar.update(
2547+
~maximize,
2548+
~minimize,
2549+
~close,
2550+
~restore,
2551+
titleBarMsg,
2552+
state.titleBar,
2553+
);
25452554
let eff =
2546-
switch (
2547-
Feature_TitleBar.update(
2548-
~maximize,
2549-
~minimize,
2550-
~close,
2551-
~restore,
2552-
titleBarMsg,
2553-
)
2554-
) {
2555+
switch (outmsg) {
25552556
| Feature_TitleBar.Effect(effect) => effect
25562557
| Feature_TitleBar.Nothing => Isolinear.Effect.none
25572558
};
25582559

2559-
(state, eff |> Isolinear.Effect.map(msg => TitleBar(msg)));
2560+
(
2561+
{...state, titleBar: titleBar'},
2562+
eff |> Isolinear.Effect.map(msg => TitleBar(msg)),
2563+
);
25602564

25612565
| ExtensionBufferUpdateQueued({triggerKey}) =>
25622566
let maybeBuffer = Selectors.getActiveBuffer(state);

‎src/UI/Root.re

+8-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module Constants = {
2020
module Styles = {
2121
open Style;
2222

23-
let root = (theme, windowDisplayMode) => {
23+
let root = (~nativeTitleBar, theme, windowDisplayMode) => {
2424
let style =
2525
ref([
2626
backgroundColor(Colors.Editor.background.from(theme)),
@@ -33,7 +33,9 @@ module Styles = {
3333
justifyContent(`Center),
3434
alignItems(`Stretch),
3535
]);
36-
if (Revery.Environment.isWindows && windowDisplayMode == State.Maximized) {
36+
if (Revery.Environment.isWindows
37+
&& windowDisplayMode == State.Maximized
38+
&& !nativeTitleBar) {
3739
style := [margin(6), ...style^];
3840
};
3941
style^;
@@ -252,7 +254,9 @@ let make = (~dispatch, ~state: State.t, ()) => {
252254
// Correct for zoom in title bar height
253255
let titlebarHeight = state.titlebarHeight /. zoom;
254256

255-
<View style={Styles.root(theme, state.windowDisplayMode)}>
257+
let nativeTitleBar = Feature_TitleBar.isNative(state.titleBar);
258+
259+
<View style={Styles.root(~nativeTitleBar, theme, state.windowDisplayMode)}>
256260
<Feature_TitleBar.View
257261
menuBar=menuBarElement
258262
activeBuffer=maybeActiveBuffer
@@ -267,6 +271,7 @@ let make = (~dispatch, ~state: State.t, ()) => {
267271
dispatch=titleDispatch
268272
registrationDispatch
269273
height=titlebarHeight
274+
model={state.titleBar}
270275
/>
271276
<View style=Styles.workspace>
272277
<View style=Styles.surface>

0 commit comments

Comments
 (0)
Please sign in to comment.