Files
proxycast/electron/updateNotificationWindowPosition.ts
T
2026-06-08 01:49:36 +08:00

95 lines
2.5 KiB
TypeScript

export interface RectangleLike {
x: number;
y: number;
width: number;
height: number;
}
export interface SizeLike {
width: number;
height: number;
}
export interface BuildUpdateNotificationWindowBoundsOptions {
anchorRect?: RectangleLike | null;
contentBounds: RectangleLike;
gap?: number;
margin?: number;
updateWindowSize: SizeLike;
workArea: RectangleLike;
}
const DEFAULT_GAP = 10;
const DEFAULT_MARGIN = 8;
const FALLBACK_ANCHOR_LEFT = 16;
const FALLBACK_ANCHOR_BOTTOM = 46;
const FALLBACK_ANCHOR_SIZE = 30;
export function buildUpdateNotificationWindowBounds({
anchorRect,
contentBounds,
gap = DEFAULT_GAP,
margin = DEFAULT_MARGIN,
updateWindowSize,
workArea,
}: BuildUpdateNotificationWindowBoundsOptions): RectangleLike {
const anchor = isValidRect(anchorRect)
? anchorRect
: buildFallbackAnchorRect(contentBounds);
const width = Math.max(1, Math.round(updateWindowSize.width));
const height = Math.max(1, Math.round(updateWindowSize.height));
const minX = Math.max(workArea.x + margin, contentBounds.x + margin);
const maxX = Math.min(
workArea.x + workArea.width - width - margin,
contentBounds.x + contentBounds.width - width - margin,
);
const minY = workArea.y + margin;
const maxY = workArea.y + workArea.height - height - margin;
const anchorScreenX = contentBounds.x + anchor.x;
const anchorScreenY = contentBounds.y + anchor.y;
const preferredX = anchorScreenX;
const preferredY = anchorScreenY - height - gap;
const fallbackY = anchorScreenY + anchor.height + gap;
const y = preferredY >= minY || fallbackY > maxY ? preferredY : fallbackY;
return {
x: Math.round(clamp(preferredX, minX, maxX)),
y: Math.round(clamp(y, minY, maxY)),
width,
height,
};
}
function buildFallbackAnchorRect(contentBounds: RectangleLike): RectangleLike {
return {
x: FALLBACK_ANCHOR_LEFT,
y: Math.max(
0,
contentBounds.height - FALLBACK_ANCHOR_BOTTOM - FALLBACK_ANCHOR_SIZE,
),
width: FALLBACK_ANCHOR_SIZE,
height: FALLBACK_ANCHOR_SIZE,
};
}
function isValidRect(
value: RectangleLike | null | undefined,
): value is RectangleLike {
return Boolean(
value &&
Number.isFinite(value.x) &&
Number.isFinite(value.y) &&
Number.isFinite(value.width) &&
Number.isFinite(value.height) &&
value.width > 0 &&
value.height > 0,
);
}
function clamp(value: number, min: number, max: number): number {
if (max < min) {
return min;
}
return Math.min(max, Math.max(min, value));
}