@aethervtt/shard-ui 0.0.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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/components.d.ts +144 -0
- package/dist/components.js +355 -0
- package/dist/flex.d.ts +107 -0
- package/dist/flex.js +751 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +133 -0
- package/dist/interaction.d.ts +44 -0
- package/dist/interaction.js +416 -0
- package/dist/length.d.ts +17 -0
- package/dist/length.js +92 -0
- package/dist/methods.d.ts +52 -0
- package/dist/methods.js +187 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +40 -0
- package/dist/render.d.ts +76 -0
- package/dist/render.js +715 -0
- package/dist/shaders.d.ts +2 -0
- package/dist/shaders.js +124 -0
- package/dist/style.d.ts +45 -0
- package/dist/style.js +111 -0
- package/dist/testing.d.ts +20 -0
- package/dist/testing.js +68 -0
- package/dist/theme.d.ts +30 -0
- package/dist/theme.js +172 -0
- package/dist/tree.d.ts +172 -0
- package/dist/tree.js +1109 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Dias
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { type AssetRef, type Entity, type Infer } from '@aethervtt/shard-core';
|
|
2
|
+
export declare const UI_DISPLAYS: readonly ['flex', 'none'];
|
|
3
|
+
export declare const UI_POSITIONS: readonly ['relative', 'absolute'];
|
|
4
|
+
export declare const UI_DIRECTIONS: readonly ['row', 'column', 'row-reverse', 'column-reverse'];
|
|
5
|
+
export declare const UI_JUSTIFY: readonly ['start', 'center', 'end', 'space-between', 'space-around', 'space-evenly'];
|
|
6
|
+
export declare const UI_ALIGN: readonly ['stretch', 'start', 'center', 'end'];
|
|
7
|
+
export declare const UI_ALIGN_SELF: readonly ['auto', 'stretch', 'start', 'center', 'end'];
|
|
8
|
+
export declare const UI_OVERFLOW: readonly ['visible', 'clip', 'scroll'];
|
|
9
|
+
export declare const UI_TEXT_ALIGN: readonly ['start', 'center', 'end'];
|
|
10
|
+
export declare const UI_FIT: readonly ['fill', 'contain', 'cover'];
|
|
11
|
+
export declare const UI_SCALES: readonly ['none', 'fit-height', 'fit-width'];
|
|
12
|
+
export declare const UI_STATES: readonly ['none', 'hovered', 'pressed', 'disabled'];
|
|
13
|
+
export declare const UI_ANCHOR_STATES: readonly ['none', 'on-screen', 'clamped', 'hidden'];
|
|
14
|
+
/** Computed by layout: the node's rect and what clips it. Managed by the UI plugin. */
|
|
15
|
+
export declare const UiLayout: import("@aethervtt/shard-core").ComponentDef<{
|
|
16
|
+
readonly x: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
17
|
+
readonly y: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
18
|
+
readonly width: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
19
|
+
readonly height: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
20
|
+
readonly clip: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32">;
|
|
21
|
+
readonly content: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
|
|
22
|
+
readonly scale: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
23
|
+
readonly angle: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
24
|
+
readonly visible: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
25
|
+
readonly anchor: import("@aethervtt/shard-core").FieldType<"clamped" | "hidden" | "none" | "on-screen", "u8">;
|
|
26
|
+
readonly distance: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
27
|
+
}>;
|
|
28
|
+
export declare const UiNode: import("@aethervtt/shard-core").ComponentDef<{
|
|
29
|
+
readonly display: import("@aethervtt/shard-core").FieldType<"flex" | "none", "u8">;
|
|
30
|
+
readonly position: import("@aethervtt/shard-core").FieldType<"absolute" | "relative", "u8">;
|
|
31
|
+
readonly direction: import("@aethervtt/shard-core").FieldType<"column" | "column-reverse" | "row" | "row-reverse", "u8">;
|
|
32
|
+
readonly wrap: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
33
|
+
readonly justify: import("@aethervtt/shard-core").FieldType<"center" | "end" | "space-around" | "space-between" | "space-evenly" | "start", "u8">;
|
|
34
|
+
readonly alignItems: import("@aethervtt/shard-core").FieldType<"center" | "end" | "start" | "stretch", "u8">;
|
|
35
|
+
readonly alignSelf: import("@aethervtt/shard-core").FieldType<"auto" | "center" | "end" | "start" | "stretch", "u8">;
|
|
36
|
+
readonly width: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
37
|
+
readonly height: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
38
|
+
readonly minWidth: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
39
|
+
readonly maxWidth: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
40
|
+
readonly minHeight: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
41
|
+
readonly maxHeight: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
42
|
+
readonly left: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
43
|
+
readonly top: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
44
|
+
readonly right: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
45
|
+
readonly bottom: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
46
|
+
readonly padding: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32">;
|
|
47
|
+
readonly margin: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32">;
|
|
48
|
+
readonly gap: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
|
|
49
|
+
readonly grow: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
50
|
+
readonly shrink: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
51
|
+
readonly basis: import("@aethervtt/shard-core").FieldType<import("./length.js").UiLength, "f32">;
|
|
52
|
+
readonly order: import("@aethervtt/shard-core").FieldType<number, "i16">;
|
|
53
|
+
readonly zIndex: import("@aethervtt/shard-core").FieldType<number, "i16">;
|
|
54
|
+
readonly overflow: import("@aethervtt/shard-core").FieldType<"clip" | "scroll" | "visible", "u8">;
|
|
55
|
+
readonly scroll: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
|
|
56
|
+
readonly style: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
57
|
+
}>;
|
|
58
|
+
export declare const UiRoot: import("@aethervtt/shard-core").ComponentDef<{
|
|
59
|
+
readonly scale: import("@aethervtt/shard-core").FieldType<"fit-height" | "fit-width" | "none", "u8">;
|
|
60
|
+
readonly referenceSize: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
|
|
61
|
+
readonly camera: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
|
|
62
|
+
readonly theme: import("@aethervtt/shard-core").FieldType<AssetRef<"UiTheme"> | null, "object">;
|
|
63
|
+
readonly order: import("@aethervtt/shard-core").FieldType<number, "i16">;
|
|
64
|
+
}>;
|
|
65
|
+
export declare const UiStyle: import("@aethervtt/shard-core").ComponentDef<{
|
|
66
|
+
readonly background: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
67
|
+
readonly borderColor: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
68
|
+
readonly borderWidth: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
69
|
+
readonly radius: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32">;
|
|
70
|
+
readonly opacity: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
71
|
+
}>;
|
|
72
|
+
export declare const UiText: import("@aethervtt/shard-core").ComponentDef<{
|
|
73
|
+
readonly text: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
74
|
+
readonly key: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
75
|
+
readonly params: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").JsonValue, "object">;
|
|
76
|
+
readonly size: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
77
|
+
readonly color: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
78
|
+
readonly font: import("@aethervtt/shard-core").FieldType<AssetRef<"Font"> | null, "object">;
|
|
79
|
+
readonly align: import("@aethervtt/shard-core").FieldType<"center" | "end" | "start", "u8">;
|
|
80
|
+
readonly wrap: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
81
|
+
readonly lineHeight: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
82
|
+
}>;
|
|
83
|
+
export declare const UiImage: import("@aethervtt/shard-core").ComponentDef<{
|
|
84
|
+
readonly texture: import("@aethervtt/shard-core").FieldType<AssetRef<"Texture"> | null, "object">;
|
|
85
|
+
readonly atlas: import("@aethervtt/shard-core").FieldType<AssetRef<"TextureAtlas"> | null, "object">;
|
|
86
|
+
readonly region: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
87
|
+
readonly slice: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32">;
|
|
88
|
+
readonly tint: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
89
|
+
readonly fit: import("@aethervtt/shard-core").FieldType<"contain" | "cover" | "fill", "u8">;
|
|
90
|
+
}>;
|
|
91
|
+
export declare const UiInteraction: import("@aethervtt/shard-core").ComponentDef<{
|
|
92
|
+
readonly state: import("@aethervtt/shard-core").FieldType<"disabled" | "hovered" | "none" | "pressed", "u8">;
|
|
93
|
+
readonly focused: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
94
|
+
}>;
|
|
95
|
+
export declare const UiButton: import("@aethervtt/shard-core").ComponentDef<{
|
|
96
|
+
readonly disabled: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
97
|
+
}>;
|
|
98
|
+
export declare const UiToggle: import("@aethervtt/shard-core").ComponentDef<{
|
|
99
|
+
readonly on: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
100
|
+
readonly disabled: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
101
|
+
}>;
|
|
102
|
+
export declare const UiSlider: import("@aethervtt/shard-core").ComponentDef<{
|
|
103
|
+
readonly value: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
104
|
+
readonly min: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
105
|
+
readonly max: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
106
|
+
readonly step: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
107
|
+
readonly fill: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
108
|
+
readonly disabled: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
109
|
+
}>;
|
|
110
|
+
export declare const UiTextInput: import("@aethervtt/shard-core").ComponentDef<{
|
|
111
|
+
readonly value: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
112
|
+
readonly placeholder: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
113
|
+
readonly maxLength: import("@aethervtt/shard-core").FieldType<number, "u16">;
|
|
114
|
+
readonly disabled: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
115
|
+
}>;
|
|
116
|
+
export declare const UiAnchor: import("@aethervtt/shard-core").ComponentDef<{
|
|
117
|
+
readonly target: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
|
|
118
|
+
readonly offset: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
|
|
119
|
+
readonly screenOffset: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
|
|
120
|
+
readonly pivot: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
|
|
121
|
+
readonly clamp: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
122
|
+
readonly margin: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
123
|
+
readonly hideBehind: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
124
|
+
readonly scaleDistance: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
125
|
+
readonly minScale: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
126
|
+
readonly maxScale: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
127
|
+
}>;
|
|
128
|
+
export declare const UiAnchorArrow: import("@aethervtt/shard-core").TagDef;
|
|
129
|
+
export interface UiEntityEvent {
|
|
130
|
+
entity: Entity;
|
|
131
|
+
}
|
|
132
|
+
export declare const UiClick: import("@aethervtt/shard-core").EventDef<UiEntityEvent>;
|
|
133
|
+
export declare const UiChanged: import("@aethervtt/shard-core").EventDef<UiEntityEvent>;
|
|
134
|
+
export declare const UiHover: import("@aethervtt/shard-core").EventDef<UiEntityEvent & {
|
|
135
|
+
hovered: boolean;
|
|
136
|
+
}>;
|
|
137
|
+
export interface UiDefaultsValue {
|
|
138
|
+
/** Font for text without one (and no theme font). */
|
|
139
|
+
font: AssetRef<'Font'> | null;
|
|
140
|
+
}
|
|
141
|
+
export declare const UiDefaults: import("@aethervtt/shard-core").ResourceDef<UiDefaultsValue>;
|
|
142
|
+
export type UiNodeValue = Infer<typeof UiNode>;
|
|
143
|
+
export type UiStyleValue = Infer<typeof UiStyle>;
|
|
144
|
+
export type UiTextValue = Infer<typeof UiText>;
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
defineEvent,
|
|
4
|
+
defineResource,
|
|
5
|
+
defineTag,
|
|
6
|
+
t
|
|
7
|
+
} from "@aethervtt/shard-core";
|
|
8
|
+
import { uiLength } from "./length.js";
|
|
9
|
+
const UI_DISPLAYS = ["flex", "none"];
|
|
10
|
+
const UI_POSITIONS = ["relative", "absolute"];
|
|
11
|
+
const UI_DIRECTIONS = ["row", "column", "row-reverse", "column-reverse"];
|
|
12
|
+
const UI_JUSTIFY = [
|
|
13
|
+
"start",
|
|
14
|
+
"center",
|
|
15
|
+
"end",
|
|
16
|
+
"space-between",
|
|
17
|
+
"space-around",
|
|
18
|
+
"space-evenly"
|
|
19
|
+
];
|
|
20
|
+
const UI_ALIGN = ["stretch", "start", "center", "end"];
|
|
21
|
+
const UI_ALIGN_SELF = ["auto", "stretch", "start", "center", "end"];
|
|
22
|
+
const UI_OVERFLOW = ["visible", "clip", "scroll"];
|
|
23
|
+
const UI_TEXT_ALIGN = ["start", "center", "end"];
|
|
24
|
+
const UI_FIT = ["fill", "contain", "cover"];
|
|
25
|
+
const UI_SCALES = ["none", "fit-height", "fit-width"];
|
|
26
|
+
const UI_STATES = ["none", "hovered", "pressed", "disabled"];
|
|
27
|
+
const UI_ANCHOR_STATES = ["none", "on-screen", "clamped", "hidden"];
|
|
28
|
+
const UiLayout = defineComponent(
|
|
29
|
+
"ui/UiLayout",
|
|
30
|
+
{
|
|
31
|
+
x: t.f32({ readonly: true, description: "Left edge, in the root's pixels." }),
|
|
32
|
+
y: t.f32({ readonly: true, description: "Top edge, in the root's pixels." }),
|
|
33
|
+
width: t.f32({ readonly: true, description: "Border-box width." }),
|
|
34
|
+
height: t.f32({ readonly: true, description: "Border-box height." }),
|
|
35
|
+
clip: t.vec4({
|
|
36
|
+
readonly: true,
|
|
37
|
+
description: "The visible region (x, y, width, height) after ancestors that clip or scroll; zero size when nothing shows."
|
|
38
|
+
}),
|
|
39
|
+
content: t.vec2({
|
|
40
|
+
readonly: true,
|
|
41
|
+
description: "Size of what the children cover: the scrollable extent of a scroll node."
|
|
42
|
+
}),
|
|
43
|
+
scale: t.f32({
|
|
44
|
+
default: 1,
|
|
45
|
+
readonly: true,
|
|
46
|
+
description: "Draw scale from a scaling anchor (1: none)."
|
|
47
|
+
}),
|
|
48
|
+
angle: t.f32({
|
|
49
|
+
readonly: true,
|
|
50
|
+
unit: "rad",
|
|
51
|
+
description: "UiAnchorArrow: rotation toward an off-screen target."
|
|
52
|
+
}),
|
|
53
|
+
visible: t.bool({
|
|
54
|
+
readonly: true,
|
|
55
|
+
description: "Drawn and hit: false for display none, hidden anchors, and nodes outside a root."
|
|
56
|
+
}),
|
|
57
|
+
anchor: t.enum(UI_ANCHOR_STATES, {
|
|
58
|
+
readonly: true,
|
|
59
|
+
description: "UiAnchor nodes: on-screen, clamped to an edge, or hidden (behind the camera)."
|
|
60
|
+
}),
|
|
61
|
+
distance: t.f32({
|
|
62
|
+
readonly: true,
|
|
63
|
+
unit: "m",
|
|
64
|
+
description: "UiAnchor nodes: the target's distance from the camera."
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
description: "A UI node's computed rect (in pixels of its root's reference size), clip, and anchor state. Written by layout; read it to check where things are.",
|
|
69
|
+
serialize: false
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
const UiNode = defineComponent(
|
|
73
|
+
"ui/UiNode",
|
|
74
|
+
{
|
|
75
|
+
display: t.enum(UI_DISPLAYS, { description: "none: not laid out, drawn, or hit." }),
|
|
76
|
+
position: t.enum(UI_POSITIONS, {
|
|
77
|
+
description: "relative: in the parent's flow (left/top/right/bottom nudge it). absolute: out of flow, placed by left/top/right/bottom in the parent's padding box."
|
|
78
|
+
}),
|
|
79
|
+
direction: t.enum(UI_DIRECTIONS, { description: "Main axis children flow along." }),
|
|
80
|
+
wrap: t.bool({ description: "Wrap children onto new lines when they don't fit." }),
|
|
81
|
+
justify: t.enum(UI_JUSTIFY, { description: "Distribution of children along the main axis." }),
|
|
82
|
+
alignItems: t.enum(UI_ALIGN, { description: "Placement of children on the cross axis." }),
|
|
83
|
+
alignSelf: t.enum(UI_ALIGN_SELF, { description: "Overrides the parent's alignItems." }),
|
|
84
|
+
width: uiLength({ description: 'Border-box width: pixels, "50%" of the parent, or "auto".' }),
|
|
85
|
+
height: uiLength({ description: "Border-box height." }),
|
|
86
|
+
minWidth: uiLength({ description: 'Lower bound on width ("auto": none).' }),
|
|
87
|
+
maxWidth: uiLength({ description: 'Upper bound on width ("auto": none).' }),
|
|
88
|
+
minHeight: uiLength({ description: "Lower bound on height." }),
|
|
89
|
+
maxHeight: uiLength({ description: "Upper bound on height." }),
|
|
90
|
+
left: uiLength({ description: "Offset from the left (absolute: of the parent)." }),
|
|
91
|
+
top: uiLength({ description: "Offset from the top." }),
|
|
92
|
+
right: uiLength({ description: "Offset from the right." }),
|
|
93
|
+
bottom: uiLength({ description: "Offset from the bottom." }),
|
|
94
|
+
padding: t.vec4({ min: 0, unit: "px", description: "Inside space: top, right, bottom, left." }),
|
|
95
|
+
margin: t.vec4({ unit: "px", description: "Outside space: top, right, bottom, left." }),
|
|
96
|
+
gap: t.vec2({
|
|
97
|
+
min: 0,
|
|
98
|
+
unit: "px",
|
|
99
|
+
description: "Space between children: horizontal, vertical."
|
|
100
|
+
}),
|
|
101
|
+
grow: t.f32({ min: 0, description: "Share of free main-axis space this node takes." }),
|
|
102
|
+
shrink: t.f32({ default: 1, min: 0, description: "Share of overflow this node gives back." }),
|
|
103
|
+
basis: uiLength({ description: 'Main size before growing and shrinking ("auto": its size).' }),
|
|
104
|
+
order: t.i16({ description: "Sorts siblings (stable): lower first." }),
|
|
105
|
+
zIndex: t.i16({
|
|
106
|
+
description: "Draw and hit order among siblings: higher draws over, and gets clicks first."
|
|
107
|
+
}),
|
|
108
|
+
overflow: t.enum(UI_OVERFLOW, {
|
|
109
|
+
description: "visible: children may spill out. clip: they're cut at the padding box. scroll: cut, and the wheel scrolls it."
|
|
110
|
+
}),
|
|
111
|
+
scroll: t.vec2({ min: 0, unit: "px", description: "Scroll offset (overflow scroll)." }),
|
|
112
|
+
style: t.string({
|
|
113
|
+
description: `A style in the root's theme ("panel", "button"); fields set here win.`
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
description: "A UI box laid out by flexbox under a UiRoot: sizes, flex, spacing, positioning, clipping. Children are ChildOf entities, in order.",
|
|
118
|
+
requires: [UiLayout]
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
const UiRoot = defineComponent(
|
|
122
|
+
"ui/UiRoot",
|
|
123
|
+
{
|
|
124
|
+
scale: t.enum(UI_SCALES, {
|
|
125
|
+
description: "none: one UI pixel per screen pixel. fit-height / fit-width: scale so referenceSize fills the screen that way."
|
|
126
|
+
}),
|
|
127
|
+
referenceSize: t.vec2({
|
|
128
|
+
default: [1920, 1080],
|
|
129
|
+
min: 1,
|
|
130
|
+
unit: "px",
|
|
131
|
+
description: "The size the UI is authored at (and its size headless)."
|
|
132
|
+
}),
|
|
133
|
+
camera: t.entity({
|
|
134
|
+
description: "The camera whose view it draws over and anchors from (null: the first)."
|
|
135
|
+
}),
|
|
136
|
+
theme: t.handle("UiTheme", { description: "Styles its nodes name (*.theme.json)." }),
|
|
137
|
+
order: t.i16({ description: "Draw order among roots: higher draws over." })
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
description: "The top of a UI tree: fills the screen and lays out its UiNode children.",
|
|
141
|
+
requires: [UiNode]
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
const UiStyle = defineComponent(
|
|
145
|
+
"ui/UiStyle",
|
|
146
|
+
{
|
|
147
|
+
background: t.color({ default: [0, 0, 0, 0], description: "Fill color (linear)." }),
|
|
148
|
+
borderColor: t.color({ default: [0, 0, 0, 0], description: "Border color (linear)." }),
|
|
149
|
+
borderWidth: t.f32({ min: 0, unit: "px", description: "Border thickness, inside the box." }),
|
|
150
|
+
radius: t.vec4({
|
|
151
|
+
min: 0,
|
|
152
|
+
unit: "px",
|
|
153
|
+
description: "Corner radii: top left, top right, bottom right, bottom left."
|
|
154
|
+
}),
|
|
155
|
+
opacity: t.f32({
|
|
156
|
+
default: 1,
|
|
157
|
+
min: 0,
|
|
158
|
+
max: 1,
|
|
159
|
+
description: "Multiplies the node and its children."
|
|
160
|
+
})
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
description: "How a node looks: background, border, rounded corners, opacity. Fields at their default take the theme style.",
|
|
164
|
+
requires: [UiNode]
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
const UiText = defineComponent(
|
|
168
|
+
"ui/UiText",
|
|
169
|
+
{
|
|
170
|
+
text: t.string({ description: 'The text. "\\n" breaks a line.' }),
|
|
171
|
+
key: t.string({
|
|
172
|
+
description: "Localization key in the locales/*.strings.json tables: the node shows the current locale's string, and follows locale switches. text shows until it resolves."
|
|
173
|
+
}),
|
|
174
|
+
params: t.json({
|
|
175
|
+
default: {},
|
|
176
|
+
description: `Values for the key's {name} placeholders: { "amount": 42 }. Numbers format for the locale; "n" (or "count") picks the plural form.`
|
|
177
|
+
}),
|
|
178
|
+
size: t.f32({ default: 16, min: 0, unit: "px", description: "Height of one em." }),
|
|
179
|
+
color: t.color({ default: [1, 1, 1, 1], description: "Text color (linear)." }),
|
|
180
|
+
font: t.handle("Font", { description: "Font (null: the theme's, then UiDefaults.font)." }),
|
|
181
|
+
align: t.enum(UI_TEXT_ALIGN, { description: "Line alignment in the node." }),
|
|
182
|
+
wrap: t.bool({ default: true, description: "Wrap at the node width." }),
|
|
183
|
+
lineHeight: t.f32({
|
|
184
|
+
default: 1.2,
|
|
185
|
+
min: 0.1,
|
|
186
|
+
description: "Distance between baselines, as a multiple of the size."
|
|
187
|
+
})
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
description: "A node's text (MSDF): its size sets the node's size unless width/height do.",
|
|
191
|
+
requires: [UiNode]
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
const UiImage = defineComponent(
|
|
195
|
+
"ui/UiImage",
|
|
196
|
+
{
|
|
197
|
+
texture: t.handle("Texture", {
|
|
198
|
+
description: "The image (ignored when atlas and region are set)."
|
|
199
|
+
}),
|
|
200
|
+
atlas: t.handle("TextureAtlas", { description: "An atlas to draw a region of." }),
|
|
201
|
+
region: t.string({ description: "Atlas region name." }),
|
|
202
|
+
slice: t.vec4({
|
|
203
|
+
min: 0,
|
|
204
|
+
unit: "px",
|
|
205
|
+
description: "Nine-slice borders (top, right, bottom, left) in image pixels: corners keep their size, edges and middle stretch. Zero: no slicing."
|
|
206
|
+
}),
|
|
207
|
+
tint: t.color({ default: [1, 1, 1, 1], description: "Multiplied in (linear)." }),
|
|
208
|
+
fit: t.enum(UI_FIT, {
|
|
209
|
+
description: "fill: stretch to the box. contain: fit inside, keeping aspect. cover: fill, cropping."
|
|
210
|
+
})
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
description: "An image in a node: a texture or atlas region, nine-sliced or fitted. Its pixel size is the node's size unless width/height set it.",
|
|
214
|
+
requires: [UiNode]
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
const UiInteraction = defineComponent(
|
|
218
|
+
"ui/UiInteraction",
|
|
219
|
+
{
|
|
220
|
+
state: t.enum(UI_STATES, { readonly: true, description: "Pointer state." }),
|
|
221
|
+
focused: t.bool({ readonly: true, description: "Has keyboard and gamepad focus." })
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
description: "Hover, press, and focus of an interactive node. Written by the UI plugin.",
|
|
225
|
+
serialize: false
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
const UiButton = defineComponent(
|
|
229
|
+
"ui/UiButton",
|
|
230
|
+
{ disabled: t.bool({ description: "Ignores the pointer and focus; state is disabled." }) },
|
|
231
|
+
{
|
|
232
|
+
description: "Clickable: sends ui/UiClick on a click, Enter, Space, or the gamepad south button.",
|
|
233
|
+
requires: [UiNode, UiInteraction]
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
const UiToggle = defineComponent(
|
|
237
|
+
"ui/UiToggle",
|
|
238
|
+
{
|
|
239
|
+
on: t.bool({ description: "Checked." }),
|
|
240
|
+
disabled: t.bool({ description: "Ignores input." })
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
description: "A checkbox or switch: a click flips on and sends ui/UiChanged (and ui/UiClick).",
|
|
244
|
+
requires: [UiNode, UiInteraction]
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
const UiSlider = defineComponent(
|
|
248
|
+
"ui/UiSlider",
|
|
249
|
+
{
|
|
250
|
+
value: t.f32({ description: "Current value, min to max." }),
|
|
251
|
+
min: t.f32({ description: "Value at the left edge." }),
|
|
252
|
+
max: t.f32({ default: 1, description: "Value at the right edge." }),
|
|
253
|
+
step: t.f32({ min: 0, description: "Snap to multiples of this from min (0: continuous)." }),
|
|
254
|
+
fill: t.color({
|
|
255
|
+
default: [0.25, 0.55, 1, 1],
|
|
256
|
+
description: "Color of the filled part (min to value), drawn inside the node's padding."
|
|
257
|
+
}),
|
|
258
|
+
disabled: t.bool({ description: "Ignores input." })
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
description: "Dragging sets value from the pointer; left and right step it while focused. Sends ui/UiChanged.",
|
|
262
|
+
requires: [UiNode, UiInteraction]
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
const UiTextInput = defineComponent(
|
|
266
|
+
"ui/UiTextInput",
|
|
267
|
+
{
|
|
268
|
+
value: t.string({ description: "The text typed." }),
|
|
269
|
+
placeholder: t.string({ description: "Shown dimmed while value is empty." }),
|
|
270
|
+
maxLength: t.u16({ description: "Most characters (0: no limit)." }),
|
|
271
|
+
disabled: t.bool({ description: "Ignores input." })
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
description: "A text field: clicking focuses it, typing edits value (sends ui/UiChanged), Enter or Escape blurs. Shows through the node's UiText.",
|
|
275
|
+
requires: [UiNode, UiInteraction]
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
const UiAnchor = defineComponent(
|
|
279
|
+
"ui/UiAnchor",
|
|
280
|
+
{
|
|
281
|
+
target: t.entity({ description: "The world entity to follow." }),
|
|
282
|
+
offset: t.vec3({ unit: "m", description: "World offset from the target's position." }),
|
|
283
|
+
screenOffset: t.vec2({ unit: "px", description: "Offset on screen after projecting, y down." }),
|
|
284
|
+
pivot: t.vec2({
|
|
285
|
+
default: [0.5, 0.5],
|
|
286
|
+
description: "Point of the node on the target: [0, 0] top left, [0.5, 1] bottom center."
|
|
287
|
+
}),
|
|
288
|
+
clamp: t.bool({ description: "Keep it on screen when the target is off it (or behind)." }),
|
|
289
|
+
margin: t.f32({
|
|
290
|
+
default: 16,
|
|
291
|
+
min: 0,
|
|
292
|
+
unit: "px",
|
|
293
|
+
description: "Distance kept from the edge when clamped."
|
|
294
|
+
}),
|
|
295
|
+
hideBehind: t.bool({ description: "Hide while the target is behind the camera." }),
|
|
296
|
+
scaleDistance: t.f32({
|
|
297
|
+
min: 0,
|
|
298
|
+
unit: "m",
|
|
299
|
+
description: "Scale by distance: 1 at this distance, larger nearer (0: never scale)."
|
|
300
|
+
}),
|
|
301
|
+
minScale: t.f32({ default: 0.5, min: 0, description: "Smallest distance scale." }),
|
|
302
|
+
maxScale: t.f32({ default: 1.5, min: 0, description: "Largest distance scale." })
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
description: "Places a node over a world entity's screen position (planet markers): positioned absolute, clamped to the edges, hidden behind the camera, scaled by distance. A UiAnchorArrow child points at an off-screen target.",
|
|
306
|
+
requires: [UiNode]
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
const UiAnchorArrow = defineTag("ui/UiAnchorArrow", {
|
|
310
|
+
description: "A child of a UiAnchor node shown only while the anchor is clamped: placed just outside the node's edge toward the target and rotated to point at it (draw it pointing right)."
|
|
311
|
+
});
|
|
312
|
+
const UiClick = defineEvent("ui/UiClick", {
|
|
313
|
+
description: "A button (or toggle) was clicked, activated from focus, or clicked by ui.click."
|
|
314
|
+
});
|
|
315
|
+
const UiChanged = defineEvent("ui/UiChanged", {
|
|
316
|
+
description: "A toggle, slider, or text input's value changed from input."
|
|
317
|
+
});
|
|
318
|
+
const UiHover = defineEvent("ui/UiHover", {
|
|
319
|
+
description: "The pointer entered (hovered true) or left an interactive node."
|
|
320
|
+
});
|
|
321
|
+
const UiDefaults = defineResource("ui/UiDefaults", {
|
|
322
|
+
description: "Fallbacks: the font UI text uses when neither it nor its theme names one.",
|
|
323
|
+
init: () => ({ font: null })
|
|
324
|
+
});
|
|
325
|
+
export {
|
|
326
|
+
UI_ALIGN,
|
|
327
|
+
UI_ALIGN_SELF,
|
|
328
|
+
UI_ANCHOR_STATES,
|
|
329
|
+
UI_DIRECTIONS,
|
|
330
|
+
UI_DISPLAYS,
|
|
331
|
+
UI_FIT,
|
|
332
|
+
UI_JUSTIFY,
|
|
333
|
+
UI_OVERFLOW,
|
|
334
|
+
UI_POSITIONS,
|
|
335
|
+
UI_SCALES,
|
|
336
|
+
UI_STATES,
|
|
337
|
+
UI_TEXT_ALIGN,
|
|
338
|
+
UiAnchor,
|
|
339
|
+
UiAnchorArrow,
|
|
340
|
+
UiButton,
|
|
341
|
+
UiChanged,
|
|
342
|
+
UiClick,
|
|
343
|
+
UiDefaults,
|
|
344
|
+
UiHover,
|
|
345
|
+
UiImage,
|
|
346
|
+
UiInteraction,
|
|
347
|
+
UiLayout,
|
|
348
|
+
UiNode,
|
|
349
|
+
UiRoot,
|
|
350
|
+
UiSlider,
|
|
351
|
+
UiStyle,
|
|
352
|
+
UiText,
|
|
353
|
+
UiTextInput,
|
|
354
|
+
UiToggle
|
|
355
|
+
};
|
package/dist/flex.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flexbox subset UI lays out with, over a flat node array (no ECS, no allocation per layout
|
|
3
|
+
* once the arrays are big enough). Sizes are border-box pixels; borders draw inside the box and
|
|
4
|
+
* take no space. Follows CSS Flexbox §9 for the fields UiNode has: basis, grow and shrink with
|
|
5
|
+
* min/max freezing, wrap with stretched lines, justify, align-items/align-self, absolute children
|
|
6
|
+
* (static position from justify and align), relative offsets, percentages of the parent's inner
|
|
7
|
+
* size, and order. Differences from a browser: an item's automatic minimum size is its min-content
|
|
8
|
+
* size only for text and image leaves (containers may shrink to their padding), and there's no
|
|
9
|
+
* align-content (lines stretch, as CSS's default does).
|
|
10
|
+
*/
|
|
11
|
+
/** Length slots per node, two floats each (value, unit). */
|
|
12
|
+
export declare const L: {
|
|
13
|
+
readonly width: 0;
|
|
14
|
+
readonly height: 1;
|
|
15
|
+
readonly minWidth: 2;
|
|
16
|
+
readonly maxWidth: 3;
|
|
17
|
+
readonly minHeight: 4;
|
|
18
|
+
readonly maxHeight: 5;
|
|
19
|
+
readonly left: 6;
|
|
20
|
+
readonly top: 7;
|
|
21
|
+
readonly right: 8;
|
|
22
|
+
readonly bottom: 9;
|
|
23
|
+
readonly basis: 10;
|
|
24
|
+
};
|
|
25
|
+
export declare const LENGTHS = 11;
|
|
26
|
+
export declare const Dir: {
|
|
27
|
+
readonly Row: 0;
|
|
28
|
+
readonly Column: 1;
|
|
29
|
+
readonly RowReverse: 2;
|
|
30
|
+
readonly ColumnReverse: 3;
|
|
31
|
+
};
|
|
32
|
+
export declare const Justify: {
|
|
33
|
+
readonly Start: 0;
|
|
34
|
+
readonly Center: 1;
|
|
35
|
+
readonly End: 2;
|
|
36
|
+
readonly SpaceBetween: 3;
|
|
37
|
+
readonly SpaceAround: 4;
|
|
38
|
+
readonly SpaceEvenly: 5;
|
|
39
|
+
};
|
|
40
|
+
/** alignItems values; alignSelf is these plus 1 (0 = auto). */
|
|
41
|
+
export declare const Align: {
|
|
42
|
+
readonly Stretch: 0;
|
|
43
|
+
readonly Start: 1;
|
|
44
|
+
readonly Center: 2;
|
|
45
|
+
readonly End: 3;
|
|
46
|
+
};
|
|
47
|
+
/** Content size of a leaf (text, image): writes width and height (content box) into `out`. */
|
|
48
|
+
export type Measure = (node: number, maxWidth: number, out: Float32Array) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Nodes and their layout inputs and outputs, as typed arrays indexed by node. Node 0 is laid out
|
|
51
|
+
* at the size given to `layout`; each node's children are a linked list (firstChild, nextSibling)
|
|
52
|
+
* in flow order.
|
|
53
|
+
*/
|
|
54
|
+
export declare class FlexTree {
|
|
55
|
+
count: number;
|
|
56
|
+
firstChild: Int32Array<ArrayBuffer>;
|
|
57
|
+
nextSibling: Int32Array<ArrayBuffer>;
|
|
58
|
+
/** 1: display none (skipped with its subtree). */
|
|
59
|
+
hidden: Uint8Array<ArrayBuffer>;
|
|
60
|
+
absolute: Uint8Array<ArrayBuffer>;
|
|
61
|
+
direction: Uint8Array<ArrayBuffer>;
|
|
62
|
+
wrap: Uint8Array<ArrayBuffer>;
|
|
63
|
+
justify: Uint8Array<ArrayBuffer>;
|
|
64
|
+
alignItems: Uint8Array<ArrayBuffer>;
|
|
65
|
+
alignSelf: Uint8Array<ArrayBuffer>;
|
|
66
|
+
/** Per node, LENGTHS × (value, unit). */
|
|
67
|
+
lengths: Float32Array<ArrayBuffer>;
|
|
68
|
+
/** top, right, bottom, left. */
|
|
69
|
+
padding: Float32Array<ArrayBuffer>;
|
|
70
|
+
margin: Float32Array<ArrayBuffer>;
|
|
71
|
+
/** horizontal, vertical. */
|
|
72
|
+
gap: Float32Array<ArrayBuffer>;
|
|
73
|
+
flexGrow: Float32Array<ArrayBuffer>;
|
|
74
|
+
flexShrink: Float32Array<ArrayBuffer>;
|
|
75
|
+
/** 1: a leaf measured by the Measure callback (text, image). */
|
|
76
|
+
measured: Uint8Array<ArrayBuffer>;
|
|
77
|
+
/** 1: positioned by `anchorX/Y` (root coordinates) minus pivot × size, ignoring its parent. */
|
|
78
|
+
anchored: Uint8Array<ArrayBuffer>;
|
|
79
|
+
anchorX: Float32Array<ArrayBuffer>;
|
|
80
|
+
anchorY: Float32Array<ArrayBuffer>;
|
|
81
|
+
pivotX: Float32Array<ArrayBuffer>;
|
|
82
|
+
pivotY: Float32Array<ArrayBuffer>;
|
|
83
|
+
/** Output: position relative to the parent's border box, and border-box size. */
|
|
84
|
+
x: Float32Array<ArrayBuffer>;
|
|
85
|
+
y: Float32Array<ArrayBuffer>;
|
|
86
|
+
w: Float32Array<ArrayBuffer>;
|
|
87
|
+
h: Float32Array<ArrayBuffer>;
|
|
88
|
+
/** Measure-only results, and the inputs they were measured for. */
|
|
89
|
+
mw: Float32Array<ArrayBuffer>;
|
|
90
|
+
mh: Float32Array<ArrayBuffer>;
|
|
91
|
+
cache: Float32Array<ArrayBuffer>;
|
|
92
|
+
cacheValid: Uint8Array<ArrayBuffer>;
|
|
93
|
+
/** Layout calls this pass (measure and perform), for describe. */
|
|
94
|
+
calls: number;
|
|
95
|
+
reserve(n: number): void;
|
|
96
|
+
/** Clears to `n` nodes with default inputs (row, stretch, auto sizes, shrink 1). */
|
|
97
|
+
reset(n: number): void;
|
|
98
|
+
/** Sets a length slot: unit 0 px, 1 percent, 2 auto. */
|
|
99
|
+
setLength(node: number, slot: number, value: number, unit: number): void;
|
|
100
|
+
/** Appends `child` to `parent`'s children (after its last). */
|
|
101
|
+
append(parent: number, child: number, last: Int32Array): void;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Lays out the tree from node 0 at a given size: every node's x, y (relative to its parent),
|
|
105
|
+
* w, h. `measure` sizes text and image leaves.
|
|
106
|
+
*/
|
|
107
|
+
export declare function layoutTree(t: FlexTree, width: number, height: number, measure: Measure): void;
|