diff options
Diffstat (limited to 'app/window-controller.js')
| -rw-r--r-- | app/window-controller.js | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/app/window-controller.js b/app/window-controller.js new file mode 100644 index 0000000000..849da007cf --- /dev/null +++ b/app/window-controller.js @@ -0,0 +1,160 @@ +// @flow + +import electron, { screen } from 'electron'; +import type { BrowserWindow, Tray, Display } from 'electron'; + +export default class WindowController { + _window: BrowserWindow; + _tray: Tray; + _isWindowReady = false; + + get window(): BrowserWindow { + return this._window; + } + + constructor(window: BrowserWindow, tray: Tray) { + this._window = window; + this._tray = tray; + + this._installDisplayMetricsHandler(); + this._installWindowReadyHandlers(); + } + + show(whenReady: boolean = true) { + if (whenReady) { + this._executeWhenWindowIsReady(() => this._showImmediately()); + } else { + this._showImmediately(); + } + } + + hide() { + this._window.hide(); + } + + toggle() { + if (this._window.isVisible()) { + this.hide(); + } else { + this.show(); + } + } + + _showImmediately() { + const window = this._window; + + this._updatePosition(); + + window.show(); + window.focus(); + } + + _updatePosition() { + const { x, y } = this._getWindowPosition(); + this._window.setPosition(x, y, false); + } + + _getTrayPlacement() { + switch (process.platform) { + case 'darwin': + // macOS has menubar always placed at the top + return 'top'; + + case 'win32': { + // taskbar occupies some part of the screen excluded from work area + const primaryDisplay = electron.screen.getPrimaryDisplay(); + const displaySize = primaryDisplay.size; + const workArea = primaryDisplay.workArea; + + if (workArea.width < displaySize.width) { + return workArea.x > 0 ? 'left' : 'right'; + } else if (workArea.height < displaySize.height) { + return workArea.y > 0 ? 'top' : 'bottom'; + } else { + return 'none'; + } + } + + default: + return 'none'; + } + } + + _getWindowPosition(): { x: number, y: number } { + const windowBounds = this._window.getBounds(); + const trayBounds = this._tray.getBounds(); + + const primaryDisplay = electron.screen.getPrimaryDisplay(); + const workArea = primaryDisplay.workArea; + const placement = this._getTrayPlacement(); + const maxX = workArea.x + workArea.width - windowBounds.width; + const maxY = workArea.y + workArea.height - windowBounds.height; + + let x = 0, + y = 0; + switch (placement) { + case 'top': + x = trayBounds.x + (trayBounds.width - windowBounds.width) * 0.5; + y = workArea.y; + break; + + case 'bottom': + x = trayBounds.x + (trayBounds.width - windowBounds.width) * 0.5; + y = workArea.y + workArea.height - windowBounds.height; + break; + + case 'left': + x = workArea.x; + y = trayBounds.y + (trayBounds.height - windowBounds.height) * 0.5; + break; + + case 'right': + x = workArea.width - windowBounds.width; + y = trayBounds.y + (trayBounds.height - windowBounds.height) * 0.5; + break; + + case 'none': + x = workArea.x + (workArea.width - windowBounds.width) * 0.5; + y = workArea.y + (workArea.height - windowBounds.height) * 0.5; + break; + } + + x = Math.min(Math.max(x, workArea.x), maxX); + y = Math.min(Math.max(y, workArea.y), maxY); + + return { + x: Math.round(x), + y: Math.round(y), + }; + } + + // Installs display event handlers to update the window position on any changes in the display or workarea dimensions. + _installDisplayMetricsHandler() { + screen.addListener('display-metrics-changed', this._onDisplayMetricsChanged); + this._window.once('closed', () => { + screen.removeListener('display-metrics-changed', this._onDisplayMetricsChanged); + }); + } + + _onDisplayMetricsChanged = (_event: any, _display: Display, changedMetrics: Array<string>) => { + if (changedMetrics.includes('workArea') && this._window.isVisible()) { + this._updatePosition(); + } + }; + + _installWindowReadyHandlers() { + this._window.once('ready-to-show', () => { + this._isWindowReady = true; + }); + } + + _executeWhenWindowIsReady(closure: () => any) { + if (this._isWindowReady) { + closure(); + } else { + this._window.once('ready-to-show', () => { + closure(); + }); + } + } +} |
