tgui-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +10 -0
- package/.eslintrc.cjs +78 -0
- package/.gitattributes +4 -0
- package/.prettierrc.yml +1 -0
- package/.vscode/extensions.json +6 -0
- package/.vscode/settings.json +5 -0
- package/README.md +1 -0
- package/global.d.ts +173 -0
- package/package.json +25 -0
- package/src/assets.ts +43 -0
- package/src/backend.ts +369 -0
- package/src/common/collections.ts +349 -0
- package/src/common/color.ts +94 -0
- package/src/common/events.ts +45 -0
- package/src/common/exhaustive.ts +19 -0
- package/src/common/fp.ts +38 -0
- package/src/common/keycodes.ts +86 -0
- package/src/common/keys.ts +39 -0
- package/src/common/math.ts +98 -0
- package/src/common/perf.ts +72 -0
- package/src/common/random.ts +32 -0
- package/src/common/react.ts +65 -0
- package/src/common/redux.ts +196 -0
- package/src/common/storage.js +196 -0
- package/src/common/string.ts +173 -0
- package/src/common/timer.ts +68 -0
- package/src/common/type-utils.ts +41 -0
- package/src/common/types.ts +9 -0
- package/src/common/uuid.ts +24 -0
- package/src/common/vector.ts +51 -0
- package/src/components/AnimatedNumber.tsx +185 -0
- package/src/components/Autofocus.tsx +23 -0
- package/src/components/Blink.jsx +69 -0
- package/src/components/BlockQuote.tsx +15 -0
- package/src/components/BodyZoneSelector.tsx +149 -0
- package/src/components/Box.tsx +255 -0
- package/src/components/Button.tsx +415 -0
- package/src/components/ByondUi.jsx +121 -0
- package/src/components/Chart.tsx +160 -0
- package/src/components/Collapsible.tsx +45 -0
- package/src/components/ColorBox.tsx +30 -0
- package/src/components/Dialog.tsx +85 -0
- package/src/components/Dimmer.tsx +19 -0
- package/src/components/Divider.tsx +26 -0
- package/src/components/DmIcon.tsx +72 -0
- package/src/components/DraggableControl.jsx +282 -0
- package/src/components/Dropdown.tsx +246 -0
- package/src/components/FakeTerminal.jsx +52 -0
- package/src/components/FitText.tsx +99 -0
- package/src/components/Flex.tsx +105 -0
- package/src/components/Grid.tsx +44 -0
- package/src/components/Icon.tsx +91 -0
- package/src/components/Image.tsx +63 -0
- package/src/components/InfinitePlane.jsx +192 -0
- package/src/components/Input.tsx +181 -0
- package/src/components/KeyListener.tsx +40 -0
- package/src/components/Knob.tsx +185 -0
- package/src/components/LabeledControls.tsx +50 -0
- package/src/components/LabeledList.tsx +130 -0
- package/src/components/MenuBar.tsx +238 -0
- package/src/components/Modal.tsx +25 -0
- package/src/components/NoticeBox.tsx +48 -0
- package/src/components/NumberInput.tsx +328 -0
- package/src/components/Popper.tsx +100 -0
- package/src/components/ProgressBar.tsx +79 -0
- package/src/components/RestrictedInput.jsx +301 -0
- package/src/components/RoundGauge.tsx +189 -0
- package/src/components/Section.tsx +125 -0
- package/src/components/Slider.tsx +173 -0
- package/src/components/Stack.tsx +101 -0
- package/src/components/StyleableSection.tsx +30 -0
- package/src/components/Table.tsx +90 -0
- package/src/components/Tabs.tsx +90 -0
- package/src/components/TextArea.tsx +198 -0
- package/src/components/TimeDisplay.jsx +64 -0
- package/src/components/Tooltip.tsx +147 -0
- package/src/components/TrackOutsideClicks.tsx +35 -0
- package/src/components/VirtualList.tsx +69 -0
- package/src/constants.ts +355 -0
- package/src/debug/KitchenSink.jsx +56 -0
- package/src/debug/actions.js +11 -0
- package/src/debug/hooks.js +10 -0
- package/src/debug/index.ts +10 -0
- package/src/debug/middleware.js +86 -0
- package/src/debug/reducer.js +22 -0
- package/src/debug/selectors.js +7 -0
- package/src/drag.ts +280 -0
- package/src/events.ts +237 -0
- package/src/focus.ts +25 -0
- package/src/format.ts +173 -0
- package/src/hotkeys.ts +212 -0
- package/src/http.ts +16 -0
- package/src/layouts/Layout.tsx +75 -0
- package/src/layouts/NtosWindow.tsx +162 -0
- package/src/layouts/Pane.tsx +56 -0
- package/src/layouts/Window.tsx +227 -0
- package/src/renderer.ts +50 -0
- package/styles/base.scss +32 -0
- package/styles/colors.scss +92 -0
- package/styles/components/BlockQuote.scss +20 -0
- package/styles/components/Button.scss +175 -0
- package/styles/components/ColorBox.scss +12 -0
- package/styles/components/Dialog.scss +105 -0
- package/styles/components/Dimmer.scss +22 -0
- package/styles/components/Divider.scss +27 -0
- package/styles/components/Dropdown.scss +72 -0
- package/styles/components/Flex.scss +31 -0
- package/styles/components/Icon.scss +25 -0
- package/styles/components/Input.scss +68 -0
- package/styles/components/Knob.scss +131 -0
- package/styles/components/LabeledList.scss +49 -0
- package/styles/components/MenuBar.scss +75 -0
- package/styles/components/Modal.scss +14 -0
- package/styles/components/NoticeBox.scss +65 -0
- package/styles/components/NumberInput.scss +76 -0
- package/styles/components/ProgressBar.scss +63 -0
- package/styles/components/RoundGauge.scss +88 -0
- package/styles/components/Section.scss +143 -0
- package/styles/components/Slider.scss +54 -0
- package/styles/components/Stack.scss +59 -0
- package/styles/components/Table.scss +44 -0
- package/styles/components/Tabs.scss +144 -0
- package/styles/components/TextArea.scss +84 -0
- package/styles/components/Tooltip.scss +24 -0
- package/styles/functions.scss +79 -0
- package/styles/layouts/Layout.scss +57 -0
- package/styles/layouts/NtosHeader.scss +20 -0
- package/styles/layouts/NtosWindow.scss +26 -0
- package/styles/layouts/TitleBar.scss +111 -0
- package/styles/layouts/Window.scss +103 -0
- package/styles/main.scss +97 -0
- package/styles/reset.scss +68 -0
- package/styles/themes/abductor.scss +68 -0
- package/styles/themes/admin.scss +38 -0
- package/styles/themes/cardtable.scss +57 -0
- package/styles/themes/hackerman.scss +70 -0
- package/styles/themes/malfunction.scss +67 -0
- package/styles/themes/neutral.scss +50 -0
- package/styles/themes/ntOS95.scss +166 -0
- package/styles/themes/ntos.scss +44 -0
- package/styles/themes/ntos_cat.scss +148 -0
- package/styles/themes/ntos_darkmode.scss +44 -0
- package/styles/themes/ntos_lightmode.scss +67 -0
- package/styles/themes/ntos_spooky.scss +69 -0
- package/styles/themes/ntos_synth.scss +99 -0
- package/styles/themes/ntos_terminal.scss +112 -0
- package/styles/themes/paper.scss +184 -0
- package/styles/themes/retro.scss +72 -0
- package/styles/themes/spookyconsole.scss +73 -0
- package/styles/themes/syndicate.scss +67 -0
- package/styles/themes/wizard.scss +68 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { Flex, Section, Tabs } from '../components';
|
|
10
|
+
import { Pane, Window } from '../layouts';
|
|
11
|
+
|
|
12
|
+
const r = require.context('../stories', false, /\.stories\.jsx$/);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @returns {{
|
|
16
|
+
* meta: {
|
|
17
|
+
* title: string,
|
|
18
|
+
* render: () => any,
|
|
19
|
+
* },
|
|
20
|
+
* }[]}
|
|
21
|
+
*/
|
|
22
|
+
const getStories = () => r.keys().map((path) => r(path));
|
|
23
|
+
|
|
24
|
+
export const KitchenSink = (props) => {
|
|
25
|
+
const { panel } = props;
|
|
26
|
+
const [theme] = useState(null);
|
|
27
|
+
const [pageIndex, setPageIndex] = useState(0);
|
|
28
|
+
const stories = getStories();
|
|
29
|
+
const story = stories[pageIndex];
|
|
30
|
+
const Layout = panel ? Pane : Window;
|
|
31
|
+
return (
|
|
32
|
+
<Layout title="Kitchen Sink" width={600} height={500} theme={theme}>
|
|
33
|
+
<Flex height="100%">
|
|
34
|
+
<Flex.Item m={1} mr={0}>
|
|
35
|
+
<Section fill fitted>
|
|
36
|
+
<Tabs vertical>
|
|
37
|
+
{stories.map((story, i) => (
|
|
38
|
+
<Tabs.Tab
|
|
39
|
+
key={i}
|
|
40
|
+
color="transparent"
|
|
41
|
+
selected={i === pageIndex}
|
|
42
|
+
onClick={() => setPageIndex(i)}
|
|
43
|
+
>
|
|
44
|
+
{story.meta.title}
|
|
45
|
+
</Tabs.Tab>
|
|
46
|
+
))}
|
|
47
|
+
</Tabs>
|
|
48
|
+
</Section>
|
|
49
|
+
</Flex.Item>
|
|
50
|
+
<Flex.Item position="relative" grow={1}>
|
|
51
|
+
<Layout.Content scrollable>{story.meta.render()}</Layout.Content>
|
|
52
|
+
</Flex.Item>
|
|
53
|
+
</Flex>
|
|
54
|
+
</Layout>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createAction } from 'common/redux';
|
|
8
|
+
|
|
9
|
+
export const toggleKitchenSink = createAction('debug/toggleKitchenSink');
|
|
10
|
+
export const toggleDebugLayout = createAction('debug/toggleDebugLayout');
|
|
11
|
+
export const openExternalBrowser = createAction('debug/openExternalBrowser');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { useDebug } from './hooks';
|
|
8
|
+
export { KitchenSink } from './KitchenSink';
|
|
9
|
+
export { debugMiddleware, relayMiddleware } from './middleware';
|
|
10
|
+
export { debugReducer } from './reducer';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { KEY_BACKSPACE, KEY_F10, KEY_F11, KEY_F12 } from 'common/keycodes';
|
|
8
|
+
|
|
9
|
+
import { globalEvents } from '../events';
|
|
10
|
+
import { acquireHotKey } from '../hotkeys';
|
|
11
|
+
import {
|
|
12
|
+
openExternalBrowser,
|
|
13
|
+
toggleDebugLayout,
|
|
14
|
+
toggleKitchenSink,
|
|
15
|
+
} from './actions';
|
|
16
|
+
|
|
17
|
+
// prettier-ignore
|
|
18
|
+
const relayedTypes = [
|
|
19
|
+
'backend/update',
|
|
20
|
+
'chat/message',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export const debugMiddleware = (store) => {
|
|
24
|
+
acquireHotKey(KEY_F11);
|
|
25
|
+
acquireHotKey(KEY_F12);
|
|
26
|
+
globalEvents.on('keydown', (key) => {
|
|
27
|
+
if (key.code === KEY_F11) {
|
|
28
|
+
store.dispatch(toggleDebugLayout());
|
|
29
|
+
}
|
|
30
|
+
if (key.code === KEY_F12) {
|
|
31
|
+
store.dispatch(toggleKitchenSink());
|
|
32
|
+
}
|
|
33
|
+
if (key.ctrl && key.alt && key.code === KEY_BACKSPACE) {
|
|
34
|
+
// NOTE: We need to call this in a timeout, because we need a clean
|
|
35
|
+
// stack in order for this to be a fatal error.
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
// prettier-ignore
|
|
38
|
+
throw new Error(
|
|
39
|
+
'OOPSIE WOOPSIE!! UwU We made a fucky wucky!! A wittle'
|
|
40
|
+
+ ' fucko boingo! The code monkeys at our headquarters are'
|
|
41
|
+
+ ' working VEWY HAWD to fix this!');
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return (next) => (action) => next(action);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const relayMiddleware = (store) => {
|
|
49
|
+
const devServer = require('tgui-dev-server/link/client.cjs');
|
|
50
|
+
const externalBrowser = location.search === '?external';
|
|
51
|
+
if (externalBrowser) {
|
|
52
|
+
devServer.subscribe((msg) => {
|
|
53
|
+
const { type, payload } = msg;
|
|
54
|
+
if (type === 'relay' && payload.windowId === Byond.windowId) {
|
|
55
|
+
store.dispatch({
|
|
56
|
+
...payload.action,
|
|
57
|
+
relayed: true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
acquireHotKey(KEY_F10);
|
|
63
|
+
globalEvents.on('keydown', (key) => {
|
|
64
|
+
if (key === KEY_F10) {
|
|
65
|
+
store.dispatch(openExternalBrowser());
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return (next) => (action) => {
|
|
70
|
+
const { type, payload, relayed } = action;
|
|
71
|
+
if (type === openExternalBrowser.type) {
|
|
72
|
+
window.open(location.href + '?external', '_blank');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (relayedTypes.includes(type) && !relayed && !externalBrowser) {
|
|
76
|
+
devServer.sendMessage({
|
|
77
|
+
type: 'relay',
|
|
78
|
+
payload: {
|
|
79
|
+
windowId: Byond.windowId,
|
|
80
|
+
action,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return next(action);
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const debugReducer = (state = {}, action) => {
|
|
8
|
+
const { type, payload } = action;
|
|
9
|
+
if (type === 'debug/toggleKitchenSink') {
|
|
10
|
+
return {
|
|
11
|
+
...state,
|
|
12
|
+
kitchenSink: !state.kitchenSink,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (type === 'debug/toggleDebugLayout') {
|
|
16
|
+
return {
|
|
17
|
+
...state,
|
|
18
|
+
debugLayout: !state.debugLayout,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return state;
|
|
22
|
+
};
|
package/src/drag.ts
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { storage } from './common/storage';
|
|
8
|
+
import { vecAdd, vecMultiply, vecScale, vecSubtract } from './common/vector';
|
|
9
|
+
|
|
10
|
+
const pixelRatio = window.devicePixelRatio ?? 1;
|
|
11
|
+
let windowKey = Byond.windowId;
|
|
12
|
+
let dragging = false;
|
|
13
|
+
let resizing = false;
|
|
14
|
+
let screenOffset: [number, number] = [0, 0];
|
|
15
|
+
let screenOffsetPromise: Promise<[number, number]>;
|
|
16
|
+
let dragPointOffset: [number, number];
|
|
17
|
+
let resizeMatrix: [number, number];
|
|
18
|
+
let initialSize: [number, number];
|
|
19
|
+
let size: [number, number];
|
|
20
|
+
|
|
21
|
+
// Set the window key
|
|
22
|
+
export const setWindowKey = (key: string): void => {
|
|
23
|
+
windowKey = key;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Get window position
|
|
27
|
+
export const getWindowPosition = (): [number, number] => [
|
|
28
|
+
window.screenLeft * pixelRatio,
|
|
29
|
+
window.screenTop * pixelRatio,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// Get window size
|
|
33
|
+
export const getWindowSize = (): [number, number] => [
|
|
34
|
+
window.innerWidth * pixelRatio,
|
|
35
|
+
window.innerHeight * pixelRatio,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Set window position
|
|
39
|
+
const setWindowPosition = (vec: [number, number]) => {
|
|
40
|
+
const byondPos = vecAdd(vec, screenOffset);
|
|
41
|
+
return Byond.winset(Byond.windowId, {
|
|
42
|
+
pos: byondPos[0] + ',' + byondPos[1],
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Set window size
|
|
47
|
+
const setWindowSize = (vec: [number, number]) => {
|
|
48
|
+
return Byond.winset(Byond.windowId, {
|
|
49
|
+
size: vec[0] + 'x' + vec[1],
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Get screen position
|
|
54
|
+
const getScreenPosition = (): [number, number] => [
|
|
55
|
+
0 - screenOffset[0],
|
|
56
|
+
0 - screenOffset[1],
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// Get screen size
|
|
60
|
+
const getScreenSize = (): [number, number] => [
|
|
61
|
+
window.screen.availWidth * pixelRatio,
|
|
62
|
+
window.screen.availHeight * pixelRatio,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Moves an item to the top of the recents array, and keeps its length
|
|
67
|
+
* limited to the number in `limit` argument.
|
|
68
|
+
*
|
|
69
|
+
* Uses a strict equality check for comparisons.
|
|
70
|
+
*
|
|
71
|
+
* Returns new recents and an item which was trimmed.
|
|
72
|
+
*/
|
|
73
|
+
export const touchRecents = (
|
|
74
|
+
recents: string[],
|
|
75
|
+
touchedItem: string,
|
|
76
|
+
limit = 50
|
|
77
|
+
): [string[], string | undefined] => {
|
|
78
|
+
const nextRecents: string[] = [touchedItem];
|
|
79
|
+
let trimmedItem: string | undefined;
|
|
80
|
+
for (let i = 0; i < recents.length; i++) {
|
|
81
|
+
const item = recents[i];
|
|
82
|
+
if (item === touchedItem) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (nextRecents.length < limit) {
|
|
86
|
+
nextRecents.push(item);
|
|
87
|
+
} else {
|
|
88
|
+
trimmedItem = item;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return [nextRecents, trimmedItem];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Store window geometry in local storage
|
|
95
|
+
const storeWindowGeometry = async () => {
|
|
96
|
+
const geometry = {
|
|
97
|
+
pos: getWindowPosition(),
|
|
98
|
+
size: getWindowSize(),
|
|
99
|
+
};
|
|
100
|
+
storage.set(windowKey, geometry);
|
|
101
|
+
// Update the list of stored geometries
|
|
102
|
+
const [geometries, trimmedKey] = touchRecents(
|
|
103
|
+
(await storage.get('geometries')) || [],
|
|
104
|
+
windowKey
|
|
105
|
+
);
|
|
106
|
+
if (trimmedKey) {
|
|
107
|
+
storage.remove(trimmedKey);
|
|
108
|
+
}
|
|
109
|
+
storage.set('geometries', geometries);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Recall window geometry from local storage and apply it
|
|
113
|
+
export const recallWindowGeometry = async (
|
|
114
|
+
options: {
|
|
115
|
+
fancy?: boolean;
|
|
116
|
+
pos?: [number, number];
|
|
117
|
+
size?: [number, number];
|
|
118
|
+
locked?: boolean;
|
|
119
|
+
} = {}
|
|
120
|
+
) => {
|
|
121
|
+
const geometry = options.fancy && (await storage.get(windowKey));
|
|
122
|
+
// options.pos is assumed to already be in display-pixels
|
|
123
|
+
let pos = geometry?.pos || options.pos;
|
|
124
|
+
let size = options.size;
|
|
125
|
+
// Convert size from css-pixels to display-pixels
|
|
126
|
+
if (size) {
|
|
127
|
+
size = [size[0] * pixelRatio, size[1] * pixelRatio];
|
|
128
|
+
}
|
|
129
|
+
// Wait until screen offset gets resolved
|
|
130
|
+
await screenOffsetPromise;
|
|
131
|
+
const areaAvailable = getScreenSize();
|
|
132
|
+
// Set window size
|
|
133
|
+
if (size) {
|
|
134
|
+
// Constraint size to not exceed available screen area
|
|
135
|
+
size = [
|
|
136
|
+
Math.min(areaAvailable[0], size[0]),
|
|
137
|
+
Math.min(areaAvailable[1], size[1]),
|
|
138
|
+
];
|
|
139
|
+
setWindowSize(size);
|
|
140
|
+
}
|
|
141
|
+
// Set window position
|
|
142
|
+
if (pos) {
|
|
143
|
+
// Constraint window position if monitor lock was set in preferences.
|
|
144
|
+
if (size && options.locked) {
|
|
145
|
+
pos = constraintPosition(pos, size)[1];
|
|
146
|
+
}
|
|
147
|
+
setWindowPosition(pos);
|
|
148
|
+
// Set window position at the center of the screen.
|
|
149
|
+
} else if (size) {
|
|
150
|
+
pos = vecAdd(
|
|
151
|
+
vecScale(areaAvailable, 0.5),
|
|
152
|
+
vecScale(size, -0.5),
|
|
153
|
+
vecScale(screenOffset, -1.0)
|
|
154
|
+
);
|
|
155
|
+
setWindowPosition(pos);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Setup draggable window
|
|
160
|
+
export const setupDrag = async () => {
|
|
161
|
+
// Calculate screen offset caused by the windows taskbar
|
|
162
|
+
let windowPosition = getWindowPosition();
|
|
163
|
+
|
|
164
|
+
screenOffsetPromise = Byond.winget(Byond.windowId, 'pos').then((pos) => [
|
|
165
|
+
pos.x - windowPosition[0],
|
|
166
|
+
pos.y - windowPosition[1],
|
|
167
|
+
]);
|
|
168
|
+
screenOffset = await screenOffsetPromise;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Constraints window position to safe screen area, accounting for safe
|
|
173
|
+
* margins which could be a system taskbar.
|
|
174
|
+
*/
|
|
175
|
+
const constraintPosition = (
|
|
176
|
+
pos: [number, number],
|
|
177
|
+
size: [number, number]
|
|
178
|
+
): [boolean, [number, number]] => {
|
|
179
|
+
const screenPos = getScreenPosition();
|
|
180
|
+
const screenSize = getScreenSize();
|
|
181
|
+
const nextPos: [number, number] = [pos[0], pos[1]];
|
|
182
|
+
let relocated = false;
|
|
183
|
+
for (let i = 0; i < 2; i++) {
|
|
184
|
+
const leftBoundary = screenPos[i];
|
|
185
|
+
const rightBoundary = screenPos[i] + screenSize[i];
|
|
186
|
+
if (pos[i] < leftBoundary) {
|
|
187
|
+
nextPos[i] = leftBoundary;
|
|
188
|
+
relocated = true;
|
|
189
|
+
} else if (pos[i] + size[i] > rightBoundary) {
|
|
190
|
+
nextPos[i] = rightBoundary - size[i];
|
|
191
|
+
relocated = true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return [relocated, nextPos];
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Start dragging the window
|
|
198
|
+
export const dragStartHandler = (event) => {
|
|
199
|
+
dragging = true;
|
|
200
|
+
dragPointOffset = vecSubtract(
|
|
201
|
+
[event.screenX, event.screenY],
|
|
202
|
+
getWindowPosition()
|
|
203
|
+
) as [number, number];
|
|
204
|
+
// Focus click target
|
|
205
|
+
(event.target as HTMLElement)?.focus();
|
|
206
|
+
document.addEventListener('mousemove', dragMoveHandler);
|
|
207
|
+
document.addEventListener('mouseup', dragEndHandler);
|
|
208
|
+
dragMoveHandler(event);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// End dragging the window
|
|
212
|
+
const dragEndHandler = (event) => {
|
|
213
|
+
dragMoveHandler(event);
|
|
214
|
+
document.removeEventListener('mousemove', dragMoveHandler);
|
|
215
|
+
document.removeEventListener('mouseup', dragEndHandler);
|
|
216
|
+
dragging = false;
|
|
217
|
+
storeWindowGeometry();
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Move the window while dragging
|
|
221
|
+
const dragMoveHandler = (event: MouseEvent) => {
|
|
222
|
+
if (!dragging) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
event.preventDefault();
|
|
226
|
+
setWindowPosition(
|
|
227
|
+
vecSubtract([event.screenX, event.screenY], dragPointOffset) as [
|
|
228
|
+
number,
|
|
229
|
+
number
|
|
230
|
+
]
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Start resizing the window
|
|
235
|
+
export const resizeStartHandler =
|
|
236
|
+
(x: number, y: number) => (event: MouseEvent) => {
|
|
237
|
+
resizeMatrix = [x, y];
|
|
238
|
+
resizing = true;
|
|
239
|
+
dragPointOffset = vecSubtract(
|
|
240
|
+
[event.screenX, event.screenY],
|
|
241
|
+
getWindowPosition()
|
|
242
|
+
) as [number, number];
|
|
243
|
+
initialSize = getWindowSize();
|
|
244
|
+
// Focus click target
|
|
245
|
+
(event.target as HTMLElement)?.focus();
|
|
246
|
+
document.addEventListener('mousemove', resizeMoveHandler);
|
|
247
|
+
document.addEventListener('mouseup', resizeEndHandler);
|
|
248
|
+
resizeMoveHandler(event);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// End resizing the window
|
|
252
|
+
const resizeEndHandler = (event: MouseEvent) => {
|
|
253
|
+
resizeMoveHandler(event);
|
|
254
|
+
document.removeEventListener('mousemove', resizeMoveHandler);
|
|
255
|
+
document.removeEventListener('mouseup', resizeEndHandler);
|
|
256
|
+
resizing = false;
|
|
257
|
+
storeWindowGeometry();
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Move the window while resizing
|
|
261
|
+
const resizeMoveHandler = (event: MouseEvent) => {
|
|
262
|
+
if (!resizing) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
const currentOffset = vecSubtract(
|
|
267
|
+
[event.screenX, event.screenY],
|
|
268
|
+
getWindowPosition()
|
|
269
|
+
);
|
|
270
|
+
const delta = vecSubtract(currentOffset, dragPointOffset);
|
|
271
|
+
// Extra 1x1 area is added to ensure the browser can see the cursor
|
|
272
|
+
size = vecAdd(initialSize, vecMultiply(resizeMatrix, delta), [1, 1]) as [
|
|
273
|
+
number,
|
|
274
|
+
number
|
|
275
|
+
];
|
|
276
|
+
// Sane window size values
|
|
277
|
+
size[0] = Math.max(size[0], 150 * pixelRatio);
|
|
278
|
+
size[1] = Math.max(size[1], 50 * pixelRatio);
|
|
279
|
+
setWindowSize(size);
|
|
280
|
+
};
|