updated window docking logic

This commit is contained in:
Eugene Pankov
2021-12-15 23:21:56 +01:00
parent 899484a5d9
commit 0e9723cb3b
6 changed files with 78 additions and 47 deletions

View File

@@ -23,6 +23,7 @@ export class Application {
private windows: Window[] = [] private windows: Window[] = []
private globalHotkey$ = new Subject<void>() private globalHotkey$ = new Subject<void>()
private quitRequested = false private quitRequested = false
private configStore: any
userPluginsPath: string userPluginsPath: string
constructor () { constructor () {
@@ -32,6 +33,7 @@ export class Application {
ipcMain.on('app:config-change', (_event, config) => { ipcMain.on('app:config-change', (_event, config) => {
this.broadcast('host:config-change', config) this.broadcast('host:config-change', config)
this.configStore = config
}) })
ipcMain.on('app:register-global-hotkey', (_event, specs) => { ipcMain.on('app:register-global-hotkey', (_event, specs) => {
@@ -61,10 +63,10 @@ export class Application {
} }
}) })
const configData = loadConfig() this.configStore = loadConfig()
if (process.platform === 'linux') { if (process.platform === 'linux') {
app.commandLine.appendSwitch('no-sandbox') app.commandLine.appendSwitch('no-sandbox')
if (((configData.appearance || {}).opacity || 1) !== 1) { if (((this.configStore.appearance || {}).opacity || 1) !== 1) {
app.commandLine.appendSwitch('enable-transparent-visuals') app.commandLine.appendSwitch('enable-transparent-visuals')
app.disableHardwareAcceleration() app.disableHardwareAcceleration()
} }
@@ -84,7 +86,7 @@ export class Application {
app.commandLine.appendSwitch('lang', 'EN') app.commandLine.appendSwitch('lang', 'EN')
app.allowRendererProcessReuse = false app.allowRendererProcessReuse = false
for (const flag of configData.flags || [['force_discrete_gpu', '0']]) { for (const flag of this.configStore.flags || [['force_discrete_gpu', '0']]) {
app.commandLine.appendSwitch(flag[0], flag[1]) app.commandLine.appendSwitch(flag[0], flag[1])
} }
@@ -104,6 +106,9 @@ export class Application {
async newWindow (options?: WindowOptions): Promise<Window> { async newWindow (options?: WindowOptions): Promise<Window> {
const window = new Window(this, options) const window = new Window(this, options)
this.windows.push(window) this.windows.push(window)
if (this.windows.length === 1){
window.makeMain()
}
window.visible$.subscribe(visible => { window.visible$.subscribe(visible => {
if (visible) { if (visible) {
this.disableTray() this.disableTray()
@@ -113,16 +118,28 @@ export class Application {
}) })
window.closed$.subscribe(() => { window.closed$.subscribe(() => {
this.windows = this.windows.filter(x => x !== window) this.windows = this.windows.filter(x => x !== window)
if (!this.windows.some(x => x.isMainWindow)) {
this.windows[0]?.makeMain()
this.windows[0]?.present()
}
}) })
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
this.setupMenu() this.setupMenu()
} }
await window.ready await window.ready
window.present()
return window return window
} }
onGlobalHotkey (): void { onGlobalHotkey (): void {
if (this.windows.some(x => x.isFocused() && x.isVisible())) { let isPresent = this.windows.some(x => x.isFocused() && x.isVisible())
const isDockedOnTop = this.windows.some(x => x.isDockedOnTop())
if (isDockedOnTop) {
// if docked and on top, hide even if not focused right now
isPresent = this.windows.some(x => x.isVisible())
}
if (isPresent) {
for (const window of this.windows) { for (const window of this.windows) {
window.hide() window.hide()
} }
@@ -191,7 +208,7 @@ export class Application {
focus (): void { focus (): void {
for (const window of this.windows) { for (const window of this.windows) {
window.show() window.present()
} }
} }

View File

@@ -32,6 +32,7 @@ const activityIcon = nativeImage.createFromPath(`${app.getAppPath()}/assets/acti
export class Window { export class Window {
ready: Promise<void> ready: Promise<void>
isMainWindow = false
private visible = new Subject<boolean>() private visible = new Subject<boolean>()
private closed = new Subject<void>() private closed = new Subject<void>()
private window?: GlasstronWindow private window?: GlasstronWindow
@@ -157,6 +158,11 @@ export class Window {
}) })
} }
makeMain () {
this.isMainWindow = true
this.window.webContents.send('host:became-main-window')
}
setVibrancy (enabled: boolean, type?: string, userRequested?: boolean): void { setVibrancy (enabled: boolean, type?: string, userRequested?: boolean): void {
if (userRequested ?? true) { if (userRequested ?? true) {
this.lastVibrancy = { enabled, type } this.lastVibrancy = { enabled, type }
@@ -181,11 +187,6 @@ export class Window {
} }
} }
show (): void {
this.window.show()
this.window.moveTop()
}
focus (): void { focus (): void {
this.window.focus() this.window.focus()
} }
@@ -197,6 +198,7 @@ export class Window {
this.window.webContents.send(event, ...args) this.window.webContents.send(event, ...args)
if (event === 'host:config-change') { if (event === 'host:config-change') {
this.configStore = args[0] this.configStore = args[0]
this.enableDockedWindowStyles(this.isDockedOnTop())
} }
} }
@@ -212,45 +214,53 @@ export class Window {
return this.window.isVisible() return this.window.isVisible()
} }
hide (): void { isDockedOnTop (): boolean {
return this.isMainWindow && this.configStore.appearance?.dock && this.configStore.appearance?.dock !== 'off' && (this.configStore.appearance?.dockAlwaysOnTop ?? true)
}
async hide (): Promise<void> {
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
// Lose focus // Lose focus
Menu.sendActionToFirstResponder('hide:') Menu.sendActionToFirstResponder('hide:')
} if (this.isDockedOnTop()) {
this.window.blur() await this.enableDockedWindowStyles(false)
if (process.platform !== 'darwin') {
this.window.hide()
}
}
present (): void {
if (!this.window.isVisible()) {
// unfocused, invisible
this.window.show()
this.window.focus()
} else {
if (!this.configStore.appearance?.dock || this.configStore.appearance?.dock === 'off') {
// not docked, visible
setTimeout(() => {
this.window.show()
this.window.focus()
})
} else {
if (this.configStore.appearance?.dockAlwaysOnTop) {
// docked, visible, on top
this.window.hide()
} else {
// docked, visible, not on top
this.window.focus()
}
} }
} }
this.window.blur()
this.window.hide()
}
async show (): Promise<void> {
await this.enableDockedWindowStyles(this.isDockedOnTop())
this.window.show()
this.window.focus()
}
async present (): Promise<void> {
await this.show()
this.window.moveTop()
} }
passCliArguments (argv: string[], cwd: string, secondInstance: boolean): void { passCliArguments (argv: string[], cwd: string, secondInstance: boolean): void {
this.send('cli', parseArgs(argv, cwd), cwd, secondInstance) this.send('cli', parseArgs(argv, cwd), cwd, secondInstance)
} }
private async enableDockedWindowStyles (enabled: boolean) {
if (process.platform === 'darwin') {
if (enabled) {
app.dock.hide()
this.window.setAlwaysOnTop(true, 'screen-saver', 1)
this.window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true })
this.window.setFullScreenable(false)
} else {
await app.dock.show()
this.window.setAlwaysOnTop(false)
this.window.setVisibleOnAllWorkspaces(false)
this.window.setFullScreenable(true)
}
}
}
private setupWindowManagement () { private setupWindowManagement () {
this.window.on('show', () => { this.window.on('show', () => {
this.visible.next(true) this.visible.next(true)
@@ -312,7 +322,7 @@ export class Window {
config: this.configStore, config: this.configStore,
executable: app.getPath('exe'), executable: app.getPath('exe'),
windowID: this.window.id, windowID: this.window.id,
isFirstWindow: this.window.id === 1, isMainWindow: this.isMainWindow,
userPluginsPath: this.application.userPluginsPath, userPluginsPath: this.application.userPluginsPath,
}) })
}) })
@@ -359,8 +369,7 @@ export class Window {
if (this.window.isMinimized()) { if (this.window.isMinimized()) {
this.window.restore() this.window.restore()
} }
this.window.show() this.present()
this.window.moveTop()
}) })
ipcMain.on('window-close', event => { ipcMain.on('window-close', event => {

View File

@@ -16,7 +16,7 @@ export interface PluginInfo {
export interface BootstrapData { export interface BootstrapData {
config: Record<string, any> config: Record<string, any>
executable: string executable: string
isFirstWindow: boolean isMainWindow: boolean
windowID: number windowID: number
installedPlugins: PluginInfo[] installedPlugins: PluginInfo[]
userPluginsPath: string userPluginsPath: string

View File

@@ -89,7 +89,7 @@ export class AppService {
}, 30000) }, 30000)
config.ready$.toPromise().then(async () => { config.ready$.toPromise().then(async () => {
if (this.bootstrapData.isFirstWindow) { if (this.bootstrapData.isMainWindow) {
if (config.store.recoverTabs) { if (config.store.recoverTabs) {
const tabs = await this.tabRecovery.recoverTabs() const tabs = await this.tabRecovery.recoverTabs()
for (const tab of tabs) { for (const tab of tabs) {
@@ -115,7 +115,7 @@ export class AppService {
this.tabsChanged.next() this.tabsChanged.next()
this.tabOpened.next(tab) this.tabOpened.next(tab)
if (this.bootstrapData.isFirstWindow) { if (this.bootstrapData.isMainWindow) {
tab.recoveryStateChangedHint$.subscribe(() => { tab.recoveryStateChangedHint$.subscribe(() => {
this.tabRecovery.saveTabs(this.tabs) this.tabRecovery.saveTabs(this.tabs)
}) })

View File

@@ -1,6 +1,6 @@
import { Injectable, NgZone } from '@angular/core' import { Injectable, NgZone, Inject } from '@angular/core'
import type { Display } from 'electron' import type { Display } from 'electron'
import { ConfigService, DockingService, Screen, PlatformService } from 'tabby-core' import { ConfigService, DockingService, Screen, PlatformService, BootstrapData, BOOTSTRAP_DATA } from 'tabby-core'
import { ElectronService } from '../services/electron.service' import { ElectronService } from '../services/electron.service'
import { ElectronHostWindow, Bounds } from './hostWindow.service' import { ElectronHostWindow, Bounds } from './hostWindow.service'
@@ -12,6 +12,7 @@ export class ElectronDockingService extends DockingService {
private zone: NgZone, private zone: NgZone,
private hostWindow: ElectronHostWindow, private hostWindow: ElectronHostWindow,
platform: PlatformService, platform: PlatformService,
@Inject(BOOTSTRAP_DATA) private bootstrapData: BootstrapData,
) { ) {
super() super()
this.screensChanged$.subscribe(() => this.repositionWindow()) this.screensChanged$.subscribe(() => this.repositionWindow())
@@ -25,7 +26,7 @@ export class ElectronDockingService extends DockingService {
dock (): void { dock (): void {
const dockSide = this.config.store.appearance.dock const dockSide = this.config.store.appearance.dock
if (dockSide === 'off') { if (dockSide === 'off' || !this.bootstrapData.isMainWindow) {
this.hostWindow.setAlwaysOnTop(false) this.hostWindow.setAlwaysOnTop(false)
return return
} }

View File

@@ -43,6 +43,10 @@ export class ElectronHostWindow extends HostWindowService {
electron.ipcRenderer.on('host:window-focused', () => zone.run(() => { electron.ipcRenderer.on('host:window-focused', () => zone.run(() => {
this.windowFocused.next() this.windowFocused.next()
})) }))
electron.ipcRenderer.on('host:became-main-window', () => zone.run(() => {
this.bootstrapData.isMainWindow = true
}))
} }
getWindow (): BrowserWindow { getWindow (): BrowserWindow {