mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-19 12:04:18 +02:00
174 lines
4.7 KiB
TypeScript
174 lines
4.7 KiB
TypeScript
import type { WindowBounds } from '../types/popout'
|
|
|
|
export interface WorkAreaBounds {
|
|
x: number
|
|
y: number
|
|
width: number
|
|
height: number
|
|
}
|
|
|
|
export interface StackableWindowLike {
|
|
isDestroyed(): boolean
|
|
isAlwaysOnTop(): boolean
|
|
moveTop(): void
|
|
}
|
|
|
|
function getRight(bounds: WorkAreaBounds): number {
|
|
return bounds.x + bounds.width
|
|
}
|
|
|
|
function getBottom(bounds: WorkAreaBounds): number {
|
|
return bounds.y + bounds.height
|
|
}
|
|
|
|
function rangesOverlap(startA: number, endA: number, startB: number, endB: number): boolean {
|
|
return startA < endB && startB < endA
|
|
}
|
|
|
|
function rectsIntersect(a: WorkAreaBounds, b: WorkAreaBounds): boolean {
|
|
return rangesOverlap(a.x, getRight(a), b.x, getRight(b))
|
|
&& rangesOverlap(a.y, getBottom(a), b.y, getBottom(b))
|
|
}
|
|
|
|
function clamp(value: number, min: number, max: number): number {
|
|
if (min > max) {
|
|
return Math.round((min + max) / 2)
|
|
}
|
|
|
|
return Math.min(max, Math.max(min, value))
|
|
}
|
|
|
|
function unionBounds(boundsList: readonly WorkAreaBounds[]): WorkAreaBounds {
|
|
const left = Math.min(...boundsList.map((bounds) => bounds.x))
|
|
const top = Math.min(...boundsList.map((bounds) => bounds.y))
|
|
const right = Math.max(...boundsList.map((bounds) => getRight(bounds)))
|
|
const bottom = Math.max(...boundsList.map((bounds) => getBottom(bounds)))
|
|
|
|
return {
|
|
x: left,
|
|
y: top,
|
|
width: right - left,
|
|
height: bottom - top,
|
|
}
|
|
}
|
|
|
|
function normalizeWorkAreas(workAreas: readonly WorkAreaBounds[]): WorkAreaBounds[] {
|
|
return workAreas
|
|
.filter((bounds) => bounds.width > 0 && bounds.height > 0)
|
|
.map((bounds) => ({
|
|
x: Math.round(bounds.x),
|
|
y: Math.round(bounds.y),
|
|
width: Math.round(bounds.width),
|
|
height: Math.round(bounds.height),
|
|
}))
|
|
}
|
|
|
|
export function buildDisplayEnvelope(
|
|
anchorBounds: WindowBounds,
|
|
projectedBounds: WorkAreaBounds,
|
|
workAreas: readonly WorkAreaBounds[],
|
|
): WorkAreaBounds {
|
|
const normalizedWorkAreas = normalizeWorkAreas(workAreas)
|
|
if (normalizedWorkAreas.length === 0) {
|
|
return { ...projectedBounds }
|
|
}
|
|
|
|
const relevant = normalizedWorkAreas.filter((workArea) => {
|
|
return rectsIntersect(workArea, projectedBounds)
|
|
|| rangesOverlap(workArea.x, getRight(workArea), anchorBounds.x, getRight(anchorBounds))
|
|
})
|
|
|
|
return unionBounds(relevant.length > 0 ? relevant : normalizedWorkAreas)
|
|
}
|
|
|
|
export function clampBoundsWithinEnvelope(
|
|
bounds: WindowBounds,
|
|
envelope: WorkAreaBounds,
|
|
): WindowBounds {
|
|
const maxX = getRight(envelope) - bounds.width
|
|
const maxY = getBottom(envelope) - bounds.height
|
|
|
|
return {
|
|
x: clamp(bounds.x, envelope.x, maxX),
|
|
y: clamp(bounds.y, envelope.y, maxY),
|
|
width: bounds.width,
|
|
height: bounds.height,
|
|
}
|
|
}
|
|
|
|
export function clampBoundsWithVisibleMargin(
|
|
bounds: WindowBounds,
|
|
envelope: WorkAreaBounds,
|
|
visibleMargin: number,
|
|
): WindowBounds {
|
|
const margin = Math.max(0, Math.round(visibleMargin))
|
|
const minX = envelope.x + margin - bounds.width
|
|
const maxX = getRight(envelope) - margin
|
|
const minY = envelope.y + margin - bounds.height
|
|
const maxY = getBottom(envelope) - margin
|
|
|
|
return {
|
|
x: clamp(bounds.x, minX, maxX),
|
|
y: clamp(bounds.y, minY, maxY),
|
|
width: bounds.width,
|
|
height: bounds.height,
|
|
}
|
|
}
|
|
|
|
export function resolveExpandedMainWindowBounds(
|
|
logicalBounds: WindowBounds,
|
|
settingsHeight: number,
|
|
workAreas: readonly WorkAreaBounds[],
|
|
): WindowBounds {
|
|
const nextSettingsHeight = Math.max(0, Math.round(settingsHeight))
|
|
if (nextSettingsHeight === 0) {
|
|
return { ...logicalBounds }
|
|
}
|
|
|
|
const projectedBounds: WindowBounds = {
|
|
x: logicalBounds.x,
|
|
y: logicalBounds.y,
|
|
width: logicalBounds.width,
|
|
height: logicalBounds.height + nextSettingsHeight,
|
|
}
|
|
|
|
const envelope = buildDisplayEnvelope(logicalBounds, projectedBounds, workAreas)
|
|
return clampBoundsWithinEnvelope(projectedBounds, envelope)
|
|
}
|
|
|
|
export function clampDraggedMainWindowBounds(
|
|
actualBounds: WindowBounds,
|
|
workAreas: readonly WorkAreaBounds[],
|
|
visibleMargin: number,
|
|
): WindowBounds {
|
|
const envelope = buildDisplayEnvelope(actualBounds, actualBounds, workAreas)
|
|
return clampBoundsWithVisibleMargin(actualBounds, envelope, visibleMargin)
|
|
}
|
|
|
|
export function clampRestoredWindowBounds(
|
|
bounds: WindowBounds,
|
|
workAreas: readonly WorkAreaBounds[],
|
|
visibleMargin: number,
|
|
): WindowBounds {
|
|
const envelope = buildDisplayEnvelope(bounds, bounds, workAreas)
|
|
return clampBoundsWithVisibleMargin(bounds, envelope, visibleMargin)
|
|
}
|
|
|
|
export function raiseWindowAboveNormalPopouts(
|
|
mainWindow: StackableWindowLike | null,
|
|
popouts: Iterable<StackableWindowLike>,
|
|
): boolean {
|
|
if (!mainWindow || mainWindow.isDestroyed()) {
|
|
return false
|
|
}
|
|
|
|
for (const popout of popouts) {
|
|
if (!popout.isDestroyed() && !popout.isAlwaysOnTop()) {
|
|
mainWindow.moveTop()
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|