mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 03:14:57 +08:00
✨ feat: 完善歌词窗口 IPC
This commit is contained in:
@@ -13,56 +13,82 @@ const initLyricIpc = (): void => {
|
||||
// 歌词窗口
|
||||
let lyricWin: BrowserWindow | null = null;
|
||||
|
||||
/**
|
||||
* 窗口是否存活
|
||||
* @param win 窗口实例
|
||||
* @returns 是否存活
|
||||
*/
|
||||
const isWinAlive = (win: BrowserWindow | null): win is BrowserWindow =>
|
||||
!!win && !win.isDestroyed();
|
||||
|
||||
// 切换桌面歌词
|
||||
ipcMain.on("toggle-desktop-lyric", (_event, val: boolean) => {
|
||||
if (val) {
|
||||
if (!lyricWin) {
|
||||
if (!isWinAlive(lyricWin)) {
|
||||
lyricWin = lyricWindow.create();
|
||||
// 监听关闭,置空引用,防止后续调用报错
|
||||
lyricWin?.on("closed", () => {
|
||||
lyricWin = null;
|
||||
});
|
||||
// 设置位置
|
||||
const { x, y } = store.get("lyric");
|
||||
const xPos = Number(x);
|
||||
const yPos = Number(y);
|
||||
if (Number.isFinite(xPos) && Number.isFinite(yPos)) {
|
||||
lyricWin?.setPosition(Math.round(xPos), Math.round(yPos));
|
||||
}
|
||||
} else {
|
||||
lyricWin?.show();
|
||||
lyricWin.show();
|
||||
}
|
||||
if (isWinAlive(lyricWin)) {
|
||||
lyricWin.setAlwaysOnTop(true, "screen-saver");
|
||||
}
|
||||
lyricWin?.setAlwaysOnTop(true, "screen-saver");
|
||||
} else {
|
||||
// 关闭:不销毁窗口,直接隐藏,保留位置与状态
|
||||
if (!lyricWin) return;
|
||||
if (!isWinAlive(lyricWin)) return;
|
||||
lyricWin.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// 向主窗口发送事件
|
||||
ipcMain.on("send-to-main", (_, eventName, ...args) => {
|
||||
mainWin?.webContents.send(eventName, ...args);
|
||||
});
|
||||
|
||||
// 更新歌词窗口数据
|
||||
ipcMain.on("update-desktop-lyric-data", (_, lyricData) => {
|
||||
if (!lyricData || !lyricWin) return;
|
||||
lyricWin?.webContents.send("update-desktop-lyric-data", lyricData);
|
||||
if (!lyricData || !isWinAlive(lyricWin)) return;
|
||||
lyricWin.webContents.send("update-desktop-lyric-data", lyricData);
|
||||
});
|
||||
|
||||
// 更新歌词窗口配置
|
||||
ipcMain.on("update-desktop-lyric-option", (_, option) => {
|
||||
if (!option || !lyricWin) return;
|
||||
lyricWin?.webContents.send("desktop-lyric-option-change", option);
|
||||
if (!option || !isWinAlive(lyricWin)) return;
|
||||
lyricWin.webContents.send("desktop-lyric-option-change", option);
|
||||
});
|
||||
|
||||
// 播放状态更改
|
||||
ipcMain.on("play-status-change", (_, status) => {
|
||||
if (!status || !lyricWin) return;
|
||||
lyricWin?.webContents.send("update-desktop-lyric-data", { playStatus: status });
|
||||
if (!isWinAlive(lyricWin)) return;
|
||||
lyricWin.webContents.send("update-desktop-lyric-data", { playStatus: status });
|
||||
});
|
||||
|
||||
// 音乐名称更改
|
||||
ipcMain.on("play-song-change", (_, title) => {
|
||||
if (!title || !lyricWin) return;
|
||||
lyricWin?.webContents.send("update-desktop-lyric-data", { playName: title });
|
||||
if (!title || !isWinAlive(lyricWin)) return;
|
||||
lyricWin.webContents.send("update-desktop-lyric-data", { playName: title });
|
||||
});
|
||||
|
||||
// 音乐歌词更改
|
||||
ipcMain.on("play-lyric-change", (_, lyricData) => {
|
||||
if (!lyricData || !lyricWin) return;
|
||||
lyricWin?.webContents.send("update-desktop-lyric-data", lyricData);
|
||||
if (!lyricData || !isWinAlive(lyricWin)) return;
|
||||
lyricWin.webContents.send("update-desktop-lyric-data", lyricData);
|
||||
});
|
||||
|
||||
// 获取窗口位置
|
||||
ipcMain.handle("get-window-bounds", () => {
|
||||
if (!lyricWin) return {};
|
||||
return lyricWin?.getBounds();
|
||||
if (!isWinAlive(lyricWin)) return {};
|
||||
return lyricWin.getBounds();
|
||||
});
|
||||
|
||||
// 获取屏幕尺寸
|
||||
@@ -71,24 +97,40 @@ const initLyricIpc = (): void => {
|
||||
return { width, height };
|
||||
});
|
||||
|
||||
// 获取多屏虚拟边界(支持负坐标)
|
||||
ipcMain.handle("get-virtual-screen-bounds", () => {
|
||||
const displays = screen.getAllDisplays();
|
||||
const bounds = displays.map((d) => d.workArea);
|
||||
const minX = Math.min(...bounds.map((b) => b.x));
|
||||
const minY = Math.min(...bounds.map((b) => b.y));
|
||||
const maxX = Math.max(...bounds.map((b) => b.x + b.width));
|
||||
const maxY = Math.max(...bounds.map((b) => b.y + b.height));
|
||||
return { minX, minY, maxX, maxY };
|
||||
});
|
||||
|
||||
// 移动窗口
|
||||
ipcMain.on("move-window", (_, x, y, width, height) => {
|
||||
if (!lyricWin) return;
|
||||
if (!isWinAlive(lyricWin)) return;
|
||||
lyricWin.setBounds({ x, y, width, height });
|
||||
// 保存配置
|
||||
store.set("lyric", { ...store.get("lyric"), x, y, width, height });
|
||||
// 保持置顶
|
||||
lyricWin?.setAlwaysOnTop(true, "screen-saver");
|
||||
});
|
||||
|
||||
// 更新高度
|
||||
ipcMain.on("update-window-height", (_, height) => {
|
||||
if (!lyricWin) return;
|
||||
if (!isWinAlive(lyricWin)) return;
|
||||
const { width } = lyricWin.getBounds();
|
||||
// 更新窗口高度
|
||||
lyricWin.setBounds({ width, height });
|
||||
});
|
||||
|
||||
// 请求歌词数据及配置
|
||||
ipcMain.on("request-desktop-lyric-data", () => {
|
||||
if (!isWinAlive(lyricWin)) return;
|
||||
// 触发窗口更新
|
||||
mainWin?.webContents.send("request-desktop-lyric-data");
|
||||
});
|
||||
|
||||
// 获取配置
|
||||
ipcMain.handle("get-desktop-lyric-option", () => {
|
||||
return store.get("lyric");
|
||||
@@ -98,7 +140,7 @@ const initLyricIpc = (): void => {
|
||||
ipcMain.on("set-desktop-lyric-option", (_, option, callback: boolean = false) => {
|
||||
store.set("lyric", option);
|
||||
// 触发窗口更新
|
||||
if (callback && lyricWin) {
|
||||
if (callback && isWinAlive(lyricWin)) {
|
||||
lyricWin.webContents.send("desktop-lyric-option-change", option);
|
||||
}
|
||||
mainWin?.webContents.send("desktop-lyric-option-change", option);
|
||||
@@ -111,14 +153,14 @@ const initLyricIpc = (): void => {
|
||||
|
||||
// 关闭桌面歌词
|
||||
ipcMain.on("closeDesktopLyric", () => {
|
||||
if (!lyricWin) return;
|
||||
if (!isWinAlive(lyricWin)) return;
|
||||
lyricWin.hide();
|
||||
mainWin?.webContents.send("closeDesktopLyric");
|
||||
});
|
||||
|
||||
// 锁定/解锁桌面歌词
|
||||
ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => {
|
||||
if (!lyricWin) return;
|
||||
if (!isWinAlive(lyricWin)) return;
|
||||
// 是否穿透
|
||||
if (isLock) {
|
||||
lyricWin.setIgnoreMouseEvents(true, { forward: true });
|
||||
|
||||
@@ -29,6 +29,16 @@ const initWindowsIpc = (): void => {
|
||||
if (isMaximized) mainWin?.maximize();
|
||||
mainWin?.show();
|
||||
mainWin?.focus();
|
||||
// 解决窗口不立即显示
|
||||
mainWin?.setAlwaysOnTop(true);
|
||||
// 100ms 后取消置顶
|
||||
const timer = setTimeout(() => {
|
||||
if (mainWin && !mainWin.isDestroyed()) {
|
||||
mainWin.setAlwaysOnTop(false);
|
||||
mainWin.focus();
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}, 100);
|
||||
// 初始化缩略图工具栏
|
||||
if (mainWin) initThumbar(mainWin);
|
||||
});
|
||||
@@ -64,6 +74,7 @@ const initWindowsIpc = (): void => {
|
||||
// 显示
|
||||
ipcMain.on("win-show", () => {
|
||||
mainWin?.show();
|
||||
mainWin?.focus();
|
||||
});
|
||||
|
||||
// 重启
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { screen } from "electron";
|
||||
import { storeLog } from "../logger";
|
||||
import type { LyricConfig } from "../../../src/types/desktop-lyric";
|
||||
import Store from "electron-store";
|
||||
|
||||
storeLog.info("🌱 Store init");
|
||||
@@ -21,6 +21,8 @@ export interface StoreType {
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
// 配置
|
||||
config?: LyricConfig;
|
||||
};
|
||||
proxy: string;
|
||||
}
|
||||
@@ -40,10 +42,22 @@ export const useStore = () => {
|
||||
fontSize: 30,
|
||||
mainColor: "#fff",
|
||||
shadowColor: "rgba(0, 0, 0, 0.5)",
|
||||
x: screen.getPrimaryDisplay().workAreaSize.width / 2 - 400,
|
||||
y: screen.getPrimaryDisplay().workAreaSize.height - 90,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 800,
|
||||
height: 180,
|
||||
config: {
|
||||
isLock: false,
|
||||
playedColor: "#fe7971",
|
||||
unplayedColor: "#ccc",
|
||||
stroke: "#000",
|
||||
strokeWidth: 2,
|
||||
fontFamily: "system-ui",
|
||||
fontSize: 24,
|
||||
isDoubleLine: true,
|
||||
position: "both",
|
||||
limitBounds: false,
|
||||
},
|
||||
},
|
||||
proxy: "",
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ export const createWindow = (
|
||||
title: appName,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
frame: false, // 创建后是否显示窗口
|
||||
frame: false, // 是否显示窗口边框
|
||||
center: true, // 窗口居中
|
||||
icon, // 窗口图标
|
||||
autoHideMenuBar: true, // 隐藏菜单栏
|
||||
|
||||
@@ -38,25 +38,27 @@ class LyricWindow {
|
||||
height: height || 180,
|
||||
minWidth: 440,
|
||||
minHeight: 120,
|
||||
maxWidth: 1600,
|
||||
maxHeight: 300,
|
||||
center: !(x && y), // 没有指定位置时居中显示
|
||||
// maxWidth: 1600,
|
||||
// maxHeight: 300,
|
||||
// 窗口位置
|
||||
x,
|
||||
y,
|
||||
transparent: true,
|
||||
backgroundColor: "rgba(0, 0, 0, 0)",
|
||||
// transparent: true,
|
||||
// backgroundColor: "rgba(0, 0, 0, 0)",
|
||||
alwaysOnTop: true,
|
||||
resizable: true,
|
||||
movable: true,
|
||||
show: false,
|
||||
// 不在任务栏显示
|
||||
skipTaskbar: true,
|
||||
// 窗口不能最小化
|
||||
minimizable: false,
|
||||
// 窗口不能最大化
|
||||
maximizable: false,
|
||||
// 窗口不能进入全屏状态
|
||||
fullscreenable: false,
|
||||
// skipTaskbar: true,
|
||||
// // 窗口不能最小化
|
||||
// minimizable: false,
|
||||
// // 窗口不能最大化
|
||||
// maximizable: false,
|
||||
// // 窗口不能进入全屏状态
|
||||
// fullscreenable: false,
|
||||
frame: true,
|
||||
});
|
||||
if (!this.win) return null;
|
||||
// 加载地址
|
||||
|
||||
@@ -3,16 +3,16 @@ import { LyricType } from "@/types/main";
|
||||
/** 桌面歌词数据 */
|
||||
export interface LyricData {
|
||||
/** 播放歌曲名称 */
|
||||
playName: string;
|
||||
playName?: string;
|
||||
/** 播放状态 */
|
||||
playStatus: boolean;
|
||||
playStatus?: boolean;
|
||||
/** 播放进度 */
|
||||
progress: number;
|
||||
progress?: number;
|
||||
/** 歌词数据 */
|
||||
lrcData: LyricType[];
|
||||
yrcData: LyricType[];
|
||||
lrcData?: LyricType[];
|
||||
yrcData?: LyricType[];
|
||||
/** 歌词播放索引 */
|
||||
lyricIndex: number;
|
||||
lyricIndex?: number;
|
||||
}
|
||||
|
||||
/** 桌面歌词配置 */
|
||||
@@ -31,10 +31,22 @@ export interface LyricConfig {
|
||||
fontFamily: string;
|
||||
/** 字体大小 */
|
||||
fontSize: number;
|
||||
/** 行高 */
|
||||
lineHeight: number;
|
||||
/** 是否双行 */
|
||||
isDoubleLine: boolean;
|
||||
/** 文本排版位置 */
|
||||
position: "left" | "center" | "right" | "both";
|
||||
/** 是否限制在屏幕边界内拖动 */
|
||||
limitBounds: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染的歌词行
|
||||
*/
|
||||
interface RenderLine {
|
||||
/** 歌词文本 */
|
||||
text: string;
|
||||
/** 唯一键 */
|
||||
key: string;
|
||||
/** 是否高亮 */
|
||||
active: boolean;
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import { openUpdateApp } from "./modal";
|
||||
import { useMusicStore, useDataStore, useStatusStore } from "@/stores";
|
||||
import { toLikeSong } from "./auth";
|
||||
import player from "./player";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { getPlayerInfo } from "./player-utils/song";
|
||||
|
||||
// 关闭更新状态
|
||||
const closeUpdateStatus = () => {
|
||||
@@ -39,6 +41,23 @@ const initIpc = () => {
|
||||
// 桌面歌词开关
|
||||
window.electron.ipcRenderer.on("toogleDesktopLyric", () => player.toggleDesktopLyric());
|
||||
window.electron.ipcRenderer.on("closeDesktopLyric", () => player.toggleDesktopLyric());
|
||||
// 请求歌词数据
|
||||
window.electron.ipcRenderer.on("request-desktop-lyric-data", () => {
|
||||
const musicStore = useMusicStore();
|
||||
const statusStore = useStatusStore();
|
||||
if (player) {
|
||||
window.electron.ipcRenderer.send(
|
||||
"update-desktop-lyric-data",
|
||||
cloneDeep({
|
||||
playStatus: statusStore.playStatus,
|
||||
playName: getPlayerInfo() ?? "未知歌曲",
|
||||
lrcData: musicStore.songLyric.lrcData ?? [],
|
||||
yrcData: musicStore.songLyric.yrcData ?? [],
|
||||
lyricIndex: statusStore.lyricIndex,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
// 无更新
|
||||
window.electron.ipcRenderer.on("update-not-available", () => {
|
||||
closeUpdateStatus();
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
<div
|
||||
ref="desktopLyricsRef"
|
||||
:class="[
|
||||
'desktop-lyrics',
|
||||
'desktop-lyric',
|
||||
{
|
||||
locked: lyricConfig.isLock,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<div class="header" align="center" justify="space-between">
|
||||
<n-flex :wrap="false" align="center" justify="flex-start" size="small">
|
||||
<div class="menu-btn" title="返回应用">
|
||||
<n-flex :wrap="false" align="center" justify="flex-start" size="small" @pointerdown.stop>
|
||||
<div class="menu-btn" title="返回应用" @click.stop="sendToMain('win-show')">
|
||||
<SvgIcon name="Music" />
|
||||
</div>
|
||||
<div class="menu-btn" title="增加字体大小">
|
||||
@@ -22,18 +22,22 @@
|
||||
</div>
|
||||
<span class="song-name">{{ lyricData.playName }}</span>
|
||||
</n-flex>
|
||||
<n-flex :wrap="false" align="center" justify="center" size="small">
|
||||
<div class="menu-btn" title="上一曲">
|
||||
<n-flex :wrap="false" align="center" justify="center" size="small" @pointerdown.stop>
|
||||
<div class="menu-btn" title="上一曲" @click.stop="sendToMain('playPrev')">
|
||||
<SvgIcon name="SkipPrev" />
|
||||
</div>
|
||||
<div class="menu-btn" :title="lyricData.playStatus ? '暂停' : '播放'">
|
||||
<div
|
||||
class="menu-btn"
|
||||
:title="lyricData.playStatus ? '暂停' : '播放'"
|
||||
@click.stop="sendToMain('playOrPause')"
|
||||
>
|
||||
<SvgIcon :name="lyricData.playStatus ? 'Pause' : 'Play'" />
|
||||
</div>
|
||||
<div class="menu-btn" title="下一曲">
|
||||
<div class="menu-btn" title="下一曲" @click.stop="sendToMain('playNext')">
|
||||
<SvgIcon name="SkipNext" />
|
||||
</div>
|
||||
</n-flex>
|
||||
<n-flex :wrap="false" align="center" justify="flex-end" size="small">
|
||||
<n-flex :wrap="false" align="center" justify="flex-end" size="small" @pointerdown.stop>
|
||||
<div class="menu-btn" title="锁定">
|
||||
<SvgIcon name="Lock" />
|
||||
</div>
|
||||
@@ -45,25 +49,32 @@
|
||||
</div>
|
||||
</n-flex>
|
||||
</div>
|
||||
<div
|
||||
<n-flex
|
||||
:style="{
|
||||
fontSize: lyricConfig.fontSize + 'px',
|
||||
fontFamily: lyricConfig.fontFamily,
|
||||
lineHeight: lyricConfig.lineHeight + 'px',
|
||||
}"
|
||||
class="lyrics-container"
|
||||
:class="['lyric-container', lyricConfig.position]"
|
||||
vertical
|
||||
>
|
||||
<span v-for="(item, index) in displayLyricLines" :key="index" class="lyric-line">
|
||||
{{ item }}
|
||||
<span
|
||||
v-for="line in renderLyricLines"
|
||||
:key="line.key"
|
||||
:class="['lyric-line', { active: line.active }]"
|
||||
:style="{
|
||||
color: line.active ? lyricConfig.playedColor : lyricConfig.unplayedColor,
|
||||
}"
|
||||
>
|
||||
{{ line.text }}
|
||||
</span>
|
||||
</div>
|
||||
</n-flex>
|
||||
</div>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Position } from "@vueuse/core";
|
||||
import { LyricConfig, LyricData } from ".";
|
||||
import { LyricConfig, LyricData, RenderLine } from "@/types/desktop-lyric";
|
||||
|
||||
// 桌面歌词数据
|
||||
const lyricData = reactive<LyricData>({
|
||||
@@ -78,71 +89,172 @@ const lyricData = reactive<LyricData>({
|
||||
// 桌面歌词配置
|
||||
const lyricConfig = reactive<LyricConfig>({
|
||||
isLock: false,
|
||||
playedColor: "#fff",
|
||||
playedColor: "#fe7971",
|
||||
unplayedColor: "#ccc",
|
||||
stroke: "#000",
|
||||
strokeWidth: 2,
|
||||
fontFamily: "system-ui",
|
||||
fontSize: 24,
|
||||
lineHeight: 48,
|
||||
isDoubleLine: false,
|
||||
position: "center",
|
||||
isDoubleLine: true,
|
||||
position: "both",
|
||||
limitBounds: false,
|
||||
});
|
||||
|
||||
// 桌面歌词元素
|
||||
const desktopLyricsRef = useTemplateRef<HTMLElement>("desktopLyricsRef");
|
||||
|
||||
// 需要显示的歌词行
|
||||
const displayLyricLines = computed<string[]>(() => {
|
||||
// 优先使用逐字歌词,无则使用普通歌词
|
||||
const lyrics = lyricData.yrcData.length ? lyricData.yrcData : lyricData.lrcData;
|
||||
if (!lyrics.length) return ["纯音乐,请欣赏"];
|
||||
// 当前播放歌词索引
|
||||
let idx = lyricData.lyricIndex;
|
||||
if (idx < 0) idx = 0; // 开头之前按首句处理
|
||||
// 当前与下一句
|
||||
/**
|
||||
* 渲染的歌词行
|
||||
* @returns 渲染的歌词行数组
|
||||
*/
|
||||
const renderLyricLines = computed<RenderLine[]>(() => {
|
||||
const lyrics = lyricData?.yrcData?.length ? lyricData.yrcData : lyricData.lrcData;
|
||||
if (!lyrics?.length) {
|
||||
return [{ text: "纯音乐,请欣赏", key: "placeholder", active: true }];
|
||||
}
|
||||
let idx = lyricData?.lyricIndex ?? -1;
|
||||
// 显示歌名
|
||||
if (idx < 0) {
|
||||
return [{ text: lyricData.playName ?? "未知歌曲", key: "placeholder", active: true }];
|
||||
}
|
||||
const current = lyrics[idx];
|
||||
const next = lyrics[idx + 1];
|
||||
if (!current) return [];
|
||||
// 若当前句有翻译,无论单/双行设置都显示两行:原文 + 翻译
|
||||
// 有翻译
|
||||
if (current.tran && current.tran.trim().length > 0) {
|
||||
return [current.content, current.tran];
|
||||
const lines: RenderLine[] = [
|
||||
{ text: current.content, key: `${idx}:orig`, active: true },
|
||||
{ text: current.tran, key: `${idx}:tran`, active: false },
|
||||
];
|
||||
return lines.filter((l) => l.text && l.text.trim().length > 0);
|
||||
}
|
||||
// 单行:仅返回当前句原文
|
||||
// 单行:仅当前句原文,高亮
|
||||
if (!lyricConfig.isDoubleLine) {
|
||||
return [current.content];
|
||||
return [{ text: current.content, key: `${idx}:orig`, active: true }].filter(
|
||||
(l) => l.text && l.text.trim().length > 0,
|
||||
);
|
||||
}
|
||||
// 双行:两行内容交替
|
||||
// 双行交替:只高亮当前句所在行
|
||||
const isEven = idx % 2 === 0;
|
||||
if (isEven) {
|
||||
// 偶数句:第一行当前句,第二行下一句
|
||||
return [current.content, next?.content ?? ""];
|
||||
const lines: RenderLine[] = [
|
||||
{ text: current.content, key: `${idx}:orig`, active: true },
|
||||
{ text: next?.content ?? "", key: `${idx + 1}:next`, active: false },
|
||||
];
|
||||
return lines.filter((l) => l.text && l.text.trim().length > 0);
|
||||
}
|
||||
// 奇数句:第一行下一句,第二行当前句
|
||||
return [next?.content ?? "", current.content];
|
||||
const lines: RenderLine[] = [
|
||||
{ text: next?.content ?? "", key: `${idx + 1}:next`, active: false },
|
||||
{ text: current.content, key: `${idx}:orig`, active: true },
|
||||
];
|
||||
return lines.filter((l) => l.text && l.text.trim().length > 0);
|
||||
});
|
||||
|
||||
// 桌面歌词拖动
|
||||
const lyricDragMove = (position: Position) => {
|
||||
console.log(position);
|
||||
// 拖拽窗口状态
|
||||
const dragState = reactive({
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
startWinX: 0,
|
||||
startWinY: 0,
|
||||
winWidth: 0,
|
||||
winHeight: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
* 桌面歌词拖动开始
|
||||
* @param _position 拖动位置
|
||||
* @param event 指针事件
|
||||
*/
|
||||
const lyricDragStart = async (_position: Position, event: PointerEvent) => {
|
||||
if (lyricConfig.isLock) return;
|
||||
dragState.isDragging = true;
|
||||
const { x, y, width, height } = await window.electron.ipcRenderer.invoke("get-window-bounds");
|
||||
dragState.startX = (event?.screenX ?? 0) as number;
|
||||
dragState.startY = (event?.screenY ?? 0) as number;
|
||||
dragState.startWinX = x as number;
|
||||
dragState.startWinY = y as number;
|
||||
dragState.winWidth = width as number;
|
||||
dragState.winHeight = height as number;
|
||||
};
|
||||
|
||||
/**
|
||||
* 桌面歌词拖动移动
|
||||
* @param _position 拖动位置
|
||||
* @param event 指针事件
|
||||
*/
|
||||
const lyricDragMove = async (_position: Position, event: PointerEvent) => {
|
||||
if (!dragState.isDragging || lyricConfig.isLock) return;
|
||||
const screenX = (event?.screenX ?? 0) as number;
|
||||
const screenY = (event?.screenY ?? 0) as number;
|
||||
let newWinX = dragState.startWinX + (screenX - dragState.startX);
|
||||
let newWinY = dragState.startWinY + (screenY - dragState.startY);
|
||||
// 可选:限制在屏幕边界(支持多屏)
|
||||
if (lyricConfig.limitBounds) {
|
||||
const { minX, minY, maxX, maxY } = await window.electron.ipcRenderer.invoke(
|
||||
"get-virtual-screen-bounds",
|
||||
);
|
||||
newWinX = Math.max(minX as number, Math.min((maxX as number) - dragState.winWidth, newWinX));
|
||||
newWinY = Math.max(minY as number, Math.min((maxY as number) - dragState.winHeight, newWinY));
|
||||
}
|
||||
window.electron.ipcRenderer.send(
|
||||
"move-window",
|
||||
newWinX,
|
||||
newWinY,
|
||||
dragState.winWidth,
|
||||
dragState.winHeight,
|
||||
);
|
||||
};
|
||||
|
||||
// 监听桌面歌词拖动
|
||||
useDraggable(desktopLyricsRef, {
|
||||
onMove: lyricDragMove,
|
||||
onStart: (position, event) => {
|
||||
lyricDragStart(position, event);
|
||||
},
|
||||
onMove: (position, event) => {
|
||||
lyricDragMove(position, event);
|
||||
},
|
||||
onEnd: () => {
|
||||
dragState.isDragging = false;
|
||||
},
|
||||
});
|
||||
|
||||
// 发送至主窗口
|
||||
const sendToMain = (eventName: string, ...args: any[]) => {
|
||||
// 特殊处理
|
||||
if (eventName === "win-show") {
|
||||
window.electron.ipcRenderer.send("win-show");
|
||||
return;
|
||||
}
|
||||
window.electron.ipcRenderer.send("send-to-main", eventName, ...args);
|
||||
};
|
||||
|
||||
// 处理歌词数据
|
||||
const handleLyricData = (data: LyricData) => {
|
||||
Object.assign(lyricData, data);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 接收歌词配置
|
||||
// 接收歌词数据
|
||||
window.electron.ipcRenderer.on("update-desktop-lyric-data", (_event, data: LyricData) => {
|
||||
handleLyricData(data);
|
||||
});
|
||||
// 请求歌词数据及配置
|
||||
window.electron.ipcRenderer.send("request-desktop-lyric-data");
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.desktop-lyrics {
|
||||
.n-config-provider {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.desktop-lyric {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
transition: background-color 0.3s;
|
||||
cursor: move;
|
||||
@@ -191,20 +303,43 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.lyric-container {
|
||||
padding: 0 8px;
|
||||
&.center {
|
||||
align-items: center;
|
||||
}
|
||||
&.right {
|
||||
align-items: flex-end;
|
||||
}
|
||||
&.both {
|
||||
.lyric-line {
|
||||
&:nth-child(2n) {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
&:not(.lock) {
|
||||
&:not(.locked) {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
.header {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.locked {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
.header {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
<!-- <style>
|
||||
body {
|
||||
background-color: transparent !important;
|
||||
/* background-image: url("https://picsum.photos/1920/1080"); */
|
||||
}
|
||||
</style>
|
||||
</style> -->
|
||||
|
||||
Reference in New Issue
Block a user