mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2026-08-25 14:16:58 +01:00
[MIRROR] dragport [NO GBP] (#12123)
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
co-authored by
Kashargul
parent
574791511f
commit
9282d5fa37
+127
-71
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user