feat: 添加 mica-electron 支持实现窗口毛玻璃效果

- 安装 mica-electron 依赖
- 在 WindowManager 中集成 MicaBrowserWindow 和各种效果方法
- 添加窗口主题、效果、圆角等设置选项
- 扩展 settingsSpec 类型支持新的窗口设置
This commit is contained in:
Linkon
2026-01-20 01:35:07 +08:00
parent 6b41ae079a
commit 95ee5705f6
5 changed files with 265 additions and 6 deletions
+18 -1
View File
@@ -59,7 +59,6 @@ export class SettingsService extends Service {
wc.setZoomFactor(zoom)
})
// 仅更新全局侧边栏窗口的位置(不调整大小,大小由 GlobalSidebar 组件管理)
const sidebarWindow = BrowserWindow.getAllWindows().find((win) => {
try {
return win.webContents.getURL().includes('#global-sidebar')
@@ -81,6 +80,24 @@ export class SettingsService extends Service {
})
}
}
},
window_theme: {
kind: 'string',
defaultValue: 'auto',
writePermission: 'admin',
validate: (v) => v === 'auto' || v === 'dark' || v === 'light'
},
window_effect: {
kind: 'string',
defaultValue: 'mica',
writePermission: 'admin',
validate: (v) => ['mica', 'tabbed', 'acrylic', 'blur', 'transparent', 'none'].includes(v as string)
},
window_radius: {
kind: 'string',
defaultValue: 'rounded',
writePermission: 'admin',
validate: (v) => v === 'rounded' || v === 'small' || v === 'square'
}
}
+222 -5
View File
@@ -3,6 +3,39 @@ import { MainContext } from '../context'
import { BrowserWindow, shell, screen } from 'electron'
import type { BrowserWindowConstructorOptions } from 'electron'
let micaElectron: typeof import('mica-electron') | null = null
let MicaBrowserWindow: any = null
let IS_WINDOWS_11 = false
try {
if (process.platform === 'win32') {
const micaModule = require('mica-electron')
micaElectron = micaModule
MicaBrowserWindow = micaModule.MicaBrowserWindow
IS_WINDOWS_11 = micaModule.IS_WINDOWS_11
}
} catch (error) {
console.warn('mica-electron not available:', error)
}
interface MicaWindow extends BrowserWindow {
setMicaEffect(): void
setMicaTabbedEffect(): void
setMicaAcrylicEffect(): void
setAcrylic(): void
setBlur(): void
setTransparent(): void
setDarkTheme(): void
setLightTheme(): void
setAutoTheme(): void
setRoundedCorner(): void
setSmallRoundedCorner(): void
setSquareCorner(): void
setBorderColor(color: string | null): void
setCaptionColor(color: string | null): void
setTitleTextColor(color: string | null): void
}
export type windowOpenInput = {
key: string
title?: string
@@ -52,7 +85,6 @@ export class WindowManager extends Service {
const existing = this.get(input.key)
if (existing) {
if (input.route) {
// Use soft navigation if window exists to prevent reload
existing.webContents.send('app:navigate', input.route)
}
existing.show()
@@ -60,12 +92,12 @@ export class WindowManager extends Service {
return existing
}
const win = new BrowserWindow({
const baseOptions: BrowserWindowConstructorOptions = {
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
frame: false, // Custom title bar
frame: false,
icon: this.opts.icon,
title: input.title,
webPreferences: {
@@ -73,7 +105,15 @@ export class WindowManager extends Service {
sandbox: false
},
...input.options
})
}
let win: BrowserWindow
if (MicaBrowserWindow) {
win = new MicaBrowserWindow(baseOptions)
this.applyMicaEffect(win)
} else {
win = new BrowserWindow(baseOptions)
}
// Special positioning for global sidebar
if (input.key === 'global-sidebar') {
@@ -130,6 +170,156 @@ export class WindowManager extends Service {
return win
}
private applyMicaEffect(win: BrowserWindow) {
if (!micaElectron) return
const micaWin = win as MicaWindow
const theme = this.mainCtx.settings.getValue('window_theme')
switch (theme) {
case 'dark':
micaWin.setDarkTheme()
break
case 'light':
micaWin.setLightTheme()
break
default:
micaWin.setAutoTheme()
}
const effect = this.mainCtx.settings.getValue('window_effect')
switch (effect) {
case 'mica':
micaWin.setMicaEffect()
break
case 'tabbed':
micaWin.setMicaTabbedEffect()
break
case 'acrylic':
if (IS_WINDOWS_11) {
micaWin.setMicaAcrylicEffect()
} else {
micaWin.setAcrylic()
}
break
case 'blur':
if (!IS_WINDOWS_11) {
micaWin.setBlur()
}
break
case 'transparent':
if (!IS_WINDOWS_11) {
micaWin.setTransparent()
}
break
}
const radius = this.mainCtx.settings.getValue('window_radius')
switch (radius) {
case 'small':
micaWin.setSmallRoundedCorner()
break
case 'square':
micaWin.setSquareCorner()
break
default:
micaWin.setRoundedCorner()
}
}
public setMicaEffect(win: BrowserWindow, effect: 'mica' | 'tabbed' | 'acrylic' | 'blur' | 'transparent' | 'none' = 'mica') {
if (!micaElectron) return
const micaWin = win as MicaWindow
switch (effect) {
case 'mica':
micaWin.setMicaEffect()
break
case 'tabbed':
micaWin.setMicaTabbedEffect()
break
case 'acrylic':
if (IS_WINDOWS_11) {
micaWin.setMicaAcrylicEffect()
} else {
micaWin.setAcrylic()
}
break
case 'blur':
if (!IS_WINDOWS_11) {
micaWin.setBlur()
}
break
case 'transparent':
if (!IS_WINDOWS_11) {
micaWin.setTransparent()
}
break
case 'none':
if (IS_WINDOWS_11) {
micaWin.setMicaEffect()
}
break
}
}
public setMicaTheme(win: BrowserWindow, theme: 'auto' | 'dark' | 'light' = 'auto') {
if (!micaElectron) return
const micaWin = win as MicaWindow
switch (theme) {
case 'dark':
micaWin.setDarkTheme()
break
case 'light':
micaWin.setLightTheme()
break
default:
micaWin.setAutoTheme()
}
}
public setMicaCorner(win: BrowserWindow, corner: 'rounded' | 'small' | 'square' = 'rounded') {
if (!micaElectron) return
const micaWin = win as MicaWindow
switch (corner) {
case 'small':
micaWin.setSmallRoundedCorner()
break
case 'square':
micaWin.setSquareCorner()
break
default:
micaWin.setRoundedCorner()
}
}
public setMicaBorderColor(win: BrowserWindow, color: string | null) {
if (!micaElectron) return
const micaWin = win as MicaWindow
micaWin.setBorderColor(color)
}
public setMicaCaptionColor(win: BrowserWindow, color: string | null) {
if (!micaElectron) return
const micaWin = win as MicaWindow
micaWin.setCaptionColor(color)
}
public setMicaTitleTextColor(win: BrowserWindow, color: string | null) {
if (!micaElectron) return
const micaWin = win as MicaWindow
micaWin.setTitleTextColor(color)
}
public isMicaAvailable(): boolean {
return micaElectron !== null
}
public isWindows11(): boolean {
return IS_WINDOWS_11
}
public navigate(key: string, route: string) {
const win = this.get(key)
if (!win) return false
@@ -236,7 +426,6 @@ export class WindowManager extends Service {
const win = BrowserWindow.fromWebContents(event.sender)
if (win) {
const bounds = win.getBounds()
// Keep right side pinned
const newX = bounds.x + (bounds.width - width)
win.setBounds({
x: newX,
@@ -246,5 +435,33 @@ export class WindowManager extends Service {
})
}
})
this.mainCtx.handle('window:mica-effect', (_event, effect: string) => {
const win = BrowserWindow.fromWebContents(_event.sender)
if (win) {
this.setMicaEffect(win, effect as any)
}
})
this.mainCtx.handle('window:mica-theme', (_event, theme: string) => {
const win = BrowserWindow.fromWebContents(_event.sender)
if (win) {
this.setMicaTheme(win, theme as any)
}
})
this.mainCtx.handle('window:mica-corner', (_event, corner: string) => {
const win = BrowserWindow.fromWebContents(_event.sender)
if (win) {
this.setMicaCorner(win, corner as any)
}
})
this.mainCtx.handle('window:mica-info', () => {
return {
available: this.isMicaAvailable(),
isWindows11: this.isWindows11()
}
})
}
}
+3
View File
@@ -21,6 +21,9 @@ export type settingsSpec = {
is_wizard_completed: boolean
log_level: logLevel
window_zoom: number
window_theme: 'auto' | 'dark' | 'light'
window_effect: 'mica' | 'tabbed' | 'acrylic' | 'blur' | 'transparent' | 'none'
window_radius: 'rounded' | 'small' | 'square'
}
export type settingsKey = keyof settingsSpec