import { EventDataTypes, EventModel } from "@/api/api-models"; import { CURRENT_HOST } from "@/data/constants"; import { useDanmakuClient } from "@/store/useDanmakuClient"; import { useWebFetcher } from "@/store/useWebFetcher"; import { PhysicalPosition, PhysicalSize } from "@tauri-apps/api/dpi"; import { Webview } from "@tauri-apps/api/webview"; import { WebviewWindow } from "@tauri-apps/api/webviewWindow"; import { Window } from "@tauri-apps/api/window"; export type DanmakuWindowSettings = { width: number; height: number; x: number; y: number; opacity: number; // 窗口透明度 showAvatar: boolean; // 是否显示头像 showUsername: boolean; // 是否显示用户名 showFansMedal: boolean; // 是否显示粉丝牌 showGuardIcon: boolean; // 是否显示舰长图标 fontSize: number; // 弹幕字体大小 maxDanmakuCount: number; // 最大显示的弹幕数量 reverseOrder: boolean; // 是否倒序显示(从下往上) filterTypes: string[]; // 要显示的弹幕类型 animationDuration: number; // 动画持续时间 backgroundColor: string; // 背景色 textColor: string; // 文字颜色 alwaysOnTop: boolean; // 是否总在最前 interactive: boolean; // 是否可交互(穿透鼠标点击) borderRadius: number; // 边框圆角 itemSpacing: number; // 项目间距 enableShadow: boolean; // 是否启用阴影 shadowColor: string; // 阴影颜色 }; export const DANMAKU_WINDOW_BROADCAST_CHANNEL = 'channel.danmaku.window'; export type DanmakuWindowBCData = { type: 'danmaku', data: EventModel; } | { type: 'update-setting', data: DanmakuWindowSettings; }; export const useDanmakuWindow = defineStore('danmakuWindow', () => { const danmakuWindow = ref(); const danmakuWindowSetting = useStorage('Setting.DanmakuWindow', { width: 400, height: 600, x: 100, y: 100, opacity: 0.9, showAvatar: true, showUsername: true, showFansMedal: true, showGuardIcon: true, fontSize: 14, maxDanmakuCount: 50, reverseOrder: false, filterTypes: ["Message", "Gift", "SC", "Guard"], animationDuration: 300, backgroundColor: 'rgba(0,0,0,0.6)', textColor: '#ffffff', alwaysOnTop: true, interactive: false, borderRadius: 8, itemSpacing: 5, enableShadow: true, shadowColor: 'rgba(0,0,0,0.5)' }); const danmakuClient = useDanmakuClient(); let bc: BroadcastChannel | undefined = undefined; function hideWindow() { danmakuWindow.value?.window.hide(); danmakuWindow.value = undefined; } function closeWindow() { danmakuWindow.value?.close(); danmakuWindow.value = undefined; } function setDanmakuWindowSize(width: number, height: number) { danmakuWindowSetting.value.width = width; danmakuWindowSetting.value.height = height; danmakuWindow.value?.setSize(new PhysicalSize(width, height)); } function setDanmakuWindowPosition(x: number, y: number) { danmakuWindowSetting.value.x = x; danmakuWindowSetting.value.y = y; danmakuWindow.value?.setPosition(new PhysicalPosition(x, y)); } function updateSetting(key: K, value: DanmakuWindowSettings[K]) { danmakuWindowSetting.value[key] = value; // 特定设置需要直接应用到窗口 if (key === 'alwaysOnTop' && danmakuWindow.value) { danmakuWindow.value.window.setAlwaysOnTop(value as boolean); } if (key === 'interactive' && danmakuWindow.value) { danmakuWindow.value.window.setIgnoreCursorEvents(value as boolean); } } async function createWindow() { const appWindow = new Window('uniqueLabel', { decorations: true, resizable: true, transparent: true, fullscreen: false, alwaysOnTop: danmakuWindowSetting.value.alwaysOnTop, title: "VTsuru 弹幕窗口", }); // loading embedded asset: danmakuWindow.value = new Webview(appWindow, 'theUniqueLabel', { url: `${CURRENT_HOST}/client/danaku-window-manage`, width: danmakuWindowSetting.value.width, height: danmakuWindowSetting.value.height, x: danmakuWindowSetting.value.x, y: danmakuWindowSetting.value.y, }); appWindow.onCloseRequested(() => { danmakuWindow.value = undefined; bc?.close(); bc = undefined; }); danmakuWindow.value.once('tauri://window-created', async () => { console.log('弹幕窗口已创建'); await danmakuWindow.value?.window.setIgnoreCursorEvents(true); }); bc?.postMessage({ type: 'danmaku', data: { type: EventDataTypes.Message, msg: '弹幕窗口已打开', } as Partial, }); bc = new BroadcastChannel(DANMAKU_WINDOW_BROADCAST_CHANNEL); if (danmakuClient.connected) { danmakuClient.onEvent('danmaku', (event) => onGetDanmakus(event)); danmakuClient.onEvent('gift', (event) => onGetDanmakus(event)); danmakuClient.onEvent('sc', (event) => onGetDanmakus(event)); danmakuClient.onEvent('guard', (event) => onGetDanmakus(event)); danmakuClient.onEvent('enter', (event) => onGetDanmakus(event)); danmakuClient.onEvent('scDel', (event) => onGetDanmakus(event)); } } function onGetDanmakus(data: EventModel) { bc?.postMessage({ type: 'danmaku', data, }); } watch(danmakuWindowSetting, async (newValue) => { if (danmakuWindow.value) { if (await danmakuWindow.value.window.isVisible()) { danmakuWindow.value.setSize(new PhysicalSize(newValue.width, newValue.height)); danmakuWindow.value.setPosition(new PhysicalPosition(newValue.x, newValue.y)); } bc?.postMessage({ type: 'update-setting', data: newValue, }); } }, { deep: true }); return { danmakuWindow, danmakuWindowSetting, setDanmakuWindowSize, setDanmakuWindowPosition, updateSetting, isDanmakuWindowOpen: computed(() => !!danmakuWindow.value), createWindow, closeWindow, hideWindow }; }); if (import.meta.hot) { import.meta.hot.accept(acceptHMRUpdate(useDanmakuWindow, import.meta.hot)); }