mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-01 15:30:52 +08:00
95 lines
2.5 KiB
TypeScript
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));
|
|
}
|