@umicat/platform-sdk 0.3.0 → 0.4.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/fold/ScreensModule.d.ts +21 -0
- package/dist/fold/ScreensModule.js +101 -0
- package/dist/fold/foldVariables.d.ts +37 -0
- package/dist/fold/foldVariables.js +67 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FoldModule } from './FoldModule.js';
|
|
2
|
+
export declare class ScreensModule {
|
|
3
|
+
/** Above the crease when folded; the whole viewport when flat. */
|
|
4
|
+
readonly top: HTMLElement;
|
|
5
|
+
/** Below the crease. Hidden — but still yours, and still populated — when
|
|
6
|
+
* the device is flat. */
|
|
7
|
+
readonly bottom: HTMLElement;
|
|
8
|
+
private second;
|
|
9
|
+
private readonly listeners;
|
|
10
|
+
constructor(fold: FoldModule);
|
|
11
|
+
/** Whether `bottom` is a real second screen right now. */
|
|
12
|
+
get hasSecondScreen(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Calls `listener` on every change, and once immediately — same contract as
|
|
15
|
+
* `FoldModule.subscribe`, and for the same reason: a game told only about
|
|
16
|
+
* CHANGES lays itself out for one screen on a device that was already folded
|
|
17
|
+
* when it launched.
|
|
18
|
+
*/
|
|
19
|
+
subscribe(listener: (screens: ScreensModule) => void): () => void;
|
|
20
|
+
private apply;
|
|
21
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Two screens a game can put ANYTHING on.
|
|
2
|
+
//
|
|
3
|
+
// `FoldModule` answers "where is the crease"; this answers "here are the two
|
|
4
|
+
// surfaces". A game that wants a map, an inventory, a text box or a second
|
|
5
|
+
// rendered view on the lower screen appends it to `bottom` and is done — the
|
|
6
|
+
// SDK keeps both boxes glued to the panels as the hinge moves.
|
|
7
|
+
//
|
|
8
|
+
// **Both elements always exist, and neither is ever replaced.** The obvious
|
|
9
|
+
// alternative — `bottom: HTMLElement | null`, null when the device is flat —
|
|
10
|
+
// is more honest about the hardware and much worse to use: every game would
|
|
11
|
+
// branch at startup, and anything already appended would be destroyed on the
|
|
12
|
+
// first unfold. Instead `bottom` is simply hidden while there is no second
|
|
13
|
+
// screen, so a game appends once and its content survives folding, unfolding
|
|
14
|
+
// and folding again. `hasSecondScreen` is there for the game that genuinely
|
|
15
|
+
// wants to say something different when there is only one.
|
|
16
|
+
//
|
|
17
|
+
// **`position: fixed` children land inside their screen, not the window.** The
|
|
18
|
+
// containers carry a `transform`, and a transformed ancestor becomes the
|
|
19
|
+
// containing block for fixed descendants. That is what lets an existing HUD —
|
|
20
|
+
// authored as fixed, anchored to "bottom-right" — move onto a panel whole,
|
|
21
|
+
// without touching a single widget.
|
|
22
|
+
/** Pointer-events are off on the container and on for what the game adds, so
|
|
23
|
+
* an empty top screen does not swallow taps meant for the world below it. */
|
|
24
|
+
const BASE_STYLE = {
|
|
25
|
+
position: 'fixed',
|
|
26
|
+
pointerEvents: 'none',
|
|
27
|
+
// Clips to the panel: content must not bleed across the crease, which is the
|
|
28
|
+
// one place on this device where "slightly too tall" is physically bent.
|
|
29
|
+
overflow: 'hidden',
|
|
30
|
+
// Establishes the containing block (see above) AND its own stacking context.
|
|
31
|
+
transform: 'translateZ(0)',
|
|
32
|
+
// Under the SDK's own touch layer (z-index 10), which shares the lower panel.
|
|
33
|
+
zIndex: '5',
|
|
34
|
+
};
|
|
35
|
+
export class ScreensModule {
|
|
36
|
+
constructor(fold) {
|
|
37
|
+
this.second = false;
|
|
38
|
+
this.listeners = new Set();
|
|
39
|
+
this.top = make('top');
|
|
40
|
+
this.bottom = make('bottom');
|
|
41
|
+
fold.subscribe((layout) => this.apply(layout));
|
|
42
|
+
}
|
|
43
|
+
/** Whether `bottom` is a real second screen right now. */
|
|
44
|
+
get hasSecondScreen() {
|
|
45
|
+
return this.second;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Calls `listener` on every change, and once immediately — same contract as
|
|
49
|
+
* `FoldModule.subscribe`, and for the same reason: a game told only about
|
|
50
|
+
* CHANGES lays itself out for one screen on a device that was already folded
|
|
51
|
+
* when it launched.
|
|
52
|
+
*/
|
|
53
|
+
subscribe(listener) {
|
|
54
|
+
this.listeners.add(listener);
|
|
55
|
+
listener(this);
|
|
56
|
+
return () => { this.listeners.delete(listener); };
|
|
57
|
+
}
|
|
58
|
+
apply(layout) {
|
|
59
|
+
const split = layout.dualScreen && !!layout.top && !!layout.bottom;
|
|
60
|
+
this.second = split;
|
|
61
|
+
if (split) {
|
|
62
|
+
box(this.top, layout.top);
|
|
63
|
+
box(this.bottom, layout.bottom);
|
|
64
|
+
this.bottom.style.display = '';
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Flat: the top screen IS the window, and there is no second one.
|
|
68
|
+
Object.assign(this.top.style, { left: '0', top: '0', width: '100%', height: '100%' });
|
|
69
|
+
this.bottom.style.display = 'none';
|
|
70
|
+
}
|
|
71
|
+
for (const listener of this.listeners)
|
|
72
|
+
listener(this);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function make(name) {
|
|
76
|
+
// Headless (tests, SSR): hand back a detached stub rather than throwing. A
|
|
77
|
+
// game is never rendering in that case, so the box has nothing to be glued
|
|
78
|
+
// to — but `screens.top` must still be an element, or every caller needs a
|
|
79
|
+
// null check for a situation that only exists in a test runner.
|
|
80
|
+
if (typeof document === 'undefined') {
|
|
81
|
+
return { style: {}, dataset: {}, setAttribute() { }, appendChild() { } };
|
|
82
|
+
}
|
|
83
|
+
const el = document.createElement('div');
|
|
84
|
+
el.dataset.umicatScreen = name;
|
|
85
|
+
// Survives the editor's "hide every static element" sweep, which otherwise
|
|
86
|
+
// takes anything it does not recognise in index.html.
|
|
87
|
+
el.setAttribute('data-umicat-keep', '');
|
|
88
|
+
Object.assign(el.style, BASE_STYLE);
|
|
89
|
+
if (name === 'bottom')
|
|
90
|
+
el.style.display = 'none';
|
|
91
|
+
(document.body ?? document.documentElement).appendChild(el);
|
|
92
|
+
return el;
|
|
93
|
+
}
|
|
94
|
+
function box(el, r) {
|
|
95
|
+
Object.assign(el.style, {
|
|
96
|
+
left: `${r.x}px`,
|
|
97
|
+
top: `${r.y}px`,
|
|
98
|
+
width: `${r.width}px`,
|
|
99
|
+
height: `${r.height}px`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FoldModule, FoldLayout } from './FoldModule.js';
|
|
2
|
+
/**
|
|
3
|
+
* Publishes the fold geometry as CSS custom properties, so a game's own DOM can
|
|
4
|
+
* follow the crease without reading the SDK at all:
|
|
5
|
+
*
|
|
6
|
+
* --umicat-top-height, --umicat-bottom-top, --umicat-bottom-height,
|
|
7
|
+
* --umicat-fold-top, --umicat-fold-height
|
|
8
|
+
*
|
|
9
|
+
* plus `data-umicat-dual-screen` on `<html>`, because a stylesheet cannot ask a
|
|
10
|
+
* module a question.
|
|
11
|
+
*
|
|
12
|
+
* **Engine-agnostic on purpose.** These NAMES are a contract between the SDK and
|
|
13
|
+
* a game's stylesheet — Gomoku's chat panel moves to the lower screen with no
|
|
14
|
+
* JavaScript at all — and a contract with two definitions drifts. It lived in
|
|
15
|
+
* `@umicat/three-sdk` while 3D was the only consumer; it moved here the moment
|
|
16
|
+
* the Phaser side needed the same names.
|
|
17
|
+
*
|
|
18
|
+
* All values are `0` when there is no fold, so a stylesheet that uses them is
|
|
19
|
+
* correct on a flat screen too.
|
|
20
|
+
*
|
|
21
|
+
* **Never give these a fallback in `var()`.** Without the property the whole
|
|
22
|
+
* `calc()` is invalid, the declaration is dropped, and the unfolded rule stands
|
|
23
|
+
* — which is right. A `0px` fallback computes a real but wrong position: for a
|
|
24
|
+
* bottom-anchored element, one off the screen.
|
|
25
|
+
*/
|
|
26
|
+
export declare function installFoldVariables(fold: FoldModule): () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Puts a canvas in the panel above the crease, or back where it was.
|
|
29
|
+
*
|
|
30
|
+
* The CSS BOX only — never the drawing buffer. Which half of the job that is
|
|
31
|
+
* depends on the engine: Phaser's scale manager sizes the buffer itself once
|
|
32
|
+
* the box changes, while a three.js game has to be told (`viewportSize`).
|
|
33
|
+
* Shared because the positioning is identical and the trap is too: a buffer
|
|
34
|
+
* sized to the upper panel inside a canvas still stretched across the window
|
|
35
|
+
* draws the picture at twice its height.
|
|
36
|
+
*/
|
|
37
|
+
export declare function placeCanvasInPanel(canvas: HTMLCanvasElement, layout: FoldLayout): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publishes the fold geometry as CSS custom properties, so a game's own DOM can
|
|
3
|
+
* follow the crease without reading the SDK at all:
|
|
4
|
+
*
|
|
5
|
+
* --umicat-top-height, --umicat-bottom-top, --umicat-bottom-height,
|
|
6
|
+
* --umicat-fold-top, --umicat-fold-height
|
|
7
|
+
*
|
|
8
|
+
* plus `data-umicat-dual-screen` on `<html>`, because a stylesheet cannot ask a
|
|
9
|
+
* module a question.
|
|
10
|
+
*
|
|
11
|
+
* **Engine-agnostic on purpose.** These NAMES are a contract between the SDK and
|
|
12
|
+
* a game's stylesheet — Gomoku's chat panel moves to the lower screen with no
|
|
13
|
+
* JavaScript at all — and a contract with two definitions drifts. It lived in
|
|
14
|
+
* `@umicat/three-sdk` while 3D was the only consumer; it moved here the moment
|
|
15
|
+
* the Phaser side needed the same names.
|
|
16
|
+
*
|
|
17
|
+
* All values are `0` when there is no fold, so a stylesheet that uses them is
|
|
18
|
+
* correct on a flat screen too.
|
|
19
|
+
*
|
|
20
|
+
* **Never give these a fallback in `var()`.** Without the property the whole
|
|
21
|
+
* `calc()` is invalid, the declaration is dropped, and the unfolded rule stands
|
|
22
|
+
* — which is right. A `0px` fallback computes a real but wrong position: for a
|
|
23
|
+
* bottom-anchored element, one off the screen.
|
|
24
|
+
*/
|
|
25
|
+
export function installFoldVariables(fold) {
|
|
26
|
+
return fold.subscribe(publish);
|
|
27
|
+
}
|
|
28
|
+
function publish(layout) {
|
|
29
|
+
if (typeof document === 'undefined')
|
|
30
|
+
return;
|
|
31
|
+
const root = document.documentElement;
|
|
32
|
+
const px = (n) => `${Math.round(n ?? 0)}px`;
|
|
33
|
+
const on = layout.dualScreen;
|
|
34
|
+
root.style.setProperty('--umicat-top-height', px(on ? layout.top?.height : 0));
|
|
35
|
+
root.style.setProperty('--umicat-bottom-top', px(on ? layout.bottom?.y : 0));
|
|
36
|
+
root.style.setProperty('--umicat-bottom-height', px(on ? layout.bottom?.height : 0));
|
|
37
|
+
root.style.setProperty('--umicat-fold-top', px(on ? layout.division?.y : 0));
|
|
38
|
+
root.style.setProperty('--umicat-fold-height', px(on ? layout.division?.height : 0));
|
|
39
|
+
if (on)
|
|
40
|
+
root.dataset.umicatDualScreen = '';
|
|
41
|
+
else
|
|
42
|
+
delete root.dataset.umicatDualScreen;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Puts a canvas in the panel above the crease, or back where it was.
|
|
46
|
+
*
|
|
47
|
+
* The CSS BOX only — never the drawing buffer. Which half of the job that is
|
|
48
|
+
* depends on the engine: Phaser's scale manager sizes the buffer itself once
|
|
49
|
+
* the box changes, while a three.js game has to be told (`viewportSize`).
|
|
50
|
+
* Shared because the positioning is identical and the trap is too: a buffer
|
|
51
|
+
* sized to the upper panel inside a canvas still stretched across the window
|
|
52
|
+
* draws the picture at twice its height.
|
|
53
|
+
*/
|
|
54
|
+
export function placeCanvasInPanel(canvas, layout) {
|
|
55
|
+
if (layout.dualScreen && layout.top) {
|
|
56
|
+
Object.assign(canvas.style, {
|
|
57
|
+
position: 'fixed',
|
|
58
|
+
left: `${layout.top.x}px`,
|
|
59
|
+
top: `${layout.top.y}px`,
|
|
60
|
+
width: `${layout.top.width}px`,
|
|
61
|
+
height: `${layout.top.height}px`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
Object.assign(canvas.style, { position: '', left: '', top: '', width: '', height: '' });
|
|
66
|
+
}
|
|
67
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { AiModule, Npc } from './ai/AiModule.js';
|
|
|
17
17
|
export type { NpcConfig } from './ai/AiModule.js';
|
|
18
18
|
export { PlatformModule } from './platform/PlatformModule.js';
|
|
19
19
|
export { FoldModule } from './fold/FoldModule.js';
|
|
20
|
+
export { installFoldVariables, placeCanvasInPanel } from './fold/foldVariables.js';
|
|
20
21
|
export type { FoldLayout, FoldRect } from './fold/FoldModule.js';
|
|
21
22
|
export { VoiceModule, webVoiceSupported } from './voice/VoiceModule.js';
|
|
22
23
|
export type { VoiceCallbacks, VoiceSession, VoiceLang } from './voice/VoiceModule.js';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { UmicatRoom, PlayerDataFacade, RoomDataFacade, ChatFacade, MAX_CHAT_TEXT
|
|
|
16
16
|
export { AiModule, Npc } from './ai/AiModule.js';
|
|
17
17
|
export { PlatformModule } from './platform/PlatformModule.js';
|
|
18
18
|
export { FoldModule } from './fold/FoldModule.js';
|
|
19
|
+
export { installFoldVariables, placeCanvasInPanel } from './fold/foldVariables.js';
|
|
19
20
|
export { VoiceModule, webVoiceSupported } from './voice/VoiceModule.js';
|
|
20
21
|
export { DialogueModule } from './dialogue/DialogueModule.js';
|
|
21
22
|
export { DialogueRunner, resolveLangText } from './dialogue/runner.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umicat/platform-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Umicat's platform layer, independent of any rendering engine: identity, cloud saves, shared game data, multiplayer, runtime AI, voice and scripted dialogue. Consumed by @umicat/phaser-sdk and @umicat/three-sdk alike.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|