kaplay-plugin-ui 1.0.3 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -5
- package/dist/plugin.cjs +84 -11
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +18 -137
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +18 -137
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +84 -11
- package/dist/plugin.mjs.map +1 -1
- package/dist/plugin.umd.js +84 -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
|
@@ -88,22 +88,60 @@ k.addButton('Start', {
|
|
|
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
|
|
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.
|
|
98
136
|
|
|
99
137
|
Update button text:
|
|
100
138
|
|
|
101
139
|
```ts
|
|
102
|
-
const button = addButton(
|
|
103
|
-
button.label.text = '
|
|
140
|
+
const button = addButton('Text');
|
|
141
|
+
button.label.text = 'New Text';
|
|
104
142
|
```
|
|
105
143
|
|
|
106
|
-
The button resizes automatically when the label
|
|
144
|
+
The button resizes automatically when the label, icon, or background image dimensions change.
|
|
107
145
|
|
|
108
146
|
### getTheme
|
|
109
147
|
|
package/dist/plugin.cjs
CHANGED
|
@@ -25,47 +25,120 @@ function createButton(k, theme, text, opt) {
|
|
|
25
25
|
size,
|
|
26
26
|
font
|
|
27
27
|
});
|
|
28
|
-
const
|
|
29
|
-
const
|
|
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 };
|
|
30
30
|
const button = k.add([
|
|
31
|
-
k.rect(
|
|
31
|
+
k.rect(0, 0, rectOpt),
|
|
32
32
|
k.pos(pos),
|
|
33
33
|
k.area(),
|
|
34
34
|
k.scale(),
|
|
35
35
|
k.color(baseColor),
|
|
36
36
|
k.anchor("center")
|
|
37
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;
|
|
38
48
|
const label = button.add([
|
|
39
49
|
textComp,
|
|
40
50
|
k.color(textColor),
|
|
51
|
+
k.pos(),
|
|
41
52
|
k.anchor("center")
|
|
42
53
|
]);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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);
|
|
46
86
|
if (button.width !== nextWidth) button.width = nextWidth;
|
|
47
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);
|
|
48
95
|
}
|
|
49
|
-
|
|
50
|
-
if (opt.width === void 0 || opt.height === void 0) button.onUpdate(
|
|
96
|
+
layout();
|
|
97
|
+
if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
|
|
51
98
|
button.onClick(() => {
|
|
52
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
|
+
}
|
|
53
108
|
opt.onClick?.();
|
|
54
109
|
});
|
|
55
110
|
button.onHover(() => {
|
|
56
111
|
button.color = toColor(k, hoverColor);
|
|
57
112
|
button.scale = k.vec2(1.05, 1.05);
|
|
58
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
|
+
}
|
|
59
122
|
});
|
|
60
123
|
button.onHoverEnd(() => {
|
|
61
124
|
button.color = baseColor;
|
|
62
125
|
button.scale = k.vec2(1, 1);
|
|
63
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();
|
|
64
136
|
});
|
|
65
|
-
return {
|
|
66
|
-
|
|
137
|
+
return Object.assign(button, {
|
|
138
|
+
background,
|
|
139
|
+
icon,
|
|
67
140
|
label
|
|
68
|
-
};
|
|
141
|
+
});
|
|
69
142
|
}
|
|
70
143
|
// src/theme.ts
|
|
71
144
|
const PRIMARY = "#4f8cff";
|
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 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 width = opt.width ?? Math.max(textComp.width + paddingX * 2, minWidth);\n const height = opt.height ?? textComp.height + paddingY * 2;\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 textComp,\n k.color(textColor as never),\n k.anchor('center'),\n ]);\n\n function resize() {\n const nextWidth =\n opt.width ?? Math.max(label.width + paddingX * 2, minWidth);\n const nextHeight = opt.height ?? label.height + paddingY * 2;\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n }\n\n resize();\n\n if (opt.width === undefined || opt.height === undefined) {\n button.onUpdate(resize);\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 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.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 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":";;;;;;;;;AASA,SAAgB,QAAQ,GAAc,OAA8B;CAClE,OAAO,OAAO,UAAU,WAAW,EAAE,IAAI,KAAK,IAAI;AACpD;;ACNA,SAAgB,aACd,GACA,OACA,MACA,KAQA;CACA,MAAM,SAAS,IAAI,UAAU,MAAM,MAAM;CACzC,MAAM,EAAE,UAAU,UAAU,aAAa,MAAM,WAAW;CAC1D,MAAM,YAAY,QAAQ,GAAG,IAAI,SAAS,IAAI,QAAQ,MAAM,QAAQ,OAAO;CAC3E,MAAM,aAAa,QAAQ,GAAG,IAAI,cAAc,MAAM,QAAQ,KAAK;CACnE,MAAM,eAAe,QAAQ,GAAG,IAAI,gBAAgB,MAAM,QAAQ,OAAO;CACzE,MAAM,YAAY,QAAQ,GAAG,IAAI,aAAa,MAAM,QAAQ,IAAI;CAChE,MAAM,OAAO,IAAI,QAAQ,MAAM,WAAW;CAC1C,MAAM,OAAO,IAAI,QAAQ,MAAM,WAAW;CAC1C,MAAM,MAAM,IAAI,OAAO,EAAE,KAAK;CAE9B,MAAM,WAAW,EAAE,KAAK,MAAM;EAAE;EAAM;CAAK,CAAC;CAC5C,MAAM,aAAa,IAAI,SAAS,KAAA,KAAa,KAAK,WAAW;CAG7D,MAAM,UADW,EADK,IAAI,eAAe,KAAA,MACN,CAAC,aAEhC,SAAS,IACP,EAAE,OAAO,IACT,KAAA,IACF,EAAE,MAAM,MAAM;CAElB,MAAM,SAAS,EAAE,IAAI;EACnB,EAAE,KAAK,GAAG,GAAG,OAAO;EACpB,EAAE,IAAI,GAAG;EACT,EAAE,KAAK;EACP,EAAE,MAAM;EACR,EAAE,MAAM,SAAS;EACjB,EAAE,OAAO,QAAQ;CACnB,CAAC;CAED,MAAM,aAAa,IAAI,aACnB,OAAO,IAAI;EAAC,EAAE,OAAO,IAAI,UAAU;EAAG,EAAE,IAAI;EAAG,EAAE,OAAO,QAAQ;CAAC,CAAC,IAClE,KAAA;CAEJ,MAAM,OAAO,IAAI,OACb,OAAO,IAAI;EAAC,EAAE,OAAO,IAAI,IAAI;EAAG,EAAE,IAAI;EAAG,EAAE,OAAO,QAAQ;CAAC,CAAC,IAC5D,KAAA;CAEJ,MAAM,QAAQ,OAAO,IAAI;EACvB;EACA,EAAE,MAAM,SAAkB;EAC1B,EAAE,IAAI;EACN,EAAE,OAAO,QAAQ;CACnB,CAAC;CAED,IAAI,YAAY;CAChB,IAAI,aAAa;CACjB,IAAI,YAA0C;CAC9C,IAAI,kBAAkB;CACtB,IAAI,mBAAmB;CACvB,IAAI,qBAAqB;CACzB,IAAI,kBAAgD;;;;;;;;;CAUpD,SAAS,SAAS;EAChB,IAAI,QAAQ,cAAc,QAAQ;GAChC,YAAY,KAAK;GACjB,aAAa,KAAK;EACpB;EAEA,IACE,cACA,oBAAoB,UACpB,CAAC,uBACA,WAAW,QAAQ,KAAK,WAAW,SAAS,IAC7C;GACA,kBAAkB,WAAW;GAC7B,mBAAmB,WAAW;GAC9B,qBAAqB;EACvB;EAEA,MAAM,MAAM,QAAQ,YAAY,KAAK,MAAM,QAAQ,IAAI,MAAM,UAAU;EACvE,MAAM,eAAe,YAAY,MAAM,MAAM;EAC7C,MAAM,gBAAgB,KAAK,IAAI,YAAY,MAAM,MAAM;EACvD,MAAM,kBAAkB,eAAe,WAAW;EAClD,MAAM,mBAAmB,gBAAgB,WAAW;EAEpD,MAAM,YACJ,IAAI,UACH,aACG,KAAK,IAAI,iBAAiB,eAAe,IACzC,KAAK,IAAI,iBAAiB,QAAQ;EACxC,MAAM,aACJ,IAAI,WACH,aACG,KAAK,IAAI,kBAAkB,gBAAgB,IAC3C;EAEN,IAAI,OAAO,UAAU,WACnB,OAAO,QAAQ;EAGjB,IAAI,OAAO,WAAW,YACpB,OAAO,SAAS;EAGlB,IAAI,cAAc,oBAAoB;GACpC,WAAW,QAAQ;GACnB,WAAW,SAAS;GACpB,WAAW,MAAM,EAAE,KAAK;EAC1B;EAEA,IAAI,MACF,KAAK,MAAM,EAAE,KAAK,CAAC,eAAe,IAAI,YAAY,GAAG,CAAC;EAGxD,MAAM,MAAM,EAAE,KACZ,CAAC,eAAe,IAAI,YAAY,MAAM,MAAM,QAAQ,GACpD,CACF;CACF;CAEA,OAAO;CAEP,IACE,IAAI,UAAU,KAAA,KACd,IAAI,WAAW,KAAA,KACf,IAAI,SAAS,KAAA,KACb,IAAI,eAAe,KAAA,GAEnB,OAAO,SAAS,MAAM;CAGxB,OAAO,cAAc;EACnB,OAAO,QAAQ,QAAQ,GAAG,YAAY;EAEtC,IAAI,QAAQ,IAAI,aAAa;GAC3B,YAAY;GACZ,KAAK,SAAS,IAAI;EACpB;EAEA,IAAI,cAAc,IAAI,mBAAmB;GACvC,kBAAkB;GAClB,WAAW,SAAS,IAAI;EAC1B;EAEA,IAAI,UAAU;CAChB,CAAC;CAED,OAAO,cAAc;EACnB,OAAO,QAAQ,QAAQ,GAAG,UAAU;EACpC,OAAO,QAAQ,EAAE,KAAK,MAAM,IAAI;EAChC,EAAE,UAAU,SAAS;EAErB,IAAI,QAAQ,IAAI,WAAW;GACzB,YAAY;GACZ,KAAK,SAAS,IAAI;EACpB;EAEA,IAAI,cAAc,IAAI,iBAAiB;GACrC,kBAAkB;GAClB,WAAW,SAAS,IAAI;EAC1B;CACF,CAAC;CAED,OAAO,iBAAiB;EACtB,OAAO,QAAQ;EACf,OAAO,QAAQ,EAAE,KAAK,GAAG,CAAC;EAC1B,EAAE,UAAU,SAAS;EAErB,IAAI,QAAQ,IAAI,MAAM;GACpB,YAAY;GACZ,KAAK,SAAS,IAAI;EACpB;EAEA,IAAI,cAAc,IAAI,YAAY;GAChC,kBAAkB;GAClB,WAAW,SAAS,IAAI;EAC1B;EAEA,IAAI,QAAQ,YACV,OAAO;CAEX,CAAC;CAED,OAAO,OAAO,OAAO,QAAQ;EAC3B;EACA;EACA;CACF,CAAC;AACH;;AC1MA,MAAM,UAAU;AAChB,MAAM,OAAO;;AAGb,MAAM,QAAe;CACnB,SAAS;EACP,SAAS;EACT,WAAW;EACX,YAAY;EACZ,SAAS;EACT,MAAM;EACN,eAAe;EACf,OAAO;EACP,SAAS;EACT,UAAU;EACV,OAAO;CACT;CACA,YAAY;EACV,YAAY;EACZ,UAAU;EACV,YAAY;EACZ,YAAY;CACd;CACA,OAAO,EACL,cAAc,EAChB;CACA,SAAS;CACT,YAAY;EACV,QAAQ;GACN,UAAU;GACV,UAAU;GACV,UAAU;GACV,eAAe;EACjB;EACA,OAAO;GACL,iBAAiB;GACjB,cAAc;EAChB;EACA,SAAS;GACP,YAAY;GACZ,WAAW;GACX,OAAO;EACT;CACF;AACF;;AAGA,SAAgB,aAAa,OAAmC;CAC9D,OAAO;EACL,SAAS;GAAE,GAAG,MAAM;GAAS,GAAG,OAAO;EAAQ;EAC/C,YAAY;GAAE,GAAG,MAAM;GAAY,GAAG,OAAO;EAAW;EACxD,OAAO;GAAE,GAAG,MAAM;GAAO,GAAG,OAAO;EAAM;EACzC,SAAS,OAAO,WAAW,MAAM;EACjC,YAAY;GACV,QAAQ;IACN,GAAG,MAAM,WAAW;IACpB,GAAG,OAAO,YAAY;GACxB;GACA,OAAO;IACL,GAAG,MAAM,WAAW;IACpB,GAAG,OAAO,YAAY;GACxB;GACA,SAAS;IACP,GAAG,MAAM,WAAW;IACpB,GAAG,OAAO,YAAY;GACxB;EACF;CACF;AACF;;;ACzDA,SAAgB,SAAS,GAAc,QAA4B,CAAC,GAAG;CACrE,IAAI,YAAgC;CACpC,IAAI,eAAsB,aAAa,SAAS;CAEhD,MAAM,iBAA2B;CAEjC,MAAM,YAAsB,SAAS;EACnC,YAAY;GAAE,GAAG;GAAW,GAAG;EAAK;EACpC,eAAe,aAAa,SAAS;CACvC;CAEA,MAAM,aAAwB,OAAO,IAAI,MAAM,CAAC,MAAM;EACpD,OAAO,aAAa,GAAG,cAAc,MAAM,GAAG;CAChD;CAEA,OAAO;EACL;EACA;EACA;CACF;AACF"}
|
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;
|
|
@@ -6,143 +6,12 @@ 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
|
-
}): {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
remove(obj: import("kaplay").GameObj): void;
|
|
14
|
-
removeAll(tag: import("kaplay").Tag): void;
|
|
15
|
-
removeAll(): void;
|
|
16
|
-
get(tag: import("kaplay").Tag | import("kaplay").Tag[], opts?: import("kaplay").GetOpt): import("kaplay").GameObj[];
|
|
17
|
-
query(opt: import("kaplay").QueryOpt): import("kaplay").GameObj[];
|
|
18
|
-
parent: import("kaplay").GameObj | null;
|
|
19
|
-
children: import("kaplay").GameObj[];
|
|
20
|
-
tags: string[];
|
|
21
|
-
fixedUpdate(): void;
|
|
22
|
-
update(): void;
|
|
23
|
-
draw(): void;
|
|
24
|
-
drawInspect: () => void;
|
|
25
|
-
clearEvents: () => void;
|
|
26
|
-
use(comp: import("kaplay").Comp | import("kaplay").Tag): void;
|
|
27
|
-
unuse(comp: import("kaplay").Tag): void;
|
|
28
|
-
has(compId: string | string[], op?: "and" | "or"): boolean;
|
|
29
|
-
tag(tag: import("kaplay").Tag | import("kaplay").Tag[]): void;
|
|
30
|
-
untag(tag: import("kaplay").Tag | import("kaplay").Tag[]): void;
|
|
31
|
-
is(tag: import("kaplay").Tag | import("kaplay").Tag[], op?: "and" | "or"): boolean;
|
|
32
|
-
on(event: import("kaplay").GameObjEventNames | (string & {}), action: (...args: any) => void): import("kaplay").KEventController;
|
|
33
|
-
trigger(event: string, ...args: any): void;
|
|
34
|
-
destroy(): void;
|
|
35
|
-
c(id: string): import("kaplay").Comp | null;
|
|
36
|
-
inspect(): import("kaplay").GameObjInspect;
|
|
37
|
-
onAdd(action: () => void): import("kaplay").KEventController;
|
|
38
|
-
onUpdate(action: () => void): import("kaplay").KEventController;
|
|
39
|
-
onFixedUpdate(action: () => void): import("kaplay").KEventController;
|
|
40
|
-
onDraw(action: () => void): import("kaplay").KEventController;
|
|
41
|
-
onDestroy(action: () => void): import("kaplay").KEventController;
|
|
42
|
-
onUse(action: (id: string) => void): import("kaplay").KEventController;
|
|
43
|
-
onUnuse(action: (id: string) => void): import("kaplay").KEventController;
|
|
44
|
-
onTag(action: (tag: string) => void): import("kaplay").KEventController;
|
|
45
|
-
onUntag(action: (tag: string) => void): import("kaplay").KEventController;
|
|
46
|
-
exists(): boolean;
|
|
47
|
-
isAncestorOf(obj: import("kaplay").GameObj): boolean;
|
|
48
|
-
transform: import("kaplay").Mat4;
|
|
49
|
-
hidden: boolean;
|
|
50
|
-
paused: boolean;
|
|
51
|
-
id: import("kaplay").GameObjID | null;
|
|
52
|
-
canvas: import("kaplay").FrameBuffer | null;
|
|
53
|
-
onKeyDown: KAPLAYCtx["onKeyDown"];
|
|
54
|
-
onKeyPress: KAPLAYCtx["onKeyPress"];
|
|
55
|
-
onKeyPressRepeat: KAPLAYCtx["onKeyPressRepeat"];
|
|
56
|
-
onKeyRelease: KAPLAYCtx["onKeyRelease"];
|
|
57
|
-
onCharInput: KAPLAYCtx["onCharInput"];
|
|
58
|
-
onMouseDown: KAPLAYCtx["onMouseDown"];
|
|
59
|
-
onMousePress: KAPLAYCtx["onMousePress"];
|
|
60
|
-
onMouseRelease: KAPLAYCtx["onMouseRelease"];
|
|
61
|
-
onMouseMove: KAPLAYCtx["onMouseMove"];
|
|
62
|
-
onTouchStart: KAPLAYCtx["onTouchStart"];
|
|
63
|
-
onTouchMove: KAPLAYCtx["onTouchMove"];
|
|
64
|
-
onTouchEnd: KAPLAYCtx["onTouchEnd"];
|
|
65
|
-
onScroll: KAPLAYCtx["onScroll"];
|
|
66
|
-
onGamepadButtonDown: KAPLAYCtx["onGamepadButtonDown"];
|
|
67
|
-
onGamepadButtonPress: KAPLAYCtx["onGamepadButtonPress"];
|
|
68
|
-
onGamepadButtonRelease: KAPLAYCtx["onGamepadButtonRelease"];
|
|
69
|
-
onGamepadStick: KAPLAYCtx["onGamepadStick"];
|
|
70
|
-
onButtonDown: KAPLAYCtx["onButtonDown"];
|
|
71
|
-
onButtonPress: KAPLAYCtx["onButtonPress"];
|
|
72
|
-
onButtonRelease: KAPLAYCtx["onButtonRelease"];
|
|
73
|
-
radius?: number | [number, number, number, number] | undefined;
|
|
74
|
-
width: number;
|
|
75
|
-
height: number;
|
|
76
|
-
renderArea: () => import("kaplay").Rect;
|
|
77
|
-
pos: Vec2;
|
|
78
|
-
move: {
|
|
79
|
-
(xVel: number, yVel: number): void;
|
|
80
|
-
(vel: Vec2): void;
|
|
81
|
-
};
|
|
82
|
-
moveBy: {
|
|
83
|
-
(dx: number, dy: number): void;
|
|
84
|
-
(d: Vec2): void;
|
|
85
|
-
};
|
|
86
|
-
moveTo: {
|
|
87
|
-
(dest: Vec2, speed?: number): void;
|
|
88
|
-
(x: number, y: number, speed?: number): void;
|
|
89
|
-
};
|
|
90
|
-
screenPos: (newPos?: Vec2) => Vec2 | null;
|
|
91
|
-
worldPos: (newPos?: Vec2) => Vec2 | null;
|
|
92
|
-
toScreen: (this: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").FixedComp>, p: Vec2) => Vec2;
|
|
93
|
-
toWorld: (this: import("kaplay").GameObj<import("kaplay").PosComp>, p: Vec2) => Vec2;
|
|
94
|
-
fromScreen: (this: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").FixedComp>, p: Vec2) => Vec2;
|
|
95
|
-
fromWorld: (this: import("kaplay").GameObj<import("kaplay").PosComp>, p: Vec2) => Vec2;
|
|
96
|
-
toOther: (this: import("kaplay").GameObj<import("kaplay").PosComp>, other: import("kaplay").GameObj<import("kaplay").PosComp>, p: Vec2) => Vec2;
|
|
97
|
-
fromOther: (this: import("kaplay").GameObj<import("kaplay").PosComp>, other: import("kaplay").GameObj<import("kaplay").PosComp>, p: Vec2) => Vec2;
|
|
98
|
-
area: {
|
|
99
|
-
shape: import("kaplay").Shape | null;
|
|
100
|
-
scale: Vec2;
|
|
101
|
-
offset: Vec2;
|
|
102
|
-
cursor: import("kaplay").Cursor | null;
|
|
103
|
-
};
|
|
104
|
-
collisionIgnore: import("kaplay").Tag[];
|
|
105
|
-
isClicked: () => boolean;
|
|
106
|
-
isHovering: () => boolean;
|
|
107
|
-
checkCollision: (other: import("kaplay").GameObj<import("kaplay").AreaComp>) => import("kaplay").Collision | null;
|
|
108
|
-
getCollisions: () => import("kaplay").Collision[];
|
|
109
|
-
isColliding: (o: import("kaplay").GameObj<import("kaplay").AreaComp>) => boolean;
|
|
110
|
-
isOverlapping: (o: import("kaplay").GameObj<import("kaplay").AreaComp>) => boolean;
|
|
111
|
-
onClick: (f: () => void, btn?: import("kaplay").MouseButton) => import("kaplay").KEventController;
|
|
112
|
-
onHover: (action: () => void) => import("kaplay").KEventController;
|
|
113
|
-
onHoverUpdate: (action: () => void) => import("kaplay").KEventController;
|
|
114
|
-
onHoverEnd: (action: () => void) => import("kaplay").KEventController;
|
|
115
|
-
onCollide: {
|
|
116
|
-
(tag: import("kaplay").Tag, f: (obj: import("kaplay").GameObj, col?: import("kaplay").Collision) => void): import("kaplay").KEventController;
|
|
117
|
-
(f: (obj: import("kaplay").GameObj, col?: import("kaplay").Collision) => void): import("kaplay").KEventController;
|
|
118
|
-
};
|
|
119
|
-
onCollideUpdate: {
|
|
120
|
-
(tag: import("kaplay").Tag, f: (obj: import("kaplay").GameObj, col?: import("kaplay").Collision) => import("kaplay").KEventController): import("kaplay").KEventController;
|
|
121
|
-
(f: (obj: import("kaplay").GameObj, col?: import("kaplay").Collision) => void): import("kaplay").KEventController;
|
|
122
|
-
};
|
|
123
|
-
onCollideEnd: {
|
|
124
|
-
(tag: import("kaplay").Tag, f: (obj: import("kaplay").GameObj) => void): import("kaplay").KEventController;
|
|
125
|
-
(f: (obj: import("kaplay").GameObj) => void): void;
|
|
126
|
-
};
|
|
127
|
-
hasPoint: (p: Vec2) => boolean;
|
|
128
|
-
resolveCollision: (obj: import("kaplay").GameObj) => void;
|
|
129
|
-
localArea: () => import("kaplay").Shape;
|
|
130
|
-
worldArea: () => import("kaplay").Polygon;
|
|
131
|
-
screenArea: () => import("kaplay").Polygon;
|
|
132
|
-
scale: Vec2;
|
|
133
|
-
scaleTo: {
|
|
134
|
-
(s: number): void;
|
|
135
|
-
(s: Vec2): void;
|
|
136
|
-
(sx: number, sy: number): void;
|
|
137
|
-
};
|
|
138
|
-
scaleBy: {
|
|
139
|
-
(s: number): void;
|
|
140
|
-
(s: Vec2): void;
|
|
141
|
-
(sx: number, sy: number): void;
|
|
142
|
-
};
|
|
143
|
-
color: Color;
|
|
144
|
-
anchor: import("kaplay").Anchor | Vec2;
|
|
9
|
+
}): import("kaplay").GameObjRaw & import("kaplay").MergeComps<import("kaplay").RectComp | import("kaplay").PosComp | import("kaplay").AreaComp | import("kaplay").ScaleComp | import("kaplay").ColorComp | import("kaplay").AnchorComp> & {
|
|
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>;
|
|
145
13
|
};
|
|
14
|
+
type SpriteSource = string | SpriteData | Asset<SpriteData>;
|
|
146
15
|
declare module 'kaplay' {
|
|
147
16
|
interface KAPLAYCtx {
|
|
148
17
|
addButton: AddButton;
|
|
@@ -215,6 +84,18 @@ interface ButtonOpt {
|
|
|
215
84
|
radius?: number;
|
|
216
85
|
hoverFill?: string | Color;
|
|
217
86
|
pressedFill?: string | Color;
|
|
87
|
+
/** Sprite drawn beside the label. */
|
|
88
|
+
icon?: SpriteSource;
|
|
89
|
+
/** Sprite drawn beside the label on hover. */
|
|
90
|
+
hoverIcon?: SpriteSource;
|
|
91
|
+
/** Sprite drawn beside the label when pressed. */
|
|
92
|
+
pressedIcon?: SpriteSource;
|
|
93
|
+
/** Sprite drawn behind the label. */
|
|
94
|
+
background?: SpriteSource;
|
|
95
|
+
/** Sprite drawn behind the label on hover. */
|
|
96
|
+
hoverBackground?: SpriteSource;
|
|
97
|
+
/** Sprite drawn behind the label when pressed. */
|
|
98
|
+
pressedBackground?: SpriteSource;
|
|
218
99
|
}
|
|
219
100
|
/** KAPLAY UI plugin. */
|
|
220
101
|
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;qBACtB,8BAAA,4BAAA,4BAAA,2BAAA,4BAAA,6BAAA,6BAAA;;;;;KCZE,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;
|
|
@@ -6,143 +6,12 @@ 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
|
-
}): {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
remove(obj: import("kaplay").GameObj): void;
|
|
14
|
-
removeAll(tag: import("kaplay").Tag): void;
|
|
15
|
-
removeAll(): void;
|
|
16
|
-
get(tag: import("kaplay").Tag | import("kaplay").Tag[], opts?: import("kaplay").GetOpt): import("kaplay").GameObj[];
|
|
17
|
-
query(opt: import("kaplay").QueryOpt): import("kaplay").GameObj[];
|
|
18
|
-
parent: import("kaplay").GameObj | null;
|
|
19
|
-
children: import("kaplay").GameObj[];
|
|
20
|
-
tags: string[];
|
|
21
|
-
fixedUpdate(): void;
|
|
22
|
-
update(): void;
|
|
23
|
-
draw(): void;
|
|
24
|
-
drawInspect: () => void;
|
|
25
|
-
clearEvents: () => void;
|
|
26
|
-
use(comp: import("kaplay").Comp | import("kaplay").Tag): void;
|
|
27
|
-
unuse(comp: import("kaplay").Tag): void;
|
|
28
|
-
has(compId: string | string[], op?: "and" | "or"): boolean;
|
|
29
|
-
tag(tag: import("kaplay").Tag | import("kaplay").Tag[]): void;
|
|
30
|
-
untag(tag: import("kaplay").Tag | import("kaplay").Tag[]): void;
|
|
31
|
-
is(tag: import("kaplay").Tag | import("kaplay").Tag[], op?: "and" | "or"): boolean;
|
|
32
|
-
on(event: import("kaplay").GameObjEventNames | (string & {}), action: (...args: any) => void): import("kaplay").KEventController;
|
|
33
|
-
trigger(event: string, ...args: any): void;
|
|
34
|
-
destroy(): void;
|
|
35
|
-
c(id: string): import("kaplay").Comp | null;
|
|
36
|
-
inspect(): import("kaplay").GameObjInspect;
|
|
37
|
-
onAdd(action: () => void): import("kaplay").KEventController;
|
|
38
|
-
onUpdate(action: () => void): import("kaplay").KEventController;
|
|
39
|
-
onFixedUpdate(action: () => void): import("kaplay").KEventController;
|
|
40
|
-
onDraw(action: () => void): import("kaplay").KEventController;
|
|
41
|
-
onDestroy(action: () => void): import("kaplay").KEventController;
|
|
42
|
-
onUse(action: (id: string) => void): import("kaplay").KEventController;
|
|
43
|
-
onUnuse(action: (id: string) => void): import("kaplay").KEventController;
|
|
44
|
-
onTag(action: (tag: string) => void): import("kaplay").KEventController;
|
|
45
|
-
onUntag(action: (tag: string) => void): import("kaplay").KEventController;
|
|
46
|
-
exists(): boolean;
|
|
47
|
-
isAncestorOf(obj: import("kaplay").GameObj): boolean;
|
|
48
|
-
transform: import("kaplay").Mat4;
|
|
49
|
-
hidden: boolean;
|
|
50
|
-
paused: boolean;
|
|
51
|
-
id: import("kaplay").GameObjID | null;
|
|
52
|
-
canvas: import("kaplay").FrameBuffer | null;
|
|
53
|
-
onKeyDown: KAPLAYCtx["onKeyDown"];
|
|
54
|
-
onKeyPress: KAPLAYCtx["onKeyPress"];
|
|
55
|
-
onKeyPressRepeat: KAPLAYCtx["onKeyPressRepeat"];
|
|
56
|
-
onKeyRelease: KAPLAYCtx["onKeyRelease"];
|
|
57
|
-
onCharInput: KAPLAYCtx["onCharInput"];
|
|
58
|
-
onMouseDown: KAPLAYCtx["onMouseDown"];
|
|
59
|
-
onMousePress: KAPLAYCtx["onMousePress"];
|
|
60
|
-
onMouseRelease: KAPLAYCtx["onMouseRelease"];
|
|
61
|
-
onMouseMove: KAPLAYCtx["onMouseMove"];
|
|
62
|
-
onTouchStart: KAPLAYCtx["onTouchStart"];
|
|
63
|
-
onTouchMove: KAPLAYCtx["onTouchMove"];
|
|
64
|
-
onTouchEnd: KAPLAYCtx["onTouchEnd"];
|
|
65
|
-
onScroll: KAPLAYCtx["onScroll"];
|
|
66
|
-
onGamepadButtonDown: KAPLAYCtx["onGamepadButtonDown"];
|
|
67
|
-
onGamepadButtonPress: KAPLAYCtx["onGamepadButtonPress"];
|
|
68
|
-
onGamepadButtonRelease: KAPLAYCtx["onGamepadButtonRelease"];
|
|
69
|
-
onGamepadStick: KAPLAYCtx["onGamepadStick"];
|
|
70
|
-
onButtonDown: KAPLAYCtx["onButtonDown"];
|
|
71
|
-
onButtonPress: KAPLAYCtx["onButtonPress"];
|
|
72
|
-
onButtonRelease: KAPLAYCtx["onButtonRelease"];
|
|
73
|
-
radius?: number | [number, number, number, number] | undefined;
|
|
74
|
-
width: number;
|
|
75
|
-
height: number;
|
|
76
|
-
renderArea: () => import("kaplay").Rect;
|
|
77
|
-
pos: Vec2;
|
|
78
|
-
move: {
|
|
79
|
-
(xVel: number, yVel: number): void;
|
|
80
|
-
(vel: Vec2): void;
|
|
81
|
-
};
|
|
82
|
-
moveBy: {
|
|
83
|
-
(dx: number, dy: number): void;
|
|
84
|
-
(d: Vec2): void;
|
|
85
|
-
};
|
|
86
|
-
moveTo: {
|
|
87
|
-
(dest: Vec2, speed?: number): void;
|
|
88
|
-
(x: number, y: number, speed?: number): void;
|
|
89
|
-
};
|
|
90
|
-
screenPos: (newPos?: Vec2) => Vec2 | null;
|
|
91
|
-
worldPos: (newPos?: Vec2) => Vec2 | null;
|
|
92
|
-
toScreen: (this: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").FixedComp>, p: Vec2) => Vec2;
|
|
93
|
-
toWorld: (this: import("kaplay").GameObj<import("kaplay").PosComp>, p: Vec2) => Vec2;
|
|
94
|
-
fromScreen: (this: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").FixedComp>, p: Vec2) => Vec2;
|
|
95
|
-
fromWorld: (this: import("kaplay").GameObj<import("kaplay").PosComp>, p: Vec2) => Vec2;
|
|
96
|
-
toOther: (this: import("kaplay").GameObj<import("kaplay").PosComp>, other: import("kaplay").GameObj<import("kaplay").PosComp>, p: Vec2) => Vec2;
|
|
97
|
-
fromOther: (this: import("kaplay").GameObj<import("kaplay").PosComp>, other: import("kaplay").GameObj<import("kaplay").PosComp>, p: Vec2) => Vec2;
|
|
98
|
-
area: {
|
|
99
|
-
shape: import("kaplay").Shape | null;
|
|
100
|
-
scale: Vec2;
|
|
101
|
-
offset: Vec2;
|
|
102
|
-
cursor: import("kaplay").Cursor | null;
|
|
103
|
-
};
|
|
104
|
-
collisionIgnore: import("kaplay").Tag[];
|
|
105
|
-
isClicked: () => boolean;
|
|
106
|
-
isHovering: () => boolean;
|
|
107
|
-
checkCollision: (other: import("kaplay").GameObj<import("kaplay").AreaComp>) => import("kaplay").Collision | null;
|
|
108
|
-
getCollisions: () => import("kaplay").Collision[];
|
|
109
|
-
isColliding: (o: import("kaplay").GameObj<import("kaplay").AreaComp>) => boolean;
|
|
110
|
-
isOverlapping: (o: import("kaplay").GameObj<import("kaplay").AreaComp>) => boolean;
|
|
111
|
-
onClick: (f: () => void, btn?: import("kaplay").MouseButton) => import("kaplay").KEventController;
|
|
112
|
-
onHover: (action: () => void) => import("kaplay").KEventController;
|
|
113
|
-
onHoverUpdate: (action: () => void) => import("kaplay").KEventController;
|
|
114
|
-
onHoverEnd: (action: () => void) => import("kaplay").KEventController;
|
|
115
|
-
onCollide: {
|
|
116
|
-
(tag: import("kaplay").Tag, f: (obj: import("kaplay").GameObj, col?: import("kaplay").Collision) => void): import("kaplay").KEventController;
|
|
117
|
-
(f: (obj: import("kaplay").GameObj, col?: import("kaplay").Collision) => void): import("kaplay").KEventController;
|
|
118
|
-
};
|
|
119
|
-
onCollideUpdate: {
|
|
120
|
-
(tag: import("kaplay").Tag, f: (obj: import("kaplay").GameObj, col?: import("kaplay").Collision) => import("kaplay").KEventController): import("kaplay").KEventController;
|
|
121
|
-
(f: (obj: import("kaplay").GameObj, col?: import("kaplay").Collision) => void): import("kaplay").KEventController;
|
|
122
|
-
};
|
|
123
|
-
onCollideEnd: {
|
|
124
|
-
(tag: import("kaplay").Tag, f: (obj: import("kaplay").GameObj) => void): import("kaplay").KEventController;
|
|
125
|
-
(f: (obj: import("kaplay").GameObj) => void): void;
|
|
126
|
-
};
|
|
127
|
-
hasPoint: (p: Vec2) => boolean;
|
|
128
|
-
resolveCollision: (obj: import("kaplay").GameObj) => void;
|
|
129
|
-
localArea: () => import("kaplay").Shape;
|
|
130
|
-
worldArea: () => import("kaplay").Polygon;
|
|
131
|
-
screenArea: () => import("kaplay").Polygon;
|
|
132
|
-
scale: Vec2;
|
|
133
|
-
scaleTo: {
|
|
134
|
-
(s: number): void;
|
|
135
|
-
(s: Vec2): void;
|
|
136
|
-
(sx: number, sy: number): void;
|
|
137
|
-
};
|
|
138
|
-
scaleBy: {
|
|
139
|
-
(s: number): void;
|
|
140
|
-
(s: Vec2): void;
|
|
141
|
-
(sx: number, sy: number): void;
|
|
142
|
-
};
|
|
143
|
-
color: Color;
|
|
144
|
-
anchor: import("kaplay").Anchor | Vec2;
|
|
9
|
+
}): import("kaplay").GameObjRaw & import("kaplay").MergeComps<import("kaplay").RectComp | import("kaplay").PosComp | import("kaplay").AreaComp | import("kaplay").ScaleComp | import("kaplay").ColorComp | import("kaplay").AnchorComp> & {
|
|
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>;
|
|
145
13
|
};
|
|
14
|
+
type SpriteSource = string | SpriteData | Asset<SpriteData>;
|
|
146
15
|
declare module 'kaplay' {
|
|
147
16
|
interface KAPLAYCtx {
|
|
148
17
|
addButton: AddButton;
|
|
@@ -215,6 +84,18 @@ interface ButtonOpt {
|
|
|
215
84
|
radius?: number;
|
|
216
85
|
hoverFill?: string | Color;
|
|
217
86
|
pressedFill?: string | Color;
|
|
87
|
+
/** Sprite drawn beside the label. */
|
|
88
|
+
icon?: SpriteSource;
|
|
89
|
+
/** Sprite drawn beside the label on hover. */
|
|
90
|
+
hoverIcon?: SpriteSource;
|
|
91
|
+
/** Sprite drawn beside the label when pressed. */
|
|
92
|
+
pressedIcon?: SpriteSource;
|
|
93
|
+
/** Sprite drawn behind the label. */
|
|
94
|
+
background?: SpriteSource;
|
|
95
|
+
/** Sprite drawn behind the label on hover. */
|
|
96
|
+
hoverBackground?: SpriteSource;
|
|
97
|
+
/** Sprite drawn behind the label when pressed. */
|
|
98
|
+
pressedBackground?: SpriteSource;
|
|
218
99
|
}
|
|
219
100
|
/** KAPLAY UI plugin. */
|
|
220
101
|
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;qBACtB,8BAAA,4BAAA,4BAAA,2BAAA,4BAAA,6BAAA,6BAAA;;;;;KCZE,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
|
@@ -24,47 +24,120 @@ function createButton(k, theme, text, opt) {
|
|
|
24
24
|
size,
|
|
25
25
|
font
|
|
26
26
|
});
|
|
27
|
-
const
|
|
28
|
-
const
|
|
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 };
|
|
29
29
|
const button = k.add([
|
|
30
|
-
k.rect(
|
|
30
|
+
k.rect(0, 0, rectOpt),
|
|
31
31
|
k.pos(pos),
|
|
32
32
|
k.area(),
|
|
33
33
|
k.scale(),
|
|
34
34
|
k.color(baseColor),
|
|
35
35
|
k.anchor("center")
|
|
36
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;
|
|
37
47
|
const label = button.add([
|
|
38
48
|
textComp,
|
|
39
49
|
k.color(textColor),
|
|
50
|
+
k.pos(),
|
|
40
51
|
k.anchor("center")
|
|
41
52
|
]);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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);
|
|
45
85
|
if (button.width !== nextWidth) button.width = nextWidth;
|
|
46
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);
|
|
47
94
|
}
|
|
48
|
-
|
|
49
|
-
if (opt.width === void 0 || opt.height === void 0) button.onUpdate(
|
|
95
|
+
layout();
|
|
96
|
+
if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
|
|
50
97
|
button.onClick(() => {
|
|
51
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
|
+
}
|
|
52
107
|
opt.onClick?.();
|
|
53
108
|
});
|
|
54
109
|
button.onHover(() => {
|
|
55
110
|
button.color = toColor(k, hoverColor);
|
|
56
111
|
button.scale = k.vec2(1.05, 1.05);
|
|
57
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
|
+
}
|
|
58
121
|
});
|
|
59
122
|
button.onHoverEnd(() => {
|
|
60
123
|
button.color = baseColor;
|
|
61
124
|
button.scale = k.vec2(1, 1);
|
|
62
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();
|
|
63
135
|
});
|
|
64
|
-
return {
|
|
65
|
-
|
|
136
|
+
return Object.assign(button, {
|
|
137
|
+
background,
|
|
138
|
+
icon,
|
|
66
139
|
label
|
|
67
|
-
};
|
|
140
|
+
});
|
|
68
141
|
}
|
|
69
142
|
// src/theme.ts
|
|
70
143
|
const PRIMARY = "#4f8cff";
|
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 width = opt.width ?? Math.max(textComp.width + paddingX * 2, minWidth);\n const height = opt.height ?? textComp.height + paddingY * 2;\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 textComp,\n k.color(textColor as never),\n k.anchor('center'),\n ]);\n\n function resize() {\n const nextWidth =\n opt.width ?? Math.max(label.width + paddingX * 2, minWidth);\n const nextHeight = opt.height ?? label.height + paddingY * 2;\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n }\n\n resize();\n\n if (opt.width === undefined || opt.height === undefined) {\n button.onUpdate(resize);\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 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/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 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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"}
|
package/dist/plugin.umd.js
CHANGED
|
@@ -28,47 +28,120 @@
|
|
|
28
28
|
size,
|
|
29
29
|
font
|
|
30
30
|
});
|
|
31
|
-
const
|
|
32
|
-
const
|
|
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 };
|
|
33
33
|
const button = k.add([
|
|
34
|
-
k.rect(
|
|
34
|
+
k.rect(0, 0, rectOpt),
|
|
35
35
|
k.pos(pos),
|
|
36
36
|
k.area(),
|
|
37
37
|
k.scale(),
|
|
38
38
|
k.color(baseColor),
|
|
39
39
|
k.anchor("center")
|
|
40
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;
|
|
41
51
|
const label = button.add([
|
|
42
52
|
textComp,
|
|
43
53
|
k.color(textColor),
|
|
54
|
+
k.pos(),
|
|
44
55
|
k.anchor("center")
|
|
45
56
|
]);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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);
|
|
49
89
|
if (button.width !== nextWidth) button.width = nextWidth;
|
|
50
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);
|
|
51
98
|
}
|
|
52
|
-
|
|
53
|
-
if (opt.width === void 0 || opt.height === void 0) button.onUpdate(
|
|
99
|
+
layout();
|
|
100
|
+
if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
|
|
54
101
|
button.onClick(() => {
|
|
55
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
|
+
}
|
|
56
111
|
opt.onClick?.();
|
|
57
112
|
});
|
|
58
113
|
button.onHover(() => {
|
|
59
114
|
button.color = toColor(k, hoverColor);
|
|
60
115
|
button.scale = k.vec2(1.05, 1.05);
|
|
61
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
|
+
}
|
|
62
125
|
});
|
|
63
126
|
button.onHoverEnd(() => {
|
|
64
127
|
button.color = baseColor;
|
|
65
128
|
button.scale = k.vec2(1, 1);
|
|
66
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();
|
|
67
139
|
});
|
|
68
|
-
return {
|
|
69
|
-
|
|
140
|
+
return Object.assign(button, {
|
|
141
|
+
background,
|
|
142
|
+
icon,
|
|
70
143
|
label
|
|
71
|
-
};
|
|
144
|
+
});
|
|
72
145
|
}
|
|
73
146
|
// src/theme.ts
|
|
74
147
|
const PRIMARY = "#4f8cff";
|
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 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 width = opt.width ?? Math.max(textComp.width + paddingX * 2, minWidth);\n const height = opt.height ?? textComp.height + paddingY * 2;\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 textComp,\n k.color(textColor as never),\n k.anchor('center'),\n ]);\n\n function resize() {\n const nextWidth =\n opt.width ?? Math.max(label.width + paddingX * 2, minWidth);\n const nextHeight = opt.height ?? label.height + paddingY * 2;\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n }\n\n resize();\n\n if (opt.width === undefined || opt.height === undefined) {\n button.onUpdate(resize);\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 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.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 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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"}
|
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.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.
|
|
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()}),Object.assign(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","radius","opt","theme","baseColor","hoverColor","pressedColor","textColor","size","font","pos","textComp","text","width","paddingX","minWidth","height","paddingY","button","label","resize","nextWidth","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 width = opt.width ?? Math.max(textComp.width + paddingX * 2, minWidth);\n const height = opt.height ?? textComp.height + paddingY * 2;\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 textComp,\n k.color(textColor as never),\n k.anchor('center'),\n ]);\n\n function resize() {\n const nextWidth =\n opt.width ?? Math.max(label.width + paddingX * 2, minWidth);\n const nextHeight = opt.height ?? label.height + paddingY * 2;\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n }\n\n resize();\n\n if (opt.width === undefined || opt.height === undefined) {\n button.onUpdate(resize);\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 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.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 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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"}
|