kaplay-plugin-ui 1.0.2 → 1.1.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 +52 -8
- package/dist/plugin.cjs +92 -11
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +17 -4
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +17 -4
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +92 -11
- package/dist/plugin.mjs.map +1 -1
- package/dist/plugin.umd.js +92 -11
- package/dist/plugin.umd.js.map +1 -1
- package/dist/plugin.umd.min.js +1 -1
- package/dist/plugin.umd.min.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
🦖 KAPLAY plugin UI (see [API](#api)):
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
11
|
+
- [addButton](#addButton)
|
|
12
|
+
- [getTheme](#getTheme)
|
|
13
|
+
- [setTheme](#setTheme)
|
|
14
14
|
|
|
15
15
|
## Prerequisites
|
|
16
16
|
|
|
@@ -86,22 +86,64 @@ k.addButton('Start', {
|
|
|
86
86
|
|
|
87
87
|
## API
|
|
88
88
|
|
|
89
|
-
###
|
|
89
|
+
### addButton
|
|
90
90
|
|
|
91
|
-
Add button
|
|
91
|
+
Add text button:
|
|
92
92
|
|
|
93
93
|
```ts
|
|
94
94
|
addButton(text, opt);
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
Buttons auto-size to their content. Width uses `components.button.paddingX` and `components.button.minWidth`; height uses `components.button.paddingY`. Pass `width` or `height` to override either dimension.
|
|
98
|
+
|
|
99
|
+
Add icon button with text:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
addButton('Play', {
|
|
103
|
+
icon: 'play',
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Add icon-only button:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
addButton('', {
|
|
111
|
+
icon: 'play',
|
|
112
|
+
hoverIcon: 'play-hover',
|
|
113
|
+
pressedIcon: 'play-pressed',
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Add image button with text:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
addButton('Play', {
|
|
121
|
+
background: 'panel',
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Add image-only button:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
addButton('', {
|
|
129
|
+
background: 'panel',
|
|
130
|
+
hoverBackground: 'panel-hover',
|
|
131
|
+
pressedBackground: 'panel-pressed',
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Icons use the theme's `spacing` as the gap between the icon and label. Icon-only and background-image buttons have a transparent background. Text is centered over a background image, which stretches to fit the button.
|
|
136
|
+
|
|
97
137
|
Update button text:
|
|
98
138
|
|
|
99
139
|
```ts
|
|
100
|
-
const button = addButton(
|
|
101
|
-
button.label.text = '
|
|
140
|
+
const button = addButton('Text');
|
|
141
|
+
button.label.text = 'New Text';
|
|
102
142
|
```
|
|
103
143
|
|
|
104
|
-
|
|
144
|
+
The button resizes automatically when the label, icon, or background image dimensions change.
|
|
145
|
+
|
|
146
|
+
### getTheme
|
|
105
147
|
|
|
106
148
|
Retrieve theme:
|
|
107
149
|
|
|
@@ -109,6 +151,8 @@ Retrieve theme:
|
|
|
109
151
|
getTheme();
|
|
110
152
|
```
|
|
111
153
|
|
|
154
|
+
### setTheme
|
|
155
|
+
|
|
112
156
|
Customize theme:
|
|
113
157
|
|
|
114
158
|
```ts
|
package/dist/plugin.cjs
CHANGED
|
@@ -12,9 +12,8 @@ function toColor(k, color) {
|
|
|
12
12
|
}
|
|
13
13
|
// src/button.ts
|
|
14
14
|
function createButton(k, theme, text, opt) {
|
|
15
|
-
const width = opt.width ?? theme.components.button.width;
|
|
16
|
-
const height = opt.height ?? theme.components.button.height;
|
|
17
15
|
const radius = opt.radius ?? theme.shape.borderRadius;
|
|
16
|
+
const { minWidth, paddingX, paddingY } = theme.components.button;
|
|
18
17
|
const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);
|
|
19
18
|
const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);
|
|
20
19
|
const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);
|
|
@@ -22,38 +21,123 @@ function createButton(k, theme, text, opt) {
|
|
|
22
21
|
const size = opt.size ?? theme.typography.fontSize;
|
|
23
22
|
const font = opt.font ?? theme.typography.fontFamily;
|
|
24
23
|
const pos = opt.pos ?? k.vec2();
|
|
24
|
+
const textComp = k.text(text, {
|
|
25
|
+
size,
|
|
26
|
+
font
|
|
27
|
+
});
|
|
28
|
+
const isIconOnly = opt.icon !== void 0 && text.length === 0;
|
|
29
|
+
const rectOpt = !(opt.background !== void 0) && !isIconOnly ? radius > 0 ? { radius } : void 0 : { fill: false };
|
|
25
30
|
const button = k.add([
|
|
26
|
-
k.rect(
|
|
31
|
+
k.rect(0, 0, rectOpt),
|
|
27
32
|
k.pos(pos),
|
|
28
33
|
k.area(),
|
|
29
34
|
k.scale(),
|
|
30
35
|
k.color(baseColor),
|
|
31
36
|
k.anchor("center")
|
|
32
37
|
]);
|
|
38
|
+
const background = opt.background ? button.add([
|
|
39
|
+
k.sprite(opt.background),
|
|
40
|
+
k.pos(),
|
|
41
|
+
k.anchor("center")
|
|
42
|
+
]) : void 0;
|
|
43
|
+
const icon = opt.icon ? button.add([
|
|
44
|
+
k.sprite(opt.icon),
|
|
45
|
+
k.pos(),
|
|
46
|
+
k.anchor("center")
|
|
47
|
+
]) : void 0;
|
|
33
48
|
const label = button.add([
|
|
34
|
-
|
|
35
|
-
size,
|
|
36
|
-
font
|
|
37
|
-
}),
|
|
49
|
+
textComp,
|
|
38
50
|
k.color(textColor),
|
|
51
|
+
k.pos(),
|
|
39
52
|
k.anchor("center")
|
|
40
53
|
]);
|
|
54
|
+
let iconWidth = 0;
|
|
55
|
+
let iconHeight = 0;
|
|
56
|
+
let iconState = "base";
|
|
57
|
+
let backgroundWidth = 0;
|
|
58
|
+
let backgroundHeight = 0;
|
|
59
|
+
let backgroundMeasured = false;
|
|
60
|
+
let backgroundState = "base";
|
|
61
|
+
/**
|
|
62
|
+
* Update button layout:
|
|
63
|
+
*
|
|
64
|
+
* - caches the base icon/background dimensions,
|
|
65
|
+
* - calculates content width/height,
|
|
66
|
+
* - updates `button.width` / `button.height`,
|
|
67
|
+
* - stretches the background and repositions the icon and label.
|
|
68
|
+
*/
|
|
69
|
+
function layout() {
|
|
70
|
+
if (icon && iconState === "base") {
|
|
71
|
+
iconWidth = icon.width;
|
|
72
|
+
iconHeight = icon.height;
|
|
73
|
+
}
|
|
74
|
+
if (background && backgroundState === "base" && !backgroundMeasured && (background.width > 0 || background.height > 0)) {
|
|
75
|
+
backgroundWidth = background.width;
|
|
76
|
+
backgroundHeight = background.height;
|
|
77
|
+
backgroundMeasured = true;
|
|
78
|
+
}
|
|
79
|
+
const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;
|
|
80
|
+
const contentWidth = iconWidth + gap + label.width;
|
|
81
|
+
const contentHeight = Math.max(iconHeight, label.height);
|
|
82
|
+
const contentBoxWidth = contentWidth + paddingX * 2;
|
|
83
|
+
const contentBoxHeight = contentHeight + paddingY * 2;
|
|
84
|
+
const nextWidth = opt.width ?? (background ? Math.max(contentBoxWidth, backgroundWidth) : Math.max(contentBoxWidth, minWidth));
|
|
85
|
+
const nextHeight = opt.height ?? (background ? Math.max(contentBoxHeight, backgroundHeight) : contentBoxHeight);
|
|
86
|
+
if (button.width !== nextWidth) button.width = nextWidth;
|
|
87
|
+
if (button.height !== nextHeight) button.height = nextHeight;
|
|
88
|
+
if (background && backgroundMeasured) {
|
|
89
|
+
background.width = nextWidth;
|
|
90
|
+
background.height = nextHeight;
|
|
91
|
+
background.pos = k.vec2();
|
|
92
|
+
}
|
|
93
|
+
if (icon) icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);
|
|
94
|
+
label.pos = k.vec2(-contentWidth / 2 + iconWidth + gap + label.width / 2, 0);
|
|
95
|
+
}
|
|
96
|
+
layout();
|
|
97
|
+
if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
|
|
41
98
|
button.onClick(() => {
|
|
42
99
|
button.color = toColor(k, pressedColor);
|
|
100
|
+
if (icon && opt.pressedIcon) {
|
|
101
|
+
iconState = "pressed";
|
|
102
|
+
icon.sprite = opt.pressedIcon;
|
|
103
|
+
}
|
|
104
|
+
if (background && opt.pressedBackground) {
|
|
105
|
+
backgroundState = "pressed";
|
|
106
|
+
background.sprite = opt.pressedBackground;
|
|
107
|
+
}
|
|
43
108
|
opt.onClick?.();
|
|
44
109
|
});
|
|
45
110
|
button.onHover(() => {
|
|
46
111
|
button.color = toColor(k, hoverColor);
|
|
47
112
|
button.scale = k.vec2(1.05, 1.05);
|
|
48
113
|
k.setCursor("pointer");
|
|
114
|
+
if (icon && opt.hoverIcon) {
|
|
115
|
+
iconState = "hover";
|
|
116
|
+
icon.sprite = opt.hoverIcon;
|
|
117
|
+
}
|
|
118
|
+
if (background && opt.hoverBackground) {
|
|
119
|
+
backgroundState = "hover";
|
|
120
|
+
background.sprite = opt.hoverBackground;
|
|
121
|
+
}
|
|
49
122
|
});
|
|
50
123
|
button.onHoverEnd(() => {
|
|
51
124
|
button.color = baseColor;
|
|
52
125
|
button.scale = k.vec2(1, 1);
|
|
53
126
|
k.setCursor("default");
|
|
127
|
+
if (icon && opt.icon) {
|
|
128
|
+
iconState = "base";
|
|
129
|
+
icon.sprite = opt.icon;
|
|
130
|
+
}
|
|
131
|
+
if (background && opt.background) {
|
|
132
|
+
backgroundState = "base";
|
|
133
|
+
background.sprite = opt.background;
|
|
134
|
+
}
|
|
135
|
+
if (icon || background) layout();
|
|
54
136
|
});
|
|
55
137
|
return {
|
|
56
138
|
...button,
|
|
139
|
+
background,
|
|
140
|
+
icon,
|
|
57
141
|
label
|
|
58
142
|
};
|
|
59
143
|
}
|
|
@@ -84,8 +168,6 @@ const theme = {
|
|
|
84
168
|
spacing: 8,
|
|
85
169
|
components: {
|
|
86
170
|
button: {
|
|
87
|
-
width: 120,
|
|
88
|
-
height: 40,
|
|
89
171
|
minWidth: 120,
|
|
90
172
|
paddingX: 12,
|
|
91
173
|
paddingY: 8,
|
|
@@ -121,8 +203,7 @@ function resolveTheme(input) {
|
|
|
121
203
|
components: {
|
|
122
204
|
button: {
|
|
123
205
|
...theme.components.button,
|
|
124
|
-
...input?.components?.button
|
|
125
|
-
width: input?.components?.button?.width ?? input?.components?.button?.minWidth ?? theme.components.button.width
|
|
206
|
+
...input?.components?.button
|
|
126
207
|
},
|
|
127
208
|
modal: {
|
|
128
209
|
...theme.components.modal,
|
package/dist/plugin.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs","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 width = opt.width ?? theme.components.button.width;\n const height = opt.height ?? theme.components.button.height;\n const radius = opt.radius ?? theme.shape.borderRadius;\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 button = k.add([\n k.rect(width, height, radius > 0 ? { radius } : undefined),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ]);\n\n const label = button.add([\n k.text(text, { size, font }),\n k.color(textColor as never),\n k.anchor('center'),\n ]);\n\n button.onClick(() => {\n button.color = toColor(k, pressedColor);\n opt.onClick?.();\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\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n });\n\n return {\n ...button,\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 width: 120,\n height: 40,\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 width:\n input?.components?.button?.width ??\n input?.components?.button?.minWidth ??\n theme.components.button.width,\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.cjs","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 button = k.add([\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\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 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 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 {\n ...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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"}
|
package/dist/plugin.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Color, KAPLAYCtx, Vec2 } from "kaplay";
|
|
1
|
+
import { Asset, Color, KAPLAYCtx, SpriteData, Vec2 } from "kaplay";
|
|
2
2
|
declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: ButtonOpt & {
|
|
3
3
|
pos?: Vec2;
|
|
4
4
|
onClick?: () => void;
|
|
@@ -7,7 +7,9 @@ declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: But
|
|
|
7
7
|
pressedColor?: string | Color;
|
|
8
8
|
textColor?: string | Color;
|
|
9
9
|
}): {
|
|
10
|
-
|
|
10
|
+
background: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
|
|
11
|
+
icon: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
|
|
12
|
+
label: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").ColorComp | import("kaplay").AnchorComp | import("kaplay").TextComp>;
|
|
11
13
|
add<T>(comps?: import("kaplay").CompList<T> | import("kaplay").GameObj<T>): import("kaplay").GameObj<T>;
|
|
12
14
|
readd<T>(obj: import("kaplay").GameObj<T>): import("kaplay").GameObj<T>;
|
|
13
15
|
remove(obj: import("kaplay").GameObj): void;
|
|
@@ -143,6 +145,7 @@ declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: But
|
|
|
143
145
|
color: Color;
|
|
144
146
|
anchor: import("kaplay").Anchor | Vec2;
|
|
145
147
|
};
|
|
148
|
+
type SpriteSource = string | SpriteData | Asset<SpriteData>;
|
|
146
149
|
declare module 'kaplay' {
|
|
147
150
|
interface KAPLAYCtx {
|
|
148
151
|
addButton: AddButton;
|
|
@@ -188,8 +191,6 @@ interface Theme {
|
|
|
188
191
|
spacing: number;
|
|
189
192
|
components: {
|
|
190
193
|
button: {
|
|
191
|
-
width: number;
|
|
192
|
-
height: number;
|
|
193
194
|
minWidth: number;
|
|
194
195
|
paddingX: number;
|
|
195
196
|
paddingY: number;
|
|
@@ -217,6 +218,18 @@ interface ButtonOpt {
|
|
|
217
218
|
radius?: number;
|
|
218
219
|
hoverFill?: string | Color;
|
|
219
220
|
pressedFill?: string | Color;
|
|
221
|
+
/** Sprite drawn beside the label. */
|
|
222
|
+
icon?: SpriteSource;
|
|
223
|
+
/** Sprite drawn beside the label on hover. */
|
|
224
|
+
hoverIcon?: SpriteSource;
|
|
225
|
+
/** Sprite drawn beside the label when pressed. */
|
|
226
|
+
pressedIcon?: SpriteSource;
|
|
227
|
+
/** Sprite drawn behind the label. */
|
|
228
|
+
background?: SpriteSource;
|
|
229
|
+
/** Sprite drawn behind the label on hover. */
|
|
230
|
+
hoverBackground?: SpriteSource;
|
|
231
|
+
/** Sprite drawn behind the label when pressed. */
|
|
232
|
+
pressedBackground?: SpriteSource;
|
|
220
233
|
}
|
|
221
234
|
/** KAPLAY UI plugin. */
|
|
222
235
|
export declare function uiPlugin(k: KAPLAYCtx, theme?: DeepPartial<Theme>): {
|
package/dist/plugin.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.cts","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
|
|
1
|
+
{"version":3,"file":"plugin.d.cts","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;;;;;;;;;;;;;;;;;;;;;;;;;;EA+LmntM,GAAA,wBAAA,mCAAA,YAAG,sCAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAA34hJ,sBAAA,KAAA,IAAA,sBAAA,SAAA,uBAAC,sCAAA;KAA8K,IAAA,sBAAA,SAAA,uBAAC,sCAAA;;;KAAsN,sBAAA,KAAA,IAAA,sBAAA,SAAA,uBAAC,+BAAA,oCAAA;KAAuM,IAAA,sBAAA,SAAA,uBAAC,sCAAA;;;;;;;;;;;;;;;;;;;;;;;;;KC1M90sD,wBAAwB,aAAa,MAAM;;YAGpC;IACR,WAAW;IACX,UAAU;IACV,UAAU;;;KAIF,aACV,eACA,MAAM;EACJ,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;MAEpB,kBAAkB;KAEX,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;;IAEF;MACE,qBAAqB;MACrB,oBAAoB;MACpB;;;;;UAMW;EACf;EACA;EACA;EACA;EACA,gBAAgB;EAChB,qBAAqB;EACrB;EACA,qBAAqB;EACrB,uBAAuB;;EAEvB,OAAO;;EAEP,YAAY;;EAEZ,cAAc;;EAEd,aAAa;;EAEb,kBAAkB;;EAElB,oBAAoB;;;wBCvFN,SAAS,GAAG,WAAW,QAAO,YAAY"}
|
package/dist/plugin.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Color, KAPLAYCtx, Vec2 } from "kaplay";
|
|
1
|
+
import { Asset, Color, KAPLAYCtx, SpriteData, Vec2 } from "kaplay";
|
|
2
2
|
declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: ButtonOpt & {
|
|
3
3
|
pos?: Vec2;
|
|
4
4
|
onClick?: () => void;
|
|
@@ -7,7 +7,9 @@ declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: But
|
|
|
7
7
|
pressedColor?: string | Color;
|
|
8
8
|
textColor?: string | Color;
|
|
9
9
|
}): {
|
|
10
|
-
|
|
10
|
+
background: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
|
|
11
|
+
icon: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
|
|
12
|
+
label: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").ColorComp | import("kaplay").AnchorComp | import("kaplay").TextComp>;
|
|
11
13
|
add<T>(comps?: import("kaplay").CompList<T> | import("kaplay").GameObj<T>): import("kaplay").GameObj<T>;
|
|
12
14
|
readd<T>(obj: import("kaplay").GameObj<T>): import("kaplay").GameObj<T>;
|
|
13
15
|
remove(obj: import("kaplay").GameObj): void;
|
|
@@ -143,6 +145,7 @@ declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: But
|
|
|
143
145
|
color: Color;
|
|
144
146
|
anchor: import("kaplay").Anchor | Vec2;
|
|
145
147
|
};
|
|
148
|
+
type SpriteSource = string | SpriteData | Asset<SpriteData>;
|
|
146
149
|
declare module 'kaplay' {
|
|
147
150
|
interface KAPLAYCtx {
|
|
148
151
|
addButton: AddButton;
|
|
@@ -188,8 +191,6 @@ interface Theme {
|
|
|
188
191
|
spacing: number;
|
|
189
192
|
components: {
|
|
190
193
|
button: {
|
|
191
|
-
width: number;
|
|
192
|
-
height: number;
|
|
193
194
|
minWidth: number;
|
|
194
195
|
paddingX: number;
|
|
195
196
|
paddingY: number;
|
|
@@ -217,6 +218,18 @@ interface ButtonOpt {
|
|
|
217
218
|
radius?: number;
|
|
218
219
|
hoverFill?: string | Color;
|
|
219
220
|
pressedFill?: string | Color;
|
|
221
|
+
/** Sprite drawn beside the label. */
|
|
222
|
+
icon?: SpriteSource;
|
|
223
|
+
/** Sprite drawn beside the label on hover. */
|
|
224
|
+
hoverIcon?: SpriteSource;
|
|
225
|
+
/** Sprite drawn beside the label when pressed. */
|
|
226
|
+
pressedIcon?: SpriteSource;
|
|
227
|
+
/** Sprite drawn behind the label. */
|
|
228
|
+
background?: SpriteSource;
|
|
229
|
+
/** Sprite drawn behind the label on hover. */
|
|
230
|
+
hoverBackground?: SpriteSource;
|
|
231
|
+
/** Sprite drawn behind the label when pressed. */
|
|
232
|
+
pressedBackground?: SpriteSource;
|
|
220
233
|
}
|
|
221
234
|
/** KAPLAY UI plugin. */
|
|
222
235
|
export declare function uiPlugin(k: KAPLAYCtx, theme?: DeepPartial<Theme>): {
|
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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;EA+LmntM,GAAA,wBAAA,mCAAA,YAAG,sCAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAA34hJ,sBAAA,KAAA,IAAA,sBAAA,SAAA,uBAAC,sCAAA;KAA8K,IAAA,sBAAA,SAAA,uBAAC,sCAAA;;;KAAsN,sBAAA,KAAA,IAAA,sBAAA,SAAA,uBAAC,+BAAA,oCAAA;KAAuM,IAAA,sBAAA,SAAA,uBAAC,sCAAA;;;;;;;;;;;;;;;;;;;;;;;;;KC1M90sD,wBAAwB,aAAa,MAAM;;YAGpC;IACR,WAAW;IACX,UAAU;IACV,UAAU;;;KAIF,aACV,eACA,MAAM;EACJ,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;MAEpB,kBAAkB;KAEX,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;;IAEF;MACE,qBAAqB;MACrB,oBAAoB;MACpB;;;;;UAMW;EACf;EACA;EACA;EACA;EACA,gBAAgB;EAChB,qBAAqB;EACrB;EACA,qBAAqB;EACrB,uBAAuB;;EAEvB,OAAO;;EAEP,YAAY;;EAEZ,cAAc;;EAEd,aAAa;;EAEb,kBAAkB;;EAElB,oBAAoB;;;wBCvFN,SAAS,GAAG,WAAW,QAAO,YAAY"}
|
package/dist/plugin.mjs
CHANGED
|
@@ -11,9 +11,8 @@ function toColor(k, color) {
|
|
|
11
11
|
}
|
|
12
12
|
// src/button.ts
|
|
13
13
|
function createButton(k, theme, text, opt) {
|
|
14
|
-
const width = opt.width ?? theme.components.button.width;
|
|
15
|
-
const height = opt.height ?? theme.components.button.height;
|
|
16
14
|
const radius = opt.radius ?? theme.shape.borderRadius;
|
|
15
|
+
const { minWidth, paddingX, paddingY } = theme.components.button;
|
|
17
16
|
const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);
|
|
18
17
|
const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);
|
|
19
18
|
const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);
|
|
@@ -21,38 +20,123 @@ function createButton(k, theme, text, opt) {
|
|
|
21
20
|
const size = opt.size ?? theme.typography.fontSize;
|
|
22
21
|
const font = opt.font ?? theme.typography.fontFamily;
|
|
23
22
|
const pos = opt.pos ?? k.vec2();
|
|
23
|
+
const textComp = k.text(text, {
|
|
24
|
+
size,
|
|
25
|
+
font
|
|
26
|
+
});
|
|
27
|
+
const isIconOnly = opt.icon !== void 0 && text.length === 0;
|
|
28
|
+
const rectOpt = !(opt.background !== void 0) && !isIconOnly ? radius > 0 ? { radius } : void 0 : { fill: false };
|
|
24
29
|
const button = k.add([
|
|
25
|
-
k.rect(
|
|
30
|
+
k.rect(0, 0, rectOpt),
|
|
26
31
|
k.pos(pos),
|
|
27
32
|
k.area(),
|
|
28
33
|
k.scale(),
|
|
29
34
|
k.color(baseColor),
|
|
30
35
|
k.anchor("center")
|
|
31
36
|
]);
|
|
37
|
+
const background = opt.background ? button.add([
|
|
38
|
+
k.sprite(opt.background),
|
|
39
|
+
k.pos(),
|
|
40
|
+
k.anchor("center")
|
|
41
|
+
]) : void 0;
|
|
42
|
+
const icon = opt.icon ? button.add([
|
|
43
|
+
k.sprite(opt.icon),
|
|
44
|
+
k.pos(),
|
|
45
|
+
k.anchor("center")
|
|
46
|
+
]) : void 0;
|
|
32
47
|
const label = button.add([
|
|
33
|
-
|
|
34
|
-
size,
|
|
35
|
-
font
|
|
36
|
-
}),
|
|
48
|
+
textComp,
|
|
37
49
|
k.color(textColor),
|
|
50
|
+
k.pos(),
|
|
38
51
|
k.anchor("center")
|
|
39
52
|
]);
|
|
53
|
+
let iconWidth = 0;
|
|
54
|
+
let iconHeight = 0;
|
|
55
|
+
let iconState = "base";
|
|
56
|
+
let backgroundWidth = 0;
|
|
57
|
+
let backgroundHeight = 0;
|
|
58
|
+
let backgroundMeasured = false;
|
|
59
|
+
let backgroundState = "base";
|
|
60
|
+
/**
|
|
61
|
+
* Update button layout:
|
|
62
|
+
*
|
|
63
|
+
* - caches the base icon/background dimensions,
|
|
64
|
+
* - calculates content width/height,
|
|
65
|
+
* - updates `button.width` / `button.height`,
|
|
66
|
+
* - stretches the background and repositions the icon and label.
|
|
67
|
+
*/
|
|
68
|
+
function layout() {
|
|
69
|
+
if (icon && iconState === "base") {
|
|
70
|
+
iconWidth = icon.width;
|
|
71
|
+
iconHeight = icon.height;
|
|
72
|
+
}
|
|
73
|
+
if (background && backgroundState === "base" && !backgroundMeasured && (background.width > 0 || background.height > 0)) {
|
|
74
|
+
backgroundWidth = background.width;
|
|
75
|
+
backgroundHeight = background.height;
|
|
76
|
+
backgroundMeasured = true;
|
|
77
|
+
}
|
|
78
|
+
const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;
|
|
79
|
+
const contentWidth = iconWidth + gap + label.width;
|
|
80
|
+
const contentHeight = Math.max(iconHeight, label.height);
|
|
81
|
+
const contentBoxWidth = contentWidth + paddingX * 2;
|
|
82
|
+
const contentBoxHeight = contentHeight + paddingY * 2;
|
|
83
|
+
const nextWidth = opt.width ?? (background ? Math.max(contentBoxWidth, backgroundWidth) : Math.max(contentBoxWidth, minWidth));
|
|
84
|
+
const nextHeight = opt.height ?? (background ? Math.max(contentBoxHeight, backgroundHeight) : contentBoxHeight);
|
|
85
|
+
if (button.width !== nextWidth) button.width = nextWidth;
|
|
86
|
+
if (button.height !== nextHeight) button.height = nextHeight;
|
|
87
|
+
if (background && backgroundMeasured) {
|
|
88
|
+
background.width = nextWidth;
|
|
89
|
+
background.height = nextHeight;
|
|
90
|
+
background.pos = k.vec2();
|
|
91
|
+
}
|
|
92
|
+
if (icon) icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);
|
|
93
|
+
label.pos = k.vec2(-contentWidth / 2 + iconWidth + gap + label.width / 2, 0);
|
|
94
|
+
}
|
|
95
|
+
layout();
|
|
96
|
+
if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
|
|
40
97
|
button.onClick(() => {
|
|
41
98
|
button.color = toColor(k, pressedColor);
|
|
99
|
+
if (icon && opt.pressedIcon) {
|
|
100
|
+
iconState = "pressed";
|
|
101
|
+
icon.sprite = opt.pressedIcon;
|
|
102
|
+
}
|
|
103
|
+
if (background && opt.pressedBackground) {
|
|
104
|
+
backgroundState = "pressed";
|
|
105
|
+
background.sprite = opt.pressedBackground;
|
|
106
|
+
}
|
|
42
107
|
opt.onClick?.();
|
|
43
108
|
});
|
|
44
109
|
button.onHover(() => {
|
|
45
110
|
button.color = toColor(k, hoverColor);
|
|
46
111
|
button.scale = k.vec2(1.05, 1.05);
|
|
47
112
|
k.setCursor("pointer");
|
|
113
|
+
if (icon && opt.hoverIcon) {
|
|
114
|
+
iconState = "hover";
|
|
115
|
+
icon.sprite = opt.hoverIcon;
|
|
116
|
+
}
|
|
117
|
+
if (background && opt.hoverBackground) {
|
|
118
|
+
backgroundState = "hover";
|
|
119
|
+
background.sprite = opt.hoverBackground;
|
|
120
|
+
}
|
|
48
121
|
});
|
|
49
122
|
button.onHoverEnd(() => {
|
|
50
123
|
button.color = baseColor;
|
|
51
124
|
button.scale = k.vec2(1, 1);
|
|
52
125
|
k.setCursor("default");
|
|
126
|
+
if (icon && opt.icon) {
|
|
127
|
+
iconState = "base";
|
|
128
|
+
icon.sprite = opt.icon;
|
|
129
|
+
}
|
|
130
|
+
if (background && opt.background) {
|
|
131
|
+
backgroundState = "base";
|
|
132
|
+
background.sprite = opt.background;
|
|
133
|
+
}
|
|
134
|
+
if (icon || background) layout();
|
|
53
135
|
});
|
|
54
136
|
return {
|
|
55
137
|
...button,
|
|
138
|
+
background,
|
|
139
|
+
icon,
|
|
56
140
|
label
|
|
57
141
|
};
|
|
58
142
|
}
|
|
@@ -83,8 +167,6 @@ const theme = {
|
|
|
83
167
|
spacing: 8,
|
|
84
168
|
components: {
|
|
85
169
|
button: {
|
|
86
|
-
width: 120,
|
|
87
|
-
height: 40,
|
|
88
170
|
minWidth: 120,
|
|
89
171
|
paddingX: 12,
|
|
90
172
|
paddingY: 8,
|
|
@@ -120,8 +202,7 @@ function resolveTheme(input) {
|
|
|
120
202
|
components: {
|
|
121
203
|
button: {
|
|
122
204
|
...theme.components.button,
|
|
123
|
-
...input?.components?.button
|
|
124
|
-
width: input?.components?.button?.width ?? input?.components?.button?.minWidth ?? theme.components.button.width
|
|
205
|
+
...input?.components?.button
|
|
125
206
|
},
|
|
126
207
|
modal: {
|
|
127
208
|
...theme.components.modal,
|
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 width = opt.width ?? theme.components.button.width;\n const height = opt.height ?? theme.components.button.height;\n const radius = opt.radius ?? theme.shape.borderRadius;\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 button = k.add([\n k.rect(width, height, radius > 0 ? { radius } : undefined),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ]);\n\n const label = button.add([\n k.text(text, { size, font }),\n k.color(textColor as never),\n k.anchor('center'),\n ]);\n\n button.onClick(() => {\n button.color = toColor(k, pressedColor);\n opt.onClick?.();\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\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n });\n\n return {\n ...button,\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 width: 120,\n height: 40,\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 width:\n input?.components?.button?.width ??\n input?.components?.button?.minWidth ??\n theme.components.button.width,\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/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 button = k.add([\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\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 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 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 {\n ...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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"}
|
package/dist/plugin.umd.js
CHANGED
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
}
|
|
16
16
|
// src/button.ts
|
|
17
17
|
function createButton(k, theme, text, opt) {
|
|
18
|
-
const width = opt.width ?? theme.components.button.width;
|
|
19
|
-
const height = opt.height ?? theme.components.button.height;
|
|
20
18
|
const radius = opt.radius ?? theme.shape.borderRadius;
|
|
19
|
+
const { minWidth, paddingX, paddingY } = theme.components.button;
|
|
21
20
|
const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);
|
|
22
21
|
const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);
|
|
23
22
|
const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);
|
|
@@ -25,38 +24,123 @@
|
|
|
25
24
|
const size = opt.size ?? theme.typography.fontSize;
|
|
26
25
|
const font = opt.font ?? theme.typography.fontFamily;
|
|
27
26
|
const pos = opt.pos ?? k.vec2();
|
|
27
|
+
const textComp = k.text(text, {
|
|
28
|
+
size,
|
|
29
|
+
font
|
|
30
|
+
});
|
|
31
|
+
const isIconOnly = opt.icon !== void 0 && text.length === 0;
|
|
32
|
+
const rectOpt = !(opt.background !== void 0) && !isIconOnly ? radius > 0 ? { radius } : void 0 : { fill: false };
|
|
28
33
|
const button = k.add([
|
|
29
|
-
k.rect(
|
|
34
|
+
k.rect(0, 0, rectOpt),
|
|
30
35
|
k.pos(pos),
|
|
31
36
|
k.area(),
|
|
32
37
|
k.scale(),
|
|
33
38
|
k.color(baseColor),
|
|
34
39
|
k.anchor("center")
|
|
35
40
|
]);
|
|
41
|
+
const background = opt.background ? button.add([
|
|
42
|
+
k.sprite(opt.background),
|
|
43
|
+
k.pos(),
|
|
44
|
+
k.anchor("center")
|
|
45
|
+
]) : void 0;
|
|
46
|
+
const icon = opt.icon ? button.add([
|
|
47
|
+
k.sprite(opt.icon),
|
|
48
|
+
k.pos(),
|
|
49
|
+
k.anchor("center")
|
|
50
|
+
]) : void 0;
|
|
36
51
|
const label = button.add([
|
|
37
|
-
|
|
38
|
-
size,
|
|
39
|
-
font
|
|
40
|
-
}),
|
|
52
|
+
textComp,
|
|
41
53
|
k.color(textColor),
|
|
54
|
+
k.pos(),
|
|
42
55
|
k.anchor("center")
|
|
43
56
|
]);
|
|
57
|
+
let iconWidth = 0;
|
|
58
|
+
let iconHeight = 0;
|
|
59
|
+
let iconState = "base";
|
|
60
|
+
let backgroundWidth = 0;
|
|
61
|
+
let backgroundHeight = 0;
|
|
62
|
+
let backgroundMeasured = false;
|
|
63
|
+
let backgroundState = "base";
|
|
64
|
+
/**
|
|
65
|
+
* Update button layout:
|
|
66
|
+
*
|
|
67
|
+
* - caches the base icon/background dimensions,
|
|
68
|
+
* - calculates content width/height,
|
|
69
|
+
* - updates `button.width` / `button.height`,
|
|
70
|
+
* - stretches the background and repositions the icon and label.
|
|
71
|
+
*/
|
|
72
|
+
function layout() {
|
|
73
|
+
if (icon && iconState === "base") {
|
|
74
|
+
iconWidth = icon.width;
|
|
75
|
+
iconHeight = icon.height;
|
|
76
|
+
}
|
|
77
|
+
if (background && backgroundState === "base" && !backgroundMeasured && (background.width > 0 || background.height > 0)) {
|
|
78
|
+
backgroundWidth = background.width;
|
|
79
|
+
backgroundHeight = background.height;
|
|
80
|
+
backgroundMeasured = true;
|
|
81
|
+
}
|
|
82
|
+
const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;
|
|
83
|
+
const contentWidth = iconWidth + gap + label.width;
|
|
84
|
+
const contentHeight = Math.max(iconHeight, label.height);
|
|
85
|
+
const contentBoxWidth = contentWidth + paddingX * 2;
|
|
86
|
+
const contentBoxHeight = contentHeight + paddingY * 2;
|
|
87
|
+
const nextWidth = opt.width ?? (background ? Math.max(contentBoxWidth, backgroundWidth) : Math.max(contentBoxWidth, minWidth));
|
|
88
|
+
const nextHeight = opt.height ?? (background ? Math.max(contentBoxHeight, backgroundHeight) : contentBoxHeight);
|
|
89
|
+
if (button.width !== nextWidth) button.width = nextWidth;
|
|
90
|
+
if (button.height !== nextHeight) button.height = nextHeight;
|
|
91
|
+
if (background && backgroundMeasured) {
|
|
92
|
+
background.width = nextWidth;
|
|
93
|
+
background.height = nextHeight;
|
|
94
|
+
background.pos = k.vec2();
|
|
95
|
+
}
|
|
96
|
+
if (icon) icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);
|
|
97
|
+
label.pos = k.vec2(-contentWidth / 2 + iconWidth + gap + label.width / 2, 0);
|
|
98
|
+
}
|
|
99
|
+
layout();
|
|
100
|
+
if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
|
|
44
101
|
button.onClick(() => {
|
|
45
102
|
button.color = toColor(k, pressedColor);
|
|
103
|
+
if (icon && opt.pressedIcon) {
|
|
104
|
+
iconState = "pressed";
|
|
105
|
+
icon.sprite = opt.pressedIcon;
|
|
106
|
+
}
|
|
107
|
+
if (background && opt.pressedBackground) {
|
|
108
|
+
backgroundState = "pressed";
|
|
109
|
+
background.sprite = opt.pressedBackground;
|
|
110
|
+
}
|
|
46
111
|
opt.onClick?.();
|
|
47
112
|
});
|
|
48
113
|
button.onHover(() => {
|
|
49
114
|
button.color = toColor(k, hoverColor);
|
|
50
115
|
button.scale = k.vec2(1.05, 1.05);
|
|
51
116
|
k.setCursor("pointer");
|
|
117
|
+
if (icon && opt.hoverIcon) {
|
|
118
|
+
iconState = "hover";
|
|
119
|
+
icon.sprite = opt.hoverIcon;
|
|
120
|
+
}
|
|
121
|
+
if (background && opt.hoverBackground) {
|
|
122
|
+
backgroundState = "hover";
|
|
123
|
+
background.sprite = opt.hoverBackground;
|
|
124
|
+
}
|
|
52
125
|
});
|
|
53
126
|
button.onHoverEnd(() => {
|
|
54
127
|
button.color = baseColor;
|
|
55
128
|
button.scale = k.vec2(1, 1);
|
|
56
129
|
k.setCursor("default");
|
|
130
|
+
if (icon && opt.icon) {
|
|
131
|
+
iconState = "base";
|
|
132
|
+
icon.sprite = opt.icon;
|
|
133
|
+
}
|
|
134
|
+
if (background && opt.background) {
|
|
135
|
+
backgroundState = "base";
|
|
136
|
+
background.sprite = opt.background;
|
|
137
|
+
}
|
|
138
|
+
if (icon || background) layout();
|
|
57
139
|
});
|
|
58
140
|
return {
|
|
59
141
|
...button,
|
|
142
|
+
background,
|
|
143
|
+
icon,
|
|
60
144
|
label
|
|
61
145
|
};
|
|
62
146
|
}
|
|
@@ -87,8 +171,6 @@
|
|
|
87
171
|
spacing: 8,
|
|
88
172
|
components: {
|
|
89
173
|
button: {
|
|
90
|
-
width: 120,
|
|
91
|
-
height: 40,
|
|
92
174
|
minWidth: 120,
|
|
93
175
|
paddingX: 12,
|
|
94
176
|
paddingY: 8,
|
|
@@ -124,8 +206,7 @@
|
|
|
124
206
|
components: {
|
|
125
207
|
button: {
|
|
126
208
|
...theme.components.button,
|
|
127
|
-
...input?.components?.button
|
|
128
|
-
width: input?.components?.button?.width ?? input?.components?.button?.minWidth ?? theme.components.button.width
|
|
209
|
+
...input?.components?.button
|
|
129
210
|
},
|
|
130
211
|
modal: {
|
|
131
212
|
...theme.components.modal,
|
package/dist/plugin.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.umd.js","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 width = opt.width ?? theme.components.button.width;\n const height = opt.height ?? theme.components.button.height;\n const radius = opt.radius ?? theme.shape.borderRadius;\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 button = k.add([\n k.rect(width, height, radius > 0 ? { radius } : undefined),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ]);\n\n const label = button.add([\n k.text(text, { size, font }),\n k.color(textColor as never),\n k.anchor('center'),\n ]);\n\n button.onClick(() => {\n button.color = toColor(k, pressedColor);\n opt.onClick?.();\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\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n });\n\n return {\n ...button,\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 width: 120,\n height: 40,\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 width:\n input?.components?.button?.width ??\n input?.components?.button?.minWidth ??\n theme.components.button.width,\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.umd.js","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 button = k.add([\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\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 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 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 {\n ...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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"}
|
package/dist/plugin.umd.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.KaplayPluginUI={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e,t){return typeof t==`string`?e.rgb(t):t}function n(e,n,r,i){let a=i.
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.KaplayPluginUI={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e,t){return typeof t==`string`?e.rgb(t):t}function n(e,n,r,i){let a=i.radius??n.shape.borderRadius,{minWidth:o,paddingX:s,paddingY:c}=n.components.button,l=t(e,i.color??i.fill??n.palette.primary),u=t(e,i.hoverColor??n.palette.hover),d=t(e,i.pressedColor??n.palette.pressed),f=t(e,i.textColor??n.palette.text),p=i.size??n.typography.fontSize,m=i.font??n.typography.fontFamily,h=i.pos??e.vec2(),g=e.text(r,{size:p,font:m}),_=i.icon!==void 0&&r.length===0,v=i.background===void 0&&!_?a>0?{radius:a}:void 0:{fill:!1},y=e.add([e.rect(0,0,v),e.pos(h),e.area(),e.scale(),e.color(l),e.anchor(`center`)]),b=i.background?y.add([e.sprite(i.background),e.pos(),e.anchor(`center`)]):void 0,x=i.icon?y.add([e.sprite(i.icon),e.pos(),e.anchor(`center`)]):void 0,S=y.add([g,e.color(f),e.pos(),e.anchor(`center`)]),C=0,w=0,T=`base`,E=0,D=0,O=!1,k=`base`;function A(){x&&T===`base`&&(C=x.width,w=x.height),b&&k===`base`&&!O&&(b.width>0||b.height>0)&&(E=b.width,D=b.height,O=!0);let t=x&&C>0&&S.width>0?n.spacing:0,r=C+t+S.width,a=Math.max(w,S.height),l=r+s*2,u=a+c*2,d=i.width??(b?Math.max(l,E):Math.max(l,o)),f=i.height??(b?Math.max(u,D):u);y.width!==d&&(y.width=d),y.height!==f&&(y.height=f),b&&O&&(b.width=d,b.height=f,b.pos=e.vec2()),x&&(x.pos=e.vec2(-r/2+C/2,0)),S.pos=e.vec2(-r/2+C+t+S.width/2,0)}return A(),(i.width===void 0||i.height===void 0||i.icon!==void 0||i.background!==void 0)&&y.onUpdate(A),y.onClick(()=>{y.color=t(e,d),x&&i.pressedIcon&&(T=`pressed`,x.sprite=i.pressedIcon),b&&i.pressedBackground&&(k=`pressed`,b.sprite=i.pressedBackground),i.onClick?.()}),y.onHover(()=>{y.color=t(e,u),y.scale=e.vec2(1.05,1.05),e.setCursor(`pointer`),x&&i.hoverIcon&&(T=`hover`,x.sprite=i.hoverIcon),b&&i.hoverBackground&&(k=`hover`,b.sprite=i.hoverBackground)}),y.onHoverEnd(()=>{y.color=l,y.scale=e.vec2(1,1),e.setCursor(`default`),x&&i.icon&&(T=`base`,x.sprite=i.icon),b&&i.background&&(k=`base`,b.sprite=i.background),(x||b)&&A()}),{...y,background:b,icon:x,label:S}}let r=`#4f8cff`,i=`#ffffff`,a={palette:{primary:r,secondary:r,background:`#111827`,surface:`#1f2937`,text:i,textSecondary:i,hover:r,pressed:r,disabled:`#6b7280`,error:`#ef4444`},typography:{fontFamily:`sans-serif`,fontSize:18,fontWeight:400,lineHeight:1.2},shape:{borderRadius:0},spacing:8,components:{button:{minWidth:120,paddingX:12,paddingY:8,textTransform:`none`},modal:{backdropOpacity:.5,borderRadius:8},tooltip:{background:`#111827`,textColor:i,delay:300}}};function o(e){return{palette:{...a.palette,...e?.palette},typography:{...a.typography,...e?.typography},shape:{...a.shape,...e?.shape},spacing:e?.spacing??a.spacing,components:{button:{...a.components.button,...e?.components?.button},modal:{...a.components.modal,...e?.components?.modal},tooltip:{...a.components.tooltip,...e?.components?.tooltip}}}}function s(e,t={}){let r=t,i=o(r);return{addButton:(t=``,r={})=>n(e,i,t,r),setTheme:e=>{r={...r,...e},i=o(r)},getTheme:()=>i}}e.uiPlugin=s});
|
|
2
2
|
//# sourceMappingURL=plugin.umd.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.umd.min.js","names":["factory","global","exports","toColor","color","k","createButton","width","opt","theme","height","radius","baseColor","hoverColor","pressedColor","textColor","size","font","pos","button","label","text","PRIMARY","TEXT","resolveTheme","input","uiPlugin","userTheme","currentTheme","next"],"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 width = opt.width ?? theme.components.button.width;\n const height = opt.height ?? theme.components.button.height;\n const radius = opt.radius ?? theme.shape.borderRadius;\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 button = k.add([\n k.rect(width, height, radius > 0 ? { radius } : undefined),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ]);\n\n const label = button.add([\n k.text(text, { size, font }),\n k.color(textColor as never),\n k.anchor('center'),\n ]);\n\n button.onClick(() => {\n button.color = toColor(k, pressedColor);\n opt.onClick?.();\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\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n });\n\n return {\n ...button,\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 width: 120,\n height: 40,\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 width:\n input?.components?.button?.width ??\n input?.components?.button?.minWidth ??\n theme.components.button.width,\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.umd.min.js","names":["factory","global","exports","toColor","color","k","createButton","radius","opt","theme","baseColor","hoverColor","pressedColor","textColor","size","font","pos","textComp","text","isIconOnly","rectOpt","button","background","icon","label","iconWidth","iconHeight","iconState","backgroundWidth","backgroundHeight","backgroundMeasured","backgroundState","layout","gap","contentWidth","contentHeight","contentBoxWidth","paddingX","contentBoxHeight","paddingY","nextWidth","minWidth","nextHeight","PRIMARY","TEXT","resolveTheme","input","uiPlugin","userTheme","currentTheme","next"],"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 button = k.add([\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\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 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 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 {\n ...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":"C,qRASA,SAAgBG,EAAQ,EAAc,EAA8B,CAClE,OAAO,OAAOC,GAAU,SAAWC,EAAE,IAAID,CAAK,EAAIA,CACpD,CCNA,SAAgBE,EACd,EACA,EACA,EACA,EAQA,CACA,IAAMC,EAASC,EAAI,QAAUC,EAAM,MAAM,aACnC,CAAE,WAAU,WAAU,YAAaA,EAAM,WAAW,OACpDC,EAAYP,EAAQE,EAAGG,EAAI,OAASA,EAAI,MAAQC,EAAM,QAAQ,OAAO,EACrEE,EAAaR,EAAQE,EAAGG,EAAI,YAAcC,EAAM,QAAQ,KAAK,EAC7DG,EAAeT,EAAQE,EAAGG,EAAI,cAAgBC,EAAM,QAAQ,OAAO,EACnEI,EAAYV,EAAQE,EAAGG,EAAI,WAAaC,EAAM,QAAQ,IAAI,EAC1DK,EAAON,EAAI,MAAQC,EAAM,WAAW,SACpCM,EAAOP,EAAI,MAAQC,EAAM,WAAW,WACpCO,EAAMR,EAAI,KAAOH,EAAE,KAAK,EAExBY,EAAWZ,EAAE,KAAKa,EAAM,CAAE,OAAM,MAAK,CAAC,EACtCC,EAAaX,EAAI,OAAS,IAAA,IAAaU,EAAK,SAAW,EAGvDE,EAFgBZ,EAAI,aAAe,IAAA,IACN,CAACW,EAEhCZ,EAAS,EACP,CAAE,QAAO,EACT,IAAA,GACF,CAAE,KAAM,EAAM,EAEZc,EAAShB,EAAE,IAAI,CACnBA,EAAE,KAAK,EAAG,EAAGe,CAAO,EACpBf,EAAE,IAAIW,CAAG,EACTX,EAAE,KAAK,EACPA,EAAE,MAAM,EACRA,EAAE,MAAMK,CAAS,EACjBL,EAAE,OAAO,QAAQ,CACnB,CAAC,EAEKiB,EAAad,EAAI,WACnBa,EAAO,IAAI,CAAChB,EAAE,OAAOG,EAAI,UAAU,EAAGH,EAAE,IAAI,EAAGA,EAAE,OAAO,QAAQ,CAAC,CAAC,EAClE,IAAA,GAEEkB,EAAOf,EAAI,KACba,EAAO,IAAI,CAAChB,EAAE,OAAOG,EAAI,IAAI,EAAGH,EAAE,IAAI,EAAGA,EAAE,OAAO,QAAQ,CAAC,CAAC,EAC5D,IAAA,GAEEmB,EAAQH,EAAO,IAAI,CACvBJ,EACAZ,EAAE,MAAMQ,CAAkB,EAC1BR,EAAE,IAAI,EACNA,EAAE,OAAO,QAAQ,CACnB,CAAC,EAEGoB,EAAY,EACZC,EAAa,EACbC,EAA0C,OAC1CC,EAAkB,EAClBC,EAAmB,EACnBC,EAAqB,GACrBC,EAAgD,OAUpD,SAASC,GAAS,CACZT,GAAQI,IAAc,SACxB,EAAYJ,EAAK,MACjB,EAAaA,EAAK,QAIlBD,GACAS,IAAoB,QACpB,CAACD,IACAR,EAAW,MAAQ,GAAKA,EAAW,OAAS,KAE7C,EAAkBA,EAAW,MAC7B,EAAmBA,EAAW,OAC9B,EAAqB,IAGvB,IAAMW,EAAMV,GAAQE,EAAY,GAAKD,EAAM,MAAQ,EAAIf,EAAM,QAAU,EACjEyB,EAAeT,EAAYQ,EAAMT,EAAM,MACvCW,EAAgB,KAAK,IAAIT,EAAYF,EAAM,MAAM,EACjDY,EAAkBF,EAAeG,EAAW,EAC5CC,EAAmBH,EAAgBI,EAAW,EAE9CC,EACJhC,EAAI,QACHc,EACG,KAAK,IAAIc,EAAiBR,CAAe,EACzC,KAAK,IAAIQ,EAAiBK,CAAQ,GAClCC,EACJlC,EAAI,SACHc,EACG,KAAK,IAAIgB,EAAkBT,CAAgB,EAC3CS,GAEFjB,EAAO,QAAUmB,IACnB,EAAO,MAAQA,GAGbnB,EAAO,SAAWqB,IACpB,EAAO,OAASA,GAGdpB,GAAcQ,IAChB,EAAW,MAAQU,EACnB,EAAW,OAASE,EACpB,EAAW,IAAMrC,EAAE,KAAK,GAGtBkB,IACF,EAAK,IAAMlB,EAAE,KAAK,CAAC6B,EAAe,EAAIT,EAAY,EAAG,CAAC,GAGxD,EAAM,IAAMpB,EAAE,KACZ,CAAC6B,EAAe,EAAIT,EAAYQ,EAAMT,EAAM,MAAQ,EACpD,CACF,CACF,CAiEA,OA/DAQ,EAAO,GAGLxB,EAAI,QAAU,IAAA,IACdA,EAAI,SAAW,IAAA,IACfA,EAAI,OAAS,IAAA,IACbA,EAAI,aAAe,IAAA,KAEnBa,EAAO,SAASW,CAAM,EAGxBX,EAAO,YAAc,CACnB,EAAO,MAAQlB,EAAQE,EAAGO,CAAY,EAElCW,GAAQf,EAAI,cACd,EAAY,UACZ,EAAK,OAASA,EAAI,aAGhBc,GAAcd,EAAI,oBACpB,EAAkB,UAClB,EAAW,OAASA,EAAI,mBAG1BA,EAAI,UAAU,CAChB,CAAC,EAEDa,EAAO,YAAc,CACnB,EAAO,MAAQlB,EAAQE,EAAGM,CAAU,EACpC,EAAO,MAAQN,EAAE,KAAK,KAAM,IAAI,EAChCA,EAAE,UAAU,SAAS,EAEjBkB,GAAQf,EAAI,YACd,EAAY,QACZ,EAAK,OAASA,EAAI,WAGhBc,GAAcd,EAAI,kBACpB,EAAkB,QAClB,EAAW,OAASA,EAAI,gBAE5B,CAAC,EAEDa,EAAO,eAAiB,CACtB,EAAO,MAAQX,EACf,EAAO,MAAQL,EAAE,KAAK,EAAG,CAAC,EAC1BA,EAAE,UAAU,SAAS,EAEjBkB,GAAQf,EAAI,OACd,EAAY,OACZ,EAAK,OAASA,EAAI,MAGhBc,GAAcd,EAAI,aACpB,EAAkB,OAClB,EAAW,OAASA,EAAI,aAGtBe,GAAQD,IACVU,EAAO,CAEX,CAAC,EAEM,CACL,GAAGX,EACH,aACA,OACA,OACF,CACF,CC3MA,IAAMsB,EAAU,UACVC,EAAO,UAGPnC,EAAe,CACnB,QAAS,CACP,QAASkC,EACT,UAAWA,EACX,WAAY,UACZ,QAAS,UACT,KAAMC,EACN,cAAeA,EACf,MAAOD,EACP,QAASA,EACT,SAAU,UACV,MAAO,SACT,EACA,WAAY,CACV,WAAY,aACZ,SAAU,GACV,WAAY,IACZ,WAAY,GACd,EACA,MAAO,CACL,aAAc,CAChB,EACA,QAAS,EACT,WAAY,CACV,OAAQ,CACN,SAAU,IACV,SAAU,GACV,SAAU,EACV,cAAe,MACjB,EACA,MAAO,CACL,gBAAiB,GACjB,aAAc,CAChB,EACA,QAAS,CACP,WAAY,UACZ,UAAWC,EACX,MAAO,GACT,CACF,CACF,EAGA,SAAgBC,EAAa,EAAmC,CAC9D,MAAO,CACL,QAAS,CAAE,GAAGpC,EAAM,QAAS,GAAGqC,GAAO,OAAQ,EAC/C,WAAY,CAAE,GAAGrC,EAAM,WAAY,GAAGqC,GAAO,UAAW,EACxD,MAAO,CAAE,GAAGrC,EAAM,MAAO,GAAGqC,GAAO,KAAM,EACzC,QAASA,GAAO,SAAWrC,EAAM,QACjC,WAAY,CACV,OAAQ,CACN,GAAGA,EAAM,WAAW,OACpB,GAAGqC,GAAO,YAAY,MACxB,EACA,MAAO,CACL,GAAGrC,EAAM,WAAW,MACpB,GAAGqC,GAAO,YAAY,KACxB,EACA,QAAS,CACP,GAAGrC,EAAM,WAAW,QACpB,GAAGqC,GAAO,YAAY,OACxB,CACF,CACF,CACF,CCzDA,SAAgBC,EAAS,EAAc,EAA4B,CAAC,EAAG,CACrE,IAAIC,EAAgCvC,EAChCwC,EAAsBJ,EAAaG,CAAS,EAahD,MAAO,CACL,WAL4B,EAAO,GAAI,EAAM,CAAC,IACvC1C,EAAaD,EAAG4C,EAAc/B,EAAMV,CAAG,EAK9C,SAX0B,GAAS,CACnC,EAAY,CAAE,GAAGwC,EAAW,GAAGE,CAAK,EACpC,EAAeL,EAAaG,CAAS,CACvC,EASE,aAd+BC,CAejC,CACF,C"}
|