diff --git a/app/scripts/comp/launcher/launcher-electron.js b/app/scripts/comp/launcher/launcher-electron.js index 5e20e63f..fb563e2d 100644 --- a/app/scripts/comp/launcher/launcher-electron.js +++ b/app/scripts/comp/launcher/launcher-electron.js @@ -312,6 +312,18 @@ const Launcher = { }, setGlobalShortcuts(appSettings) { this.remoteApp().setGlobalShortcuts(appSettings); + }, + minimizeMainWindow() { + this.getMainWindow().minimize(); + }, + maximizeMainWindow() { + this.getMainWindow().maximize(); + }, + restoreMainWindow() { + this.getMainWindow().restore(); + }, + mainWindowMaximized() { + return this.getMainWindow().isMaximized(); } }; @@ -319,6 +331,8 @@ Events.on('launcher-exit-request', () => { setTimeout(() => Launcher.exit(), 0); }); Events.on('launcher-minimize', () => setTimeout(() => Events.emit('app-minimized'), 0)); +Events.on('launcher-maximize', () => setTimeout(() => Events.emit('app-maximized'), 0)); +Events.on('launcher-unmaximize', () => setTimeout(() => Events.emit('app-unmaximized'), 0)); Events.on('launcher-started-minimized', () => setTimeout(() => Launcher.minimizeApp(), 0)); Events.on('start-profile', (data) => StartProfiler.reportAppProfile(data)); Events.on('log', (e) => new Logger(e.category || 'remote-app')[e.method || 'info'](e.message)); diff --git a/app/scripts/util/features.js b/app/scripts/util/features.js index 33734ab1..23c48882 100644 --- a/app/scripts/util/features.js +++ b/app/scripts/util/features.js @@ -18,7 +18,10 @@ const Features = { isLocal: location.origin.indexOf('localhost') >= 0, supportsTitleBarStyles() { - return this.isMac; + return isDesktop && (this.isMac || this.isWindows); + }, + renderCustomTitleBar() { + return isDesktop && this.isWindows; }, hasUnicodeFlags() { return this.isMac; diff --git a/app/scripts/views/app-view.js b/app/scripts/views/app-view.js index bd4f7fd6..2d862882 100644 --- a/app/scripts/views/app-view.js +++ b/app/scripts/views/app-view.js @@ -25,6 +25,7 @@ import { OpenView } from 'views/open-view'; import { SettingsView } from 'views/settings/settings-view'; import { TagView } from 'views/tag-view'; import { ImportCsvView } from 'views/import-csv-view'; +import { TitlebarButtonsView } from 'views/titlebar-buttons-view'; import template from 'templates/app.hbs'; class AppView extends View { @@ -45,6 +46,9 @@ class AppView extends View { constructor(model) { super(model); + + this.titlebarStyle = this.model.settings.titlebarStyle; + this.views.menu = new MenuView(this.model.menu, { ownParent: true }); this.views.menuDrag = new DragView('x', { parent: '.app__menu-drag' }); this.views.footer = new FooterView(this.model, { ownParent: true }); @@ -54,12 +58,13 @@ class AppView extends View { this.views.list.dragView = this.views.listDrag; this.views.details = new DetailsView(undefined, { ownParent: true }); this.views.details.appModel = this.model; + if (this.titlebarStyle !== 'default' && Features.renderCustomTitleBar()) { + this.views.titlebarButtons = new TitlebarButtonsView(this.model); + } this.views.menu.listenDrag(this.views.menuDrag); this.views.list.listenDrag(this.views.listDrag); - this.titlebarStyle = this.model.settings.titlebarStyle; - this.listenTo(this.model.settings, 'change:theme', this.setTheme); this.listenTo(this.model.settings, 'change:locale', this.setLocale); this.listenTo(this.model.settings, 'change:fontSize', this.setFontSize); @@ -120,6 +125,9 @@ class AppView extends View { } if (this.titlebarStyle !== 'default') { document.body.classList.add('titlebar-' + this.titlebarStyle); + if (Features.renderCustomTitleBar()) { + document.body.classList.add('titlebar-custom'); + } } if (Features.isMobile) { document.body.classList.add('mobile'); @@ -129,7 +137,8 @@ class AppView extends View { render() { super.render({ beta: this.model.isBeta, - titlebarStyle: this.titlebarStyle + titlebarStyle: this.titlebarStyle, + customTitlebar: Features.renderCustomTitleBar() }); this.panelEl = this.$el.find('.app__panel:first'); this.views.listWrap.render(); @@ -139,6 +148,7 @@ class AppView extends View { this.views.list.render(); this.views.listDrag.render(); this.views.details.render(); + this.views.titlebarButtons?.render(); this.showLastOpenFile(); } diff --git a/app/scripts/views/settings/settings-general-view.js b/app/scripts/views/settings/settings-general-view.js index 968c1544..88ae3bd3 100644 --- a/app/scripts/views/settings/settings-general-view.js +++ b/app/scripts/views/settings/settings-general-view.js @@ -136,7 +136,7 @@ class SettingsGeneralView extends View { directAutotype: AppSettingsModel.directAutotype, fieldLabelDblClickAutoType: AppSettingsModel.fieldLabelDblClickAutoType, useLegacyAutoType: AppSettingsModel.useLegacyAutoType, - supportsTitleBarStyles: Launcher && Features.supportsTitleBarStyles(), + supportsTitleBarStyles: Features.supportsTitleBarStyles(), titlebarStyle: AppSettingsModel.titlebarStyle, storageProviders, showReloadApp: Features.isStandalone, diff --git a/app/scripts/views/titlebar-buttons-view.js b/app/scripts/views/titlebar-buttons-view.js new file mode 100644 index 00000000..4b3ca6f1 --- /dev/null +++ b/app/scripts/views/titlebar-buttons-view.js @@ -0,0 +1,60 @@ +import { View } from 'framework/views/view'; +import { Events } from 'framework/events'; +import { Launcher } from 'comp/launcher'; +import template from 'templates/titlebar-buttons.hbs'; + +class TitlebarButtonsView extends View { + parent = '.app__titlebar'; + + template = template; + + events = { + 'click .titlebar-buttons-minimize': 'clickMinimize', + 'click .titlebar-buttons-maximize': 'clickMaximize', + 'click .titlebar-buttons-restore': 'clickRestore', + 'click .titlebar-buttons-close': 'clickClose' + }; + + constructor() { + super(); + + this.maximized = Launcher.mainWindowMaximized(); + + this.listenTo(Events, 'app-maximized', this.appMaximized); + this.listenTo(Events, 'app-unmaximized', this.appUnmaximized); + } + + render() { + super.render({ + maximized: this.maximized + }); + } + + clickMinimize() { + Launcher.minimizeMainWindow(); + } + + clickMaximize() { + Launcher.maximizeMainWindow(); + } + + clickRestore() { + Launcher.restoreMainWindow(); + } + + clickClose() { + window.close(); + } + + appMaximized() { + this.maximized = true; + this.render(); + } + + appUnmaximized() { + this.maximized = false; + this.render(); + } +} + +export { TitlebarButtonsView }; diff --git a/app/styles/areas/_app.scss b/app/styles/areas/_app.scss index 8bc05e36..e0c2d703 100644 --- a/app/styles/areas/_app.scss +++ b/app/styles/areas/_app.scss @@ -28,6 +28,18 @@ pointer-events: none; } + &__titlebar { + .titlebar-custom & { + position: fixed; + top: 0; + left: 0; + width: 100%; + -webkit-app-region: drag; + display: flex; + justify-content: flex-end; + } + } + &__menu { flex: 0 0 auto; display: flex; @@ -123,6 +135,9 @@ display: flex; } } + .titlebar-custom & { + margin-top: $custom-titlebar-height; + } } &__panel { diff --git a/app/styles/areas/_titlebar-buttons.scss b/app/styles/areas/_titlebar-buttons.scss new file mode 100644 index 00000000..25b06d94 --- /dev/null +++ b/app/styles/areas/_titlebar-buttons.scss @@ -0,0 +1,19 @@ +.titlebar-buttons { + font-size: 0; + + > .fa { + -webkit-app-region: no-drag; + font-size: 16px; + padding: 4px 16px; + height: $custom-titlebar-height; + box-sizing: border-box; + &:hover { + background: var(--titlebar-button-background-color); + } + &.fa-titlebar-close { + &:hover { + background: $titlebar-close-button-background-color; + } + } + } +} diff --git a/app/styles/base/_icon-font.scss b/app/styles/base/_icon-font.scss index e8e7eb60..6878988c 100644 --- a/app/styles/base/_icon-font.scss +++ b/app/styles/base/_icon-font.scss @@ -196,3 +196,7 @@ $fa-var-at: next-fa-glyph(); $fa-var-usb-token: next-fa-glyph(); $fa-var-bell: next-fa-glyph(); $fa-var-fingerprint: next-fa-glyph(); +$fa-var-titlebar-close: next-fa-glyph(); +$fa-var-titlebar-maximize: next-fa-glyph(); +$fa-var-titlebar-minimize: next-fa-glyph(); +$fa-var-titlebar-restore: next-fa-glyph(); diff --git a/app/styles/base/_theme-vars.scss b/app/styles/base/_theme-vars.scss index 6889543c..4e915444 100644 --- a/app/styles/base/_theme-vars.scss +++ b/app/styles/base/_theme-vars.scss @@ -69,7 +69,8 @@ selectable-on-secondary-item-color: mix(map-get($t, medium-color), map-get($t, background-color), 14%), clickable-on-secondary-color: - mix(map-get($t, medium-color), map-get($t, background-color), 75%) + mix(map-get($t, medium-color), map-get($t, background-color), 75%), + titlebar-button-background-color: rgba(map-get($t, text-color), 0.085) ), $t ); diff --git a/app/styles/base/_variables.scss b/app/styles/base/_variables.scss index 8b23c1e6..bc2cab32 100644 --- a/app/styles/base/_variables.scss +++ b/app/styles/base/_variables.scss @@ -84,3 +84,7 @@ $z-index-modal: 100000; // Screen sizes $tablet-width: 736px; $mobile-width: 620px; + +// Title bar and window buttons +$custom-titlebar-height: 32px; +$titlebar-close-button-background-color: #d71525; diff --git a/app/styles/main.scss b/app/styles/main.scss index bf00cae9..8a8a24e0 100644 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -38,3 +38,4 @@ $fa-font-path: '~font-awesome/fonts'; @import 'areas/open'; @import 'areas/settings'; @import 'areas/import-csv'; +@import 'areas/titlebar-buttons'; diff --git a/app/templates/app.hbs b/app/templates/app.hbs index 67a65b9f..84d76a3a 100644 --- a/app/templates/app.hbs +++ b/app/templates/app.hbs @@ -1,6 +1,10 @@
{{#if beta}}
{{res 'appBeta'}}
{{/if}} - {{#ifeq titlebarStyle 'hidden'}}
{{/ifeq}} + {{#if customTitlebar}} +
+ {{else}} + {{#ifeq titlebarStyle 'hidden'}}
{{/ifeq}} + {{/if}}
diff --git a/app/templates/titlebar-buttons.hbs b/app/templates/titlebar-buttons.hbs new file mode 100644 index 00000000..e21f4647 --- /dev/null +++ b/app/templates/titlebar-buttons.hbs @@ -0,0 +1,9 @@ +
+ + {{#if maximized}} + + {{else}} + + {{/if}} + +
diff --git a/desktop/main.js b/desktop/main.js index 9b99735d..2ff2d4de 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -263,6 +263,9 @@ function createMainWindow() { theme = selectDarkOrLightTheme(theme); } const bgColor = themeBgColors[theme] || defaultBgColor; + const frameless = + process.platform === 'win32' && + ['hidden', 'hidden-inset'].includes(appSettings.titlebarStyle); const windowOptions = { show: false, width: 1000, @@ -270,6 +273,7 @@ function createMainWindow() { minWidth: 700, minHeight: 400, titleBarStyle: appSettings.titlebarStyle, + frame: !frameless, backgroundColor: bgColor, webPreferences: { contextIsolation: false, @@ -324,9 +328,11 @@ function createMainWindow() { }); mainWindow.on('maximize', () => { mainWindowMaximized = true; + emitRemoteEvent('launcher-maximize'); }); mainWindow.on('unmaximize', () => { mainWindowMaximized = false; + emitRemoteEvent('launcher-unmaximize'); }); mainWindow.on('leave-full-screen', () => { emitRemoteEvent('leave-full-screen'); diff --git a/graphics/svg/titlebar-close.svg b/graphics/svg/titlebar-close.svg new file mode 100644 index 00000000..1f3e831d --- /dev/null +++ b/graphics/svg/titlebar-close.svg @@ -0,0 +1 @@ + diff --git a/graphics/svg/titlebar-maximize.svg b/graphics/svg/titlebar-maximize.svg new file mode 100644 index 00000000..5f1b0785 --- /dev/null +++ b/graphics/svg/titlebar-maximize.svg @@ -0,0 +1 @@ + diff --git a/graphics/svg/titlebar-minimize.svg b/graphics/svg/titlebar-minimize.svg new file mode 100644 index 00000000..ae84617f --- /dev/null +++ b/graphics/svg/titlebar-minimize.svg @@ -0,0 +1 @@ + diff --git a/graphics/svg/titlebar-restore.svg b/graphics/svg/titlebar-restore.svg new file mode 100644 index 00000000..7dc275ab --- /dev/null +++ b/graphics/svg/titlebar-restore.svg @@ -0,0 +1 @@ + diff --git a/release-notes.md b/release-notes.md index 9e07a3de..dd169653 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,11 +2,12 @@ Release notes ------------- ##### v1.17.0 (TBD) `+` opening files with Touch ID on macOS -`+` password quality warnings +`+` password quality warnings `+` "Have I Been Pwned" service integration (opt-in) `+` automatically switching between dark and light theme +`+` custom title bar on Windows `*` new updater capable to upgrade major versions -`+` clear searchbox button +`+` clear searchbox button `+` more options for auto-lock timeout `+` favicon download improvements `+` auto-type field selection dropdown improvements