mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
257 lines
7.4 KiB
TypeScript
257 lines
7.4 KiB
TypeScript
import type { ModalStateReader, RendererContext } from '../context';
|
|
import {
|
|
YOMITAN_POPUP_HIDDEN_EVENT,
|
|
YOMITAN_POPUP_SHOWN_EVENT,
|
|
isYomitanPopupVisible,
|
|
isYomitanPopupIframe,
|
|
} from '../yomitan-popup.js';
|
|
|
|
export function createMouseHandlers(
|
|
ctx: RendererContext,
|
|
options: {
|
|
modalStateReader: ModalStateReader;
|
|
applyYPercent: (yPercent: number) => void;
|
|
getCurrentYPercent: () => number;
|
|
persistSubtitlePositionPatch: (patch: { yPercent: number }) => void;
|
|
getSubtitleHoverAutoPauseEnabled: () => boolean;
|
|
getYomitanPopupAutoPauseEnabled: () => boolean;
|
|
getPlaybackPaused: () => Promise<boolean | null>;
|
|
sendMpvCommand: (command: (string | number)[]) => void;
|
|
},
|
|
) {
|
|
let yomitanPopupVisible = false;
|
|
let hoverPauseRequestId = 0;
|
|
let popupPauseRequestId = 0;
|
|
let pausedBySubtitleHover = false;
|
|
let pausedByYomitanPopup = false;
|
|
|
|
function maybeResumeHoverPause(): void {
|
|
if (!pausedBySubtitleHover) return;
|
|
if (pausedByYomitanPopup) return;
|
|
if (ctx.state.isOverSubtitle) return;
|
|
pausedBySubtitleHover = false;
|
|
options.sendMpvCommand(['set_property', 'pause', 'no']);
|
|
}
|
|
|
|
function maybeResumeYomitanPopupPause(): void {
|
|
if (!pausedByYomitanPopup) return;
|
|
pausedByYomitanPopup = false;
|
|
if (ctx.state.isOverSubtitle && options.getSubtitleHoverAutoPauseEnabled()) {
|
|
pausedBySubtitleHover = true;
|
|
return;
|
|
}
|
|
options.sendMpvCommand(['set_property', 'pause', 'no']);
|
|
}
|
|
|
|
async function maybePauseForYomitanPopup(): Promise<void> {
|
|
if (!yomitanPopupVisible || !options.getYomitanPopupAutoPauseEnabled()) {
|
|
return;
|
|
}
|
|
|
|
const requestId = ++popupPauseRequestId;
|
|
if (pausedByYomitanPopup) return;
|
|
|
|
if (pausedBySubtitleHover) {
|
|
pausedBySubtitleHover = false;
|
|
pausedByYomitanPopup = true;
|
|
return;
|
|
}
|
|
|
|
let paused: boolean | null = null;
|
|
try {
|
|
paused = await options.getPlaybackPaused();
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
requestId !== popupPauseRequestId ||
|
|
!yomitanPopupVisible ||
|
|
!options.getYomitanPopupAutoPauseEnabled()
|
|
) {
|
|
return;
|
|
}
|
|
if (paused !== false) return;
|
|
|
|
options.sendMpvCommand(['set_property', 'pause', 'yes']);
|
|
pausedByYomitanPopup = true;
|
|
}
|
|
|
|
function enablePopupInteraction(): void {
|
|
yomitanPopupVisible = true;
|
|
ctx.state.yomitanPopupVisible = true;
|
|
ctx.dom.overlay.classList.add('interactive');
|
|
if (ctx.platform.shouldToggleMouseIgnore) {
|
|
window.electronAPI.setIgnoreMouseEvents(false);
|
|
}
|
|
if (ctx.platform.isMacOSPlatform) {
|
|
window.focus();
|
|
}
|
|
}
|
|
|
|
function disablePopupInteractionIfIdle(): void {
|
|
if (typeof document !== 'undefined' && isYomitanPopupVisible(document)) {
|
|
yomitanPopupVisible = true;
|
|
ctx.state.yomitanPopupVisible = true;
|
|
return;
|
|
}
|
|
|
|
yomitanPopupVisible = false;
|
|
ctx.state.yomitanPopupVisible = false;
|
|
popupPauseRequestId += 1;
|
|
maybeResumeYomitanPopupPause();
|
|
maybeResumeHoverPause();
|
|
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
|
|
ctx.dom.overlay.classList.remove('interactive');
|
|
if (ctx.platform.shouldToggleMouseIgnore) {
|
|
window.electronAPI.setIgnoreMouseEvents(true, { forward: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
async function handleMouseEnter(): Promise<void> {
|
|
ctx.state.isOverSubtitle = true;
|
|
ctx.dom.overlay.classList.add('interactive');
|
|
if (ctx.platform.shouldToggleMouseIgnore) {
|
|
window.electronAPI.setIgnoreMouseEvents(false);
|
|
}
|
|
|
|
if (yomitanPopupVisible && options.getYomitanPopupAutoPauseEnabled()) {
|
|
return;
|
|
}
|
|
|
|
if (!options.getSubtitleHoverAutoPauseEnabled()) {
|
|
return;
|
|
}
|
|
|
|
const requestId = ++hoverPauseRequestId;
|
|
let paused: boolean | null = null;
|
|
try {
|
|
paused = await options.getPlaybackPaused();
|
|
} catch {
|
|
return;
|
|
}
|
|
if (requestId !== hoverPauseRequestId || !ctx.state.isOverSubtitle) {
|
|
return;
|
|
}
|
|
if (paused !== false) {
|
|
return;
|
|
}
|
|
options.sendMpvCommand(['set_property', 'pause', 'yes']);
|
|
pausedBySubtitleHover = true;
|
|
}
|
|
|
|
async function handleMouseLeave(): Promise<void> {
|
|
ctx.state.isOverSubtitle = false;
|
|
hoverPauseRequestId += 1;
|
|
maybeResumeHoverPause();
|
|
if (yomitanPopupVisible) return;
|
|
disablePopupInteractionIfIdle();
|
|
}
|
|
|
|
function setupDragging(): void {
|
|
ctx.dom.subtitleContainer.addEventListener('mousedown', (e: MouseEvent) => {
|
|
if (e.button === 2) {
|
|
e.preventDefault();
|
|
ctx.state.isDragging = true;
|
|
ctx.state.dragStartY = e.clientY;
|
|
ctx.state.startYPercent = options.getCurrentYPercent();
|
|
ctx.dom.subtitleContainer.style.cursor = 'grabbing';
|
|
}
|
|
});
|
|
|
|
document.addEventListener('mousemove', (e: MouseEvent) => {
|
|
if (!ctx.state.isDragging) return;
|
|
|
|
const deltaY = ctx.state.dragStartY - e.clientY;
|
|
const deltaPercent = (deltaY / window.innerHeight) * 100;
|
|
const newYPercent = ctx.state.startYPercent + deltaPercent;
|
|
|
|
options.applyYPercent(newYPercent);
|
|
});
|
|
|
|
document.addEventListener('mouseup', (e: MouseEvent) => {
|
|
if (ctx.state.isDragging && e.button === 2) {
|
|
ctx.state.isDragging = false;
|
|
ctx.dom.subtitleContainer.style.cursor = '';
|
|
|
|
const yPercent = options.getCurrentYPercent();
|
|
options.persistSubtitlePositionPatch({ yPercent });
|
|
}
|
|
});
|
|
|
|
ctx.dom.subtitleContainer.addEventListener('contextmenu', (e: Event) => {
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
|
|
function setupResizeHandler(): void {
|
|
window.addEventListener('resize', () => {
|
|
options.applyYPercent(options.getCurrentYPercent());
|
|
});
|
|
}
|
|
|
|
function setupSelectionObserver(): void {
|
|
document.addEventListener('selectionchange', () => {
|
|
const selection = window.getSelection();
|
|
const hasSelection = selection && selection.rangeCount > 0 && !selection.isCollapsed;
|
|
|
|
if (hasSelection) {
|
|
ctx.dom.subtitleRoot.classList.add('has-selection');
|
|
} else {
|
|
ctx.dom.subtitleRoot.classList.remove('has-selection');
|
|
}
|
|
});
|
|
}
|
|
|
|
function setupYomitanObserver(): void {
|
|
yomitanPopupVisible = isYomitanPopupVisible(document);
|
|
ctx.state.yomitanPopupVisible = yomitanPopupVisible;
|
|
void maybePauseForYomitanPopup();
|
|
|
|
window.addEventListener(YOMITAN_POPUP_SHOWN_EVENT, () => {
|
|
enablePopupInteraction();
|
|
void maybePauseForYomitanPopup();
|
|
});
|
|
|
|
window.addEventListener(YOMITAN_POPUP_HIDDEN_EVENT, () => {
|
|
disablePopupInteractionIfIdle();
|
|
});
|
|
|
|
const observer = new MutationObserver((mutations: MutationRecord[]) => {
|
|
for (const mutation of mutations) {
|
|
mutation.addedNodes.forEach((node) => {
|
|
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
const element = node as Element;
|
|
if (isYomitanPopupIframe(element)) {
|
|
enablePopupInteraction();
|
|
void maybePauseForYomitanPopup();
|
|
}
|
|
});
|
|
|
|
mutation.removedNodes.forEach((node) => {
|
|
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
const element = node as Element;
|
|
if (isYomitanPopupIframe(element)) {
|
|
disablePopupInteractionIfIdle();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
observer.observe(document.body, {
|
|
childList: true,
|
|
subtree: true,
|
|
});
|
|
}
|
|
|
|
return {
|
|
handleMouseEnter,
|
|
handleMouseLeave,
|
|
setupDragging,
|
|
setupResizeHandler,
|
|
setupSelectionObserver,
|
|
setupYomitanObserver,
|
|
};
|
|
}
|