mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-12 05:10:51 +02:00
native os window snapping
This commit is contained in:
+11
-15
@@ -763,9 +763,9 @@ function isCursorInsideWindow(window: BrowserWindow): boolean {
|
||||
&& cursor.y < bounds.y + bounds.height
|
||||
}
|
||||
|
||||
function getFramelessWindowChromeOptions(): Pick<
|
||||
function getSnapCapableFramelessWindowOptions(): Pick<
|
||||
BrowserWindowConstructorOptions,
|
||||
'frame' | 'transparent' | 'backgroundColor' | 'roundedCorners' | 'hasShadow' | 'thickFrame' | 'backgroundMaterial'
|
||||
'frame' | 'transparent' | 'backgroundColor' | 'roundedCorners' | 'hasShadow' | 'thickFrame' | 'backgroundMaterial' | 'resizable' | 'maximizable' | 'fullscreenable' | 'minimizable' | 'skipTaskbar'
|
||||
> {
|
||||
return {
|
||||
frame: false,
|
||||
@@ -775,10 +775,15 @@ function getFramelessWindowChromeOptions(): Pick<
|
||||
hasShadow: false,
|
||||
...(process.platform === 'win32'
|
||||
? {
|
||||
thickFrame: false,
|
||||
thickFrame: true,
|
||||
backgroundMaterial: 'none',
|
||||
}
|
||||
: {}),
|
||||
resizable: true,
|
||||
maximizable: true,
|
||||
fullscreenable: true,
|
||||
minimizable: true,
|
||||
skipTaskbar: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -988,7 +993,7 @@ async function showCustomDialog(options: DialogOptions): Promise<DialogResult> {
|
||||
function createMainWindow(): void {
|
||||
mainWindow = new BrowserWindow({
|
||||
...WINDOW_DEFAULTS,
|
||||
...getFramelessWindowChromeOptions(),
|
||||
...getSnapCapableFramelessWindowOptions(),
|
||||
alwaysOnTop: getWindowStateStore().getMainAlwaysOnTop(),
|
||||
autoHideMenuBar: true,
|
||||
resizable: true,
|
||||
@@ -1192,12 +1197,7 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro
|
||||
height: bounds.height,
|
||||
minWidth: POPOUT_DEFAULTS.minWidth,
|
||||
minHeight: POPOUT_DEFAULTS.minHeight,
|
||||
...getFramelessWindowChromeOptions(),
|
||||
resizable: true,
|
||||
fullscreenable: false,
|
||||
maximizable: false,
|
||||
minimizable: true,
|
||||
skipTaskbar: true,
|
||||
...getSnapCapableFramelessWindowOptions(),
|
||||
autoHideMenuBar: true,
|
||||
title: `Prism ${SCOPE_LABELS[kind]}`,
|
||||
alwaysOnTop: getWindowStateStore().getPopoutAlwaysOnTop(kind),
|
||||
@@ -1365,11 +1365,7 @@ function createNowPlayingConfigWindow(): BrowserWindow {
|
||||
height: bounds.height,
|
||||
minWidth: NOW_PLAYING_CONFIG_DEFAULTS.minWidth,
|
||||
minHeight: NOW_PLAYING_CONFIG_DEFAULTS.minHeight,
|
||||
...getFramelessWindowChromeOptions(),
|
||||
resizable: true,
|
||||
fullscreenable: false,
|
||||
maximizable: false,
|
||||
minimizable: true,
|
||||
...getSnapCapableFramelessWindowOptions(),
|
||||
autoHideMenuBar: true,
|
||||
title: 'Prism Now Playing',
|
||||
show: false,
|
||||
|
||||
@@ -4,7 +4,6 @@ import Toolbar from './components/Toolbar'
|
||||
import SettingsPanel from './components/SettingsPanel'
|
||||
import BottomBar from './components/BottomBar'
|
||||
import ScopePopoutBridge from './components/ScopePopoutBridge'
|
||||
import WindowResizeOverlay from './components/WindowResizeOverlay'
|
||||
import AppBanner from './components/AppBanner'
|
||||
import { resolveMainWindowSettingsHeight } from './mainWindowSettings'
|
||||
import { useSettingsStore } from './stores/settingsStore'
|
||||
@@ -306,7 +305,6 @@ export default function App(): JSX.Element {
|
||||
<BottomBar onClose={handleCloseSettings} onHeightChange={setBottomBarHeight} />
|
||||
</div>
|
||||
|
||||
<WindowResizeOverlay />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from 'react'
|
||||
import type { NowPlayingProviderDefinition, NowPlayingProviderId, NowPlayingProviderState } from '../../types/nowPlaying'
|
||||
import AppBanner from './AppBanner'
|
||||
import WindowResizeOverlay from './WindowResizeOverlay'
|
||||
import { useNowPlayingStore } from '../stores/nowPlayingStore'
|
||||
import { useThemeStore } from '../stores/themeStore'
|
||||
import { useUiStore } from '../stores/uiStore'
|
||||
@@ -307,8 +306,8 @@ export default function NowPlayingConfigWindow(): JSX.Element {
|
||||
const [expandedProviderId, setExpandedProviderId] = useState<NowPlayingProviderId | null>('astra')
|
||||
const [draggedProviderId, setDraggedProviderId] = useState<NowPlayingProviderId | null>(null)
|
||||
const [dropTargetProviderId, setDropTargetProviderId] = useState<NowPlayingProviderId | null>(null)
|
||||
const useNativeDragRegions = getRendererWindowCapabilities().useNativeDragRegions
|
||||
const platform = window.electronAPI.platform
|
||||
const useWindowManagerDragRegions = getRendererWindowCapabilities().useNativeDragRegions
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false
|
||||
@@ -341,14 +340,14 @@ export default function NowPlayingConfigWindow(): JSX.Element {
|
||||
}, [nowPlayingState.configs.astra.baseUrl, nowPlayingState.configs.astra.hasToken])
|
||||
|
||||
const handleToolbarDragStart = useCallback((event: ReactPointerEvent<HTMLDivElement>): void => {
|
||||
if (useNativeDragRegions || isToolbarInteractiveTarget(event.target) || event.button !== 0) return
|
||||
if (useWindowManagerDragRegions || isToolbarInteractiveTarget(event.target) || event.button !== 0) return
|
||||
event.preventDefault()
|
||||
event.currentTarget.setPointerCapture(event.pointerId)
|
||||
window.electronAPI.startWindowMove()
|
||||
}, [useNativeDragRegions])
|
||||
}, [useWindowManagerDragRegions])
|
||||
|
||||
const handleToolbarDragEnd = useCallback((event: ReactPointerEvent<HTMLDivElement>): void => {
|
||||
if (useNativeDragRegions) {
|
||||
if (useWindowManagerDragRegions) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -356,7 +355,7 @@ export default function NowPlayingConfigWindow(): JSX.Element {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId)
|
||||
}
|
||||
window.electronAPI.stopWindowMove()
|
||||
}, [useNativeDragRegions])
|
||||
}, [useWindowManagerDragRegions])
|
||||
|
||||
const handleSaveAstraConfig = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
@@ -437,11 +436,11 @@ export default function NowPlayingConfigWindow(): JSX.Element {
|
||||
<div className="now-playing-config">
|
||||
<div className="now-playing-config__shell">
|
||||
<header
|
||||
className={`toolbar now-playing-config__toolbar ${useNativeDragRegions ? 'is-native-drag' : ''}`.trim()}
|
||||
onPointerDown={useNativeDragRegions ? undefined : handleToolbarDragStart}
|
||||
onPointerUp={useNativeDragRegions ? undefined : handleToolbarDragEnd}
|
||||
onPointerCancel={useNativeDragRegions ? undefined : handleToolbarDragEnd}
|
||||
onLostPointerCapture={useNativeDragRegions ? undefined : handleToolbarDragEnd}
|
||||
className={`toolbar now-playing-config__toolbar ${useWindowManagerDragRegions ? 'is-native-drag' : ''}`.trim()}
|
||||
onPointerDown={useWindowManagerDragRegions ? undefined : handleToolbarDragStart}
|
||||
onPointerUp={useWindowManagerDragRegions ? undefined : handleToolbarDragEnd}
|
||||
onPointerCancel={useWindowManagerDragRegions ? undefined : handleToolbarDragEnd}
|
||||
onLostPointerCapture={useWindowManagerDragRegions ? undefined : handleToolbarDragEnd}
|
||||
>
|
||||
<div className="now-playing-config__toolbar-copy">
|
||||
<div className="now-playing-config__toolbar-title">Now Playing</div>
|
||||
@@ -699,7 +698,6 @@ export default function NowPlayingConfigWindow(): JSX.Element {
|
||||
</div>
|
||||
|
||||
<AppBanner />
|
||||
<WindowResizeOverlay />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DEFAULT_SCOPE_SETTINGS, type ScopeSettings } from '../../types/settings
|
||||
import { applyResolvedThemeToDocument, createDefaultTheme, resolveTheme } from '../../shared/themeState'
|
||||
import ScopeModule from '../components/ScopeModule'
|
||||
import ScopeSettingsSection from '../components/ScopeSettingsSection'
|
||||
import WindowResizeOverlay from '../components/WindowResizeOverlay'
|
||||
import { usePerformanceStore } from '../stores/performanceStore'
|
||||
import { useUiStore } from '../stores/uiStore'
|
||||
import { getRendererWindowCapabilities } from '../windowCapabilities'
|
||||
@@ -67,7 +66,7 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
||||
const setMiniSettingsOpen = useUiStore((s) => s.setSettingsOpen)
|
||||
const frameScheduler = useMemo(() => new FrameScheduler({ frameTarget }), [])
|
||||
const dataSource = useMemo(() => new ScopePopoutDataSource(scopeKind), [scopeKind])
|
||||
const useNativeDragRegions = getRendererWindowCapabilities().useNativeDragRegions
|
||||
const useWindowManagerDragRegions = getRendererWindowCapabilities().useNativeDragRegions
|
||||
|
||||
useEffect(() => {
|
||||
void window.electronAPI.isAlwaysOnTop().then(setIsAlwaysOnTop)
|
||||
@@ -148,14 +147,14 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
||||
}
|
||||
|
||||
const handleDragStart = useCallback((event: ReactPointerEvent<HTMLButtonElement>): void => {
|
||||
if (useNativeDragRegions || event.button !== 0) return
|
||||
if (useWindowManagerDragRegions || event.button !== 0) return
|
||||
event.preventDefault()
|
||||
event.currentTarget.setPointerCapture(event.pointerId)
|
||||
window.electronAPI.startWindowMove()
|
||||
}, [useNativeDragRegions])
|
||||
}, [useWindowManagerDragRegions])
|
||||
|
||||
const handleDragEnd = useCallback((event: ReactPointerEvent<HTMLButtonElement>): void => {
|
||||
if (useNativeDragRegions) {
|
||||
if (useWindowManagerDragRegions) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -163,10 +162,10 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
||||
event.currentTarget.releasePointerCapture(event.pointerId)
|
||||
}
|
||||
window.electronAPI.stopWindowMove()
|
||||
}, [useNativeDragRegions])
|
||||
}, [useWindowManagerDragRegions])
|
||||
|
||||
const handleAltDragStart = useCallback((event: ReactMouseEvent<HTMLDivElement>): void => {
|
||||
if (useNativeDragRegions || !event.altKey || event.button !== 0) return
|
||||
if (useWindowManagerDragRegions || !event.altKey || event.button !== 0) return
|
||||
|
||||
const target = event.target
|
||||
if (target instanceof Element && target.closest('.scope-popout__drag-handle')) {
|
||||
@@ -175,15 +174,15 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
||||
|
||||
event.preventDefault()
|
||||
window.electronAPI.startWindowMove()
|
||||
}, [useNativeDragRegions])
|
||||
}, [useWindowManagerDragRegions])
|
||||
|
||||
const handleAltDragEnd = useCallback((): void => {
|
||||
if (useNativeDragRegions) {
|
||||
if (useWindowManagerDragRegions) {
|
||||
return
|
||||
}
|
||||
|
||||
window.electronAPI.stopWindowMove()
|
||||
}, [useNativeDragRegions])
|
||||
}, [useWindowManagerDragRegions])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (miniSettingsOpen && !prevMiniSettingsOpenRef.current) {
|
||||
@@ -210,8 +209,8 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
||||
onMouseEnter={() => setCursorInsideWindow(true)}
|
||||
onMouseMove={() => setCursorInsideWindow(true)}
|
||||
onMouseLeave={() => setCursorInsideWindow(false)}
|
||||
onMouseDown={useNativeDragRegions ? undefined : handleAltDragStart}
|
||||
onMouseUp={useNativeDragRegions ? undefined : handleAltDragEnd}
|
||||
onMouseDown={useWindowManagerDragRegions ? undefined : handleAltDragStart}
|
||||
onMouseUp={useWindowManagerDragRegions ? undefined : handleAltDragEnd}
|
||||
>
|
||||
<div
|
||||
className="scope-popout__viewport"
|
||||
@@ -224,15 +223,15 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
||||
cursorInsideWindow ? 'is-cursor-inside' : '',
|
||||
].join(' ').trim()}
|
||||
>
|
||||
<header className={`scope-popout__header ${useNativeDragRegions ? 'is-native-drag' : ''}`.trim()}>
|
||||
<header className={`scope-popout__header ${useWindowManagerDragRegions ? 'is-native-drag' : ''}`.trim()}>
|
||||
<div className="scope-popout__drag">
|
||||
<button
|
||||
type="button"
|
||||
className={`scope-popout__drag-handle ${useNativeDragRegions ? 'is-native-drag' : ''}`.trim()}
|
||||
onPointerDown={useNativeDragRegions ? undefined : handleDragStart}
|
||||
onPointerUp={useNativeDragRegions ? undefined : handleDragEnd}
|
||||
onPointerCancel={useNativeDragRegions ? undefined : handleDragEnd}
|
||||
onLostPointerCapture={useNativeDragRegions ? undefined : handleDragEnd}
|
||||
className={`scope-popout__drag-handle ${useWindowManagerDragRegions ? 'is-native-drag' : ''}`.trim()}
|
||||
onPointerDown={useWindowManagerDragRegions ? undefined : handleDragStart}
|
||||
onPointerUp={useWindowManagerDragRegions ? undefined : handleDragEnd}
|
||||
onPointerCancel={useWindowManagerDragRegions ? undefined : handleDragEnd}
|
||||
onLostPointerCapture={useWindowManagerDragRegions ? undefined : handleDragEnd}
|
||||
aria-label="Drag window"
|
||||
title="Drag window"
|
||||
>
|
||||
@@ -305,8 +304,6 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<WindowResizeOverlay />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ function resolveLinuxDisplayServer(
|
||||
}
|
||||
|
||||
export function resolveWindowCapabilities(options: WindowCapabilityResolutionOptions): WindowCapabilities {
|
||||
if (options.platform === 'win32') {
|
||||
if (options.platform === 'darwin' || options.platform === 'win32') {
|
||||
return {
|
||||
...DEFAULT_WINDOW_CAPABILITIES,
|
||||
useNativeDragRegions: true,
|
||||
|
||||
Reference in New Issue
Block a user