kaplay-plugin-ui 1.2.1 → 1.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/README.md +67 -0
- package/dist/plugin.cjs +291 -8
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +49 -2
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +49 -2
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +291 -8
- package/dist/plugin.mjs.map +1 -1
- package/dist/plugin.umd.js +291 -8
- package/dist/plugin.umd.js.map +1 -1
- package/dist/plugin.umd.min.js +3 -1
- package/dist/plugin.umd.min.js.map +1 -1
- package/global.d.ts +23 -0
- package/package.json +1 -1
package/dist/plugin.d.mts
CHANGED
|
@@ -6,15 +6,27 @@ declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: But
|
|
|
6
6
|
hoverColor?: string | Color;
|
|
7
7
|
pressedColor?: string | Color;
|
|
8
8
|
textColor?: string | Color;
|
|
9
|
-
}): import("kaplay").GameObjRaw & import("kaplay").MergeComps<import("kaplay").
|
|
9
|
+
}): import("kaplay").GameObjRaw & import("kaplay").MergeComps<import("kaplay").PosComp | import("kaplay").RectComp | import("kaplay").ColorComp | import("kaplay").AnchorComp | import("kaplay").AreaComp | import("kaplay").ScaleComp> & {
|
|
10
10
|
background: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
|
|
11
11
|
icon: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
|
|
12
12
|
label: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").ColorComp | import("kaplay").AnchorComp | import("kaplay").TextComp>;
|
|
13
|
+
} & {
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
};
|
|
16
|
+
interface ModalHooks {
|
|
17
|
+
isTopmost: (modal: GameObj) => boolean;
|
|
18
|
+
onClose: (modal: GameObj) => void;
|
|
19
|
+
onOpen: (modal: GameObj) => void;
|
|
20
|
+
}
|
|
21
|
+
declare function createModal(k: KAPLAYCtx, theme: Theme, opt: ModalOpt, hooks: ModalHooks): import("kaplay").GameObjRaw & import("kaplay").MergeComps<import("kaplay").PosComp | import("kaplay").FixedComp | import("kaplay").ZComp> & {
|
|
22
|
+
actions: GameObj<import("kaplay").PosComp>;
|
|
23
|
+
content: GameObj<import("kaplay").PosComp | import("kaplay").RectComp | import("kaplay").ColorComp | import("kaplay").AnchorComp | import("kaplay").AreaComp>;
|
|
13
24
|
};
|
|
14
25
|
type SpriteSource = string | SpriteData | Asset<SpriteData>;
|
|
15
26
|
declare module 'kaplay' {
|
|
16
27
|
interface KAPLAYCtx {
|
|
17
28
|
addButton: AddButton;
|
|
29
|
+
addModal: AddModal;
|
|
18
30
|
getTheme: GetTheme;
|
|
19
31
|
setTheme: SetTheme;
|
|
20
32
|
}
|
|
@@ -27,6 +39,7 @@ type AddButton = (text?: string, opt?: ButtonOpt & {
|
|
|
27
39
|
pressedColor?: string | Color;
|
|
28
40
|
textColor?: string | Color;
|
|
29
41
|
}) => ReturnType<typeof createButton>;
|
|
42
|
+
type AddModal = (opt?: ModalOpt) => ReturnType<typeof createModal>;
|
|
30
43
|
type GetTheme = () => Theme;
|
|
31
44
|
type SetTheme = (theme: DeepPartial<Theme>) => void;
|
|
32
45
|
/** Recursively partial version of a type. */
|
|
@@ -65,6 +78,11 @@ interface Theme {
|
|
|
65
78
|
modal: {
|
|
66
79
|
backdropOpacity: number;
|
|
67
80
|
borderRadius: number;
|
|
81
|
+
gap: number;
|
|
82
|
+
maxWidth: number;
|
|
83
|
+
minWidth: number;
|
|
84
|
+
paddingX: number;
|
|
85
|
+
paddingY: number;
|
|
68
86
|
};
|
|
69
87
|
tooltip: {
|
|
70
88
|
background: string | Color;
|
|
@@ -73,6 +91,26 @@ interface Theme {
|
|
|
73
91
|
};
|
|
74
92
|
};
|
|
75
93
|
}
|
|
94
|
+
/** Modal options. */
|
|
95
|
+
interface ModalOpt {
|
|
96
|
+
title?: string;
|
|
97
|
+
text?: string;
|
|
98
|
+
width?: number;
|
|
99
|
+
height?: number;
|
|
100
|
+
actions?: ModalAction[];
|
|
101
|
+
/** Allow closing with the backdrop or Escape. Defaults to true. */
|
|
102
|
+
dismissible?: boolean;
|
|
103
|
+
/** Z index of the modal. Defaults to 1000. */
|
|
104
|
+
z?: number;
|
|
105
|
+
}
|
|
106
|
+
/** Modal action. */
|
|
107
|
+
interface ModalAction {
|
|
108
|
+
text: string;
|
|
109
|
+
/** Runs after the modal closes, unless `close` is false. */
|
|
110
|
+
onClick?: () => void;
|
|
111
|
+
/** Close the modal after the action. Defaults to true. */
|
|
112
|
+
close?: boolean;
|
|
113
|
+
}
|
|
76
114
|
/** Button options. */
|
|
77
115
|
interface ButtonOpt {
|
|
78
116
|
width?: number;
|
|
@@ -98,10 +136,19 @@ interface ButtonOpt {
|
|
|
98
136
|
hoverBackground?: SpriteSource;
|
|
99
137
|
/** Sprite drawn behind the label when pressed. */
|
|
100
138
|
pressedBackground?: SpriteSource;
|
|
139
|
+
/** Disable the button. Greys it out and ignores click/hover. */
|
|
140
|
+
disabled?: boolean;
|
|
101
141
|
}
|
|
102
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Kaplay UI plugin.
|
|
144
|
+
*
|
|
145
|
+
* @param k - The Kaplay context.
|
|
146
|
+
* @param theme - The theme config.
|
|
147
|
+
* @returns The plugin methods.
|
|
148
|
+
*/
|
|
103
149
|
export declare function uiPlugin(k: KAPLAYCtx, theme?: DeepPartial<Theme>): {
|
|
104
150
|
addButton: AddButton;
|
|
151
|
+
addModal: AddModal;
|
|
105
152
|
setTheme: SetTheme;
|
|
106
153
|
getTheme: GetTheme;
|
|
107
154
|
};
|
package/dist/plugin.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/button.ts","../src/types.ts","../src/plugin.ts"],"mappings":";iBAKgB,aACd,GAAG,WACH,OAAO,OACP,cACA,KAAK;EACH,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;qBACtB,8BAAA,4BAAA,
|
|
1
|
+
{"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/button.ts","../src/modal.ts","../src/types.ts","../src/plugin.ts"],"mappings":";iBAKgB,aACd,GAAG,WACH,OAAO,OACP,cACA,KAAK;EACH,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;qBACtB,8BAAA,4BAAA,2BAAA,4BAAA,6BAAA,8BAAA,4BAAA;;;;;EA8OyC;;UChP3B;EACf,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,SAAS,OAAO;;iBAQF,YACd,GAAG,WACH,OAAO,OACP,KAAK,UACL,OAAO,8BAAU,8BAAA,4BAAA,2BAAA,6BAAA;;;;KCxBd,wBAAwB,aAAa,MAAM;;YAGpC;IACR,WAAW;IACX,UAAU;IACV,UAAU;IACV,UAAU;;;KAIF,aACV,eACA,MAAM;EACJ,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;MAEpB,kBAAkB;KAEX,YAAY,MAAM,aAAa,kBAAkB;KAEjD,iBAAiB;KAEjB,YAAY,OAAO,YAAY;;KAG/B,YAAY,QACrB,WAAW,KAAK,EAAE,oBAAoB,YAAY,EAAE,MAAM,EAAE;;UAI9C;EACf;IACE,kBAAkB;IAClB,oBAAoB;IACpB,qBAAqB;IACrB,kBAAkB;IAClB,eAAe;IACf,wBAAwB;IACxB,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;IACnB,gBAAgB;;EAElB;IACE;IACA;IACA;IACA;;EAEF;IACE;;EAEF;EACA;IACE;MACE;MACA;MACA;MACA;;IAEF;MACE;MACA;MACA;MACA;MACA;MACA;MACA;;IAEF;MACE,qBAAqB;MACrB,oBAAoB;MACpB;;;;;UAMW;EACf;EACA;EACA;EACA;EACA,UAAU;;EAEV;;EAEA;;;UAIe;EACf;;EAEA;;EAEA;;;UAIe;EACf;EACA;;EAEA,SAAS;EACT;EACA;EACA,gBAAgB;EAChB,qBAAqB;EACrB;EACA,qBAAqB;EACrB,uBAAuB;;EAEvB,OAAO;;EAEP,YAAY;;EAEZ,cAAc;;EAEd,aAAa;;EAEb,kBAAkB;;EAElB,oBAAoB;;EAEpB;;;;;;;;;wBClHc,SAAS,GAAG,WAAW,QAAO,YAAY"}
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,6 +1,229 @@
|
|
|
1
|
+
// src/modal.ts
|
|
2
|
+
/** Tag applied to modal game objects. */
|
|
3
|
+
const MODAL_TAG = uniqueTag("modal");
|
|
4
|
+
function isSized(obj) {
|
|
5
|
+
const sized = obj;
|
|
6
|
+
return typeof sized.width === "number" && typeof sized.height === "number";
|
|
7
|
+
}
|
|
8
|
+
function createModal(k, theme, opt, hooks) {
|
|
9
|
+
const { backdropOpacity, borderRadius, gap, maxWidth, minWidth, paddingX, paddingY } = theme.components.modal;
|
|
10
|
+
const fontSize = theme.typography.fontSize;
|
|
11
|
+
const titleSize = Math.round(fontSize * 1.25);
|
|
12
|
+
const font = theme.typography.fontFamily;
|
|
13
|
+
const dismissible = opt.dismissible ?? true;
|
|
14
|
+
const modal = k.add([
|
|
15
|
+
k.pos(k.center()),
|
|
16
|
+
k.fixed(),
|
|
17
|
+
k.z(opt.z ?? 1e3),
|
|
18
|
+
MODAL_TAG
|
|
19
|
+
]);
|
|
20
|
+
modal.onDestroy(() => {
|
|
21
|
+
hooks.onClose(modal);
|
|
22
|
+
});
|
|
23
|
+
hooks.onOpen(modal);
|
|
24
|
+
const backdrop = modal.add([
|
|
25
|
+
k.rect(k.width(), k.height()),
|
|
26
|
+
k.pos(),
|
|
27
|
+
k.color(0, 0, 0),
|
|
28
|
+
k.opacity(backdropOpacity),
|
|
29
|
+
k.anchor("center"),
|
|
30
|
+
k.area()
|
|
31
|
+
]);
|
|
32
|
+
const content = modal.add([
|
|
33
|
+
k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : void 0),
|
|
34
|
+
k.pos(),
|
|
35
|
+
k.color(toColor(k, theme.palette.surface)),
|
|
36
|
+
k.anchor("center"),
|
|
37
|
+
k.area()
|
|
38
|
+
]);
|
|
39
|
+
const actions = content.add([k.pos()]);
|
|
40
|
+
for (const action of opt.actions ?? []) createButton(k, theme, action.text, {
|
|
41
|
+
parent: actions,
|
|
42
|
+
onClick: () => {
|
|
43
|
+
if (action.close !== false) modal.destroy();
|
|
44
|
+
action.onClick?.();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const titleMeasure = opt.title ? k.text(opt.title, {
|
|
48
|
+
size: titleSize,
|
|
49
|
+
font
|
|
50
|
+
}) : void 0;
|
|
51
|
+
const bodyMeasure = opt.text ? k.text(opt.text, {
|
|
52
|
+
size: fontSize,
|
|
53
|
+
font
|
|
54
|
+
}) : void 0;
|
|
55
|
+
const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);
|
|
56
|
+
const title = opt.title ? content.add([
|
|
57
|
+
k.text(opt.title, {
|
|
58
|
+
size: titleSize,
|
|
59
|
+
font,
|
|
60
|
+
width: textWrapWidth > 0 ? textWrapWidth : void 0,
|
|
61
|
+
align: "center"
|
|
62
|
+
}),
|
|
63
|
+
k.pos(),
|
|
64
|
+
k.color(toColor(k, theme.palette.text)),
|
|
65
|
+
k.anchor("center")
|
|
66
|
+
]) : void 0;
|
|
67
|
+
const body = opt.text ? content.add([
|
|
68
|
+
k.text(opt.text, {
|
|
69
|
+
size: fontSize,
|
|
70
|
+
font,
|
|
71
|
+
width: textWrapWidth > 0 ? textWrapWidth : void 0,
|
|
72
|
+
align: "center"
|
|
73
|
+
}),
|
|
74
|
+
k.pos(),
|
|
75
|
+
k.color(toColor(k, theme.palette.textSecondary)),
|
|
76
|
+
k.anchor("center")
|
|
77
|
+
]) : void 0;
|
|
78
|
+
/**
|
|
79
|
+
* Update modal layout:
|
|
80
|
+
*
|
|
81
|
+
* - fits the backdrop to the screen,
|
|
82
|
+
* - sizes the content to its title, text, and actions,
|
|
83
|
+
* - repositions the title, text, and actions.
|
|
84
|
+
*/
|
|
85
|
+
function layout() {
|
|
86
|
+
const screenWidth = k.width();
|
|
87
|
+
const screenHeight = k.height();
|
|
88
|
+
if (backdrop.width !== screenWidth) backdrop.width = screenWidth;
|
|
89
|
+
if (backdrop.height !== screenHeight) backdrop.height = screenHeight;
|
|
90
|
+
modal.pos = k.center();
|
|
91
|
+
const buttons = actions.children.filter(isSized);
|
|
92
|
+
const actionsWidth = buttons.reduce((width, button) => width + button.width, 0) + gap * Math.max(0, buttons.length - 1);
|
|
93
|
+
const actionsHeight = Math.max(0, ...buttons.map((button) => button.height));
|
|
94
|
+
const textContentWidth = Math.max(titleMeasure?.width ?? 0, bodyMeasure?.width ?? 0);
|
|
95
|
+
const nextWidth = opt.width ?? Math.max(Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2, actionsWidth + paddingX * 2, minWidth);
|
|
96
|
+
const titleHeight = title?.height ?? 0;
|
|
97
|
+
const bodyHeight = body?.height ?? 0;
|
|
98
|
+
const hasTitle = title !== void 0;
|
|
99
|
+
const hasBody = body !== void 0;
|
|
100
|
+
const hasActions = buttons.length > 0;
|
|
101
|
+
const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);
|
|
102
|
+
const contentHeight = titleHeight + bodyHeight + actionsHeight + gap * Math.max(0, blockCount - 1);
|
|
103
|
+
const nextHeight = opt.height ?? contentHeight + paddingY * 2;
|
|
104
|
+
if (content.width !== nextWidth) content.width = nextWidth;
|
|
105
|
+
if (content.height !== nextHeight) content.height = nextHeight;
|
|
106
|
+
let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;
|
|
107
|
+
if (title) {
|
|
108
|
+
title.pos = k.vec2(0, y + title.height / 2);
|
|
109
|
+
y += title.height + gap;
|
|
110
|
+
}
|
|
111
|
+
if (body) {
|
|
112
|
+
body.pos = k.vec2(0, y + body.height / 2);
|
|
113
|
+
y += body.height + gap;
|
|
114
|
+
}
|
|
115
|
+
if (hasActions) {
|
|
116
|
+
let x = -actionsWidth / 2;
|
|
117
|
+
for (const button of buttons) {
|
|
118
|
+
button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);
|
|
119
|
+
x += button.width + gap;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
layout();
|
|
124
|
+
modal.onUpdate(layout);
|
|
125
|
+
if (dismissible) {
|
|
126
|
+
k.wait(0, () => {
|
|
127
|
+
backdrop.onClick(() => {
|
|
128
|
+
if (!hooks.isTopmost(modal)) return;
|
|
129
|
+
if (content.hasPoint(k.mousePos())) return;
|
|
130
|
+
modal.destroy();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
modal.onKeyPress("escape", () => {
|
|
134
|
+
if (hooks.isTopmost(modal)) modal.destroy();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return Object.assign(modal, {
|
|
138
|
+
actions,
|
|
139
|
+
content
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function createModalManager(k) {
|
|
143
|
+
const modals = [];
|
|
144
|
+
let pausedScene;
|
|
145
|
+
let cancelPauseOnAdd;
|
|
146
|
+
const isInsideModal = (gameObj) => modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));
|
|
147
|
+
function pauseSceneTree(gameObj) {
|
|
148
|
+
if (isModal(gameObj) || isInsideModal(gameObj)) return;
|
|
149
|
+
pausedScene ??= /* @__PURE__ */ new Map();
|
|
150
|
+
// v8 ignore if
|
|
151
|
+
if (!pausedScene.has(gameObj)) pausedScene.set(gameObj, gameObj.paused);
|
|
152
|
+
setPaused(gameObj);
|
|
153
|
+
gameObj.children.forEach(pauseSceneTree);
|
|
154
|
+
}
|
|
155
|
+
function openModal(modal) {
|
|
156
|
+
k.setCursor("default");
|
|
157
|
+
if (modals.length > 0) setPaused(getLast(modals), true, true);
|
|
158
|
+
modals.push(modal);
|
|
159
|
+
if (modals.length === 1) {
|
|
160
|
+
pausedScene = /* @__PURE__ */ new Map();
|
|
161
|
+
k.getTreeRoot().children.forEach(pauseSceneTree);
|
|
162
|
+
const controller = k.onAdd((gameObj) => {
|
|
163
|
+
if (!isModal(gameObj) && !isInsideModal(gameObj)) pauseSceneTree(gameObj);
|
|
164
|
+
});
|
|
165
|
+
cancelPauseOnAdd = () => {
|
|
166
|
+
controller.cancel();
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function closeModal(modal) {
|
|
171
|
+
const index = modals.indexOf(modal);
|
|
172
|
+
if (index === -1) return;
|
|
173
|
+
const wasTopmost = index === modals.length - 1;
|
|
174
|
+
modals.splice(index, 1);
|
|
175
|
+
if (!modals.length) {
|
|
176
|
+
cancelPauseOnAdd?.();
|
|
177
|
+
cancelPauseOnAdd = void 0;
|
|
178
|
+
// v8 ignore if
|
|
179
|
+
if (pausedScene) {
|
|
180
|
+
for (const [gameObj, paused] of pausedScene) if (gameObj.exists()) setPaused(gameObj, paused);
|
|
181
|
+
pausedScene = void 0;
|
|
182
|
+
}
|
|
183
|
+
} else if (wasTopmost) setPaused(getLast(modals), false, true);
|
|
184
|
+
}
|
|
185
|
+
const isTopmost = (modal) => getLast(modals) === modal;
|
|
186
|
+
return {
|
|
187
|
+
openModal,
|
|
188
|
+
closeModal,
|
|
189
|
+
isTopmost,
|
|
190
|
+
isInsideModal
|
|
191
|
+
};
|
|
192
|
+
}
|
|
1
193
|
// src/utils.ts
|
|
2
194
|
/**
|
|
3
|
-
*
|
|
195
|
+
* Get the last item in an array.
|
|
196
|
+
*
|
|
197
|
+
* @param items - The items.
|
|
198
|
+
* @returns The last item.
|
|
199
|
+
*/
|
|
200
|
+
function getLast(items) {
|
|
201
|
+
return items[items.length - 1];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Check if the game object is a modal.
|
|
205
|
+
*
|
|
206
|
+
* @param gameObj - The game object.
|
|
207
|
+
* @returns Whether game object is a modal.
|
|
208
|
+
*/
|
|
209
|
+
function isModal(gameObj) {
|
|
210
|
+
return gameObj.is(MODAL_TAG);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Pause or unpause game object(s).
|
|
214
|
+
*
|
|
215
|
+
* @param gameObj - The root game object.
|
|
216
|
+
* @param isPaused - Whether it should be paused.
|
|
217
|
+
* @param includeTree - Whether to pause all game objects in tree.
|
|
218
|
+
*/
|
|
219
|
+
function setPaused(gameObj, isPaused = true, includeTree = false) {
|
|
220
|
+
gameObj.paused = isPaused;
|
|
221
|
+
if (includeTree) gameObj.children.forEach((childGameObj) => {
|
|
222
|
+
setPaused(childGameObj, isPaused, includeTree);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Convert a hex string or color to color.
|
|
4
227
|
*
|
|
5
228
|
* @param k - The Kaplay context.
|
|
6
229
|
* @param color - The hex string or color.
|
|
@@ -9,11 +232,25 @@
|
|
|
9
232
|
function toColor(k, color) {
|
|
10
233
|
return typeof color === "string" ? k.rgb(color) : color;
|
|
11
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Generate a unique tag.
|
|
237
|
+
*
|
|
238
|
+
* @param id - The identifier.
|
|
239
|
+
* @returns The tag.
|
|
240
|
+
*/
|
|
241
|
+
function uniqueTag(id) {
|
|
242
|
+
return [
|
|
243
|
+
"kaplay-plugin-ui",
|
|
244
|
+
id,
|
|
245
|
+
crypto?.randomUUID?.()
|
|
246
|
+
].join(":");
|
|
247
|
+
}
|
|
12
248
|
// src/button.ts
|
|
13
249
|
function createButton(k, theme, text, opt) {
|
|
14
250
|
const radius = opt.radius ?? theme.shape.borderRadius;
|
|
15
251
|
const { minWidth, paddingX, paddingY } = theme.components.button;
|
|
16
252
|
const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);
|
|
253
|
+
let disabled = opt.disabled ?? false;
|
|
17
254
|
const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);
|
|
18
255
|
const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);
|
|
19
256
|
const textColor = toColor(k, opt.textColor ?? theme.palette.text);
|
|
@@ -24,14 +261,14 @@ function createButton(k, theme, text, opt) {
|
|
|
24
261
|
size,
|
|
25
262
|
font
|
|
26
263
|
});
|
|
27
|
-
const isIconOnly = opt.icon !== void 0 && text.length
|
|
264
|
+
const isIconOnly = opt.icon !== void 0 && !text.length;
|
|
28
265
|
const rectOpt = !(opt.background !== void 0) && !isIconOnly ? radius > 0 ? { radius } : void 0 : { fill: false };
|
|
29
266
|
const components = [
|
|
30
267
|
k.rect(0, 0, rectOpt),
|
|
31
268
|
k.pos(pos),
|
|
32
269
|
k.area(),
|
|
33
270
|
k.scale(),
|
|
34
|
-
k.color(baseColor),
|
|
271
|
+
k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),
|
|
35
272
|
k.anchor("center")
|
|
36
273
|
];
|
|
37
274
|
const button = opt.parent ? opt.parent.add(components) : k.add(components);
|
|
@@ -96,6 +333,7 @@ function createButton(k, theme, text, opt) {
|
|
|
96
333
|
layout();
|
|
97
334
|
if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
|
|
98
335
|
button.onClick(() => {
|
|
336
|
+
if (disabled) return;
|
|
99
337
|
k.wait(0, () => {
|
|
100
338
|
button.color = toColor(k, pressedColor);
|
|
101
339
|
if (icon && opt.pressedIcon) {
|
|
@@ -107,12 +345,18 @@ function createButton(k, theme, text, opt) {
|
|
|
107
345
|
background.sprite = opt.pressedBackground;
|
|
108
346
|
}
|
|
109
347
|
opt.onClick?.();
|
|
348
|
+
// v8 ignore if
|
|
349
|
+
if (disabled) k.setCursor("not-allowed");
|
|
350
|
+
else if (!button.isHovering()) k.setCursor("default");
|
|
110
351
|
});
|
|
111
352
|
});
|
|
112
353
|
button.onHover(() => {
|
|
354
|
+
if (disabled) {
|
|
355
|
+
k.setCursor("not-allowed");
|
|
356
|
+
return;
|
|
357
|
+
} else k.setCursor("pointer");
|
|
113
358
|
button.color = toColor(k, hoverColor);
|
|
114
359
|
button.scale = k.vec2(1.05, 1.05);
|
|
115
|
-
k.setCursor("pointer");
|
|
116
360
|
if (icon && opt.hoverIcon) {
|
|
117
361
|
iconState = "hover";
|
|
118
362
|
icon.sprite = opt.hoverIcon;
|
|
@@ -123,9 +367,10 @@ function createButton(k, theme, text, opt) {
|
|
|
123
367
|
}
|
|
124
368
|
});
|
|
125
369
|
button.onHoverEnd(() => {
|
|
370
|
+
k.setCursor("default");
|
|
371
|
+
if (disabled) return;
|
|
126
372
|
button.color = baseColor;
|
|
127
373
|
button.scale = k.vec2(1, 1);
|
|
128
|
-
k.setCursor("default");
|
|
129
374
|
if (icon && opt.icon) {
|
|
130
375
|
iconState = "base";
|
|
131
376
|
icon.sprite = opt.icon;
|
|
@@ -136,11 +381,29 @@ function createButton(k, theme, text, opt) {
|
|
|
136
381
|
}
|
|
137
382
|
if (icon || background) layout();
|
|
138
383
|
});
|
|
139
|
-
|
|
384
|
+
button.onDestroy(() => {
|
|
385
|
+
if (button.isHovering()) k.setCursor("default");
|
|
386
|
+
});
|
|
387
|
+
function applyDisabledState() {
|
|
388
|
+
button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;
|
|
389
|
+
}
|
|
390
|
+
applyDisabledState();
|
|
391
|
+
const assignedButton = Object.assign(button, {
|
|
140
392
|
background,
|
|
141
393
|
icon,
|
|
142
394
|
label
|
|
143
395
|
});
|
|
396
|
+
return Object.defineProperty(assignedButton, "disabled", {
|
|
397
|
+
get() {
|
|
398
|
+
return disabled;
|
|
399
|
+
},
|
|
400
|
+
set(value) {
|
|
401
|
+
disabled = value;
|
|
402
|
+
applyDisabledState();
|
|
403
|
+
},
|
|
404
|
+
enumerable: true,
|
|
405
|
+
configurable: true
|
|
406
|
+
});
|
|
144
407
|
}
|
|
145
408
|
// src/theme.ts
|
|
146
409
|
const PRIMARY = "#4f8cff";
|
|
@@ -176,7 +439,12 @@ const theme = {
|
|
|
176
439
|
},
|
|
177
440
|
modal: {
|
|
178
441
|
backdropOpacity: .5,
|
|
179
|
-
borderRadius: 8
|
|
442
|
+
borderRadius: 8,
|
|
443
|
+
gap: 16,
|
|
444
|
+
maxWidth: 480,
|
|
445
|
+
minWidth: 280,
|
|
446
|
+
paddingX: 24,
|
|
447
|
+
paddingY: 20
|
|
180
448
|
},
|
|
181
449
|
tooltip: {
|
|
182
450
|
background: "#111827",
|
|
@@ -218,7 +486,13 @@ function resolveTheme(input) {
|
|
|
218
486
|
};
|
|
219
487
|
}
|
|
220
488
|
// src/plugin.ts
|
|
221
|
-
/**
|
|
489
|
+
/**
|
|
490
|
+
* Kaplay UI plugin.
|
|
491
|
+
*
|
|
492
|
+
* @param k - The Kaplay context.
|
|
493
|
+
* @param theme - The theme config.
|
|
494
|
+
* @returns The plugin methods.
|
|
495
|
+
*/
|
|
222
496
|
function uiPlugin(k, theme = {}) {
|
|
223
497
|
let userTheme = theme;
|
|
224
498
|
let currentTheme = resolveTheme(userTheme);
|
|
@@ -233,8 +507,17 @@ function uiPlugin(k, theme = {}) {
|
|
|
233
507
|
const addButton = (text = "", opt = {}) => {
|
|
234
508
|
return createButton(k, currentTheme, text, opt);
|
|
235
509
|
};
|
|
510
|
+
const modalManager = createModalManager(k);
|
|
511
|
+
const addModal = (opt = {}) => {
|
|
512
|
+
return createModal(k, currentTheme, opt, {
|
|
513
|
+
isTopmost: modalManager.isTopmost,
|
|
514
|
+
onClose: modalManager.closeModal,
|
|
515
|
+
onOpen: modalManager.openModal
|
|
516
|
+
});
|
|
517
|
+
};
|
|
236
518
|
return {
|
|
237
519
|
addButton,
|
|
520
|
+
addModal,
|
|
238
521
|
setTheme,
|
|
239
522
|
getTheme
|
|
240
523
|
};
|
package/dist/plugin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","names":[],"sources":["../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { Color, KAPLAYCtx } from 'kaplay';\n\n/**\n * Convert hex string to color or return color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && text.length === 0;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n // prevent stray click from click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n });\n });\n\n button.onHover(() => {\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n k.setCursor('pointer');\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n return Object.assign(button, {\n background,\n icon,\n label,\n });\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/** KAPLAY UI plugin. */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n return {\n addButton,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore if\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n k.setCursor('default');\n\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore if\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n let disabled = opt.disabled ?? false;\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n if (disabled) {\n return;\n }\n\n // prevent click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n\n // v8 ignore if\n if (disabled) {\n k.setCursor('not-allowed');\n } else if (!button.isHovering()) {\n k.setCursor('default');\n }\n });\n });\n\n button.onHover(() => {\n if (disabled) {\n k.setCursor('not-allowed');\n return;\n } else {\n k.setCursor('pointer');\n }\n\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n k.setCursor('default');\n\n if (disabled) {\n return;\n }\n\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n button.onDestroy(() => {\n if (button.isHovering()) {\n k.setCursor('default');\n }\n });\n\n function applyDisabledState() {\n button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;\n }\n\n applyDisabledState();\n\n const assignedButton = Object.assign(button, {\n background,\n icon,\n label,\n });\n\n const definedButton = Object.defineProperty(assignedButton, 'disabled', {\n get() {\n return disabled;\n },\n set(value: boolean) {\n disabled = value;\n applyDisabledState();\n },\n enumerable: true,\n configurable: true,\n }) as typeof assignedButton & { disabled: boolean };\n\n return definedButton;\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
|