uxml-preview 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +166 -1
- package/README.md +93 -51
- package/dist/controls/registry.d.ts +59 -2
- package/dist/controls/theme.d.ts +87 -0
- package/dist/index.d.ts +36 -8
- package/dist/index.js +1198 -860
- package/dist/known-divergences.d.ts +36 -0
- package/dist/layout/yoga.d.ts +34 -1
- package/dist/model/types.d.ts +39 -3
- package/dist/parser/entities.d.ts +19 -0
- package/dist/render/css-map.d.ts +10 -1
- package/dist/render/paint.d.ts +14 -2
- package/dist/style/resolve.d.ts +50 -2
- package/dist/style/selector.d.ts +50 -9
- package/dist/unity-project/index.d.ts +14 -0
- package/dist/unity-project/index.js +41 -0
- package/package.json +90 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,171 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.3.0 — Unreleased
|
|
4
|
+
|
|
5
|
+
Four fixes the VSCode extension (`vscode-uxml-preview`) hit while consuming
|
|
6
|
+
this package as a dependency — not speculative additions.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`KNOWN_DIVERGENCES`** gives a host something to tell apart "a limit we
|
|
11
|
+
know about" from "a bug". Three entries, and the kinds carry the
|
|
12
|
+
distinction that matters. Font metrics move with the platform, so the
|
|
13
|
+
difference cannot be reproduced here at all. Unity's rule for a wrap
|
|
14
|
+
container's height is simply not specified, so there is nothing to match
|
|
15
|
+
yet. And the Yoga vendored in UI Toolkit resolves a main-axis percentage
|
|
16
|
+
against an indefinite parent differently from the one this package depends
|
|
17
|
+
on — that one reproduces perfectly every time; it just cannot be fixed
|
|
18
|
+
from inside this repository. The 1px case is deliberately absent: that is
|
|
19
|
+
the golden tests' judgement parameter, not a limit of the renderer, and a
|
|
20
|
+
host showing it would read as a defect.
|
|
21
|
+
- **`uxml-preview/unity-project`**, a Node-only subpath exporting
|
|
22
|
+
`buildGuidIndex(projectRoot)`. Builds a GUID → absolute-path index from a
|
|
23
|
+
Unity project's `.meta` files, for the case where an asset reference's path
|
|
24
|
+
has gone stale but its GUID is still good. Skips `Library/`, `Temp/`,
|
|
25
|
+
`obj/`, `.git/`, and any `.meta` file it cannot read. Kept out of the main
|
|
26
|
+
entry point because it uses `node:fs` and the main entry must stay
|
|
27
|
+
browser-safe.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **`resolveAsset` now receives a second argument, `form: 'url' | 'resource'`**,
|
|
32
|
+
telling `url("...")` and `resource("...")` background-image references
|
|
33
|
+
apart. They resolve differently — `url()` is a path, `resource()` is a
|
|
34
|
+
Unity Resources-folder name — and a host that resolved both the same way
|
|
35
|
+
before this had no way to know which one it was looking at.
|
|
36
|
+
**Non-breaking**: existing `(path) => ...` implementations keep working
|
|
37
|
+
unchanged: `form` is an additional argument, not a replacement one.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- `UxmlDocument.warnings` and `RenderResult.warnings` doc comments corrected.
|
|
42
|
+
The former said it covers "malformed input" only; it also carries
|
|
43
|
+
`'import-unresolved'` for an unreadable `<Style src="…">` or `@import`. The
|
|
44
|
+
latter said it is distinct from the former on that same wrong premise.
|
|
45
|
+
Comment-only — no behavior changed.
|
|
46
|
+
|
|
47
|
+
### Docs
|
|
48
|
+
|
|
49
|
+
- **`docs/accuracy.md`'s published figures were stale**: two comparable cases
|
|
50
|
+
(`state-vs-id`, `state-vs-inline`) were merged after the doc's numbers were
|
|
51
|
+
last written, and nothing caught it. Recomputed from a live run rather than
|
|
52
|
+
trusted: **548 / 564 values (97.2%)**, **31 / 33 cases matching**, **33 / 33**
|
|
53
|
+
Unity baseline coverage — up from the stale 532/548, 29/31, 31/31. Mirrored
|
|
54
|
+
into `README.md` (both languages) and `docs/uss-vs-css.md`/`.ko.md`.
|
|
55
|
+
`docs/accuracy.en.md` was updated too but is not machine-checked (see next).
|
|
56
|
+
- **Added a regression guard**: `tests/golden/golden.test.ts` now has
|
|
57
|
+
`matches the figures published in docs/accuracy.md`, which recomputes the
|
|
58
|
+
three headline numbers from the actual case set and fails if
|
|
59
|
+
`docs/accuracy.md` says anything else — including failing (not skipping) if
|
|
60
|
+
it cannot find a row to check, so the guard can't be silently defeated by a
|
|
61
|
+
table-format change. This is the third time this specific number went stale
|
|
62
|
+
publicly (`docs/accuracy.md` itself already records the first two); the doc
|
|
63
|
+
fix alone would only prevent a third, not a fourth.
|
|
64
|
+
- Left unfixed and flagged in `docs/accuracy.md`/`accuracy.en.md`: the
|
|
65
|
+
"which controls this covers" breakdown table (`Button`/`ScrollView`/etc.
|
|
66
|
+
row counts) was already internally inconsistent before this change
|
|
67
|
+
(17+1+6+5+1 = 30, not the stated 31) and is not machine-checked. Recomputing
|
|
68
|
+
it requires a counting rule that is not written down anywhere; a guess
|
|
69
|
+
risked swapping one wrong number for another, so it was left alone and
|
|
70
|
+
called out inline instead.
|
|
71
|
+
|
|
72
|
+
## 0.2.0 — 2026-08-06
|
|
73
|
+
|
|
74
|
+
Five controls instead of three, a representative screen measured end to end
|
|
75
|
+
against Unity, and two cascade defects that a synthetic case set could not have
|
|
76
|
+
found.
|
|
77
|
+
|
|
78
|
+
### Changed — this affects what you see on screen
|
|
79
|
+
|
|
80
|
+
- **Controls with no renderer of their own are now drawn as plain boxes**
|
|
81
|
+
instead of being dropped along with everything inside them. One
|
|
82
|
+
`<ui:ScrollView>` used to empty a panel. Each still reports a warning, and
|
|
83
|
+
what is missing is only whatever makes that control look like itself.
|
|
84
|
+
- **`Button` now carries Unity's default `margin: 1px 3px`.** Existing layouts
|
|
85
|
+
containing buttons will shift by a few pixels — toward Unity, not away from
|
|
86
|
+
it. Unity supplies this through a theme stylesheet and this library supplied
|
|
87
|
+
nothing, which is why the first `Button` ever compared missed on 18 of 48
|
|
88
|
+
values. Overridable from your own USS exactly as in Unity, and the values are
|
|
89
|
+
in `src/controls/theme.ts`.
|
|
90
|
+
- **`Button` labels are now centred**, which is what Unity does. Found by the eye
|
|
91
|
+
check rather than by measurement: where a glyph sits inside a box does not move
|
|
92
|
+
the box, so all 548 compared coordinates agreed while the two screenshots
|
|
93
|
+
plainly did not.
|
|
94
|
+
- **Control defaults now lose to your USS regardless of specificity.** They are a
|
|
95
|
+
lower origin, like a browser's user-agent sheet — measured, and the opposite of
|
|
96
|
+
what this library previously did and documented. `Button { margin: 0 }` in your
|
|
97
|
+
stylesheet now wins against the built-in `.unity-button` rule, as in Unity.
|
|
98
|
+
- **`enabled="false"` puts an element and its subtree into `:disabled`**, without
|
|
99
|
+
anyone passing `states`. Both the state and its inheritance are measured.
|
|
100
|
+
- **Disabled elements render at half opacity**, as Unity draws them. The value
|
|
101
|
+
came from sampling the screenshots rather than judging them: Unity paints the
|
|
102
|
+
disabled button rgb(41,50,67), which is its base colour composited at exactly
|
|
103
|
+
one half over the footer on all three channels.
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
|
|
107
|
+
- **`ScrollView`.** One tag becomes four elements in Unity, and the three it adds
|
|
108
|
+
decide where everything inside lands — reproducing the box alone put every
|
|
109
|
+
descendant in the wrong place and squeezed children that should have overflowed.
|
|
110
|
+
The hierarchy was observed from layout dumps rather than read from a source
|
|
111
|
+
mirror; the middle level, `unity-content-and-vertical-scroll-container`, appears
|
|
112
|
+
in no documentation. Scrollbar width is reserved but nothing is drawn: dragging,
|
|
113
|
+
wheeling and scroll position are outside a static render.
|
|
114
|
+
- **`Image`**, and `-unity-background-scale-mode` with its three values. The
|
|
115
|
+
default is `stretch-to-fill`, confirmed by the eye check.
|
|
116
|
+
- **Author USS reaches control parts.** `#unity-content-container { flex-direction: row }`
|
|
117
|
+
— how a real project lays out a scroll region's contents — previously reached
|
|
118
|
+
nothing at all, because parts were built after the cascade had run. A selector
|
|
119
|
+
naming any `unity-` element that matches nothing now warns and says why.
|
|
120
|
+
- **`<Style src="…">` is read**, through the same `resolveImport` hook as
|
|
121
|
+
`@import`. UI Builder writes that element into a file whenever a stylesheet is
|
|
122
|
+
attached, so it is the ordinary shape of a real document — and ignoring it meant
|
|
123
|
+
a real project's UXML rendered unstyled with nothing said about why.
|
|
124
|
+
|
|
125
|
+
- **Pseudo-class states, per element.** `render(doc, el, { states: { '#UseButton':
|
|
126
|
+
['hover'], '#DropButton': ['disabled'] } })`. Keys are ordinary USS selectors.
|
|
127
|
+
States are explicit input rather than mouse events, so the same call always
|
|
128
|
+
draws the same picture — a screen you can compare against Unity, or against
|
|
129
|
+
yesterday. Per element rather than per document because real screens mix one
|
|
130
|
+
hovered button with a disabled one.
|
|
131
|
+
Unity's *own* state defaults are deliberately not included: a layout dump
|
|
132
|
+
measures geometry and a hover colour is not geometry, so there is no way to
|
|
133
|
+
prove them yet.
|
|
134
|
+
- Style provenance now records which states a rule needed (`origin.states`), so
|
|
135
|
+
an editor can tell "this is the hover colour" from "this is the colour".
|
|
136
|
+
|
|
137
|
+
### Verified
|
|
138
|
+
|
|
139
|
+
**532 of 548 element coordinates identical** against Unity 6000.0.40f1 across 31
|
|
140
|
+
layout cases, one of which is a complete inventory screen.
|
|
141
|
+
|
|
142
|
+
The ratio is lower than 0.1.0's 242/244, and the reason is worth stating: that
|
|
143
|
+
figure covered 18 cases of which 17 were `VisualElement` alone, and no `Button`
|
|
144
|
+
had ever been compared. This one includes a text-heavy working screen, and **ten
|
|
145
|
+
of the sixteen differences are font metrics rather than layout** — both engines
|
|
146
|
+
shrink the same box for the same reason, and only the ruler differs. Three more
|
|
147
|
+
are 1px, two are the `yoga-layout` version difference already known in 0.1.0, and
|
|
148
|
+
**one is genuinely unresolved**. All sixteen are named in
|
|
149
|
+
[`docs/accuracy.en.md`](docs/accuracy.en.md).
|
|
150
|
+
|
|
151
|
+
Colour, borders and corners are held by a CSS-declaration regression baseline
|
|
152
|
+
(`tests/render/visual.test.ts`), which detects our own drift and is not evidence
|
|
153
|
+
of agreement with Unity. That comparison is a one-time eye check, recorded with
|
|
154
|
+
both screenshots in [`docs/visual-check.md`](docs/visual-check.md).
|
|
155
|
+
|
|
156
|
+
### Known limitations
|
|
157
|
+
|
|
158
|
+
- **Text-driven layout will not match exactly.** A browser cannot measure Unity's
|
|
159
|
+
font asset, so any box sized by its text differs. This is the same fact that
|
|
160
|
+
makes screenshot diffing useless, and it is not fixable here.
|
|
161
|
+
- **A wrapped ScrollView's content height diverges.** Unity clamps it to the
|
|
162
|
+
viewport; Yoga grows it to the content. Unity's rule is not yet identified —
|
|
163
|
+
the fix that matches one case breaks another, so nothing was guessed.
|
|
164
|
+
- Scrollbars reserve space but are not drawn, and there is no scrolling.
|
|
165
|
+
- `-unity-background-scale-mode`'s two non-default values are implemented from
|
|
166
|
+
Unity's documented semantics but have not been seen side by side.
|
|
167
|
+
|
|
168
|
+
## 0.1.0 — 2026-08-03
|
|
4
169
|
|
|
5
170
|
First release. Parses Unity UI Toolkit `.uxml` and `.uss`, lays them out through
|
|
6
171
|
the same Yoga engine UI Toolkit uses, paints to the DOM, and writes the files
|
package/README.md
CHANGED
|
@@ -21,8 +21,19 @@ stylesheet comes out wrong.</sub>
|
|
|
21
21
|
|
|
22
22
|
[English](#english) · [한국어](#한국어)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
```bash
|
|
25
|
+
npm install uxml-preview
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> **v0.2.0 — early release.** Five controls (`VisualElement`, `Label`, `Button`,
|
|
29
|
+
> `Image`, `ScrollView`), everything else drawn as a plain box with a warning,
|
|
30
|
+
> and a support matrix that says plainly what has not been measured.
|
|
31
|
+
> Read [what is not verified yet](#what-is-not-verified-yet) before relying on it.
|
|
32
|
+
>
|
|
33
|
+
> **v0.2.0 — 초기 릴리스입니다.** 컨트롤 5종(`VisualElement`, `Label`, `Button`,
|
|
34
|
+
> `Image`, `ScrollView`)이고, 나머지는 경고와 함께 일반 박스로 그립니다.
|
|
35
|
+
> 검증되지 않은 부분은 지원 범위표에 그대로 적어뒀습니다. 쓰시기 전에
|
|
36
|
+
> [아직 검증되지 않은 것](#아직-검증되지-않은-것)을 먼저 봐주세요.
|
|
26
37
|
|
|
27
38
|
---
|
|
28
39
|
|
|
@@ -36,8 +47,10 @@ directly in a web browser. It uses **Yoga** — the same layout engine Unity UI
|
|
|
36
47
|
itself uses — compiled to WebAssembly, so results match the Unity Editor rather than
|
|
37
48
|
approximating it.
|
|
38
49
|
|
|
39
|
-
Measured against Unity 6000.0.40f1: **
|
|
40
|
-
across
|
|
50
|
+
Measured against Unity 6000.0.40f1: **548 of 564 element coordinates identical**
|
|
51
|
+
across 33 layout cases, including a full working screen. Ten of the sixteen
|
|
52
|
+
differences are font metrics rather than layout — a browser cannot measure
|
|
53
|
+
Unity's font asset. All sixteen are named in
|
|
41
54
|
[`docs/accuracy.en.md`](docs/accuracy.en.md).
|
|
42
55
|
|
|
43
56
|
```
|
|
@@ -84,8 +97,9 @@ That broken feedback loop is what this library fixes.
|
|
|
84
97
|
| 3 | USS style resolver | ✅ |
|
|
85
98
|
| 4 | Yoga layout + DOM painting | ✅ |
|
|
86
99
|
| 5 | Golden tests against Unity | ✅ |
|
|
87
|
-
| 6 | **v0.1 release** |
|
|
88
|
-
| 7
|
|
100
|
+
| 6 | **v0.1 release** | ✅ |
|
|
101
|
+
| 7 | **v0.2 — ScrollView, Image, states, a measured screen** | ✅ |
|
|
102
|
+
| 8+ | Editing layer, tool integration | ⬜ |
|
|
89
103
|
|
|
90
104
|
Full plan: [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
91
105
|
|
|
@@ -94,16 +108,25 @@ Full plan: [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
|
94
108
|
The support matrix is deliberately conservative — see
|
|
95
109
|
[`docs/supported.en.md`](docs/supported.en.md) for the full table.
|
|
96
110
|
|
|
97
|
-
- **Only geometry
|
|
98
|
-
measured at the coordinates the layout engine produces.
|
|
99
|
-
DOM reproduces them is checked by an invariant of our own
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
- **Only geometry is compared automatically, and only as far as Yoga.** The
|
|
112
|
+
figure above is measured at the coordinates the layout engine produces.
|
|
113
|
+
Whether the painted DOM reproduces them is checked by an invariant of our own
|
|
114
|
+
rather than against Unity.
|
|
115
|
+
- **Colour, borders and corners rest on a single eye comparison.** One screen was
|
|
116
|
+
put side by side with Unity and checked item by item
|
|
117
|
+
([`docs/visual-check.md`](docs/visual-check.md)); it found two real defects, so
|
|
118
|
+
it was worth doing, but it is one screen and not an automated diff. Everything
|
|
119
|
+
it did not contain is still unmeasured.
|
|
102
120
|
- **Text measurement was not compared.** Unity renders text with its own font
|
|
103
|
-
asset
|
|
104
|
-
line-height factor is still an estimate.
|
|
105
|
-
-
|
|
106
|
-
|
|
121
|
+
asset, so any box sized by its text differs — this accounts for ten of the
|
|
122
|
+
sixteen coordinate divergences. The line-height factor is still an estimate.
|
|
123
|
+
- **A wrapped `ScrollView`'s content height diverges.** Unity clamps it to the
|
|
124
|
+
viewport; Yoga grows it to the content. Unity's rule is not yet identified.
|
|
125
|
+
- **Nested disabled elements dim twice.** `enabled="false"` disables a subtree
|
|
126
|
+
and each level takes the half opacity, so a disabled control inside a disabled
|
|
127
|
+
panel comes out at a quarter. Unity almost certainly dims once; this is not
|
|
128
|
+
measured yet.
|
|
129
|
+
- Scrollbars reserve their width but are not drawn, and nothing scrolls.
|
|
107
130
|
- `-unity-slice-*`, `-unity-background-image-tint-color` and `transition` are
|
|
108
131
|
parsed and preserved, but not drawn.
|
|
109
132
|
|
|
@@ -121,6 +144,10 @@ const doc = parse(uxmlText, ussText);
|
|
|
121
144
|
// model → screen
|
|
122
145
|
const view = render(doc, document.getElementById('preview'), {
|
|
123
146
|
resolveAsset: (path) => myAssetUrlFor(path),
|
|
147
|
+
|
|
148
|
+
// Pseudo-class states are explicit input, keyed by USS selector, so the same
|
|
149
|
+
// call always draws the same picture. Per element, because real screens mix.
|
|
150
|
+
states: { '#UseButton': ['hover'], '#DropButton': ['disabled'] },
|
|
124
151
|
});
|
|
125
152
|
console.log(view.warnings);
|
|
126
153
|
|
|
@@ -183,18 +210,21 @@ Checked by comparing element geometry against the Unity Editor, case by case.
|
|
|
183
210
|
Geometry rather than pixels: Unity draws text with its own font asset and a
|
|
184
211
|
browser does not, so a pixel diff would measure the font more than the layout.
|
|
185
212
|
|
|
186
|
-
Measured against Unity on 2026-08-
|
|
213
|
+
Measured against Unity on 2026-08-06, over 33 cases and 141 elements:
|
|
187
214
|
|
|
188
215
|
| | |
|
|
189
216
|
|---|---|
|
|
190
|
-
| Cases matching exactly | **
|
|
191
|
-
| Values within 0.5px | **
|
|
192
|
-
|
|
193
|
-
The
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
| Cases matching exactly | **31 / 33** |
|
|
218
|
+
| Values within 0.5px | **548 / 564 (97.2%)** |
|
|
219
|
+
|
|
220
|
+
The sixteen divergences are four different things, and the distinction matters
|
|
221
|
+
more than the ratio: **ten are text metrics**, three are 1px, two are a
|
|
222
|
+
`yoga-layout` version difference, and **one is unresolved**. The ten are not a
|
|
223
|
+
layout defect — both engines shrink the same box for the same reason and only
|
|
224
|
+
the ruler differs, which is the same fact that makes pixel diffing useless.
|
|
225
|
+
Every one is named in [`docs/accuracy.en.md`](docs/accuracy.en.md), along with
|
|
226
|
+
the failed attempts to configure around the version difference and how to
|
|
227
|
+
reproduce the measurement.
|
|
198
228
|
|
|
199
229
|
### Questions this answers
|
|
200
230
|
|
|
@@ -216,10 +246,11 @@ as nested flex, and computed values have to be resolved to fixed numbers or
|
|
|
216
246
|
calculated in C#.
|
|
217
247
|
|
|
218
248
|
**How close is the output to Unity?**
|
|
219
|
-
|
|
220
|
-
layout cases. Geometry is compared rather
|
|
221
|
-
text with its own font asset
|
|
222
|
-
are
|
|
249
|
+
548 of 564 element coordinates identical against Unity 6000.0.40f1, across 33
|
|
250
|
+
layout cases including a complete inventory screen. Geometry is compared rather
|
|
251
|
+
than screenshots, because Unity draws text with its own font asset — which is
|
|
252
|
+
also why ten of the sixteen differences are font metrics rather than layout.
|
|
253
|
+
Every divergence is named in [`docs/accuracy.en.md`](docs/accuracy.en.md).
|
|
223
254
|
|
|
224
255
|
**Will it damage my files if I edit them?**
|
|
225
256
|
Editing goes through a model that keeps the original text. An unedited document
|
|
@@ -228,9 +259,10 @@ CRLF endings included — and an edit rewrites only the region it touched. The
|
|
|
228
259
|
playground shows this live as `round-trip: exact`.
|
|
229
260
|
|
|
230
261
|
**What happens to controls it does not support?**
|
|
231
|
-
They are
|
|
232
|
-
|
|
233
|
-
|
|
262
|
+
They are drawn as a plain `VisualElement`, reported as a warning, and written
|
|
263
|
+
back out unchanged. Their own styles and their children come out normally; only
|
|
264
|
+
what makes that control look like itself is missing. One unfamiliar tag never
|
|
265
|
+
takes down the rest of the screen, and nothing is silently dropped.
|
|
234
266
|
|
|
235
267
|
### License
|
|
236
268
|
|
|
@@ -248,9 +280,10 @@ Apache-2.0
|
|
|
248
280
|
**Yoga**를 WebAssembly로 그대로 사용하기 때문에, 결과를 "비슷하게 흉내내는" 것이 아니라
|
|
249
281
|
유니티 에디터와 일치시킵니다.
|
|
250
282
|
|
|
251
|
-
Unity 6000.0.40f1과 대조한 결과, 레이아웃 케이스
|
|
252
|
-
**요소 좌표
|
|
253
|
-
|
|
283
|
+
Unity 6000.0.40f1과 대조한 결과, 실무형 화면을 포함한 레이아웃 케이스 33개에서
|
|
284
|
+
**요소 좌표 564개 중 548개가 일치**합니다. 어긋난 16개 중 **10개는 레이아웃이 아니라
|
|
285
|
+
폰트 메트릭** 차이입니다 — 브라우저는 유니티 폰트 에셋을 잴 수 없습니다.
|
|
286
|
+
16개 전부 [`docs/accuracy.md`](docs/accuracy.md)에 이름과 사유를 적어뒀습니다.
|
|
254
287
|
|
|
255
288
|
```
|
|
256
289
|
.uxml + .uss → 파싱 → 스타일 계산 → Yoga 레이아웃 → DOM 페인팅
|
|
@@ -295,8 +328,9 @@ Unity 6000.0.40f1과 대조한 결과, 레이아웃 케이스 18개에서
|
|
|
295
328
|
| 3 | USS 스타일 리졸버 | ✅ |
|
|
296
329
|
| 4 | Yoga 레이아웃 + DOM 페인팅 | ✅ |
|
|
297
330
|
| 5 | 유니티 대조 골든 테스트 | ✅ |
|
|
298
|
-
| 6 | **v0.1 공개** |
|
|
299
|
-
| 7
|
|
331
|
+
| 6 | **v0.1 공개** | ✅ |
|
|
332
|
+
| 7 | **v0.2 — ScrollView·Image·상태, 대표 화면 대조** | ✅ |
|
|
333
|
+
| 8+ | 편집 레이어, 도구 통합 | ⬜ |
|
|
300
334
|
|
|
301
335
|
전체 계획: [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
302
336
|
|
|
@@ -305,15 +339,22 @@ Unity 6000.0.40f1과 대조한 결과, 레이아웃 케이스 18개에서
|
|
|
305
339
|
지원 범위는 일부러 보수적으로 적었습니다. 전체 표는
|
|
306
340
|
[`docs/supported.md`](docs/supported.md)에 있습니다.
|
|
307
341
|
|
|
308
|
-
-
|
|
342
|
+
- **자동 대조는 좌표만, 그것도 Yoga 층까지입니다.** 위 수치는 레이아웃 엔진이 낸
|
|
309
343
|
좌표를 잰 것입니다. 그려진 DOM이 그 좌표를 재현하는지는 유니티가 아니라 자체
|
|
310
|
-
불변식 검사로
|
|
311
|
-
|
|
344
|
+
불변식 검사로 봅니다
|
|
345
|
+
- **색·테두리·모서리는 육안 대조 1회에 기대고 있습니다.** 화면 하나를 유니티와
|
|
346
|
+
나란히 놓고 항목별로 확인했고([`docs/visual-check.md`](docs/visual-check.md)),
|
|
347
|
+
거기서 실제 결함 두 개가 나왔으니 할 값어치는 있었습니다. 다만 **화면 하나이고
|
|
348
|
+
자동 diff가 아닙니다.** 그 화면에 없던 것은 여전히 미측정입니다
|
|
312
349
|
- **텍스트 측정은 대조하지 않았습니다.** 유니티는 자기 폰트 에셋으로 글자를
|
|
313
|
-
|
|
314
|
-
계수도 아직 추정치입니다
|
|
315
|
-
-
|
|
316
|
-
|
|
350
|
+
그리므로 텍스트가 크기를 좌우하는 박스는 어긋납니다 — 좌표 불일치 16건 중
|
|
351
|
+
10건이 이것입니다. 행 높이 계수도 아직 추정치입니다
|
|
352
|
+
- **wrap된 `ScrollView`의 콘텐츠 높이가 어긋납니다.** 유니티는 뷰포트에 묶고
|
|
353
|
+
Yoga는 콘텐츠로 키웁니다. 유니티의 규칙을 아직 특정하지 못했습니다
|
|
354
|
+
- **중첩된 비활성 요소가 두 번 흐려집니다.** `enabled="false"`가 하위 트리를
|
|
355
|
+
비활성으로 만들고 각 층이 0.5 불투명도를 받아 1/4이 됩니다. 유니티는 한 번만
|
|
356
|
+
흐리게 할 것이 거의 확실하지만 아직 측정하지 않았습니다
|
|
357
|
+
- 스크롤바는 폭만 예약하고 그리지 않으며, 스크롤되지 않습니다
|
|
317
358
|
- `-unity-slice-*`, `-unity-background-image-tint-color`, `transition`은
|
|
318
359
|
파싱·보존은 되지만 그려지지 않습니다
|
|
319
360
|
|
|
@@ -340,7 +381,7 @@ pnpm dev
|
|
|
340
381
|
|
|
341
382
|
예제는 예쁜 것보다 **함정을 보여주는 것**으로 골랐습니다 — `flex-direction`의 기본값이
|
|
342
383
|
`column`이라는 것, 겹침 순서가 `z-index`가 아니라 마크업 순서로 정해진다는 것,
|
|
343
|
-
|
|
384
|
+
전용 렌더러가 없는 컨트롤도 일반 박스로 그려지고 경고와 함께 왕복에서 살아남는다는 것.
|
|
344
385
|
|
|
345
386
|
### 개발
|
|
346
387
|
|
|
@@ -373,10 +414,11 @@ pnpm build
|
|
|
373
414
|
계산이 필요한 값은 고정값으로 풀거나 C#에서 계산해야 합니다.
|
|
374
415
|
|
|
375
416
|
**유니티와 얼마나 같나요?**
|
|
376
|
-
Unity 6000.0.40f1 기준,
|
|
377
|
-
|
|
378
|
-
그리기
|
|
379
|
-
|
|
417
|
+
Unity 6000.0.40f1 기준, 인벤토리 화면 전체를 포함한 레이아웃 케이스 33개에서
|
|
418
|
+
**요소 좌표 564개 중 548개 일치**입니다. 스크린샷이 아니라 좌표를 비교하는데,
|
|
419
|
+
유니티가 자기 폰트 에셋으로 글자를 그리기 때문입니다 — 그리고 그 사실 때문에
|
|
420
|
+
불일치 16개 중 **10개가 레이아웃이 아니라 폰트 메트릭**입니다. 전부
|
|
421
|
+
[`docs/accuracy.md`](docs/accuracy.md)에 이름이 적혀 있습니다.
|
|
380
422
|
|
|
381
423
|
**편집하면 파일이 망가지지 않나요?**
|
|
382
424
|
편집은 원본 텍스트를 그대로 들고 있는 모델을 거칩니다. 고치지 않은 문서는 주석·속성
|
|
@@ -384,9 +426,9 @@ Unity 6000.0.40f1 기준, 레이아웃 케이스 18개에서 **요소 좌표 244
|
|
|
384
426
|
영역만 바뀝니다. 놀이터의 `round-trip: exact` 표시가 이걸 실시간으로 보여줍니다.
|
|
385
427
|
|
|
386
428
|
**지원하지 않는 컨트롤은 어떻게 되나요?**
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
없습니다.
|
|
429
|
+
일반 `VisualElement`처럼 그려지고, 경고로 보고되고, 저장하면 그대로 다시 나옵니다.
|
|
430
|
+
자기 스타일과 자식은 정상적으로 나오며 그 컨트롤 고유의 모양만 빠집니다. 모르는 태그
|
|
431
|
+
하나 때문에 화면 전체가 죽지 않고, 조용히 사라지는 것도 없습니다.
|
|
390
432
|
|
|
391
433
|
### 라이선스
|
|
392
434
|
|
|
@@ -1,10 +1,67 @@
|
|
|
1
1
|
import { ElementNode } from '../model/types';
|
|
2
|
+
/**
|
|
3
|
+
* An element a control builds for itself, which the file never mentions.
|
|
4
|
+
*
|
|
5
|
+
* Unity's ScrollView is one tag that becomes four elements, and the three it
|
|
6
|
+
* adds are what decide where everything inside lands — a child of a ScrollView
|
|
7
|
+
* is not a child of the ScrollView. Reproducing the box without them puts every
|
|
8
|
+
* descendant in the wrong place, so they are modelled rather than approximated.
|
|
9
|
+
*
|
|
10
|
+
* `name` is Unity's own, and it is the key the layout dump joins on: these are
|
|
11
|
+
* observed in `tests/golden/unity/scrollview-*.json`, not invented.
|
|
12
|
+
*/
|
|
13
|
+
export interface ControlPart {
|
|
14
|
+
/** Unity's name, e.g. `unity-content-viewport`. */
|
|
15
|
+
name: string;
|
|
16
|
+
/**
|
|
17
|
+
* USS this part always carries, because the control fixes it.
|
|
18
|
+
*
|
|
19
|
+
* Written as USS rather than as CSS or Yoga calls so it goes through the same
|
|
20
|
+
* cascade output, Yoga mapping and CSS mapping as everything else. A second
|
|
21
|
+
* styling path for parts would be a second set of USS bugs.
|
|
22
|
+
*/
|
|
23
|
+
style: Readonly<Record<string, string>>;
|
|
24
|
+
}
|
|
2
25
|
export interface ControlSpec {
|
|
3
|
-
/** Renders the `text` attribute as its content. */
|
|
26
|
+
/** Renders the `text` attribute as its own content. */
|
|
4
27
|
hasText: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Elements the control inserts between itself and the file's children,
|
|
30
|
+
* outermost first. The file's children go inside the last one.
|
|
31
|
+
*/
|
|
32
|
+
parts: readonly ControlPart[];
|
|
33
|
+
/**
|
|
34
|
+
* USS classes Unity's own constructor puts on this control, which the theme
|
|
35
|
+
* stylesheet then targets. Not in the `class` attribute and not in the model:
|
|
36
|
+
* the file says `<ui:Button />` and Unity adds the rest.
|
|
37
|
+
*/
|
|
38
|
+
classes: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
export interface ResolvedControl {
|
|
41
|
+
spec: ControlSpec;
|
|
42
|
+
/**
|
|
43
|
+
* No renderer of its own; drawn as a plain `VisualElement`. Callers report
|
|
44
|
+
* this once per element so the preview says what it did (CLAUDE.md rule 6).
|
|
45
|
+
*/
|
|
46
|
+
fallback: boolean;
|
|
5
47
|
}
|
|
6
48
|
/** The document container. Not a control, but it is the panel's root box. */
|
|
7
49
|
export declare const ROOT_LOCAL_NAME = "UXML";
|
|
8
|
-
export declare function
|
|
50
|
+
export declare function isNonVisual(node: ElementNode): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Purpose: the spec to draw `node` with, always.
|
|
53
|
+
* Ensures: never null. `fallback` is true only for elements a caller
|
|
54
|
+
* should report; the root is excluded because it is the panel
|
|
55
|
+
* box rather than a control that failed to resolve.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveControl(node: ElementNode): ResolvedControl;
|
|
9
58
|
export declare function isRoot(node: ElementNode): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Purpose: the USS classes Unity would have added to this element.
|
|
61
|
+
* Ensures: empty for anything with no renderer of its own — a fallback box
|
|
62
|
+
* must not pick up theme styling meant for a control we do not
|
|
63
|
+
* actually draw.
|
|
64
|
+
*/
|
|
65
|
+
export declare function implicitClassesOf(node: ElementNode): readonly string[];
|
|
66
|
+
/** Controls with a renderer of their own. Everything else still draws. */
|
|
10
67
|
export declare function supportedControlNames(): string[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unity's control defaults, as far as we have measured them.
|
|
3
|
+
*
|
|
4
|
+
* Unity ships a theme stylesheet that gives controls their own margin, padding
|
|
5
|
+
* and border before any author USS runs. This renderer supplied none of it, so
|
|
6
|
+
* every Button sat 3px left and 1px high of where Unity puts it — invisible on
|
|
7
|
+
* one button and compounding across a row.
|
|
8
|
+
*
|
|
9
|
+
* **Only measured values live here.** Every declaration below was derived from
|
|
10
|
+
* a Unity layout dump in tests/golden/unity/, and adding `Button`'s margin
|
|
11
|
+
* moved all five Button cases from 18 mismatched values to zero. Values read
|
|
12
|
+
* from Unity's source instead would look identical in this file while being
|
|
13
|
+
* wrong in a way no test could see: they would not appear in the unmeasured
|
|
14
|
+
* list, so they would quietly pass for ground truth.
|
|
15
|
+
*
|
|
16
|
+
* Two kinds of measurement feed this file, and the second was a long time
|
|
17
|
+
* coming. Geometry comes from the coordinate dumps. `-unity-text-align` cannot:
|
|
18
|
+
* where a glyph sits inside a box does not move the box, so no dump can ever
|
|
19
|
+
* see it. It came from the Step 6 eye check — Unity centres a Button's label
|
|
20
|
+
* and this renderer had it at the top left, which is visible the moment the two
|
|
21
|
+
* screenshots sit side by side and invisible in 368 compared numbers.
|
|
22
|
+
*
|
|
23
|
+
* `opacity` on `:disabled` came from the same screenshots, but by sampling
|
|
24
|
+
* pixels rather than looking. Unity's disabled Drop button is rgb(41,50,67);
|
|
25
|
+
* the base colour rgb(58,74,104) composited at half over the footer's
|
|
26
|
+
* rgb(24,26,31) is (41, 50, 67.5) — all three channels exact. That arithmetic
|
|
27
|
+
* is the evidence, and it is why the value is 0.5 and not a number that merely
|
|
28
|
+
* looked about right.
|
|
29
|
+
*
|
|
30
|
+
* Getting there took two wrong turns worth recording. First the author's
|
|
31
|
+
* `Button:disabled` rule was blamed for not applying — it genuinely does not,
|
|
32
|
+
* because `#DropButton` outranks it, in both engines. Then Unity was accused of
|
|
33
|
+
* ranking state rules above specificity, and `state-vs-id` and `state-vs-inline`
|
|
34
|
+
* refuted that: Unity answers 40 to both, exactly as this cascade already did.
|
|
35
|
+
* The dimming was never about the cascade at all.
|
|
36
|
+
*
|
|
37
|
+
* What is deliberately absent, and why:
|
|
38
|
+
*
|
|
39
|
+
* - `Label` — measured, and it has no default margin. `inherit-vs-direct`
|
|
40
|
+
* puts an explicitly-sized Label at the origin of a sized parent, and Unity
|
|
41
|
+
* reports x=0 y=0. Nothing to add is a result, not an omission.
|
|
42
|
+
* - padding, border, font — invisible to the cases we have. Every compared
|
|
43
|
+
* element is explicitly sized, and USS is border-box, so padding and border
|
|
44
|
+
* do not move the outer rectangle. They are unmeasured, so they are not here.
|
|
45
|
+
* - `Label`'s text alignment — the eye check could not separate it from the
|
|
46
|
+
* box at this size. Unmeasured, so absent.
|
|
47
|
+
*
|
|
48
|
+
* The class names were a separate claim from the values, and are now measured
|
|
49
|
+
* too. `unity-button` came from Unity's documented `Button.ussClassName`, which
|
|
50
|
+
* the margin dumps said nothing about — they proved the spacing, not the
|
|
51
|
+
* selector delivering it. The golden case `theme-class-hook` was written to
|
|
52
|
+
* settle exactly that, and Unity answered on 2026-08-05: a `.unity-button` rule
|
|
53
|
+
* in author USS reaches a bare `<ui:Button>`, sizing it 120×40 in both engines.
|
|
54
|
+
* The selector below is therefore measured, not assumed.
|
|
55
|
+
*/
|
|
56
|
+
/** Where every value in this file was measured. Theme values move between versions. */
|
|
57
|
+
export declare const THEME_UNITY_VERSION = "6000.0.40f1";
|
|
58
|
+
/**
|
|
59
|
+
* Parsed once and spliced in ahead of author sheets, so an author rule of equal
|
|
60
|
+
* specificity wins on order — which is how a theme stylesheet behaves in Unity.
|
|
61
|
+
*
|
|
62
|
+
* Class selectors rather than type selectors, also for Unity's reasons: a theme
|
|
63
|
+
* rule at `.unity-button` (0,1,0) beats an author's `Button { }` (0,0,1), and
|
|
64
|
+
* that is a real Unity gotcha worth reproducing rather than smoothing over.
|
|
65
|
+
*/
|
|
66
|
+
export declare const THEME_USS = ":disabled {\n opacity: 0.5;\n}\n\n.unity-button {\n margin: 1px 3px;\n -unity-text-align: middle-center;\n}\n";
|
|
67
|
+
/**
|
|
68
|
+
* **Unmeasured, and reachable: nested disabled elements.**
|
|
69
|
+
*
|
|
70
|
+
* `enabled="false"` puts a whole subtree into `:disabled` — measured, by
|
|
71
|
+
* `disabled-inherits`. The rule above therefore hands every level its own
|
|
72
|
+
* `opacity: 0.5`, and CSS multiplies them, so a disabled button inside a
|
|
73
|
+
* disabled panel comes out at a quarter. Unity almost certainly dims once.
|
|
74
|
+
*
|
|
75
|
+
* Not guessed at, because the fix has a choice inside it — dim only where
|
|
76
|
+
* `enabled="false"` was written, or suppress the inherited one — and picking
|
|
77
|
+
* without evidence is how the `.unity-button` selector became a hypothesis
|
|
78
|
+
* nobody noticed for a day. Opacity leaves no trace in a coordinate dump, so
|
|
79
|
+
* settling it needs a screenshot of a nested case. Recorded in
|
|
80
|
+
* `docs/backlog.md` and visible in `visual-nested-disabled`.
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* Purpose: how much width a vertical scrollbar claims, given whether it shows.
|
|
84
|
+
* Ensures: 0 when the content fits — Unity hides the scroller rather than
|
|
85
|
+
* shrinking the viewport for a bar nobody can see.
|
|
86
|
+
*/
|
|
87
|
+
export declare function verticalScrollbarWidth(overflows: boolean): number;
|