tgui-core 1.0.2 → 1.0.3

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.
Files changed (158) hide show
  1. package/{src/components → components}/AnimatedNumber.tsx +185 -185
  2. package/{src/components → components}/BlockQuote.tsx +15 -15
  3. package/{src/components → components}/BodyZoneSelector.tsx +149 -149
  4. package/{src/components → components}/Box.tsx +255 -255
  5. package/{src/components → components}/Button.tsx +415 -415
  6. package/{src/components → components}/ByondUi.jsx +121 -121
  7. package/{src/components → components}/Chart.tsx +160 -160
  8. package/{src/components → components}/ColorBox.tsx +30 -30
  9. package/{src/components → components}/Dimmer.tsx +19 -19
  10. package/{src/components → components}/Divider.tsx +26 -26
  11. package/{src/components → components}/DmIcon.tsx +72 -72
  12. package/{src/components → components}/DraggableControl.jsx +282 -282
  13. package/{src/components → components}/Dropdown.tsx +246 -246
  14. package/{src/components → components}/Flex.tsx +105 -105
  15. package/{src/components → components}/Icon.tsx +91 -91
  16. package/{src/components → components}/Input.tsx +181 -181
  17. package/{src/components → components}/KeyListener.tsx +40 -40
  18. package/{src/components → components}/Knob.tsx +185 -185
  19. package/{src/components → components}/LabeledList.tsx +130 -130
  20. package/{src/components → components}/MenuBar.tsx +233 -238
  21. package/{src/components → components}/Modal.tsx +25 -25
  22. package/{src/components → components}/NoticeBox.tsx +48 -48
  23. package/{src/components → components}/NumberInput.tsx +328 -328
  24. package/{src/components → components}/ProgressBar.tsx +79 -79
  25. package/{src/components → components}/RestrictedInput.jsx +301 -301
  26. package/{src/components → components}/RoundGauge.tsx +189 -189
  27. package/{src/components → components}/Section.tsx +125 -125
  28. package/{src/components → components}/Slider.tsx +173 -173
  29. package/{src/components → components}/Stack.tsx +101 -101
  30. package/{src/components → components}/Table.tsx +90 -90
  31. package/{src/components → components}/Tabs.tsx +90 -90
  32. package/{src/components → components}/TextArea.tsx +198 -198
  33. package/{src/components → components}/TimeDisplay.jsx +64 -64
  34. package/components/index.ts +51 -0
  35. package/{src/debug/KitchenSink.jsx → debug/KitchenSink.tsx} +56 -56
  36. package/{src/debug/actions.js → debug/actions.ts} +11 -11
  37. package/{src/debug/hooks.js → debug/hooks.ts} +10 -10
  38. package/{src/debug/middleware.js → debug/middleware.ts} +86 -86
  39. package/{src/debug/reducer.js → debug/reducer.ts} +27 -22
  40. package/{src/debug/selectors.js → debug/selectors.ts} +7 -7
  41. package/{src/layouts → layouts}/Layout.tsx +75 -75
  42. package/{src/layouts → layouts}/NtosWindow.tsx +162 -162
  43. package/{src/layouts → layouts}/Pane.tsx +56 -56
  44. package/{src/layouts → layouts}/Window.tsx +227 -227
  45. package/layouts/index.ts +10 -0
  46. package/package.json +3 -2
  47. package/src/assets.ts +43 -43
  48. package/src/backend.ts +369 -369
  49. package/src/drag.ts +280 -280
  50. package/src/events.ts +237 -237
  51. package/src/hotkeys.ts +212 -212
  52. package/src/renderer.ts +50 -50
  53. package/stories/Blink.stories.tsx +20 -0
  54. package/stories/BlockQuote.stories.tsx +23 -0
  55. package/stories/Box.stories.tsx +27 -0
  56. package/stories/Button.stories.tsx +68 -0
  57. package/stories/ByondUi.stories.tsx +45 -0
  58. package/stories/Collapsible.stories.tsx +23 -0
  59. package/stories/Flex.stories.tsx +68 -0
  60. package/stories/Input.stories.tsx +124 -0
  61. package/stories/LabeledList.stories.tsx +73 -0
  62. package/stories/Popper.stories.tsx +58 -0
  63. package/stories/ProgressBar.stories.tsx +58 -0
  64. package/stories/Stack.stories.tsx +55 -0
  65. package/stories/Storage.stories.tsx +46 -0
  66. package/stories/Themes.stories.tsx +30 -0
  67. package/stories/Tooltip.stories.tsx +48 -0
  68. package/stories/common.tsx +19 -0
  69. package/tsconfig.json +0 -21
  70. package/src/components/Grid.tsx +0 -44
  71. /package/{src/common → common}/collections.ts +0 -0
  72. /package/{src/common → common}/color.ts +0 -0
  73. /package/{src/common → common}/events.ts +0 -0
  74. /package/{src/common → common}/exhaustive.ts +0 -0
  75. /package/{src/common → common}/fp.ts +0 -0
  76. /package/{src/common → common}/keycodes.ts +0 -0
  77. /package/{src/common → common}/keys.ts +0 -0
  78. /package/{src/common → common}/math.ts +0 -0
  79. /package/{src/common → common}/perf.ts +0 -0
  80. /package/{src/common → common}/random.ts +0 -0
  81. /package/{src/common → common}/react.ts +0 -0
  82. /package/{src/common → common}/redux.ts +0 -0
  83. /package/{src/common → common}/storage.js +0 -0
  84. /package/{src/common → common}/string.ts +0 -0
  85. /package/{src/common → common}/timer.ts +0 -0
  86. /package/{src/common → common}/type-utils.ts +0 -0
  87. /package/{src/common → common}/types.ts +0 -0
  88. /package/{src/common → common}/uuid.ts +0 -0
  89. /package/{src/common → common}/vector.ts +0 -0
  90. /package/{src/components → components}/Autofocus.tsx +0 -0
  91. /package/{src/components → components}/Blink.jsx +0 -0
  92. /package/{src/components → components}/Collapsible.tsx +0 -0
  93. /package/{src/components → components}/Dialog.tsx +0 -0
  94. /package/{src/components → components}/FakeTerminal.jsx +0 -0
  95. /package/{src/components → components}/FitText.tsx +0 -0
  96. /package/{src/components → components}/Image.tsx +0 -0
  97. /package/{src/components → components}/InfinitePlane.jsx +0 -0
  98. /package/{src/components → components}/LabeledControls.tsx +0 -0
  99. /package/{src/components → components}/Popper.tsx +0 -0
  100. /package/{src/components → components}/StyleableSection.tsx +0 -0
  101. /package/{src/components → components}/Tooltip.tsx +0 -0
  102. /package/{src/components → components}/TrackOutsideClicks.tsx +0 -0
  103. /package/{src/components → components}/VirtualList.tsx +0 -0
  104. /package/{src/debug → debug}/index.ts +0 -0
  105. /package/{src/styles → styles}/base.scss +0 -0
  106. /package/{src/styles → styles}/colors.scss +0 -0
  107. /package/{src/styles → styles}/components/BlockQuote.scss +0 -0
  108. /package/{src/styles → styles}/components/Button.scss +0 -0
  109. /package/{src/styles → styles}/components/ColorBox.scss +0 -0
  110. /package/{src/styles → styles}/components/Dialog.scss +0 -0
  111. /package/{src/styles → styles}/components/Dimmer.scss +0 -0
  112. /package/{src/styles → styles}/components/Divider.scss +0 -0
  113. /package/{src/styles → styles}/components/Dropdown.scss +0 -0
  114. /package/{src/styles → styles}/components/Flex.scss +0 -0
  115. /package/{src/styles → styles}/components/Icon.scss +0 -0
  116. /package/{src/styles → styles}/components/Input.scss +0 -0
  117. /package/{src/styles → styles}/components/Knob.scss +0 -0
  118. /package/{src/styles → styles}/components/LabeledList.scss +0 -0
  119. /package/{src/styles → styles}/components/MenuBar.scss +0 -0
  120. /package/{src/styles → styles}/components/Modal.scss +0 -0
  121. /package/{src/styles → styles}/components/NoticeBox.scss +0 -0
  122. /package/{src/styles → styles}/components/NumberInput.scss +0 -0
  123. /package/{src/styles → styles}/components/ProgressBar.scss +0 -0
  124. /package/{src/styles → styles}/components/RoundGauge.scss +0 -0
  125. /package/{src/styles → styles}/components/Section.scss +0 -0
  126. /package/{src/styles → styles}/components/Slider.scss +0 -0
  127. /package/{src/styles → styles}/components/Stack.scss +0 -0
  128. /package/{src/styles → styles}/components/Table.scss +0 -0
  129. /package/{src/styles → styles}/components/Tabs.scss +0 -0
  130. /package/{src/styles → styles}/components/TextArea.scss +0 -0
  131. /package/{src/styles → styles}/components/Tooltip.scss +0 -0
  132. /package/{src/styles → styles}/functions.scss +0 -0
  133. /package/{src/styles → styles}/layouts/Layout.scss +0 -0
  134. /package/{src/styles → styles}/layouts/NtosHeader.scss +0 -0
  135. /package/{src/styles → styles}/layouts/NtosWindow.scss +0 -0
  136. /package/{src/styles → styles}/layouts/TitleBar.scss +0 -0
  137. /package/{src/styles → styles}/layouts/Window.scss +0 -0
  138. /package/{src/styles → styles}/main.scss +0 -0
  139. /package/{src/styles → styles}/reset.scss +0 -0
  140. /package/{src/styles → styles}/themes/abductor.scss +0 -0
  141. /package/{src/styles → styles}/themes/admin.scss +0 -0
  142. /package/{src/styles → styles}/themes/cardtable.scss +0 -0
  143. /package/{src/styles → styles}/themes/hackerman.scss +0 -0
  144. /package/{src/styles → styles}/themes/malfunction.scss +0 -0
  145. /package/{src/styles → styles}/themes/neutral.scss +0 -0
  146. /package/{src/styles → styles}/themes/ntOS95.scss +0 -0
  147. /package/{src/styles → styles}/themes/ntos.scss +0 -0
  148. /package/{src/styles → styles}/themes/ntos_cat.scss +0 -0
  149. /package/{src/styles → styles}/themes/ntos_darkmode.scss +0 -0
  150. /package/{src/styles → styles}/themes/ntos_lightmode.scss +0 -0
  151. /package/{src/styles → styles}/themes/ntos_spooky.scss +0 -0
  152. /package/{src/styles → styles}/themes/ntos_synth.scss +0 -0
  153. /package/{src/styles → styles}/themes/ntos_terminal.scss +0 -0
  154. /package/{src/styles → styles}/themes/paper.scss +0 -0
  155. /package/{src/styles → styles}/themes/retro.scss +0 -0
  156. /package/{src/styles → styles}/themes/spookyconsole.scss +0 -0
  157. /package/{src/styles → styles}/themes/syndicate.scss +0 -0
  158. /package/{src/styles → styles}/themes/wizard.scss +0 -0
package/src/drag.ts CHANGED
@@ -1,280 +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
- };
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
+ };