[MIRROR] dragport [NO GBP] (#12123)

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-12-15 17:21:22 +01:00
committed by GitHub
co-authored by Kashargul
parent 574791511f
commit 9282d5fa37
+127 -71
View File
@@ -7,64 +7,118 @@
import { storage } from 'common/storage';
import type { BooleanLike } from 'tgui-core/react';
import { vecAdd, vecMultiply, vecScale, vecSubtract } from 'tgui-core/vector';
import { createLogger } from './logging';
type Point = [number, number];
type GeometryPayload = Partial<{
pos: string;
size: string;
}>;
const logger = createLogger('drag');
const pixelRatio = window.devicePixelRatio ?? 1;
let windowKey = Byond.windowId;
let dragging = false;
let resizing = false;
let screenOffset: [number, number] = [0, 0];
let screenOffsetPromise: Promise<[number, number]>;
let dragPointOffset: [number, number];
let resizeMatrix: [number, number];
let initialSize: [number, number];
let size: [number, number];
let screenOffset: Point = [0, 0];
let screenOffsetPromise: Promise<Point>;
let dragPointOffset: Point;
let resizeMatrix: Point;
let initialSize: Point;
let size: Point;
let winsetRaf: number | undefined;
let pendingWinset: GeometryPayload = {};
let lastSentWinset: GeometryPayload = {};
/** Expends all stored winset calls in a single call. */
function flushWinset(): void {
winsetRaf = undefined;
const payload: GeometryPayload = {};
if (pendingWinset.pos && pendingWinset.pos !== lastSentWinset.pos) {
payload.pos = pendingWinset.pos;
}
if (pendingWinset.size && pendingWinset.size !== lastSentWinset.size) {
payload.size = pendingWinset.size;
}
pendingWinset = {};
if (payload.pos || payload.size) {
Byond.winset(Byond.windowId, payload);
lastSentWinset = { ...lastSentWinset, ...payload };
}
}
function scheduleWinset(): void {
if (winsetRaf !== undefined) {
return;
}
winsetRaf = requestAnimationFrame(flushWinset);
}
function flushWinsetNow(): void {
if (winsetRaf !== undefined) {
cancelAnimationFrame(winsetRaf);
winsetRaf = undefined;
}
flushWinset();
}
// Set the window key
export const setWindowKey = (key: string): void => {
export function setWindowKey(key: string): void {
windowKey = key;
};
}
// Get window position
export const getWindowPosition = (): [number, number] => [
window.screenLeft * pixelRatio,
window.screenTop * pixelRatio,
];
export function getWindowPosition(): Point {
return [window.screenLeft * pixelRatio, window.screenTop * pixelRatio];
}
// Get window size
export const getWindowSize = (): [number, number] => [
window.innerWidth * pixelRatio,
window.innerHeight * pixelRatio,
];
export function getWindowSize(): Point {
return [window.innerWidth * pixelRatio, window.innerHeight * pixelRatio];
}
// Set window position
const setWindowPosition = (vec: [number, number]) => {
export function setWindowPosition(vec: Point): void {
const byondPos = vecAdd(vec, screenOffset);
return Byond.winset(Byond.windowId, {
Byond.winset(Byond.windowId, {
pos: `${byondPos[0]},${byondPos[1]}`,
});
};
}
function setWindowPositionBatched(vec: Point): void {
const byondPos = vecAdd(vec, screenOffset);
pendingWinset.pos = `${byondPos[0]},${byondPos[1]}`;
scheduleWinset();
}
// Set window size
const setWindowSize = (vec: [number, number]) => {
return Byond.winset(Byond.windowId, {
function setWindowSize(vec: Point): void {
Byond.winset(Byond.windowId, {
size: `${vec[0]}x${vec[1]}`,
});
};
}
function setWindowSizeBatched(vec: Point): void {
pendingWinset.size = `${vec[0]}x${vec[1]}`;
scheduleWinset();
}
// Get screen position
const getScreenPosition = (): [number, number] => [
0 - screenOffset[0],
0 - screenOffset[1],
];
function getScreenPosition(): Point {
return [0 - screenOffset[0], 0 - screenOffset[1]];
}
// Get screen size
const getScreenSize = (): [number, number] => [
window.screen.availWidth * pixelRatio,
window.screen.availHeight * pixelRatio,
];
function getScreenSize(): Point {
return [
window.screen.availWidth * pixelRatio,
window.screen.availHeight * pixelRatio,
];
}
/**
* Moves an item to the top of the recents array, and keeps its length
@@ -74,11 +128,11 @@ const getScreenSize = (): [number, number] => [
*
* Returns new recents and an item which was trimmed.
*/
export const touchRecents = (
export function touchRecents(
recents: string[],
touchedItem: string,
limit = 50,
): [string[], string | undefined] => {
): [string[], string | undefined] {
const nextRecents: string[] = [touchedItem];
let trimmedItem: string | undefined;
for (let i = 0; i < recents.length; i++) {
@@ -93,10 +147,10 @@ export const touchRecents = (
}
}
return [nextRecents, trimmedItem];
};
}
// Store window geometry in local storage
const storeWindowGeometry = async () => {
export async function storeWindowGeometry(): Promise<void> {
logger.log('storing geometry');
const geometry = {
pos: getWindowPosition(),
@@ -112,19 +166,20 @@ const storeWindowGeometry = async () => {
storage.remove(trimmedKey);
}
storage.set('geometries', geometries);
};
}
type RecallOptions = Partial<{
pos: Point;
size: Point;
locked: BooleanLike;
scale: BooleanLike;
}>;
// Recall window geometry from local storage and apply it
export const recallWindowGeometry = async (
options: {
fancy?: BooleanLike;
pos?: [number, number];
size?: [number, number];
locked?: BooleanLike;
scale?: BooleanLike;
} = {},
) => {
const geometry = options.fancy && (await storage.get(windowKey));
export async function recallWindowGeometry(
options: RecallOptions = {},
): Promise<void> {
const geometry = await storage.get(windowKey);
if (geometry) {
logger.log('recalled geometry:', geometry);
}
@@ -175,10 +230,10 @@ export const recallWindowGeometry = async (
);
setWindowPosition(pos);
}
};
}
// Setup draggable window
export const setupDrag = async () => {
export async function setupDrag(): Promise<void> {
// Calculate screen offset caused by the windows taskbar
const windowPosition = getWindowPosition();
@@ -188,20 +243,18 @@ export const setupDrag = async () => {
]);
screenOffset = await screenOffsetPromise;
logger.debug('screen offset', screenOffset);
};
}
/**
* Constraints window position to safe screen area, accounting for safe
* margins which could be a system taskbar.
*/
const constraintPosition = (
pos: [number, number],
size: [number, number],
): [boolean, [number, number]] => {
function constraintPosition(pos: Point, size: Point): [boolean, Point] {
const screenPos = getScreenPosition();
const screenSize = getScreenSize();
const nextPos: [number, number] = [pos[0], pos[1]];
const nextPos: Point = [pos[0], pos[1]];
let relocated = false;
for (let i = 0; i < 2; i++) {
const leftBoundary = screenPos[i];
const rightBoundary = screenPos[i] + screenSize[i];
@@ -214,57 +267,59 @@ const constraintPosition = (
}
}
return [relocated, nextPos];
};
}
// Start dragging the window
export const dragStartHandler = (event) => {
export function dragStartHandler(event): void {
logger.log('drag start');
dragging = true;
dragPointOffset = vecSubtract(
[event.screenX * pixelRatio, event.screenY * pixelRatio],
getWindowPosition(),
) as [number, number];
) as Point;
// Focus click target
(event.target as HTMLElement)?.focus();
document.addEventListener('mousemove', dragMoveHandler);
document.addEventListener('mouseup', dragEndHandler);
dragMoveHandler(event);
};
}
// End dragging the window
const dragEndHandler = (event) => {
function dragEndHandler(event): void {
logger.log('drag end');
dragMoveHandler(event);
flushWinsetNow();
document.removeEventListener('mousemove', dragMoveHandler);
document.removeEventListener('mouseup', dragEndHandler);
dragging = false;
storeWindowGeometry();
};
}
// Move the window while dragging
const dragMoveHandler = (event: MouseEvent) => {
function dragMoveHandler(event: MouseEvent): void {
if (!dragging) {
return;
}
event.preventDefault();
setWindowPosition(
setWindowPositionBatched(
vecSubtract(
[event.screenX * pixelRatio, event.screenY * pixelRatio],
dragPointOffset,
) as [number, number],
) as Point,
);
};
}
// Start resizing the window
export const resizeStartHandler =
(x: number, y: number) => (event: MouseEvent) => {
(x: number, y: number) =>
(event: MouseEvent): void => {
resizeMatrix = [x, y];
logger.log('resize start', resizeMatrix);
resizing = true;
dragPointOffset = vecSubtract(
[event.screenX * pixelRatio, event.screenY * pixelRatio],
getWindowPosition(),
) as [number, number];
) as Point;
initialSize = getWindowSize();
// Focus click target
(event.target as HTMLElement)?.focus();
@@ -274,17 +329,18 @@ export const resizeStartHandler =
};
// End resizing the window
const resizeEndHandler = (event: MouseEvent) => {
function resizeEndHandler(event: MouseEvent): void {
logger.log('resize end', size);
resizeMoveHandler(event);
flushWinsetNow();
document.removeEventListener('mousemove', resizeMoveHandler);
document.removeEventListener('mouseup', resizeEndHandler);
resizing = false;
storeWindowGeometry();
};
}
// Move the window while resizing
const resizeMoveHandler = (event: MouseEvent) => {
function resizeMoveHandler(event: MouseEvent): void {
if (!resizing) {
return;
}
@@ -302,5 +358,5 @@ const resizeMoveHandler = (event: MouseEvent) => {
// Sane window size values
size[0] = Math.max(size[0], 150 * pixelRatio);
size[1] = Math.max(size[1], 50 * pixelRatio);
setWindowSize(size);
};
setWindowSizeBatched(size);
}