@guinetik/gcanvas 1.0.5 → 2.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/dist/aizawa.html +27 -0
- package/dist/clifford.html +25 -0
- package/dist/cmb.html +24 -0
- package/dist/dadras.html +26 -0
- package/dist/dejong.html +25 -0
- package/dist/gcanvas.es.js +5130 -372
- package/dist/gcanvas.es.min.js +1 -1
- package/dist/gcanvas.umd.js +1 -1
- package/dist/gcanvas.umd.min.js +1 -1
- package/dist/halvorsen.html +27 -0
- package/dist/index.html +96 -48
- package/dist/js/aizawa.js +425 -0
- package/dist/js/bezier.js +5 -5
- package/dist/js/clifford.js +236 -0
- package/dist/js/cmb.js +594 -0
- package/dist/js/dadras.js +405 -0
- package/dist/js/dejong.js +257 -0
- package/dist/js/halvorsen.js +405 -0
- package/dist/js/isometric.js +34 -46
- package/dist/js/lorenz.js +425 -0
- package/dist/js/painter.js +8 -8
- package/dist/js/rossler.js +480 -0
- package/dist/js/schrodinger.js +314 -18
- package/dist/js/thomas.js +394 -0
- package/dist/lorenz.html +27 -0
- package/dist/rossler.html +27 -0
- package/dist/scene-interactivity-test.html +220 -0
- package/dist/thomas.html +27 -0
- package/package.json +1 -1
- package/readme.md +30 -22
- package/src/game/objects/go.js +7 -0
- package/src/game/objects/index.js +2 -0
- package/src/game/objects/isometric-scene.js +53 -3
- package/src/game/objects/layoutscene.js +57 -0
- package/src/game/objects/mask.js +241 -0
- package/src/game/objects/scene.js +19 -0
- package/src/game/objects/wrapper.js +14 -2
- package/src/game/pipeline.js +17 -0
- package/src/game/ui/button.js +101 -16
- package/src/game/ui/theme.js +0 -6
- package/src/game/ui/togglebutton.js +25 -14
- package/src/game/ui/tooltip.js +12 -4
- package/src/index.js +3 -0
- package/src/io/gesture.js +409 -0
- package/src/io/index.js +4 -1
- package/src/io/keys.js +9 -1
- package/src/io/screen.js +476 -0
- package/src/math/attractors.js +664 -0
- package/src/math/heat.js +106 -0
- package/src/math/index.js +1 -0
- package/src/mixins/draggable.js +15 -19
- package/src/painter/painter.shapes.js +11 -5
- package/src/particle/particle-system.js +165 -1
- package/src/physics/index.js +26 -0
- package/src/physics/physics-updaters.js +333 -0
- package/src/physics/physics.js +375 -0
- package/src/shapes/image.js +5 -5
- package/src/shapes/index.js +2 -0
- package/src/shapes/parallelogram.js +147 -0
- package/src/shapes/righttriangle.js +115 -0
- package/src/shapes/svg.js +281 -100
- package/src/shapes/text.js +22 -6
- package/src/shapes/transformable.js +5 -0
- package/src/sound/effects.js +807 -0
- package/src/sound/index.js +13 -0
- package/src/webgl/index.js +7 -0
- package/src/webgl/shaders/clifford-point-shaders.js +131 -0
- package/src/webgl/shaders/dejong-point-shaders.js +131 -0
- package/src/webgl/shaders/point-sprite-shaders.js +152 -0
- package/src/webgl/webgl-clifford-renderer.js +477 -0
- package/src/webgl/webgl-dejong-renderer.js +472 -0
- package/src/webgl/webgl-line-renderer.js +391 -0
- package/src/webgl/webgl-particle-renderer.js +410 -0
- package/types/index.d.ts +30 -2
- package/types/io.d.ts +217 -0
- package/types/physics.d.ts +299 -0
- package/types/shapes.d.ts +8 -0
- package/types/webgl.d.ts +188 -109
package/src/io/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - {@link Keys}: Keyboard input with logical key mapping and state tracking
|
|
11
11
|
* - {@link Touch}: Touch input for mobile devices
|
|
12
12
|
* - {@link Input}: Unified input system that normalizes mouse and touch events
|
|
13
|
+
* - {@link Screen}: Screen/device detection and responsive utilities
|
|
13
14
|
*
|
|
14
15
|
* The IO module serves as the intermediary between raw browser events and your game logic,
|
|
15
16
|
* providing consistent, normalized events regardless of input source.
|
|
@@ -83,4 +84,6 @@ export {EventEmitter} from "./events.js";
|
|
|
83
84
|
export {Input} from "./input.js";
|
|
84
85
|
export {Mouse} from "./mouse.js";
|
|
85
86
|
export {Keys} from "./keys.js";
|
|
86
|
-
export {Touch} from "./touch.js";
|
|
87
|
+
export {Touch} from "./touch.js";
|
|
88
|
+
export {Screen} from "./screen.js";
|
|
89
|
+
export {Gesture} from "./gesture.js";
|
package/src/io/keys.js
CHANGED
|
@@ -34,6 +34,10 @@ export class Keys {
|
|
|
34
34
|
static E = "E";
|
|
35
35
|
static R = "R";
|
|
36
36
|
static F = "F";
|
|
37
|
+
static G = "G";
|
|
38
|
+
static J = "J";
|
|
39
|
+
static K = "K";
|
|
40
|
+
static L = "L";
|
|
37
41
|
static Z = "Z";
|
|
38
42
|
static C = "C";
|
|
39
43
|
static UP = "UP";
|
|
@@ -50,7 +54,7 @@ export class Keys {
|
|
|
50
54
|
* Customize this list as needed for your game.
|
|
51
55
|
*/
|
|
52
56
|
static _codeMap = {
|
|
53
|
-
// WASD + QE + RFZC
|
|
57
|
+
// WASD + QE + RFZC + GJKL
|
|
54
58
|
KeyW: Keys.W,
|
|
55
59
|
KeyA: Keys.A,
|
|
56
60
|
KeyS: Keys.S,
|
|
@@ -59,6 +63,10 @@ export class Keys {
|
|
|
59
63
|
KeyE: Keys.E,
|
|
60
64
|
KeyR: Keys.R,
|
|
61
65
|
KeyF: Keys.F,
|
|
66
|
+
KeyG: Keys.G,
|
|
67
|
+
KeyJ: Keys.J,
|
|
68
|
+
KeyK: Keys.K,
|
|
69
|
+
KeyL: Keys.L,
|
|
62
70
|
KeyZ: Keys.Z,
|
|
63
71
|
KeyC: Keys.C,
|
|
64
72
|
|
package/src/io/screen.js
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen.js
|
|
3
|
+
*
|
|
4
|
+
* Provides an abstraction layer over screen/device information and responsive
|
|
5
|
+
* utilities. Detects device type, orientation, pixel density, and emits events
|
|
6
|
+
* on changes.
|
|
7
|
+
*
|
|
8
|
+
* Example usage:
|
|
9
|
+
* // Initialization (usually in your Game constructor):
|
|
10
|
+
* Screen.init(this);
|
|
11
|
+
*
|
|
12
|
+
* // Check device type:
|
|
13
|
+
* if (Screen.isMobile) {
|
|
14
|
+
* this.scaleFactor = 1.5; // Higher quality for smaller screens
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* // Listen for orientation changes:
|
|
18
|
+
* this.events.on("orientationchange", () => {
|
|
19
|
+
* this.handleOrientationChange();
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Get responsive values:
|
|
23
|
+
* const fontSize = Screen.responsive(12, 16, 20); // mobile, tablet, desktop
|
|
24
|
+
*/
|
|
25
|
+
export class Screen {
|
|
26
|
+
// Breakpoints (can be customized)
|
|
27
|
+
static MOBILE_BREAKPOINT = 768;
|
|
28
|
+
static TABLET_BREAKPOINT = 1024;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Reference to the main game instance for event emission.
|
|
32
|
+
* @type {Game}
|
|
33
|
+
*/
|
|
34
|
+
static game = null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether Screen has been initialized with a game instance.
|
|
38
|
+
* @type {boolean}
|
|
39
|
+
*/
|
|
40
|
+
static _initialized = false;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Current screen/window width.
|
|
44
|
+
* @type {number}
|
|
45
|
+
*/
|
|
46
|
+
static width = 0;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Current screen/window height.
|
|
50
|
+
* @type {number}
|
|
51
|
+
*/
|
|
52
|
+
static height = 0;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Device pixel ratio (for high-DPI displays).
|
|
56
|
+
* @type {number}
|
|
57
|
+
*/
|
|
58
|
+
static pixelRatio = 1;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether the device is considered mobile (width <= MOBILE_BREAKPOINT).
|
|
62
|
+
* @type {boolean}
|
|
63
|
+
*/
|
|
64
|
+
static isMobile = false;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Whether the device is considered tablet (MOBILE_BREAKPOINT < width <= TABLET_BREAKPOINT).
|
|
68
|
+
* @type {boolean}
|
|
69
|
+
*/
|
|
70
|
+
static isTablet = false;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether the device is considered desktop (width > TABLET_BREAKPOINT).
|
|
74
|
+
* @type {boolean}
|
|
75
|
+
*/
|
|
76
|
+
static isDesktop = false;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Whether the device has touch capability.
|
|
80
|
+
* @type {boolean}
|
|
81
|
+
*/
|
|
82
|
+
static hasTouch = false;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Current orientation: 'portrait' or 'landscape'.
|
|
86
|
+
* @type {string}
|
|
87
|
+
*/
|
|
88
|
+
static orientation = "landscape";
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Whether the screen is in portrait mode.
|
|
92
|
+
* @type {boolean}
|
|
93
|
+
*/
|
|
94
|
+
static isPortrait = false;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whether the screen is in landscape mode.
|
|
98
|
+
* @type {boolean}
|
|
99
|
+
*/
|
|
100
|
+
static isLandscape = true;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Current wake lock sentinel (if active).
|
|
104
|
+
* @type {WakeLockSentinel|null}
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
static _wakeLock = null;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Whether wake lock is currently enabled (requested by user).
|
|
111
|
+
* @type {boolean}
|
|
112
|
+
*/
|
|
113
|
+
static wakeLockEnabled = false;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Whether the Wake Lock API is supported.
|
|
117
|
+
* @type {boolean}
|
|
118
|
+
*/
|
|
119
|
+
static wakeLockSupported = false;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Static initializer - runs once when the class is first loaded.
|
|
123
|
+
* Ensures properties are populated even without calling init().
|
|
124
|
+
*/
|
|
125
|
+
static {
|
|
126
|
+
// Only run in browser environment
|
|
127
|
+
if (typeof window !== "undefined") {
|
|
128
|
+
Screen._detect();
|
|
129
|
+
Screen.wakeLockSupported = "wakeLock" in navigator;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Initialize screen detection and event handling.
|
|
135
|
+
* Attaches resize and orientation change listeners.
|
|
136
|
+
*
|
|
137
|
+
* @param {Game} game - Your main Game instance with event emitter.
|
|
138
|
+
*/
|
|
139
|
+
static init(game) {
|
|
140
|
+
Screen.game = game;
|
|
141
|
+
|
|
142
|
+
// Initial detection (in case static initializer didn't run)
|
|
143
|
+
Screen._detect();
|
|
144
|
+
|
|
145
|
+
// Only attach listeners once
|
|
146
|
+
if (Screen._initialized) return;
|
|
147
|
+
Screen._initialized = true;
|
|
148
|
+
|
|
149
|
+
// Attach listeners
|
|
150
|
+
window.addEventListener("resize", Screen._onResize);
|
|
151
|
+
window.addEventListener("orientationchange", Screen._onOrientationChange);
|
|
152
|
+
|
|
153
|
+
// Also listen for media query changes for more accurate detection
|
|
154
|
+
if (window.matchMedia) {
|
|
155
|
+
const mobileQuery = window.matchMedia(
|
|
156
|
+
`(max-width: ${Screen.MOBILE_BREAKPOINT}px)`
|
|
157
|
+
);
|
|
158
|
+
const tabletQuery = window.matchMedia(
|
|
159
|
+
`(max-width: ${Screen.TABLET_BREAKPOINT}px)`
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Modern browsers
|
|
163
|
+
if (mobileQuery.addEventListener) {
|
|
164
|
+
mobileQuery.addEventListener("change", Screen._onMediaChange);
|
|
165
|
+
tabletQuery.addEventListener("change", Screen._onMediaChange);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Re-acquire wake lock when page becomes visible again
|
|
170
|
+
document.addEventListener("visibilitychange", Screen._onVisibilityChange);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Detect current screen properties.
|
|
175
|
+
* @private
|
|
176
|
+
*/
|
|
177
|
+
static _detect() {
|
|
178
|
+
// Dimensions
|
|
179
|
+
Screen.width = window.innerWidth;
|
|
180
|
+
Screen.height = window.innerHeight;
|
|
181
|
+
Screen.pixelRatio = window.devicePixelRatio || 1;
|
|
182
|
+
|
|
183
|
+
// Device type detection
|
|
184
|
+
Screen.isMobile = Screen.width <= Screen.MOBILE_BREAKPOINT;
|
|
185
|
+
Screen.isTablet =
|
|
186
|
+
Screen.width > Screen.MOBILE_BREAKPOINT &&
|
|
187
|
+
Screen.width <= Screen.TABLET_BREAKPOINT;
|
|
188
|
+
Screen.isDesktop = Screen.width > Screen.TABLET_BREAKPOINT;
|
|
189
|
+
|
|
190
|
+
// Touch capability
|
|
191
|
+
Screen.hasTouch =
|
|
192
|
+
"ontouchstart" in window ||
|
|
193
|
+
navigator.maxTouchPoints > 0 ||
|
|
194
|
+
// @ts-ignore - msMaxTouchPoints for older IE
|
|
195
|
+
navigator.msMaxTouchPoints > 0;
|
|
196
|
+
|
|
197
|
+
// Orientation
|
|
198
|
+
Screen.isPortrait = Screen.height > Screen.width;
|
|
199
|
+
Screen.isLandscape = !Screen.isPortrait;
|
|
200
|
+
Screen.orientation = Screen.isPortrait ? "portrait" : "landscape";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Handle window resize.
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
static _onResize = () => {
|
|
208
|
+
const prevMobile = Screen.isMobile;
|
|
209
|
+
const prevTablet = Screen.isTablet;
|
|
210
|
+
const prevOrientation = Screen.orientation;
|
|
211
|
+
|
|
212
|
+
Screen._detect();
|
|
213
|
+
|
|
214
|
+
// Emit resize event
|
|
215
|
+
if (Screen.game) {
|
|
216
|
+
Screen.game.events.emit("screenresize", {
|
|
217
|
+
width: Screen.width,
|
|
218
|
+
height: Screen.height,
|
|
219
|
+
isMobile: Screen.isMobile,
|
|
220
|
+
isTablet: Screen.isTablet,
|
|
221
|
+
isDesktop: Screen.isDesktop,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Emit device type change if breakpoint crossed
|
|
225
|
+
if (prevMobile !== Screen.isMobile || prevTablet !== Screen.isTablet) {
|
|
226
|
+
Screen.game.events.emit("devicechange", {
|
|
227
|
+
isMobile: Screen.isMobile,
|
|
228
|
+
isTablet: Screen.isTablet,
|
|
229
|
+
isDesktop: Screen.isDesktop,
|
|
230
|
+
previous: {
|
|
231
|
+
isMobile: prevMobile,
|
|
232
|
+
isTablet: prevTablet,
|
|
233
|
+
isDesktop: !prevMobile && !prevTablet,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Emit orientation change if changed
|
|
239
|
+
if (prevOrientation !== Screen.orientation) {
|
|
240
|
+
Screen.game.events.emit("orientationchange", {
|
|
241
|
+
orientation: Screen.orientation,
|
|
242
|
+
isPortrait: Screen.isPortrait,
|
|
243
|
+
isLandscape: Screen.isLandscape,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Handle orientation change event.
|
|
251
|
+
* @private
|
|
252
|
+
*/
|
|
253
|
+
static _onOrientationChange = () => {
|
|
254
|
+
// Delay detection slightly to allow browser to update dimensions
|
|
255
|
+
setTimeout(() => {
|
|
256
|
+
Screen._onResize();
|
|
257
|
+
}, 100);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Handle media query change.
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
static _onMediaChange = () => {
|
|
265
|
+
Screen._onResize();
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get a responsive value based on device type.
|
|
270
|
+
* Returns different values for mobile, tablet, and desktop.
|
|
271
|
+
*
|
|
272
|
+
* @param {number|any} mobile - Value to use on mobile devices.
|
|
273
|
+
* @param {number|any} [tablet] - Value to use on tablet devices. Defaults to mobile.
|
|
274
|
+
* @param {number|any} [desktop] - Value to use on desktop devices. Defaults to tablet.
|
|
275
|
+
* @returns {number|any} The appropriate value for the current device.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* const scaleFactor = Screen.responsive(1.5, 2, 3);
|
|
279
|
+
* const fontSize = Screen.responsive(12, 14, 16);
|
|
280
|
+
*/
|
|
281
|
+
static responsive(mobile, tablet, desktop) {
|
|
282
|
+
// Ensure detection has run (for environments without static initializer support)
|
|
283
|
+
if (Screen.width === 0 && typeof window !== "undefined") {
|
|
284
|
+
Screen._detect();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (tablet === undefined) tablet = mobile;
|
|
288
|
+
if (desktop === undefined) desktop = tablet;
|
|
289
|
+
|
|
290
|
+
if (Screen.isMobile) return mobile;
|
|
291
|
+
if (Screen.isTablet) return tablet;
|
|
292
|
+
return desktop;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get a value scaled by pixel ratio for high-DPI displays.
|
|
297
|
+
*
|
|
298
|
+
* @param {number} value - The base value to scale.
|
|
299
|
+
* @returns {number} The value multiplied by device pixel ratio.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* const lineWidth = Screen.scaled(2); // 4 on 2x displays
|
|
303
|
+
*/
|
|
304
|
+
static scaled(value) {
|
|
305
|
+
return value * Screen.pixelRatio;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Check if the current device is likely a touch-primary device.
|
|
310
|
+
* This is different from hasTouch - some laptops have touch screens
|
|
311
|
+
* but are primarily used with mouse/keyboard.
|
|
312
|
+
*
|
|
313
|
+
* @returns {boolean} True if device is likely touch-primary.
|
|
314
|
+
*/
|
|
315
|
+
static isTouchPrimary() {
|
|
316
|
+
return Screen.hasTouch && (Screen.isMobile || Screen.isTablet);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get the smaller dimension (useful for responsive sizing).
|
|
321
|
+
*
|
|
322
|
+
* @returns {number} The smaller of width or height.
|
|
323
|
+
*/
|
|
324
|
+
static minDimension() {
|
|
325
|
+
return Math.min(Screen.width, Screen.height);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Get the larger dimension.
|
|
330
|
+
*
|
|
331
|
+
* @returns {number} The larger of width or height.
|
|
332
|
+
*/
|
|
333
|
+
static maxDimension() {
|
|
334
|
+
return Math.max(Screen.width, Screen.height);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get the aspect ratio (width / height).
|
|
339
|
+
*
|
|
340
|
+
* @returns {number} The aspect ratio.
|
|
341
|
+
*/
|
|
342
|
+
static aspectRatio() {
|
|
343
|
+
return Screen.width / Screen.height;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Check if screen matches a media query.
|
|
348
|
+
*
|
|
349
|
+
* @param {string} query - CSS media query string.
|
|
350
|
+
* @returns {boolean} True if the query matches.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* if (Screen.matches('(prefers-color-scheme: dark)')) {
|
|
354
|
+
* // Use dark theme
|
|
355
|
+
* }
|
|
356
|
+
*/
|
|
357
|
+
static matches(query) {
|
|
358
|
+
if (!window.matchMedia) return false;
|
|
359
|
+
return window.matchMedia(query).matches;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Check if the user prefers reduced motion.
|
|
364
|
+
*
|
|
365
|
+
* @returns {boolean} True if user prefers reduced motion.
|
|
366
|
+
*/
|
|
367
|
+
static prefersReducedMotion() {
|
|
368
|
+
return Screen.matches("(prefers-reduced-motion: reduce)");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Check if the user prefers dark color scheme.
|
|
373
|
+
*
|
|
374
|
+
* @returns {boolean} True if user prefers dark mode.
|
|
375
|
+
*/
|
|
376
|
+
static prefersDarkMode() {
|
|
377
|
+
return Screen.matches("(prefers-color-scheme: dark)");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
381
|
+
// WAKE LOCK API - Prevent screen from sleeping during gameplay
|
|
382
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Handle visibility change - re-acquire wake lock when page becomes visible.
|
|
386
|
+
* @private
|
|
387
|
+
*/
|
|
388
|
+
static _onVisibilityChange = async () => {
|
|
389
|
+
if (Screen.wakeLockEnabled && document.visibilityState === "visible") {
|
|
390
|
+
await Screen._acquireWakeLock();
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Internal method to acquire wake lock.
|
|
396
|
+
* @private
|
|
397
|
+
* @returns {Promise<boolean>} True if lock was acquired.
|
|
398
|
+
*/
|
|
399
|
+
static async _acquireWakeLock() {
|
|
400
|
+
if (!Screen.wakeLockSupported) return false;
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
Screen._wakeLock = await navigator.wakeLock.request("screen");
|
|
404
|
+
Screen._wakeLock.addEventListener("release", () => {
|
|
405
|
+
// Lock was released (page hidden or manual release)
|
|
406
|
+
if (Screen.game) {
|
|
407
|
+
Screen.game.events.emit("wakelockrelease");
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
if (Screen.game) {
|
|
411
|
+
Screen.game.events.emit("wakelockacquire");
|
|
412
|
+
}
|
|
413
|
+
return true;
|
|
414
|
+
} catch (err) {
|
|
415
|
+
// Wake lock request failed (e.g., low battery, browser policy)
|
|
416
|
+
console.warn("[Screen] Wake lock request failed:", err.message);
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Request a wake lock to prevent the screen from sleeping.
|
|
423
|
+
* Useful for games/simulations that should keep the display on.
|
|
424
|
+
* The lock is automatically re-acquired when the page becomes visible.
|
|
425
|
+
*
|
|
426
|
+
* @returns {Promise<boolean>} True if wake lock was successfully acquired.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* // In your game's init():
|
|
430
|
+
* await Screen.requestWakeLock();
|
|
431
|
+
*
|
|
432
|
+
* // In your game's stop():
|
|
433
|
+
* Screen.releaseWakeLock();
|
|
434
|
+
*/
|
|
435
|
+
static async requestWakeLock() {
|
|
436
|
+
if (!Screen.wakeLockSupported) {
|
|
437
|
+
console.warn("[Screen] Wake Lock API not supported in this browser");
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
Screen.wakeLockEnabled = true;
|
|
442
|
+
return await Screen._acquireWakeLock();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Release the wake lock, allowing the screen to sleep normally.
|
|
447
|
+
* Call this when your game/simulation stops or pauses.
|
|
448
|
+
*
|
|
449
|
+
* @returns {Promise<void>}
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* // When game stops:
|
|
453
|
+
* Screen.releaseWakeLock();
|
|
454
|
+
*/
|
|
455
|
+
static async releaseWakeLock() {
|
|
456
|
+
Screen.wakeLockEnabled = false;
|
|
457
|
+
|
|
458
|
+
if (Screen._wakeLock) {
|
|
459
|
+
try {
|
|
460
|
+
await Screen._wakeLock.release();
|
|
461
|
+
Screen._wakeLock = null;
|
|
462
|
+
} catch (err) {
|
|
463
|
+
console.warn("[Screen] Wake lock release failed:", err.message);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Check if wake lock is currently active.
|
|
470
|
+
*
|
|
471
|
+
* @returns {boolean} True if wake lock is held.
|
|
472
|
+
*/
|
|
473
|
+
static isWakeLockActive() {
|
|
474
|
+
return Screen._wakeLock !== null && !Screen._wakeLock.released;
|
|
475
|
+
}
|
|
476
|
+
}
|