@rydr/game-sdk 3.0.1 → 3.1.1
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/README.md +34 -0
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/dist/ui/action-card.d.ts +39 -0
- package/dist/ui/action-card.d.ts.map +1 -0
- package/dist/ui/action-card.js +38 -0
- package/dist/ui/action-card.js.map +1 -0
- package/dist/ui/action-diamond.d.ts +41 -0
- package/dist/ui/action-diamond.d.ts.map +1 -0
- package/dist/ui/action-diamond.js +35 -0
- package/dist/ui/action-diamond.js.map +1 -0
- package/dist/ui/card.d.ts +45 -0
- package/dist/ui/card.d.ts.map +1 -0
- package/dist/ui/card.js +73 -0
- package/dist/ui/card.js.map +1 -0
- package/dist/ui/choice-card.d.ts +73 -0
- package/dist/ui/choice-card.d.ts.map +1 -0
- package/dist/ui/choice-card.js +141 -0
- package/dist/ui/choice-card.js.map +1 -0
- package/dist/ui/dialogue-card.d.ts +54 -0
- package/dist/ui/dialogue-card.d.ts.map +1 -0
- package/dist/ui/dialogue-card.js +107 -0
- package/dist/ui/dialogue-card.js.map +1 -0
- package/dist/ui/index.d.ts +31 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +31 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/keycap.d.ts +77 -0
- package/dist/ui/keycap.d.ts.map +1 -0
- package/dist/ui/keycap.js +160 -0
- package/dist/ui/keycap.js.map +1 -0
- package/dist/ui/labeled-diamond.d.ts +32 -0
- package/dist/ui/labeled-diamond.d.ts.map +1 -0
- package/dist/ui/labeled-diamond.js +86 -0
- package/dist/ui/labeled-diamond.js.map +1 -0
- package/dist/ui/showcase/gallery.d.ts +57 -0
- package/dist/ui/showcase/gallery.d.ts.map +1 -0
- package/dist/ui/showcase/gallery.js +90 -0
- package/dist/ui/showcase/gallery.js.map +1 -0
- package/dist/ui/showcase/index.d.ts +35 -0
- package/dist/ui/showcase/index.d.ts.map +1 -0
- package/dist/ui/showcase/index.js +256 -0
- package/dist/ui/showcase/index.js.map +1 -0
- package/dist/ui/styles.d.ts +12 -0
- package/dist/ui/styles.d.ts.map +1 -0
- package/dist/ui/styles.js +183 -0
- package/dist/ui/styles.js.map +1 -0
- package/package.json +13 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DIALOGUE CARD — an NPC discussion box built from the kit: a {@link Card} (dark container) holding
|
|
3
|
+
* the speaker's NAME, the LINE below (revealed with a small typewriter), and a controller KEYCAP
|
|
4
|
+
* (default A, from the barrel) in the corner as the "continue" affordance — preceded by a cue that
|
|
5
|
+
* reads ▸ while there's more to say and flips to ✓ on the last screen (the next A press closes it).
|
|
6
|
+
*
|
|
7
|
+
* The consumer authors **lines** — each one is shown DIRECTLY as a single screen (one A press per
|
|
8
|
+
* line), never re-wrapped or split by the component. To keep a long line from breaking the layout,
|
|
9
|
+
* the box is a fixed N-line frame with `overflow:hidden` (it can't grow), and each line has a soft
|
|
10
|
+
* character budget (`maxChars`): exceed it and the card logs a dev warning so the author knows the
|
|
11
|
+
* line may overflow. Char count is a rough proxy for "will this fit" — enough to give an idea.
|
|
12
|
+
*
|
|
13
|
+
* `open(name, lines)` starts a conversation; `advance()` finishes the current typewriter, steps to the
|
|
14
|
+
* next line, or — past the last line — closes the card and returns `false`. Fixed bottom-centre by
|
|
15
|
+
* default (a classic RPG bar), or `inline:true` to lay it in normal flow.
|
|
16
|
+
*/
|
|
17
|
+
import { type Card } from "./card.js";
|
|
18
|
+
import { type ButtonLetter, type ButtonSource, type Keycap } from "./keycap.js";
|
|
19
|
+
export interface DialogueCardOptions {
|
|
20
|
+
/** The "continue" button shown in the corner (default "A"). */
|
|
21
|
+
button?: ButtonLetter;
|
|
22
|
+
/** Lay the card inline (normal flow) instead of fixed bottom-centre. */
|
|
23
|
+
inline?: boolean;
|
|
24
|
+
/** Typewriter speed, ms per character (default 18; 0 = reveal instantly). */
|
|
25
|
+
typeMs?: number;
|
|
26
|
+
/** Wire the continue keycap to real controller input (pressed while its button is held). */
|
|
27
|
+
press?: ButtonSource;
|
|
28
|
+
/** Box height, in text lines: the line is clipped to this fixed frame so it can't grow (default 3). */
|
|
29
|
+
maxLines?: number;
|
|
30
|
+
/** Soft per-line character budget: a longer line logs a dev warning (default 140). */
|
|
31
|
+
maxChars?: number;
|
|
32
|
+
/** Fires with the line index whenever a NEW line becomes visible (at `open` and on each `advance` step,
|
|
33
|
+
* not when a press merely finishes the typewriter). Used e.g. to drive a speaker's talking animation. */
|
|
34
|
+
onLine?: (idx: number) => void;
|
|
35
|
+
}
|
|
36
|
+
export interface DialogueCard {
|
|
37
|
+
root: HTMLElement;
|
|
38
|
+
/** The underlying card (container) — for styling / positioning. */
|
|
39
|
+
card: Card;
|
|
40
|
+
/** The continue keycap (drive its state if you want). */
|
|
41
|
+
keycap: Keycap;
|
|
42
|
+
/** Start a conversation: speaker name + lines. Each line is shown directly as one screen. */
|
|
43
|
+
open(name: string, lines: string[]): void;
|
|
44
|
+
/** Finish the typewriter, step to the next line, or close past the last line; `false` once closed. */
|
|
45
|
+
advance(): boolean;
|
|
46
|
+
/** Hide the card and reset. */
|
|
47
|
+
close(): void;
|
|
48
|
+
setVisible(v: boolean): void;
|
|
49
|
+
setScreenPos(x: number, y: number): void;
|
|
50
|
+
dispose(): void;
|
|
51
|
+
}
|
|
52
|
+
/** Mount a discussion card: name + typed line + a "continue" cue & keycap, inside the shared dark card. */
|
|
53
|
+
export declare function mountDialogueCard(host: HTMLElement, opts?: DialogueCardOptions): DialogueCard;
|
|
54
|
+
//# sourceMappingURL=dialogue-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogue-card.d.ts","sourceRoot":"","sources":["../../src/ui/dialogue-card.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAgB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAK9F,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,uGAAuG;IACvG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;8GAC0G;IAC1G,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,mEAAmE;IACnE,IAAI,EAAE,IAAI,CAAC;IACX,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC1C,sGAAsG;IACtG,OAAO,IAAI,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,IAAI,IAAI,CAAC;IACd,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,2GAA2G;AAC3G,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,mBAAwB,GAAG,YAAY,CA6EjG"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DIALOGUE CARD — an NPC discussion box built from the kit: a {@link Card} (dark container) holding
|
|
3
|
+
* the speaker's NAME, the LINE below (revealed with a small typewriter), and a controller KEYCAP
|
|
4
|
+
* (default A, from the barrel) in the corner as the "continue" affordance — preceded by a cue that
|
|
5
|
+
* reads ▸ while there's more to say and flips to ✓ on the last screen (the next A press closes it).
|
|
6
|
+
*
|
|
7
|
+
* The consumer authors **lines** — each one is shown DIRECTLY as a single screen (one A press per
|
|
8
|
+
* line), never re-wrapped or split by the component. To keep a long line from breaking the layout,
|
|
9
|
+
* the box is a fixed N-line frame with `overflow:hidden` (it can't grow), and each line has a soft
|
|
10
|
+
* character budget (`maxChars`): exceed it and the card logs a dev warning so the author knows the
|
|
11
|
+
* line may overflow. Char count is a rough proxy for "will this fit" — enough to give an idea.
|
|
12
|
+
*
|
|
13
|
+
* `open(name, lines)` starts a conversation; `advance()` finishes the current typewriter, steps to the
|
|
14
|
+
* next line, or — past the last line — closes the card and returns `false`. Fixed bottom-centre by
|
|
15
|
+
* default (a classic RPG bar), or `inline:true` to lay it in normal flow.
|
|
16
|
+
*/
|
|
17
|
+
import { mountCard } from "./card.js";
|
|
18
|
+
import { createKeycap } from "./keycap.js";
|
|
19
|
+
const CUE_MORE = "▸"; // there's another page after this one
|
|
20
|
+
const CUE_END = "✓"; // last page — the next press closes the conversation
|
|
21
|
+
/** Mount a discussion card: name + typed line + a "continue" cue & keycap, inside the shared dark card. */
|
|
22
|
+
export function mountDialogueCard(host, opts = {}) {
|
|
23
|
+
const ms = opts.typeMs ?? 18;
|
|
24
|
+
const maxLines = Math.max(1, opts.maxLines ?? 3);
|
|
25
|
+
const maxChars = Math.max(1, opts.maxChars ?? 140);
|
|
26
|
+
const wrap = document.createElement("div");
|
|
27
|
+
wrap.className = "rydr-ui-dlg";
|
|
28
|
+
const nameEl = document.createElement("div");
|
|
29
|
+
nameEl.className = "rydr-ui-dlg-name";
|
|
30
|
+
const lineEl = document.createElement("div");
|
|
31
|
+
lineEl.className = "rydr-ui-dlg-line";
|
|
32
|
+
const go = document.createElement("div");
|
|
33
|
+
go.className = "rydr-ui-dlg-go";
|
|
34
|
+
const cueEl = document.createElement("span");
|
|
35
|
+
cueEl.className = "rydr-ui-dlg-cue";
|
|
36
|
+
const keycap = createKeycap(opts.button ?? "A", { variant: "solo", animate: true, press: opts.press });
|
|
37
|
+
go.append(cueEl, keycap.el);
|
|
38
|
+
wrap.append(nameEl, lineEl, go);
|
|
39
|
+
const card = mountCard(host, { content: wrap, inline: opts.inline });
|
|
40
|
+
card.setVisible(false);
|
|
41
|
+
// Lock the visible line to a fixed N-line frame (line-height is 1.4) and clip overflow: a line that
|
|
42
|
+
// runs past the budget is trimmed, not allowed to grow the box and break the surrounding layout.
|
|
43
|
+
lineEl.style.height = `${(maxLines * 1.4).toFixed(2)}em`;
|
|
44
|
+
lineEl.style.overflow = "hidden";
|
|
45
|
+
let lines = [];
|
|
46
|
+
let idx = 0;
|
|
47
|
+
let timer = null;
|
|
48
|
+
const stop = () => { if (timer !== null) {
|
|
49
|
+
clearInterval(timer);
|
|
50
|
+
timer = null;
|
|
51
|
+
} };
|
|
52
|
+
const showLine = () => {
|
|
53
|
+
stop();
|
|
54
|
+
opts.onLine?.(idx); // a new line is now on screen → let the caller react (e.g. alternate a talking clip)
|
|
55
|
+
const full = lines[idx] ?? "";
|
|
56
|
+
cueEl.textContent = idx >= lines.length - 1 ? CUE_END : CUE_MORE; // ✓ on the last line, else ▸
|
|
57
|
+
if (ms <= 0) {
|
|
58
|
+
lineEl.textContent = full;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
let shown = 0;
|
|
62
|
+
lineEl.textContent = "";
|
|
63
|
+
timer = setInterval(() => {
|
|
64
|
+
shown++;
|
|
65
|
+
lineEl.textContent = full.slice(0, shown);
|
|
66
|
+
if (shown >= full.length)
|
|
67
|
+
stop();
|
|
68
|
+
}, ms);
|
|
69
|
+
};
|
|
70
|
+
const close = () => { stop(); card.setVisible(false); lines = []; idx = 0; };
|
|
71
|
+
return {
|
|
72
|
+
root: card.root,
|
|
73
|
+
card,
|
|
74
|
+
keycap,
|
|
75
|
+
open(name, ls) {
|
|
76
|
+
// Char count is a rough proxy for "will this fit the box" — enough to flag a likely overflow.
|
|
77
|
+
ls.forEach((l, i) => {
|
|
78
|
+
if (l.length > maxChars)
|
|
79
|
+
console.warn(`[dialogue-card] line ${i} is ${l.length} chars (budget ${maxChars}); it may be clipped: "${l.slice(0, 48)}…"`);
|
|
80
|
+
});
|
|
81
|
+
nameEl.textContent = name;
|
|
82
|
+
lines = ls;
|
|
83
|
+
idx = 0;
|
|
84
|
+
card.setVisible(true);
|
|
85
|
+
showLine();
|
|
86
|
+
},
|
|
87
|
+
advance() {
|
|
88
|
+
if (timer !== null) {
|
|
89
|
+
stop();
|
|
90
|
+
lineEl.textContent = lines[idx] ?? "";
|
|
91
|
+
return true;
|
|
92
|
+
} // finish the line
|
|
93
|
+
idx++;
|
|
94
|
+
if (idx >= lines.length) {
|
|
95
|
+
close();
|
|
96
|
+
return false;
|
|
97
|
+
} // past the last line → close
|
|
98
|
+
showLine();
|
|
99
|
+
return true;
|
|
100
|
+
},
|
|
101
|
+
close,
|
|
102
|
+
setVisible: card.setVisible,
|
|
103
|
+
setScreenPos: card.setScreenPos,
|
|
104
|
+
dispose: () => { stop(); card.dispose(); },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=dialogue-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogue-card.js","sourceRoot":"","sources":["../../src/ui/dialogue-card.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@rydr/game-sdk/ui` — the reusable controller-button UI: a DOM/CSS keycap of the A/B/Y/Z face
|
|
3
|
+
* buttons and the UP/DOWN/LEFT/RIGHT d-pad, plus higher-level pieces built from it (a dark glass card
|
|
4
|
+
* with a content slot, an action card, the full A/B/Y/Z move diamond, an NPC dialogue card, a player
|
|
5
|
+
* choice menu, and a labeled diamond). Games use it to show "which button to press" in one consistent
|
|
6
|
+
* visual language across the library.
|
|
7
|
+
*
|
|
8
|
+
* Self-contained, like `@rydr/game-sdk/three`'s perf-overlay: it injects its own scoped `.rydr-ui-*`
|
|
9
|
+
* CSS once into `<head>`, depends on no global styles or CSS variables, and pulls in no three.js — so
|
|
10
|
+
* a game gets the look just by constructing a component. Wire any keycap to real input by passing the
|
|
11
|
+
* `session` (it satisfies the structural {@link ButtonSource}); the keycap then reflects real presses.
|
|
12
|
+
*
|
|
13
|
+
* ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
* **Do NOT restyle the components — their type sizes and dimensions are deliberate.** Every RYDR
|
|
15
|
+
* game is played on a **trainer screen viewed from a few feet away while pedaling**, so the keycaps,
|
|
16
|
+
* card labels, dialogue/choice type, and diamond spacing are tuned to stay legible at that distance.
|
|
17
|
+
* Shrinking a label "to fit", overriding the `.rydr-ui-*` font sizes / paddings / widths, or wrapping
|
|
18
|
+
* a component in a `transform: scale()` breaks that contract and makes the UI unreadable on a real
|
|
19
|
+
* setup. Fill the **content slot** (`mountCard`/`mountActionCard` take any DOM, the diamond takes a
|
|
20
|
+
* label or custom node) and use the provided state methods — but treat the frame's sizing as fixed.
|
|
21
|
+
* If a label doesn't fit, shorten the text, don't shrink the type.
|
|
22
|
+
* ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
*/
|
|
24
|
+
export * from "./keycap.js";
|
|
25
|
+
export * from "./card.js";
|
|
26
|
+
export * from "./action-card.js";
|
|
27
|
+
export * from "./action-diamond.js";
|
|
28
|
+
export * from "./dialogue-card.js";
|
|
29
|
+
export * from "./choice-card.js";
|
|
30
|
+
export * from "./labeled-diamond.js";
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC"}
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@rydr/game-sdk/ui` — the reusable controller-button UI: a DOM/CSS keycap of the A/B/Y/Z face
|
|
3
|
+
* buttons and the UP/DOWN/LEFT/RIGHT d-pad, plus higher-level pieces built from it (a dark glass card
|
|
4
|
+
* with a content slot, an action card, the full A/B/Y/Z move diamond, an NPC dialogue card, a player
|
|
5
|
+
* choice menu, and a labeled diamond). Games use it to show "which button to press" in one consistent
|
|
6
|
+
* visual language across the library.
|
|
7
|
+
*
|
|
8
|
+
* Self-contained, like `@rydr/game-sdk/three`'s perf-overlay: it injects its own scoped `.rydr-ui-*`
|
|
9
|
+
* CSS once into `<head>`, depends on no global styles or CSS variables, and pulls in no three.js — so
|
|
10
|
+
* a game gets the look just by constructing a component. Wire any keycap to real input by passing the
|
|
11
|
+
* `session` (it satisfies the structural {@link ButtonSource}); the keycap then reflects real presses.
|
|
12
|
+
*
|
|
13
|
+
* ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
* **Do NOT restyle the components — their type sizes and dimensions are deliberate.** Every RYDR
|
|
15
|
+
* game is played on a **trainer screen viewed from a few feet away while pedaling**, so the keycaps,
|
|
16
|
+
* card labels, dialogue/choice type, and diamond spacing are tuned to stay legible at that distance.
|
|
17
|
+
* Shrinking a label "to fit", overriding the `.rydr-ui-*` font sizes / paddings / widths, or wrapping
|
|
18
|
+
* a component in a `transform: scale()` breaks that contract and makes the UI unreadable on a real
|
|
19
|
+
* setup. Fill the **content slot** (`mountCard`/`mountActionCard` take any DOM, the diamond takes a
|
|
20
|
+
* label or custom node) and use the provided state methods — but treat the frame's sizing as fixed.
|
|
21
|
+
* If a label doesn't fit, shorten the text, don't shrink the type.
|
|
22
|
+
* ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
*/
|
|
24
|
+
export * from "./keycap.js";
|
|
25
|
+
export * from "./card.js";
|
|
26
|
+
export * from "./action-card.js";
|
|
27
|
+
export * from "./action-diamond.js";
|
|
28
|
+
export * from "./dialogue-card.js";
|
|
29
|
+
export * from "./choice-card.js";
|
|
30
|
+
export * from "./labeled-diamond.js";
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KEYCAP — a DOM/CSS keycap of the controller face buttons (zero dependencies), so a game can show
|
|
3
|
+
* "which button to press" in a consistent visual language. Two shapes:
|
|
4
|
+
*
|
|
5
|
+
* • "full" — the classic diamond: the active button enlarged + coloured, the other three small + grey.
|
|
6
|
+
* • "solo" — light variation: JUST the active button (the idle siblings removed).
|
|
7
|
+
*
|
|
8
|
+
* The keycap is a small STATE MACHINE: it can pulse (the bouncing attract animation), show a pressed
|
|
9
|
+
* sink, run a depleting COOLDOWN ring + countdown, render colored or neutral ("mono"), and hide.
|
|
10
|
+
*
|
|
11
|
+
* Two button families share that machine: `createKeycap` for the A/B/Y/Z face buttons (a letter on
|
|
12
|
+
* the active pip) and `createDpadKeycap` for the UP/DOWN/LEFT/RIGHT directional buttons (a white
|
|
13
|
+
* chevron on a steel-grey pip, rotated per direction). They differ ONLY in the face drawn and the
|
|
14
|
+
* button name the press-wiring listens for; everything else is identical.
|
|
15
|
+
*/
|
|
16
|
+
import type { ButtonName, ButtonEdge } from "../protocol/index.js";
|
|
17
|
+
/** The four face buttons (a subset of the canonical {@link ButtonName} vocabulary). */
|
|
18
|
+
export type ButtonLetter = "A" | "B" | "Y" | "Z";
|
|
19
|
+
/** The four directional buttons (the canonical d-pad {@link ButtonName}s). */
|
|
20
|
+
export type DirButton = "UP" | "DOWN" | "LEFT" | "RIGHT";
|
|
21
|
+
/** "full" = the 4-button diamond; "solo" = just the active button. */
|
|
22
|
+
export type KeycapVariant = "full" | "solo";
|
|
23
|
+
/** @deprecated alias kept for back-compat. */
|
|
24
|
+
export type DiamondVariant = KeycapVariant;
|
|
25
|
+
/**
|
|
26
|
+
* A controller button-event source — the minimal slice of the platform `session` the keycap needs to
|
|
27
|
+
* auto-reflect real presses. A `PlatformSession` satisfies it structurally (you just pass `session`).
|
|
28
|
+
*/
|
|
29
|
+
export interface ButtonSource {
|
|
30
|
+
onButton(cb: (e: {
|
|
31
|
+
name: ButtonName;
|
|
32
|
+
edge: ButtonEdge;
|
|
33
|
+
}) => void): () => void;
|
|
34
|
+
}
|
|
35
|
+
export interface KeycapOptions {
|
|
36
|
+
/** "solo" (default) shows only the active button; "full" shows the 4-button diamond. */
|
|
37
|
+
variant?: KeycapVariant;
|
|
38
|
+
/** Per-button hue (default true). `false` → a neutral palette (full colour can be too loud). */
|
|
39
|
+
colored?: boolean;
|
|
40
|
+
/** Run the bouncing pulse on the active button (default true). */
|
|
41
|
+
animate?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Wire the keycap to real controller input: pass `session` (or anything with `onButton`) and the
|
|
44
|
+
* keycap shows its pressed sink while ITS button is held (down → pressed, up → released).
|
|
45
|
+
*/
|
|
46
|
+
press?: ButtonSource;
|
|
47
|
+
}
|
|
48
|
+
/** A bare keycap with its state setters. */
|
|
49
|
+
export interface Keycap {
|
|
50
|
+
/** The root element to place wherever you want. */
|
|
51
|
+
el: HTMLElement;
|
|
52
|
+
/** Pressed-in sink (default false). */
|
|
53
|
+
setPressed(on: boolean): void;
|
|
54
|
+
/** Bouncing pulse on the active button. */
|
|
55
|
+
setAnimated(on: boolean): void;
|
|
56
|
+
/**
|
|
57
|
+
* Cooldown ring on the active button. `frac` 0..1 = portion REMAINING (depletes clockwise from 12
|
|
58
|
+
* o'clock); pass `label` to show a centred countdown (e.g. the remaining seconds). `null` clears it.
|
|
59
|
+
*/
|
|
60
|
+
setCooldown(frac: number | null, label?: string | number): void;
|
|
61
|
+
/** Per-button hue on/off. */
|
|
62
|
+
setColored(on: boolean): void;
|
|
63
|
+
/** Disabled = the action is unavailable: flat grey, inert (no pulse/press), letter dimmed. */
|
|
64
|
+
setDisabled(on: boolean): void;
|
|
65
|
+
/** Hide/show the keycap. */
|
|
66
|
+
setVisible(on: boolean): void;
|
|
67
|
+
dispose(): void;
|
|
68
|
+
}
|
|
69
|
+
/** Build a bare A/B/Y/Z keycap with state setters. */
|
|
70
|
+
export declare function createKeycap(button: ButtonLetter, opts?: KeycapOptions): Keycap;
|
|
71
|
+
/**
|
|
72
|
+
* Build a bare directional (UP/DOWN/LEFT/RIGHT) keycap. Identical state machine to {@link createKeycap}
|
|
73
|
+
* — solo/full, pulse, pressed, cooldown ring — only the active button shows a WHITE chevron (rotated
|
|
74
|
+
* per direction) on a steel-grey pip, not the loud A/B/Y/Z hues.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createDpadKeycap(dir: DirButton, opts?: KeycapOptions): Keycap;
|
|
77
|
+
//# sourceMappingURL=keycap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycap.d.ts","sourceRoot":"","sources":["../../src/ui/keycap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGnE,uFAAuF;AACvF,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AACjD,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AACzD,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAC5C,8CAA8C;AAC9C,MAAM,MAAM,cAAc,GAAG,aAAa,CAAC;AAE3C;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC/E;AAED,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,gGAAgG;IAChG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,4CAA4C;AAC5C,MAAM,WAAW,MAAM;IACrB,mDAAmD;IACnD,EAAE,EAAE,WAAW,CAAC;IAChB,uCAAuC;IACvC,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,2CAA2C;IAC3C,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAChE,6BAA6B;IAC7B,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,8FAA8F;IAC9F,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,4BAA4B;IAC5B,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,OAAO,IAAI,IAAI,CAAC;CACjB;AA8ID,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE,aAAkB,GAAG,MAAM,CAEnF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,GAAE,aAAkB,GAAG,MAAM,CAEjF"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KEYCAP — a DOM/CSS keycap of the controller face buttons (zero dependencies), so a game can show
|
|
3
|
+
* "which button to press" in a consistent visual language. Two shapes:
|
|
4
|
+
*
|
|
5
|
+
* • "full" — the classic diamond: the active button enlarged + coloured, the other three small + grey.
|
|
6
|
+
* • "solo" — light variation: JUST the active button (the idle siblings removed).
|
|
7
|
+
*
|
|
8
|
+
* The keycap is a small STATE MACHINE: it can pulse (the bouncing attract animation), show a pressed
|
|
9
|
+
* sink, run a depleting COOLDOWN ring + countdown, render colored or neutral ("mono"), and hide.
|
|
10
|
+
*
|
|
11
|
+
* Two button families share that machine: `createKeycap` for the A/B/Y/Z face buttons (a letter on
|
|
12
|
+
* the active pip) and `createDpadKeycap` for the UP/DOWN/LEFT/RIGHT directional buttons (a white
|
|
13
|
+
* chevron on a steel-grey pip, rotated per direction). They differ ONLY in the face drawn and the
|
|
14
|
+
* button name the press-wiring listens for; everything else is identical.
|
|
15
|
+
*/
|
|
16
|
+
import { injectCss } from "./styles.js";
|
|
17
|
+
const LETTER = { top: "Y", rgt: "A", bot: "B", lft: "Z" };
|
|
18
|
+
const COLOR = { top: "#4DD5FF", rgt: "#00C968", bot: "#FF79FF", lft: "#FF7A0C" };
|
|
19
|
+
const DIR = { top: [0, -1], bot: [0, 1], lft: [-1, 0], rgt: [1, 0] };
|
|
20
|
+
const POS_OF = { Y: "top", A: "rgt", B: "bot", Z: "lft" };
|
|
21
|
+
const POS_OF_DIR = { UP: "top", DOWN: "bot", LEFT: "lft", RIGHT: "rgt" };
|
|
22
|
+
/** Rotation (deg) applied to the base-points-up chevron so it points the right way per position. */
|
|
23
|
+
const ROT = { top: 0, rgt: 90, bot: 180, lft: 270 };
|
|
24
|
+
/** The chevron glyph, drawn in a 24×24 box pointing UP (rotated per position); white on the pip. */
|
|
25
|
+
const CHEVRON = `<polyline points="5,15 12,8 19,15" fill="none" stroke="currentColor" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/>`;
|
|
26
|
+
/** Background tone of a directional keycap's active pip ("acier" / steel-grey); the glyph stays white. */
|
|
27
|
+
const DPAD_TONE = "#566273";
|
|
28
|
+
const PIP = 20, PIP_ACTIVE = 38, LETTER_FONT = 23;
|
|
29
|
+
const NEUTRAL_ACTIVE = "#ffffff"; // mono active button: white face (the letter stays dark)
|
|
30
|
+
const INACTIVE = "#3a424e"; // the idle siblings in a full diamond
|
|
31
|
+
/** Letter face — the A/B/Y/Z glyph (sized up on the active button, hidden font otherwise). */
|
|
32
|
+
const letterFace = (pos, active) => {
|
|
33
|
+
const lt = document.createElement("span");
|
|
34
|
+
lt.className = "rydr-ui-pip-letter";
|
|
35
|
+
if (active)
|
|
36
|
+
lt.style.fontSize = `${LETTER_FONT}px`;
|
|
37
|
+
lt.textContent = LETTER[pos];
|
|
38
|
+
return lt;
|
|
39
|
+
};
|
|
40
|
+
/** Directional face — a white chevron (only on the active button; siblings stay blank). */
|
|
41
|
+
const chevronFace = (pos, active, size) => {
|
|
42
|
+
if (!active)
|
|
43
|
+
return null;
|
|
44
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
45
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
46
|
+
svg.setAttribute("width", String(Math.round(size * 0.62)));
|
|
47
|
+
svg.setAttribute("height", String(Math.round(size * 0.62)));
|
|
48
|
+
svg.setAttribute("class", "rydr-ui-pip-glyph");
|
|
49
|
+
svg.style.transform = `rotate(${ROT[pos]}deg)`;
|
|
50
|
+
svg.style.color = "#ffffff"; // white icon on the steel pip
|
|
51
|
+
svg.innerHTML = CHEVRON;
|
|
52
|
+
return svg;
|
|
53
|
+
};
|
|
54
|
+
/** Make one keycap pip (a circular button). Active pips also carry cooldown scaffolding. */
|
|
55
|
+
function makePip(active, color, pos, size, animate, face) {
|
|
56
|
+
const pip = document.createElement("div");
|
|
57
|
+
pip.className = "rydr-ui-pip" + (active ? " active" : "") + (active && animate ? " anim" : "");
|
|
58
|
+
pip.style.setProperty("--c", active ? color : INACTIVE);
|
|
59
|
+
pip.style.width = pip.style.height = `${size}px`;
|
|
60
|
+
const content = face(pos, active, size);
|
|
61
|
+
if (content)
|
|
62
|
+
pip.appendChild(content);
|
|
63
|
+
if (active) {
|
|
64
|
+
const ring = document.createElement("div"); // cooldown — a circular loader inside the rim
|
|
65
|
+
ring.className = "rydr-ui-ring";
|
|
66
|
+
pip.appendChild(ring);
|
|
67
|
+
const num = document.createElement("span"); // cooldown — centred countdown number
|
|
68
|
+
num.className = "rydr-ui-cd-num";
|
|
69
|
+
pip.appendChild(num);
|
|
70
|
+
}
|
|
71
|
+
return pip;
|
|
72
|
+
}
|
|
73
|
+
/** Build the keycap for `activePos`; returns the element + a reference to the active pip (for state). */
|
|
74
|
+
function buildPips(activePos, variant, colored, animate, face, accent) {
|
|
75
|
+
const diamond = document.createElement("div");
|
|
76
|
+
diamond.className = "rydr-ui-diamond";
|
|
77
|
+
// `accent` (used by the directional keycap) overrides the per-button hue with a fixed tone.
|
|
78
|
+
const activeColor = accent ?? (colored ? COLOR[activePos] : NEUTRAL_ACTIVE);
|
|
79
|
+
if (variant === "solo") {
|
|
80
|
+
diamond.style.width = diamond.style.height = `${PIP_ACTIVE}px`;
|
|
81
|
+
const pip = makePip(true, activeColor, activePos, PIP_ACTIVE, animate, face);
|
|
82
|
+
pip.style.left = pip.style.top = "0px";
|
|
83
|
+
diamond.appendChild(pip);
|
|
84
|
+
return { el: diamond, activePip: pip };
|
|
85
|
+
}
|
|
86
|
+
const ri = PIP / 2, Ra = PIP_ACTIVE / 2, r0 = Ra, D = r0 + ri;
|
|
87
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
88
|
+
const pts = {};
|
|
89
|
+
for (const k of ["top", "lft", "rgt", "bot"]) {
|
|
90
|
+
const active = k === activePos, r = active ? Ra : ri, dist = active ? D : r0;
|
|
91
|
+
const cx = DIR[k][0] * dist, cy = DIR[k][1] * dist;
|
|
92
|
+
pts[k] = { cx, cy, r, active };
|
|
93
|
+
minX = Math.min(minX, cx - r);
|
|
94
|
+
maxX = Math.max(maxX, cx + r);
|
|
95
|
+
minY = Math.min(minY, cy - r);
|
|
96
|
+
maxY = Math.max(maxY, cy + r);
|
|
97
|
+
}
|
|
98
|
+
diamond.style.width = `${maxX - minX}px`;
|
|
99
|
+
diamond.style.height = `${maxY - minY}px`;
|
|
100
|
+
let activePip;
|
|
101
|
+
for (const k of ["top", "lft", "rgt", "bot"]) {
|
|
102
|
+
const pt = pts[k];
|
|
103
|
+
const pip = makePip(pt.active, pt.active ? activeColor : (colored ? COLOR[k] : NEUTRAL_ACTIVE), k, pt.r * 2, animate, face);
|
|
104
|
+
pip.style.left = `${pt.cx - pt.r - minX}px`;
|
|
105
|
+
pip.style.top = `${pt.cy - pt.r - minY}px`;
|
|
106
|
+
diamond.appendChild(pip);
|
|
107
|
+
if (pt.active)
|
|
108
|
+
activePip = pip;
|
|
109
|
+
}
|
|
110
|
+
return { el: diamond, activePip };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Build the keycap + its state setters. Shared core for both the A/B/Y/Z (`createKeycap`) and the
|
|
114
|
+
* directional (`createDpadKeycap`) factories — they differ only in the `face` drawn and the button
|
|
115
|
+
* name (`pressName`) the press-wiring listens for.
|
|
116
|
+
*/
|
|
117
|
+
function build(activePos, pressName, face, opts, accent) {
|
|
118
|
+
injectCss();
|
|
119
|
+
const variant = opts.variant ?? "solo";
|
|
120
|
+
const animate = opts.animate ?? true;
|
|
121
|
+
const colored = opts.colored ?? true;
|
|
122
|
+
const { el, activePip } = buildPips(activePos, variant, colored, animate, face, accent);
|
|
123
|
+
const num = activePip.querySelector(".rydr-ui-cd-num");
|
|
124
|
+
const setPressed = (on) => activePip.classList.toggle("pressed", on);
|
|
125
|
+
// wire to real controller input: pressed while THIS button is held (down → pressed, up → released)
|
|
126
|
+
const off = opts.press?.onButton((e) => { if (e.name === pressName)
|
|
127
|
+
setPressed(e.edge === "down"); });
|
|
128
|
+
return {
|
|
129
|
+
el,
|
|
130
|
+
setPressed,
|
|
131
|
+
setAnimated: (on) => activePip.classList.toggle("anim", on),
|
|
132
|
+
setColored: (on) => activePip.style.setProperty("--c", accent ?? (on ? COLOR[activePos] : NEUTRAL_ACTIVE)),
|
|
133
|
+
setDisabled: (on) => activePip.classList.toggle("disabled", on),
|
|
134
|
+
setVisible: (on) => { el.style.visibility = on ? "" : "hidden"; },
|
|
135
|
+
setCooldown: (frac, label) => {
|
|
136
|
+
if (frac == null) {
|
|
137
|
+
activePip.classList.remove("cooling");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
activePip.classList.add("cooling");
|
|
141
|
+
activePip.style.setProperty("--cd", String(Math.max(0, Math.min(1, frac))));
|
|
142
|
+
if (num)
|
|
143
|
+
num.textContent = label == null ? "" : String(label);
|
|
144
|
+
},
|
|
145
|
+
dispose: () => { off?.(); el.remove(); },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** Build a bare A/B/Y/Z keycap with state setters. */
|
|
149
|
+
export function createKeycap(button, opts = {}) {
|
|
150
|
+
return build(POS_OF[button], button, letterFace, opts);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Build a bare directional (UP/DOWN/LEFT/RIGHT) keycap. Identical state machine to {@link createKeycap}
|
|
154
|
+
* — solo/full, pulse, pressed, cooldown ring — only the active button shows a WHITE chevron (rotated
|
|
155
|
+
* per direction) on a steel-grey pip, not the loud A/B/Y/Z hues.
|
|
156
|
+
*/
|
|
157
|
+
export function createDpadKeycap(dir, opts = {}) {
|
|
158
|
+
return build(POS_OF_DIR[dir], dir, chevronFace, opts, DPAD_TONE);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=keycap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycap.js","sourceRoot":"","sources":["../../src/ui/keycap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LABELED DIAMOND — a central A/B/Y/Z pip cluster where ANY number of buttons can be active at once,
|
|
3
|
+
* each active button carrying a label that radiates outward from its position (Y above · B below ·
|
|
4
|
+
* Z to the left, right-aligned · A to the right, left-aligned). Where the {@link mountActionDiamond}
|
|
5
|
+
* is a HUD of four big move cards, this is the compact "here are the actions on offer" affordance —
|
|
6
|
+
* e.g. an inspect target showing `Y Inspecter · A Équiper · B Inventaire` around the button glyphs.
|
|
7
|
+
*
|
|
8
|
+
* Each label is seated in the shared dark glass {@link Card} so it stays legible on any background.
|
|
9
|
+
* Fixed-overlay by default (call `setScreenPos` to track a projected world point), or `inline:true`
|
|
10
|
+
* to lay it in normal flow.
|
|
11
|
+
*/
|
|
12
|
+
import { type ButtonLetter } from "./keycap.js";
|
|
13
|
+
export interface LabeledDiamondOptions {
|
|
14
|
+
/** Lay the diamond inline (normal flow, full width) instead of as a fixed overlay. */
|
|
15
|
+
inline?: boolean;
|
|
16
|
+
/** Initial active labels — a label string activates that button; omit a button to leave it greyed. */
|
|
17
|
+
actions?: Partial<Record<ButtonLetter, string>>;
|
|
18
|
+
}
|
|
19
|
+
export interface LabeledDiamond {
|
|
20
|
+
root: HTMLElement;
|
|
21
|
+
/** Activate a button with a radiating label, or pass `null`/`""` to deactivate it (pip greys, label hides). */
|
|
22
|
+
setLabel(button: ButtonLetter, text: string | null): void;
|
|
23
|
+
/** Disable an active action: its pip goes inert grey and its label fades back. */
|
|
24
|
+
setDisabled(button: ButtonLetter, on: boolean): void;
|
|
25
|
+
setVisible(v: boolean): void;
|
|
26
|
+
/** Centre the diamond at a viewport pixel (e.g. a target projected to screen). */
|
|
27
|
+
setScreenPos(x: number, y: number): void;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
}
|
|
30
|
+
/** Mount a labeled A/B/Y/Z diamond: a pip cluster with a card-seated label radiating from each active button. */
|
|
31
|
+
export declare function mountLabeledDiamond(host: HTMLElement, opts?: LabeledDiamondOptions): LabeledDiamond;
|
|
32
|
+
//# sourceMappingURL=labeled-diamond.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labeled-diamond.d.ts","sourceRoot":"","sources":["../../src/ui/labeled-diamond.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAUhD,MAAM,WAAW,qBAAqB;IACpC,sFAAsF;IACtF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sGAAsG;IACtG,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,+GAA+G;IAC/G,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1D,kFAAkF;IAClF,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IACrD,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,kFAAkF;IAClF,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,qBAA0B,GAAG,cAAc,CAsEvG"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LABELED DIAMOND — a central A/B/Y/Z pip cluster where ANY number of buttons can be active at once,
|
|
3
|
+
* each active button carrying a label that radiates outward from its position (Y above · B below ·
|
|
4
|
+
* Z to the left, right-aligned · A to the right, left-aligned). Where the {@link mountActionDiamond}
|
|
5
|
+
* is a HUD of four big move cards, this is the compact "here are the actions on offer" affordance —
|
|
6
|
+
* e.g. an inspect target showing `Y Inspecter · A Équiper · B Inventaire` around the button glyphs.
|
|
7
|
+
*
|
|
8
|
+
* Each label is seated in the shared dark glass {@link Card} so it stays legible on any background.
|
|
9
|
+
* Fixed-overlay by default (call `setScreenPos` to track a projected world point), or `inline:true`
|
|
10
|
+
* to lay it in normal flow.
|
|
11
|
+
*/
|
|
12
|
+
import { mountCard } from "./card.js";
|
|
13
|
+
import {} from "./keycap.js";
|
|
14
|
+
import { injectCss } from "./styles.js";
|
|
15
|
+
const POS = { Y: "top", A: "rgt", B: "bot", Z: "lft" };
|
|
16
|
+
/** Per-position hue — mirrors the keycap palette (Y cyan · A green · B magenta · Z orange). */
|
|
17
|
+
const COLOR = { top: "#4DD5FF", rgt: "#00C968", bot: "#FF79FF", lft: "#FF7A0C" };
|
|
18
|
+
const INACTIVE = "#3a424e"; // a button that isn't part of this menu (greyed, sunk)
|
|
19
|
+
const FACES = ["Y", "A", "B", "Z"];
|
|
20
|
+
/** Mount a labeled A/B/Y/Z diamond: a pip cluster with a card-seated label radiating from each active button. */
|
|
21
|
+
export function mountLabeledDiamond(host, opts = {}) {
|
|
22
|
+
injectCss();
|
|
23
|
+
const root = document.createElement("div");
|
|
24
|
+
root.className = "rydr-ui-ld-host" + (opts.inline ? " inline" : "");
|
|
25
|
+
const wrap = document.createElement("div");
|
|
26
|
+
wrap.className = "rydr-ui-ld-wrap";
|
|
27
|
+
root.appendChild(wrap);
|
|
28
|
+
// the central 4-pip cluster — every face button, active ones lit + raised, the rest greyed + sunk
|
|
29
|
+
const cluster = document.createElement("div");
|
|
30
|
+
cluster.className = "rydr-ui-ld-cluster";
|
|
31
|
+
wrap.appendChild(cluster);
|
|
32
|
+
const pips = {};
|
|
33
|
+
const slots = {};
|
|
34
|
+
const cards = {};
|
|
35
|
+
for (const b of FACES) {
|
|
36
|
+
const pos = POS[b];
|
|
37
|
+
const pip = document.createElement("div");
|
|
38
|
+
pip.className = `rydr-ui-pip pos-${pos}`;
|
|
39
|
+
pip.style.setProperty("--c", INACTIVE);
|
|
40
|
+
pip.style.opacity = "0.5";
|
|
41
|
+
const lt = document.createElement("span");
|
|
42
|
+
lt.className = "rydr-ui-pip-letter";
|
|
43
|
+
lt.style.fontSize = "18px";
|
|
44
|
+
lt.textContent = b;
|
|
45
|
+
pip.appendChild(lt);
|
|
46
|
+
cluster.appendChild(pip);
|
|
47
|
+
pips[b] = pip;
|
|
48
|
+
// one outer label cell per button (pre-created, shown only while the button is active), each
|
|
49
|
+
// seated in the dark glass card so it reads on any background
|
|
50
|
+
const slot = document.createElement("div");
|
|
51
|
+
slot.className = `rydr-ui-ld-lbl rydr-ui-ld-lbl-${pos}`;
|
|
52
|
+
slot.style.display = "none";
|
|
53
|
+
cards[b] = mountCard(slot, { content: "", inline: true });
|
|
54
|
+
wrap.appendChild(slot);
|
|
55
|
+
slots[b] = slot;
|
|
56
|
+
}
|
|
57
|
+
host.appendChild(root);
|
|
58
|
+
const setLabel = (button, text) => {
|
|
59
|
+
const on = text != null && text !== "";
|
|
60
|
+
pips[button].classList.toggle("active", on);
|
|
61
|
+
if (!on)
|
|
62
|
+
pips[button].classList.remove("disabled"); // a deactivated button can't stay "disabled"
|
|
63
|
+
pips[button].style.setProperty("--c", on ? COLOR[POS[button]] : INACTIVE);
|
|
64
|
+
pips[button].style.opacity = on ? "1" : "0.5";
|
|
65
|
+
slots[button].style.display = on ? "" : "none";
|
|
66
|
+
if (on)
|
|
67
|
+
cards[button].setLabel(text);
|
|
68
|
+
};
|
|
69
|
+
for (const b of FACES)
|
|
70
|
+
setLabel(b, opts.actions?.[b] ?? null);
|
|
71
|
+
return {
|
|
72
|
+
root,
|
|
73
|
+
setLabel,
|
|
74
|
+
setDisabled: (button, on) => {
|
|
75
|
+
pips[button].classList.toggle("disabled", on);
|
|
76
|
+
slots[button].classList.toggle("rydr-ui-ld-lbl-off", on);
|
|
77
|
+
},
|
|
78
|
+
setVisible: (v) => { root.style.visibility = v ? "" : "hidden"; },
|
|
79
|
+
setScreenPos(x, y) {
|
|
80
|
+
root.style.left = `${x}px`;
|
|
81
|
+
root.style.top = `${y}px`;
|
|
82
|
+
},
|
|
83
|
+
dispose: () => root.remove(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=labeled-diamond.js.map
|