feat(stage-tamagotchi): popup controls island and better tray with window opts (#1106)

This commit is contained in:
Liet Blue
2026-03-05 00:54:47 +08:00
committed by GitHub
parent ddcd19456f
commit ce00df0fa7
12 changed files with 327 additions and 115 deletions
@@ -2,12 +2,14 @@ import type { createContext } from '@moeru/eventa/adapters/electron/main'
import type { BrowserWindow } from 'electron'
import { defineInvokeHandler } from '@moeru/eventa'
import { app } from 'electron'
import { isLinux, isMacOS, isWindows } from 'std-env'
import { electron } from '../../../shared/eventa'
import { electron, electronAppQuit } from '../../../shared/eventa'
export function createAppService(params: { context: ReturnType<typeof createContext>['context'], window: BrowserWindow }) {
defineInvokeHandler(params.context, electron.app.isMacOS, () => isMacOS)
defineInvokeHandler(params.context, electron.app.isWindows, () => isWindows)
defineInvokeHandler(params.context, electron.app.isLinux, () => isLinux)
defineInvokeHandler(params.context, electronAppQuit, () => app.quit())
}
@@ -5,7 +5,7 @@ import { defineInvokeHandler } from '@moeru/eventa'
import { bounds, startLoopGetBounds } from '@proj-airi/electron-eventa'
import { createRendererLoop } from '@proj-airi/electron-vueuse/main'
import { electron } from '../../../shared/eventa'
import { electron, electronWindowClose } from '../../../shared/eventa'
import { onAppBeforeQuit, onAppWindowAllClosed } from '../../libs/bootkit/lifecycle'
import { resizeWindowByDelta } from '../../windows/shared/window'
@@ -70,4 +70,10 @@ export function createWindowService(params: { context: ReturnType<typeof createC
direction: payload.direction,
})
})
defineInvokeHandler(params.context, electronWindowClose, (_, options) => {
if (params.window.webContents.id === options?.raw.ipcMainEvent.sender.id) {
params.window.close()
}
})
}
+165 -37
View File
@@ -7,8 +7,8 @@ import type { WidgetsWindowManager } from '../windows/widgets'
import { env } from 'node:process'
import { is } from '@electron-toolkit/utils'
import { app, Menu, nativeImage, Tray } from 'electron'
import { once } from 'es-toolkit'
import { app, Menu, nativeImage, screen, Tray } from 'electron'
import { debounce, once } from 'es-toolkit'
import { isMacOS } from 'std-env'
import icon from '../../../resources/icon.png?asset'
@@ -18,6 +18,61 @@ import { onAppBeforeQuit } from '../libs/bootkit/lifecycle'
import { setupInlayWindow } from '../windows/inlay'
import { toggleWindowShow } from '../windows/shared/window'
const RECOMMENDED_WIDTH = 450
const RECOMMENDED_HEIGHT = 600
const ASPECT_RATIO = RECOMMENDED_WIDTH / RECOMMENDED_HEIGHT
function applyWindowSize(window: BrowserWindow, width: number, height: number, x?: number, y?: number): void {
window.setResizable(true)
const bounds = {
width: Math.round(width),
height: Math.round(height),
} as any
if (x !== undefined && y !== undefined) {
bounds.x = Math.round(x)
bounds.y = Math.round(y)
}
window.setBounds(bounds)
if (x === undefined || y === undefined) {
window.center()
}
window.show()
}
function alignWindow(window: BrowserWindow, position: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'): void {
const { width: windowWidth, height: windowHeight } = window.getBounds()
const { x: areaX, y: areaY, width: areaWidth, height: areaHeight } = screen.getPrimaryDisplay().workArea
switch (position) {
case 'center':
window.center()
break
case 'top-left':
window.setPosition(areaX, areaY)
break
case 'top-right':
window.setPosition(areaX + areaWidth - windowWidth, areaY)
break
case 'bottom-left':
window.setPosition(areaX, areaY + areaHeight - windowHeight)
break
case 'bottom-right':
window.setPosition(areaX + areaWidth - windowWidth, areaY + areaHeight - windowHeight)
break
}
window.show()
}
function isSizeMatch(window: BrowserWindow, targetWidth: number, targetHeight: number): boolean {
const { width, height } = window.getBounds()
return Math.abs(width - Math.round(targetWidth)) <= 2 && Math.abs(height - Math.round(targetHeight)) <= 2
}
function isPositionMatch(window: BrowserWindow, targetX: number, targetY: number): boolean {
const { x, y } = window.getBounds()
return Math.abs(x - targetX) <= 5 && Math.abs(y - targetY) <= 5
}
export function setupTray(params: {
mainWindow: BrowserWindow
settingsWindow: () => Promise<BrowserWindow>
@@ -33,44 +88,117 @@ export function setupTray(params: {
const appTray = new Tray(trayImage)
onAppBeforeQuit(() => appTray.destroy())
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show', click: () => toggleWindowShow(params.mainWindow) },
{ type: 'separator' },
{
label: 'Center and Reset Size',
click: () => {
params.mainWindow.setSize(450, 600)
params.mainWindow.center()
params.mainWindow.show()
const rebuildContextMenu = debounce((): void => {
const { x: areaX, y: areaY, width: areaWidth, height: areaHeight } = screen.getPrimaryDisplay().workArea
const { width: windowWidth, height: windowHeight } = params.mainWindow.getBounds()
const fullHeightTarget = areaHeight
const fullWidthTarget = Math.floor(areaHeight * ASPECT_RATIO)
const halfHeightTarget = Math.floor(areaHeight / 2)
const halfWidthTarget = Math.floor(halfHeightTarget * ASPECT_RATIO)
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show', click: () => toggleWindowShow(params.mainWindow) },
{ type: 'separator' },
{
label: 'Adjust Sizes',
submenu: [
{
label: 'Recommended (450x600)',
type: 'checkbox',
checked: isSizeMatch(params.mainWindow, RECOMMENDED_WIDTH, RECOMMENDED_HEIGHT),
click: () => applyWindowSize(params.mainWindow, RECOMMENDED_WIDTH, RECOMMENDED_HEIGHT),
},
{
label: 'Full Height',
type: 'checkbox',
checked: isSizeMatch(params.mainWindow, fullWidthTarget, fullHeightTarget),
click: () => applyWindowSize(params.mainWindow, fullWidthTarget, fullHeightTarget),
},
{
label: 'Half Height',
type: 'checkbox',
checked: isSizeMatch(params.mainWindow, halfWidthTarget, halfHeightTarget),
click: () => applyWindowSize(params.mainWindow, halfWidthTarget, halfHeightTarget),
},
{
label: 'Full Screen',
type: 'checkbox',
checked: isSizeMatch(params.mainWindow, areaWidth, areaHeight),
click: () => applyWindowSize(params.mainWindow, areaWidth, areaHeight, areaX, areaY),
},
],
},
},
{ type: 'separator' },
{ label: 'Settings...', click: () => params.settingsWindow().then(window => toggleWindowShow(window)) },
{ label: 'About...', click: () => params.aboutWindow().then(window => toggleWindowShow(window)) },
{ type: 'separator' },
{ label: 'Open Inlay...', click: () => setupInlayWindow() },
{ label: 'Open Widgets...', click: () => params.widgetsWindow.getWindow().then(window => toggleWindowShow(window)) },
{ label: 'Open Caption...', click: () => params.captionWindow.getWindow().then(window => toggleWindowShow(window)) },
{
type: 'submenu',
label: 'Caption Overlay',
submenu: Menu.buildFromTemplate([
{ type: 'checkbox', label: 'Follow window', checked: params.captionWindow.getIsFollowingWindow(), click: async menuItem => await params.captionWindow.setFollowWindow(Boolean(menuItem.checked)) },
{ label: 'Reset position', click: async () => await params.captionWindow.resetToSide() },
]),
},
{ type: 'separator' },
...is.dev || env.MAIN_APP_DEBUG || env.APP_DEBUG
? [
{ type: 'header', label: 'DevTools' },
{ label: 'Troubleshoot BeatSync...', click: () => params.beatSyncBgWindow.webContents.openDevTools() },
{
label: 'Align to',
submenu: [
{
label: 'Center',
type: 'checkbox',
checked: isPositionMatch(params.mainWindow, areaX + Math.floor((areaWidth - windowWidth) / 2), areaY + Math.floor((areaHeight - windowHeight) / 2)),
click: () => alignWindow(params.mainWindow, 'center'),
},
{ type: 'separator' },
] as const // :(
: [],
{ label: 'Quit', click: () => app.quit() },
])
{
label: 'Top Left',
type: 'checkbox',
checked: isPositionMatch(params.mainWindow, areaX, areaY),
click: () => alignWindow(params.mainWindow, 'top-left'),
},
{
label: 'Top Right',
type: 'checkbox',
checked: isPositionMatch(params.mainWindow, areaX + areaWidth - windowWidth, areaY),
click: () => alignWindow(params.mainWindow, 'top-right'),
},
{
label: 'Bottom Left',
type: 'checkbox',
checked: isPositionMatch(params.mainWindow, areaX, areaY + areaHeight - windowHeight),
click: () => alignWindow(params.mainWindow, 'bottom-left'),
},
{
label: 'Bottom Right',
type: 'checkbox',
checked: isPositionMatch(params.mainWindow, areaX + areaWidth - windowWidth, areaY + areaHeight - windowHeight),
click: () => alignWindow(params.mainWindow, 'bottom-right'),
},
],
},
{ type: 'separator' },
{ label: 'Settings...', click: () => params.settingsWindow().then(window => toggleWindowShow(window)) },
{ label: 'About...', click: () => params.aboutWindow().then(window => toggleWindowShow(window)) },
{ type: 'separator' },
{ label: 'Open Inlay...', click: () => setupInlayWindow() },
{ label: 'Open Widgets...', click: () => params.widgetsWindow.getWindow().then(window => toggleWindowShow(window)) },
{ label: 'Open Caption...', click: () => params.captionWindow.getWindow().then(window => toggleWindowShow(window)) },
{
type: 'submenu',
label: 'Caption Overlay',
submenu: Menu.buildFromTemplate([
{ type: 'checkbox', label: 'Follow window', checked: params.captionWindow.getIsFollowingWindow(), click: async menuItem => await params.captionWindow.setFollowWindow(Boolean(menuItem.checked)) },
{ label: 'Reset position', click: async () => await params.captionWindow.resetToSide() },
]),
},
{ type: 'separator' },
...is.dev || env.MAIN_APP_DEBUG || env.APP_DEBUG
? [
{ type: 'header', label: 'DevTools' },
{ label: 'Troubleshoot BeatSync...', click: () => params.beatSyncBgWindow.webContents.openDevTools() },
{ type: 'separator' },
] as const
: [],
{ label: 'Quit', click: () => app.quit() },
])
appTray.setContextMenu(contextMenu)
}, 50)
params.mainWindow.on('resize', rebuildContextMenu)
params.mainWindow.on('move', rebuildContextMenu)
rebuildContextMenu()
appTray.setContextMenu(contextMenu)
appTray.setToolTip('Project AIRI')
appTray.addListener('click', () => toggleWindowShow(params.mainWindow))
@@ -1,11 +1,10 @@
<script setup lang="ts">
import { defineInvoke } from '@moeru/eventa'
import { useElectronEventaContext, useElectronEventaInvoke } from '@proj-airi/electron-vueuse'
import { useElectronEventaContext, useElectronEventaInvoke, useElectronMouseInElement } from '@proj-airi/electron-vueuse'
import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
import { useTheme } from '@proj-airi/ui'
import { useWindowSize } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed, ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import ControlButtonTooltip from './control-button-tooltip.vue'
@@ -14,7 +13,7 @@ import ControlsIslandFadeOnHover from './controls-island-fade-on-hover.vue'
import ControlsIslandHearingConfig from './controls-island-hearing-config.vue'
import IndicatorMicVolume from './indicator-mic-volume.vue'
import { electron, electronOpenChat, electronOpenSettings, electronStartDraggingWindow } from '../../../../shared/eventa'
import { electron, electronOpenChat, electronOpenSettings, electronStartDraggingWindow, electronWindowClose } from '../../../../shared/eventa'
const { isDark, toggleDark } = useTheme()
const { t } = useI18n()
@@ -27,13 +26,36 @@ const { controlsIslandIconSize } = storeToRefs(settingsStore)
const openSettings = useElectronEventaInvoke(electronOpenSettings)
const openChat = useElectronEventaInvoke(electronOpenChat)
const isLinux = useElectronEventaInvoke(electron.app.isLinux)
const closeWindow = useElectronEventaInvoke(electronWindowClose)
// Responsive icon & button sizing based on window height
const { height: windowHeight } = useWindowSize()
// Constants: assume each icon placeholder occupies 50px and there are 7 buttons
const ICON_PLACEHOLDER_PX = 50
const BUTTON_COUNT = 7
const LARGE_THRESHOLD = ICON_PLACEHOLDER_PX * BUTTON_COUNT
const expanded = ref(false)
const islandRef = ref<HTMLElement | null>(null)
const { isOutside } = useElectronMouseInElement(islandRef)
let collapseTimer: ReturnType<typeof setTimeout> | null = null
watch(isOutside, (val) => {
if (val) {
if (expanded.value) {
collapseTimer = setTimeout(() => {
expanded.value = false
}, 1500)
}
}
else {
if (collapseTimer) {
clearTimeout(collapseTimer)
collapseTimer = null
}
}
})
watch(expanded, (isExpanded) => {
if (!isExpanded && collapseTimer) {
clearTimeout(collapseTimer)
collapseTimer = null
}
})
// Grouped classes for icon / border / padding and combined style class
const adjustStyleClasses = computed(() => {
@@ -49,7 +71,9 @@ const adjustStyleClasses = computed(() => {
break
case 'auto':
default:
isLarge = windowHeight.value > LARGE_THRESHOLD
// Fixed to large for better visibility in the new layout,
// can be changed to windowHeight based check if absolutely needed.
isLarge = true
break
}
@@ -77,81 +101,112 @@ function refreshWindow() {
</script>
<template>
<div fixed bottom-2 right-2>
<div flex flex-col gap-1>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" @click="openSettings">
<div i-solar:settings-minimalistic-outline :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<div ref="islandRef" fixed bottom-2 right-2>
<div flex flex-col items-end gap-1>
<!-- iOS Style Drawer Panel -->
<Transition
enter-active-class="transition-all duration-500 cubic-bezier(0.32, 0.72, 0, 1)"
leave-active-class="transition-all duration-400 cubic-bezier(0.32, 0.72, 0, 1)"
enter-from-class="opacity-0 translate-y-8 scale-90 blur-sm"
leave-to-class="opacity-0 translate-y-8 scale-90 blur-sm"
>
<div v-if="expanded" border="1 neutral-200 dark:neutral-800" mb-2 flex flex-col gap-1 rounded-2xl p-2 backdrop-blur-xl class="bg-neutral-100/80 shadow-2xl shadow-black/20 dark:bg-neutral-900/80">
<div grid grid-cols-3 gap-2>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" @click="openSettings">
<div i-solar:settings-minimalistic-outline :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.open-settings') }}
</template>
</ControlButtonTooltip>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.open-settings') }}
</template>
</ControlButtonTooltip>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" @click="openChat">
<div i-solar:chat-line-line-duotone :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.open-chat') }}
</template>
</ControlButtonTooltip>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" @click="openChat">
<div i-solar:chat-line-line-duotone :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" @click="refreshWindow">
<div i-solar:refresh-linear :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.refresh') }}
</template>
</ControlButtonTooltip>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.open-chat') }}
</template>
</ControlButtonTooltip>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" @click="toggleDark()">
<Transition name="fade" mode="out-in">
<div v-if="isDark" i-solar:moon-outline :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
<div v-else i-solar:sun-2-outline :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</Transition>
</ControlButton>
<template #tooltip>
{{ isDark ? t('tamagotchi.stage.controls-island.switch-to-light-mode') : t('tamagotchi.stage.controls-island.switch-to-dark-mode') }}
</template>
</ControlButtonTooltip>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" @click="refreshWindow">
<div i-solar:refresh-linear :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<ControlButtonTooltip>
<ControlsIslandHearingConfig v-model:show="hearingDialogOpen">
<div class="relative">
<ControlButton :button-style="adjustStyleClasses.button">
<Transition name="fade" mode="out-in">
<IndicatorMicVolume v-if="enabled" :class="adjustStyleClasses.icon" />
<div v-else i-ph:microphone-slash :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</Transition>
</ControlButton>
</div>
</ControlsIslandHearingConfig>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.open-hearing-controls') }}
</template>
</ControlButtonTooltip>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.refresh') }}
</template>
</ControlButtonTooltip>
<ControlsIslandFadeOnHover :icon-class="adjustStyleClasses.icon" :button-style="adjustStyleClasses.button" />
<ControlButtonTooltip>
<ControlsIslandHearingConfig v-model:show="hearingDialogOpen">
<div class="relative">
<ControlButton :button-style="adjustStyleClasses.button">
<Transition name="fade" mode="out-in">
<IndicatorMicVolume v-if="enabled" :class="adjustStyleClasses.icon" />
<div v-else i-ph:microphone-slash :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</Transition>
</ControlButton>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" hover:bg-red-500 hover:text-white @click="closeWindow()">
<div i-solar:close-circle-outline :class="adjustStyleClasses.icon" />
</ControlButton>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.close') }}
</template>
</ControlButtonTooltip>
</div>
</ControlsIslandHearingConfig>
</div>
</Transition>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.open-hearing-controls') }}
</template>
</ControlButtonTooltip>
<!-- Main Controls -->
<div flex flex-col gap-1>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" @click="expanded = !expanded">
<div
<ControlsIslandFadeOnHover :icon-class="adjustStyleClasses.icon" :button-style="adjustStyleClasses.button" />
:class="[adjustStyleClasses.icon, expanded ? 'rotate-180' : 'rotate-0']"
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" cursor-move :class="{ 'drag-region': isLinux }" @mousedown="startDraggingWindow?.()">
<div i-ph:arrows-out-cardinal :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
i-solar:alt-arrow-up-line-duotone scale-110 transition-all duration-300
text="neutral-800 dark:neutral-300"
/>
</ControlButton>
<template #tooltip>
{{ expanded ? t('tamagotchi.stage.controls-island.collapse') : t('tamagotchi.stage.controls-island.expand') }}
</template>
</ControlButtonTooltip>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.drag-to-move-window') }}
</template>
</ControlButtonTooltip>
<ControlButtonTooltip>
<!-- Recommended to use `toggleDark()` instead of `toggleDark` -->
<!-- See: https://vueuse.org/shared/useToggle/#usage -->
<ControlButton :button-style="adjustStyleClasses.button" @click="toggleDark()">
<Transition name="fade" mode="out-in">
<div v-if="isDark" i-solar:moon-outline :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
<div v-else i-solar:sun-2-outline :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</Transition>
</ControlButton>
<template #tooltip>
{{ isDark ? t('tamagotchi.stage.controls-island.switch-to-light-mode') : t('tamagotchi.stage.controls-island.switch-to-dark-mode') }}
</template>
</ControlButtonTooltip>
<ControlButtonTooltip>
<ControlButton :button-style="adjustStyleClasses.button" cursor-move :class="{ 'drag-region': isLinux }" @mousedown="startDraggingWindow?.()">
<div i-ph:arrows-out-cardinal :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.drag-to-move-window') }}
</template>
</ControlButtonTooltip>
</div>
</div>
</div>
</template>
@@ -194,6 +194,9 @@ export const widgetsUpdate = defineInvokeEventa<void, { id: string, componentPro
export const widgetsFetch = defineInvokeEventa<WidgetSnapshot | void, { id: string }>('eventa:invoke:electron:windows:widgets:fetch')
export const widgetsPrepareWindow = defineInvokeEventa<string | undefined, { id?: string }>('eventa:invoke:electron:windows:widgets:prepare')
export const electronWindowClose = defineInvokeEventa<void>('eventa:invoke:electron:window:close')
export const electronAppQuit = defineInvokeEventa<void>('eventa:invoke:electron:app:quit')
// Internal event from main -> widgets renderer when a widget should render
export const widgetsRenderEvent = defineEventa<WidgetSnapshot>('eventa:event:electron:windows:widgets:render')
export const widgetsRemoveEvent = defineEventa<{ id: string }>('eventa:event:electron:windows:widgets:remove')
@@ -3,9 +3,11 @@ import { defineInvokeEventa } from '@moeru/eventa'
const isMacOS = defineInvokeEventa<boolean>('eventa:invoke:electron:app:is-macos')
const isWindows = defineInvokeEventa<boolean>('eventa:invoke:electron:app:is-windows')
const isLinux = defineInvokeEventa<boolean>('eventa:invoke:electron:app:is-linux')
const quit = defineInvokeEventa<void>('eventa:invoke:electron:app:quit')
export const app = {
isMacOS,
isWindows,
isLinux,
quit,
}
@@ -11,6 +11,7 @@ const setIgnoreMouseEvents = defineInvokeEventa<void, [boolean, { forward: boole
const setVibrancy = defineInvokeEventa<void, Parameters<BrowserWindow['setVibrancy']> | [null]>('eventa:invoke:electron:window:set-vibrancy')
const setBackgroundMaterial = defineInvokeEventa<void, Parameters<BrowserWindow['setBackgroundMaterial']>>('eventa:invoke:electron:window:set-background-material')
const resize = defineInvokeEventa<void, { deltaX: number, deltaY: number, direction: ResizeDirection }>('eventa:invoke:electron:window:resize')
const close = defineInvokeEventa<void>('eventa:invoke:electron:window:close')
export type VibrancyType = Parameters<BrowserWindow['setVibrancy']>[0]
export type BackgroundMaterialType = Parameters<BrowserWindow['setBackgroundMaterial']>[0]
@@ -23,4 +24,5 @@ export const window = {
setVibrancy,
setBackgroundMaterial,
resize,
close,
}
@@ -19,6 +19,9 @@ docs:
'drag-to-move-window': Drag to move window
'switch-to-light-mode': Switch to light mode
'switch-to-dark-mode': Switch to dark mode
'close': Close
'expand': Expand
'collapse': Collapse
notice:
'fade-on-hover':
@@ -19,6 +19,7 @@ docs:
'drag-to-move-window': Drag to move window
'switch-to-light-mode': Switch to light mode
'switch-to-dark-mode': Switch to dark mode
'close': Fermer
notice:
'fade-on-hover':
title: Disparaître au survol
@@ -19,6 +19,9 @@ docs:
'drag-to-move-window': ウィンドウをドラッグして移動
'switch-to-light-mode': ライトモードに切り替え
'switch-to-dark-mode': ダークモードに切り替え
'close': 閉じる
'expand': 展開
'collapse': 折りたたむ
notice:
'fade-on-hover':
title: ホバー時にフェード
@@ -19,6 +19,9 @@ docs:
'drag-to-move-window': 拖动以移动窗口
'switch-to-light-mode': 切换到亮色模式
'switch-to-dark-mode': 切换到暗色模式
'close': 关闭
'expand': 展开
'collapse': 收起
notice:
'fade-on-hover':
title: 悬停淡出
@@ -18,7 +18,11 @@ docs:
'open-hearing-controls': 開啟聽力控制
'drag-to-move-window': 長按以移動視窗
'switch-to-light-mode': 切換至亮色模式
'switch-to-dark-mode': 切換至深色模式
'switch-to-dark-mode': 切換到暗色模式
'close': 關閉
'expand': 展開
'collapse': 收起
notice:
'fade-on-hover':
title: 懸停淡出