diff --git a/internal-plugins/setting/src/constants.ts b/internal-plugins/setting/src/constants.ts index 6af30536..80b8e6e1 100644 --- a/internal-plugins/setting/src/constants.ts +++ b/internal-plugins/setting/src/constants.ts @@ -12,6 +12,7 @@ export type AutoClearOption = 'immediately' | '1m' | '2m' | '3m' | '5m' | '10m' // 自动返回搜索选项类型 export type AutoBackToSearchOption = 'immediately' | '30s' | '1m' | '3m' | '5m' | '10m' | 'never' +export type WindowPositionStrategy = 'remember' | 'cursor' | 'primary' | 'lastActive' // 主题类型 export type ThemeType = 'system' | 'light' | 'dark' diff --git a/internal-plugins/setting/src/env.d.ts b/internal-plugins/setting/src/env.d.ts index a2fd34eb..2e8597ef 100644 --- a/internal-plugins/setting/src/env.d.ts +++ b/internal-plugins/setting/src/env.d.ts @@ -267,6 +267,7 @@ declare global { updateAutoPaste: (autoPaste: string) => Promise updateAutoClear: (autoClear: string) => Promise updateAutoBackToSearch: (autoBackToSearch: string) => Promise + updateWindowPositionStrategy: (strategy: string) => Promise updateShowRecentInSearch: (showRecentInSearch: boolean) => Promise updateMatchRecommendation: (showMatchRecommendation: boolean) => Promise updateLocalAppSearch: (enabled: boolean) => Promise diff --git a/internal-plugins/setting/src/views/GeneralSetting/GeneralSetting.vue b/internal-plugins/setting/src/views/GeneralSetting/GeneralSetting.vue index 113f116e..565f2754 100644 --- a/internal-plugins/setting/src/views/GeneralSetting/GeneralSetting.vue +++ b/internal-plugins/setting/src/views/GeneralSetting/GeneralSetting.vue @@ -8,7 +8,8 @@ import { type AutoPasteOption, type MouseButtonType, type PrimaryColor, - type ThemeType + type ThemeType, + type WindowPositionStrategy } from '@/constants' import { Dropdown, HotkeyInput, Slider, useToast } from '@/components' import { applyCustomColor, applyPrimaryColor } from '@/utils' @@ -56,6 +57,13 @@ const autoBackToSearchOptions = [ { label: '从不', value: 'never' } ] +const windowPositionStrategyOptions = [ + { label: '记住上次位置', value: 'remember' }, + { label: '鼠标屏居中', value: 'cursor' }, + { label: '主屏居中', value: 'primary' }, + { label: '上次活动屏居中', value: 'lastActive' } +] + const recentRowsOptions = [ { label: '1行', value: 1 }, { label: '2行', value: 2 }, @@ -129,6 +137,7 @@ const avatar = ref(DEFAULT_AVATAR) const autoPaste = ref('3s') const autoClear = ref('immediately') const autoBackToSearch = ref('never') +const windowPositionStrategy = ref('remember') const showRecentInSearch = ref(true) const showMatchRecommendation = ref(true) const localAppSearch = ref(true) @@ -501,6 +510,17 @@ async function handleAutoBackToSearchChange(): Promise { } } +// 处理窗口呼出位置策略变化 +async function handleWindowPositionStrategyChange(): Promise { + try { + await saveSettings() + await window.ztools.internal.updateWindowPositionStrategy(windowPositionStrategy.value) + console.log('窗口呼出位置策略已更新:', windowPositionStrategy.value) + } catch (error) { + console.error('保存窗口呼出位置策略失败:', error) + } +} + // 处理主题变化 async function handleThemeChange(): Promise { try { @@ -1203,6 +1223,7 @@ async function loadSettings(): Promise { autoPaste.value = data.autoPaste ?? '3s' autoClear.value = data.autoClear ?? 'immediately' autoBackToSearch.value = data.autoBackToSearch ?? 'never' + windowPositionStrategy.value = data.windowPositionStrategy ?? 'remember' showRecentInSearch.value = data.showRecentInSearch ?? true showMatchRecommendation.value = data.showMatchRecommendation ?? true localAppSearch.value = data.localAppSearch ?? true @@ -1296,6 +1317,7 @@ async function saveSettings(): Promise { autoPaste: autoPaste.value, autoClear: autoClear.value, autoBackToSearch: autoBackToSearch.value, + windowPositionStrategy: windowPositionStrategy.value, showRecentInSearch: showRecentInSearch.value, showMatchRecommendation: showMatchRecommendation.value, localAppSearch: localAppSearch.value, @@ -1872,6 +1894,20 @@ onUnmounted(() => { +
+
+ 窗口呼出位置 + 每次呼出主窗口时的定位策略 +
+
+ +
+
+
插件默认高度 diff --git a/resources/preload.js b/resources/preload.js index 1400d0a8..583b4154 100644 --- a/resources/preload.js +++ b/resources/preload.js @@ -973,6 +973,8 @@ window.ztools = { // 通知主渲染进程更新自动返回搜索配置 updateAutoBackToSearch: async (autoBackToSearch) => await electron.ipcRenderer.invoke('internal:update-auto-back-to-search', autoBackToSearch), + updateWindowPositionStrategy: async (strategy) => + await electron.ipcRenderer.invoke('internal:update-window-position-strategy', strategy), // 通知主渲染进程更新显示最近使用配置 updateShowRecentInSearch: async (showRecentInSearch) => await electron.ipcRenderer.invoke( diff --git a/src/main/api/plugin/internal.ts b/src/main/api/plugin/internal.ts index 10e7a38b..817564b9 100644 --- a/src/main/api/plugin/internal.ts +++ b/src/main/api/plugin/internal.ts @@ -751,6 +751,15 @@ export class InternalPluginAPI { } ) + // 更新窗口呼出位置策略(直接通知主进程) + ipcMain.handle('internal:update-window-position-strategy', async (event, strategy: string) => { + if (!requireInternalPlugin(this.pluginManager, event)) { + throw new PermissionDeniedError('internal:update-window-position-strategy') + } + await windowAPI.updateWindowPositionStrategy(strategy) + return { success: true } + }) + // 通知主渲染进程更新显示最近使用配置 ipcMain.handle( 'internal:update-show-recent-in-search', diff --git a/src/main/api/renderer/settings.ts b/src/main/api/renderer/settings.ts index b74a30cd..c41aee66 100644 --- a/src/main/api/renderer/settings.ts +++ b/src/main/api/renderer/settings.ts @@ -126,6 +126,11 @@ export class SettingsAPI { await windowManager.updateAutoBackToSearch(data.autoBackToSearch) console.log('[Settings] 启动时应用自动返回搜索设置:', data.autoBackToSearch) } + // 应用窗口呼出位置策略设置 + if (data.windowPositionStrategy) { + await windowManager.updateWindowPositionStrategy(data.windowPositionStrategy) + console.log('[Settings] 启动时应用窗口呼出位置策略:', data.windowPositionStrategy) + } // 应用代理配置 if (data.proxyEnabled !== undefined && data.proxyUrl !== undefined) { proxyManager.setProxyConfig({ diff --git a/src/main/api/renderer/window.ts b/src/main/api/renderer/window.ts index 831f1d09..35c17427 100644 --- a/src/main/api/renderer/window.ts +++ b/src/main/api/renderer/window.ts @@ -171,6 +171,11 @@ export class WindowAPI { await windowManager.updateAutoBackToSearch(autoBackToSearch) console.log('[WindowAPI] 更新自动返回搜索配置:', autoBackToSearch) } + + public async updateWindowPositionStrategy(strategy: string): Promise { + await windowManager.updateWindowPositionStrategy(strategy) + console.log('[WindowAPI] 更新窗口呼出位置策略:', strategy) + } } export default new WindowAPI() diff --git a/src/main/managers/windowManager.ts b/src/main/managers/windowManager.ts index 1ec3185f..0cc0a976 100644 --- a/src/main/managers/windowManager.ts +++ b/src/main/managers/windowManager.ts @@ -90,6 +90,7 @@ class WindowManager { private windowPositionsByDisplay: Record = {} private autoBackToSearchTimer: NodeJS.Timeout | null = null // 自动返回搜索定时器 private autoBackToSearchConfig: string = 'never' // 自动返回搜索配置 + private windowPositionStrategy: string = 'remember' // 窗口呼出位置策略 private lastFocusTarget: 'mainWindow' | 'plugin' | null = null // 窗口隐藏前的焦点状态 private isRestoringFocus: boolean = false // 是否正在恢复焦点状态(防止 focus 事件监听器干扰) private suppressBlurHide: boolean = false // 临时抑制 blur 事件隐藏窗口(文件关联打开等场景) @@ -843,28 +844,68 @@ class WindowManager { } /** - * 将窗口移动到鼠标所在显示器 - * 优先恢复该显示器记忆的位置,否则居中显示 + * 根据窗口呼出位置策略将窗口移动到目标显示器并定位 + * - remember:优先恢复该显示器记忆的位置,否则居中 + * - cursor:鼠标所在显示器居中 + * - primary:主显示器居中 + * - lastActive:上次活动窗口所在显示器居中(无记录时 fallback 到鼠标屏) */ private moveWindowToCursor(): void { if (!this.mainWindow) return - const { width, height, x: displayX, y: displayY, id: displayId } = this.getDisplayAtCursor() + let target: { width: number; height: number; x: number; y: number; id: number } - const savedPosition = this.windowPositionsByDisplay[displayId] - - let x: number, y: number - - if (savedPosition) { - // 恢复该显示器记忆的位置 - x = savedPosition.x - y = savedPosition.y - } else { - // 计算默认居中位置(基于最大窗口高度) - x = displayX + Math.floor((width - WINDOW_WIDTH) / 2) - y = displayY + Math.floor((height - WINDOW_DEFAULT_HEIGHT) / 2) + switch (this.windowPositionStrategy) { + case 'primary': { + const display = screen.getPrimaryDisplay() + target = { ...display.workArea, id: display.id } + break + } + case 'lastActive': { + const prev = this.previousActiveWindow + if ( + prev && + typeof prev.x === 'number' && + typeof prev.y === 'number' && + Number.isFinite(prev.x) && + Number.isFinite(prev.y) + ) { + const centerX = + prev.x + + (typeof prev.width === 'number' && Number.isFinite(prev.width) + ? Math.floor(prev.width / 2) + : 0) + const centerY = + prev.y + + (typeof prev.height === 'number' && Number.isFinite(prev.height) + ? Math.floor(prev.height / 2) + : 0) + const display = screen.getDisplayNearestPoint({ x: centerX, y: centerY }) + target = { ...display.workArea, id: display.id } + } else { + target = this.getDisplayAtCursor() + } + break + } + case 'cursor': { + target = this.getDisplayAtCursor() + break + } + case 'remember': + default: { + const { width, height, x: displayX, y: displayY, id: displayId } = this.getDisplayAtCursor() + const savedPosition = this.windowPositionsByDisplay[displayId] + if (savedPosition) { + this.mainWindow.setPosition(savedPosition.x, savedPosition.y, false) + return + } + target = { width, height, x: displayX, y: displayY, id: displayId } + break + } } + const x = target.x + Math.floor((target.width - WINDOW_WIDTH) / 2) + const y = target.y + Math.floor((target.height - WINDOW_DEFAULT_HEIGHT) / 2) this.mainWindow.setPosition(x, y, false) } @@ -1054,6 +1095,14 @@ class WindowManager { console.log('[Window] 更新自动返回搜索配置:', config) } + /** + * 更新窗口呼出位置策略 + */ + public async updateWindowPositionStrategy(strategy: string): Promise { + this.windowPositionStrategy = strategy + console.log('[Window] 更新窗口呼出位置策略:', strategy) + } + /** * 获取打开窗口前激活的窗口 */