Skip to content

Commit

Permalink
electron: Use frameless window
Browse files Browse the repository at this point in the history
This adds some charm to Cockpit.
  • Loading branch information
rafaellehmkuhl committed Jan 17, 2025
1 parent 662d7f4 commit 0b53f64
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 5 deletions.
28 changes: 27 additions & 1 deletion electron/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { app, BrowserWindow, protocol, screen } from 'electron'
import { app, BrowserWindow, ipcMain, protocol, screen } from 'electron'
import logger from 'electron-log'
import { join } from 'path'

Expand Down Expand Up @@ -30,6 +30,7 @@ function createWindow(): void {
nodeIntegration: false,
},
autoHideMenuBar: true,
frame: false,
width: store.get('windowBounds')?.width ?? screen.getPrimaryDisplay().workAreaSize.width,
height: store.get('windowBounds')?.height ?? screen.getPrimaryDisplay().workAreaSize.height,
x: store.get('windowBounds')?.x ?? screen.getPrimaryDisplay().bounds.x,
Expand All @@ -47,6 +48,31 @@ function createWindow(): void {
} else {
mainWindow.loadFile(join(ROOT_PATH.dist, 'index.html'))
}

// Window control handlers
ipcMain.on('minimize-window', () => {
mainWindow?.minimize()
})

ipcMain.on('maximize-window', () => {
if (mainWindow && mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow?.maximize()
}
})

ipcMain.on('close-window', () => {
mainWindow?.close()
})

ipcMain.handle('is-maximized', () => {
return mainWindow?.isMaximized()
})

ipcMain.handle('get-platform', () => {
return process.platform
})
}

app.on('window-all-closed', () => {
Expand Down
5 changes: 5 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
},
openCockpitFolder: () => ipcRenderer.invoke('open-cockpit-folder'),
openVideoFolder: () => ipcRenderer.invoke('open-video-folder'),
minimizeWindow: () => ipcRenderer.send('minimize-window'),
maximizeWindow: () => ipcRenderer.send('maximize-window'),
closeWindow: () => ipcRenderer.send('close-window'),
isMaximized: () => ipcRenderer.invoke('is-maximized'),
getPlatform: () => ipcRenderer.invoke('get-platform'),
})
10 changes: 8 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@
interfaceStore.isOnSmallScreen && interfaceStore.isOnSmallScreen ? topBarScaleStyle : undefined,
]"
>
<WindowControls v-if="isElectron()" />
<button
v-if="interfaceStore.mainMenuStyleTrigger === 'burger'"
class="flex items-center justify-center h-full mr-2 aspect-square top-bar-hamburger"
Expand All @@ -255,15 +256,15 @@
align="start"
/>
</div>
<div class="grow" />
<div class="grow window-dragger" />
<div class="flex-1">
<MiniWidgetContainer
:container="widgetStore.currentMiniWidgetsProfile.containers[1]"
:allow-editing="widgetStore.editingMode"
align="center"
/>
</div>
<div class="grow" />
<div class="grow window-dragger" />
<div class="flex-1">
<MiniWidgetContainer
:container="widgetStore.currentMiniWidgetsProfile.containers[2]"
Expand Down Expand Up @@ -329,6 +330,7 @@ import Tutorial from '@/components/Tutorial.vue'
import UpdateNotification from '@/components/UpdateNotification.vue'
import VehicleDiscoveryDialog from '@/components/VehicleDiscoveryDialog.vue'
import VideoLibraryModal from '@/components/VideoLibraryModal.vue'
import WindowControls from '@/components/WindowControls.vue'
import { useInteractionDialog } from '@/composables/interactionDialog'
import {
availableCockpitActions,
Expand Down Expand Up @@ -868,4 +870,8 @@ body.hide-cursor {
.top-bar-hamburger {
outline: none;
}
.window-dragger {
-webkit-app-region: drag;
}
</style>
70 changes: 70 additions & 0 deletions src/components/WindowControls.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<div class="window-controls flex items-center h-full" :class="{ 'order-first': isMacOS, 'order-last': !isMacOS }">
<button
v-for="control in controls"
:key="control.action"
class="window-control-btn flex items-center justify-center h-full hover:bg-[#ffffff22]"
:class="{ 'hover:bg-red-500': control.action === 'close' }"
@click="control.handler"
>
<v-icon size="14" class="text-lg text-slate-300" :class="[{ 'hover:text-white': control.action !== 'close' }]">
{{ control.icon }}
</v-icon>
</button>
</div>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { isElectron } from '@/libs/utils'
const isMacOS = ref(false)
const isMaximized = ref(false)
onMounted(async () => {
if (!isElectron() || !window.electronAPI) return
isMacOS.value = (await window.electronAPI.getPlatform()) === 'darwin'
isMaximized.value = await window.electronAPI.isMaximized()
// Listen for window state changes
window.addEventListener('resize', async () => {
if (window.electronAPI) {
isMaximized.value = await window.electronAPI.isMaximized()
}
})
})
const closeControl = {
action: 'close',
icon: 'mdi-window-close',
handler: () => window.electronAPI?.closeWindow(),
}
const minimizeControl = {
action: 'minimize',
icon: 'mdi-window-minimize',
handler: () => window.electronAPI?.minimizeWindow(),
}
const maximizeControl = {
action: 'maximize',
icon: isMaximized.value ? 'mdi-window-restore' : 'mdi-window-maximize',
handler: () => window.electronAPI?.maximizeWindow(),
}
const controls = computed(() => {
if (isMacOS.value) {
return [closeControl, minimizeControl, maximizeControl]
}
return [minimizeControl, maximizeControl, closeControl]
})
</script>

<style scoped>
.window-control-btn {
-webkit-app-region: no-drag;
width: 32px;
}
</style>
4 changes: 2 additions & 2 deletions src/libs/cosmos.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isBrowser } from 'browser-or-node'

import { ElectronStorageDB } from '@/types/general'
import { ElectronGeneralFunctions, ElectronStorageDB, ElectronWindowControlFunctions } from '@/types/general'
import { NetworkInfo } from '@/types/network'

import {
Expand Down Expand Up @@ -186,7 +186,7 @@ declare global {
/**
* Electron API exposed through preload script
*/
electronAPI?: ElectronStorageDB & {
electronAPI?: (ElectronStorageDB & ElectronWindowControlFunctions & ElectronGeneralFunctions) & {
/**
* Get network information from the main process
* @returns Promise containing subnet information
Expand Down
29 changes: 29 additions & 0 deletions src/types/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,32 @@ export interface ElectronStorageDB {
*/
keys: (subFolders?: string[]) => Promise<string[]>
}

/**
* Electron window control functions
*/
export interface ElectronWindowControlFunctions {
/**
* Minimize the window
*/
minimizeWindow: () => void
/**
* Maximize the window
*/
maximizeWindow: () => void
/**
* Close the window
*/
closeWindow: () => void
/**
* Check if the window is maximized
*/
isMaximized: () => Promise<boolean>
}

export interface ElectronGeneralFunctions {
/**
* Get the platform
*/
getPlatform: () => Promise<string>
}

0 comments on commit 0b53f64

Please sign in to comment.