diff --git a/docs/ISSUES.md b/docs/ISSUES.md
index 93a426b6..d255eb9b 100644
--- a/docs/ISSUES.md
+++ b/docs/ISSUES.md
@@ -61,10 +61,14 @@ back to inert generic DAT elements.
`gmUIElement_EffectsIndicator`. The retained controller consequently had no
live bindings for session packet age, Vitae, or the player load ratio and did
not dispatch Character Information or the shared logout confirmation path.
-The strip and its actions are now ported. Link Status (panel 8), both effect
-pages (4/5), Vitae (15), and the authored Mini Game shell (9) are registered as
-children of the same retained main-panel owner as Inventory/Character/Magic,
-so switching pages preserves one canonical position. Link ping RTT and Vitae
+The strip and its actions are now ported. Character Information (panel 3), Link
+Status (8), both effect pages (4/5), Vitae (15), and the authored Mini Game
+shell (9) are registered as children of the same retained main-panel owner as
+Inventory/Character/Magic, so switching pages preserves one canonical size and
+position. Helpful/Harmful use retail's authored 300x32 icon/name/time rows,
+selection states, `SELECT A SPELL` prompt, and selected spell description.
+The burden icon routes to Character Information rather than Attributes/Skills.
+Link ping RTT and Vitae
recovery XP are live; transport packet-loss averaging and Mini Game gameplay
remain the explicitly narrowed AP-110 residual.
@@ -81,7 +85,7 @@ helpful, harmful, Vitae, and burden react to live state; the burden icon opens
Character Information; effects open their matching panels; crossed swords use
the shared confirmation dialog and close gracefully only after acceptance.
Every non-ghosted detail icon opens its authored page in the shared main-panel
-position; closing a restore-previous page returns to the deferred ordinary page.
+geometry; closing a restore-previous page returns to the deferred ordinary page.
---
diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md
index 31fb7525..c2b7231e 100644
--- a/docs/architecture/retail-divergence-register.md
+++ b/docs/architecture/retail-divergence-register.md
@@ -50,7 +50,7 @@ accepted-divergence entries (#96, #49, #50).
| IA-9 | One unified camera matrix for terrain — retail's separate `LScape::update_viewpoint` landscape viewpoint does not exist | `src/AcDream.App/Rendering/TerrainModernRenderer.cs:266` | Phase W T4.2: with one matrix everywhere, viewpoint-desync bugs are unrepresentable — the unification IS the correctness argument | Anything retail derives from the landcell-relative viewpoint (float precision at extreme coords, viewpoint-keyed state) has no analogue; a future port expecting it silently reads the camera | `LScape::update_viewpoint`; `LScape::draw` 0x00506330 |
| IA-10 | Transparent groups sorted back-to-front per GROUP by first-instance position (no within-group sort) vs retail per-poly BSP-order draw | `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs:1364` (comparer :1662) | One MDI call per pass requires group-granularity ordering; per-poly sorting is incompatible with instanced multi-draw; works when group instances are spatially coherent | Spatially spread or interleaved transparent groups composite in the wrong order — popping / wrong see-through layering as the camera moves | retail per-poly BSP-order transparent draw (D3DPolyRender / PView::DrawCells) |
| IA-11 | Tier-1 cross-frame batch-classification cache for static entities (retail re-walks part arrays every frame) | `src/AcDream.App/Rendering/Wb/EntityClassificationCache.cs:12` | Issue #53 perf tier; invariants documented (keys = EntityId + OWNING-landblock hint post-**#119** fix `2163308`; invalidation at despawn/LB-unload; mutation audit 2026-05-10) | Key collision or missed invalidation serves one entity another's batches — session-sticky wrong meshes (the #119 broken-stairs/water-barrel symptom) | retail per-frame part-array classification (no cache) |
-| IA-12 | UI toolkit mirrors retail behavior from research docs, not a byte-port — keystone.dll is outside decomp coverage; observed constants embedded (drag 3 px, tooltip 1000 ms). Synthetic wrapper borders/whole-window drag regions use the exact DAT Type-2/Type-9 control cursors. `gmPanelUI` primary children are independently imported retained frames rather than one physical parent, but `RetailPanelUiController` owns their one canonical placement and exclusive child lifecycle | `src/AcDream.App/UI/README.md:3`; `src/AcDream.App/UI/CursorFeedbackController.cs`; `src/AcDream.App/UI/Layout/RetailPanelUiController.cs` | keystone.dll has no PDB/decomp; semantics are reconstructed from the retail UI deep-dives and named client methods plus production LayoutDesc media. Separate child wrappers preserve each LayoutDesc's content/resize policy while typed handle synchronization gives Inventory/Character/Magic the same persistent parent position | Edge-case input semantics can differ silently; if retail relies on parent-size anchoring across a panel switch, separate child extents may resize differently even though placement and visibility match | `UIElementManager::CheckCursor` 0x0045ABF0; `UIElement_Resizebar::StartMouseResizing` 0x0046B7E0; `UIElement_Dragbar::StartMouseMoving` 0x0046C760; `gmPanelUI::SetupChildren` 0x004BC9E0; docs/research/retail-ui/04-input-events.md |
+| IA-12 | UI toolkit mirrors retail behavior from research docs, not a byte-port — keystone.dll is outside decomp coverage; observed constants embedded (drag 3 px, tooltip 1000 ms). Synthetic wrapper borders/whole-window drag regions use the exact DAT Type-2/Type-9 control cursors. `gmPanelUI` children are independently imported retained frames rather than one physical parent, but `RetailPanelUiController` owns their one canonical geometry and exclusive child lifecycle | `src/AcDream.App/UI/README.md:3`; `src/AcDream.App/UI/CursorFeedbackController.cs`; `src/AcDream.App/UI/Layout/RetailPanelUiController.cs` | keystone.dll has no PDB/decomp; semantics are reconstructed from retail UI deep-dives, named client methods, and production LayoutDesc media. Separate child wrappers preserve each LayoutDesc's content tree while typed move/resize synchronization gives all registered toolbar/detail children the same persistent parent rectangle | Edge-case low-level input semantics can differ silently even though outer geometry, visibility, and restore-previous ownership match | `UIElementManager::CheckCursor` 0x0045ABF0; `UIElement_Resizebar::StartMouseResizing` 0x0046B7E0; `UIElement_Dragbar::StartMouseMoving` 0x0046C760; `gmPanelUI::SetupChildren` 0x004BC9E0; docs/research/retail-ui/04-input-events.md |
| IA-13 | GameEventType registry deliberately omits event types retail ignores; unknown events fall through unhandled | `src/AcDream.Core.Net/Messages/GameEventType.cs:11` | Retail also ignores them — dropping matches retail by construction | If the "retail ignores X" judgment is wrong for any opcode (or a server mod uses one), the event is silently dropped with no diagnostic pointing at the omission | retail GameEvent dispatch (ignored-event set) |
| IA-14 | Rendering + dat-handling base is WorldBuilder's tested port, not a fresh retail-decomp port (Phase N.4/O design stance) | `docs/architecture/worldbuilder-inventory.md` (code at `src/AcDream.{Core,App}/Rendering/Wb/`) | WB visually verified on the AC world, MIT, same stack; known WB↔retail deltas resolved case-by-case — terrain split kept retail `FSplitNESW` (**#51**, pinned by `SplitFormulaDivergenceTest`), scenery drift accepted (AP-31) | A WB-upstream divergence not yet caught ships silently as "our" behavior; guard = the inventory doc's 🟢/🔴 split + per-formula divergence tests | retail decomp per algorithm; `tests/.../SplitFormulaDivergenceTest.cs` |
| IA-15 | D.2b gameplay UI is our own `UiHost`/`UiRoot` retained tree, not a byte-port of Keystone. `RetailUiRuntime` owns the production import/mount graph; `RetailWindowManager`/typed handles centralize registry, raise, focus/capture cleanup, lifecycle events, reverse-order grouped controller teardown, removable Silk input subscriptions, schema-v2 per-character/per-resolution automatic layouts, and portable named `saveui/loadui` profiles; `RetailWindowFrame` is the single production/Studio mount contract for imported-chrome and shared-wrapper windows. Production LayoutDesc imports include vitals `0x2100006C`, chat `0x21000006`, toolbar `0x21000016`, character `0x2100002E`, inventory `0x21000023` plus mounted `0x21000024/22/21`, dialog catalog `0x2100003C`, and radar `0x21000074`. The dialog context/queue/callback lifecycle is now a named-client port; only its retained rendering remains under this Keystone adaptation. | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.App/UI/RetailWindowManager.cs`; `src/AcDream.App/UI/RetainedPanelControllerGroup.cs`; `src/AcDream.App/UI/UiHost.cs`; `src/AcDream.App/UI/RetailWindowLayoutPersistence.cs`; `src/AcDream.App/UI/Layout/RetailWindowFrame.cs`; `src/AcDream.App/UI/Layout/RetailDialogFactory.cs`; `src/AcDream.App/UI/Layout/RetailConfirmationDialogView.cs`; `src/AcDream.App/UI/Layout/LayoutImporter.cs`; binding supply in `GameWindow.cs` | Keystone has no matching PDB/decomp, so we preserve its observable ElementDesc/state/input behavior from DAT, named client call sites, and live evidence while using modern retained ownership. Real RenderSurfaces and imported element geometry remain the visual oracle. | Persistence and low-level widget rendering are behaviorally reconstructed from retail semantics rather than a Keystone byte-port; lifecycle edge cases remain constrained by conformance tests | Production LayoutDesc objects; `DialogFactory @ 0x004773C0..0x00478470`; `docs/research/2026-07-13-retail-dialog-factory-pseudocode.md`; Keystone behavior notes in `docs/research/retail-ui/` |
diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md
index ca94d438..78e16501 100644
--- a/docs/plans/2026-04-11-roadmap.md
+++ b/docs/plans/2026-04-11-roadmap.md
@@ -1,6 +1,6 @@
# acdream — strategic roadmap
-**Status:** Living document. Updated 2026-07-17. **M3 active; automated implementation complete, visual gates pending.** M2's connected demo loop is complete. M3 now has the retail cast-intent/component/target lifecycle, exact live enchantment wire state, scoped Magic input, retained spell bar, spellbook, component book, positive/negative effects panels, the complete authored seven-control gameplay indicator strip and its Link/Vitae detail pages, and shared `gmPanelUI` switching over the Step 9 projectile/PhysicsScript/particle/portal foundation. The remaining M3 work is the connected single-client magic-UI/casting gate and final two-client portal-out/materialization observer gate.
+**Status:** Living document. Updated 2026-07-17. **M3 active; automated implementation complete, visual gates pending.** M2's connected demo loop is complete. M3 now has the retail cast-intent/component/target lifecycle, exact live enchantment wire state, scoped Magic input, retained spell bar, spellbook, component book, positive/negative effects panels with authored rows and selected-spell details, the complete seven-control gameplay indicator strip and its Character Information/Link/Vitae detail pages, and shared `gmPanelUI` geometry/switching over the Step 9 projectile/PhysicsScript/particle/portal foundation. The remaining M3 work is the connected single-client magic-UI/casting gate and final two-client portal-out/materialization observer gate.
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.
---
@@ -23,8 +23,11 @@ active enchantment, then recall through the DAT-driven portal presentation.
positive/negative active-enchantment panels, the seven-control link/effect/
Vitae/burden/mini-game/session indicator strip, and the retail
one-active-main-panel lifecycle. Link Status, Helpful/Harmful, Vitae, and
- the inactive Mini Game shell now occupy that same canonical main-panel
- position; Link RTT and Vitae recovery XP are live. Connected-gate
+ Character Information, and the inactive Mini Game shell now occupy that same
+ canonical main-panel size and position; Link RTT and Vitae recovery XP are
+ live. Effects now use the exact icon/name/time row template, selection state,
+ and `SELECT A SPELL`/description field; burden opens Character Information.
+ Connected-gate
correction now also pins type-12 spellbook tabs, retail zero text margins,
and `PassToChildren` Closed/Open chrome through real-DAT fixtures.
3. **Pending user visual gate:** connected projectile/self-buff casts plus the
diff --git a/docs/plans/2026-05-12-milestones.md b/docs/plans/2026-05-12-milestones.md
index c89e2b3c..32a144cd 100644
--- a/docs/plans/2026-05-12-milestones.md
+++ b/docs/plans/2026-05-12-milestones.md
@@ -714,11 +714,14 @@ the burden/effect/logout actions use the shared panel/dialog owners. Main-panel
visibility now flows through retail's one-active `gmPanelUI` owner. M3 remains
active only for the connected single-client magic
UI/casting visual gate and the final two-client portal observer gate.
-The 2026-07-17 indicator follow-up also mounts the authored Link Status, Vitae,
-and Mini Game detail roots as children of that same main-panel owner. Helpful,
-Harmful, Link, and Vitae therefore replace Inventory/Character/Magic in one
-canonical window position. Link uses retail's payload-free ping/RTT cadence;
-Vitae uses PropertyInt 129/139 and the retail XP-pool threshold formula.
+The 2026-07-17 indicator follow-up also mounts authored Character Information,
+Link Status, Vitae, and Mini Game detail roots as children of that same
+main-panel owner. Helpful, Harmful, Character Information, Link, and Vitae
+therefore replace Inventory/Character/Magic in one canonical window rectangle.
+Helpful/Harmful instantiate retail's DAT icon/name/time row and selected-spell
+information presentation. The burden icon sends panel 3 (Character Information),
+not panel 11 (Attributes/Skills). Link uses retail's payload-free ping/RTT
+cadence; Vitae uses PropertyInt 129/139 and the retail XP-pool threshold formula.
The first connected gate exposed and corrected two retained-import defects:
spellbook tabs are authored stateful text controls (not buttons), and retail
text starts with zero margins. Real-DAT fixtures now pin spellbook binding,
diff --git a/docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md b/docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md
index ce0a3aa4..53758c54 100644
--- a/docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md
+++ b/docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md
@@ -569,6 +569,8 @@ negative root 0x10000121 (inherits 0x10000122)
shared effects template 0x10000122
effect list 0x10000123
information text 0x10000126
+effect row template 0x10000128 (300 x 32)
+row icon / name / duration 0x10000129 / 0x1000012A / 0x1000012B
effect UI type property 0x1000000C
```
@@ -622,6 +624,16 @@ an active token stores its spell ID, so every layer of that spell highlights
together and shows the shared name/description; clicking either layer again
clears the selection.
+`InfoRegion::InfoRegion @ 0x004F1450` creates each row with
+`UIElement_ListBox::AddItemFromTemplateList(template 0)`. In installed retail
+data the icon is x=0 width 32, name is x=37 width 188, and duration is x=225
+width 50. `EffectInfoRegion::Update @ 0x004F1C00` formats finite time as
+`m:ss` or `h:mm:ss`; a negative/permanent duration leaves the field empty.
+`gmEffectsUI::UpdateSelection @ 0x004B7F90` displays localized
+`ID_Effects_Info_SelectASpell` while nothing is selected, otherwise spell name
+plus two newlines plus description. Normal rows use state 1 and every row with
+the selected spell ID uses Highlight state 6.
+
## Portal presentation boundary
Recall and portal presentation remains DAT-driven. `/ls` starts the retail
diff --git a/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md b/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md
index 72ac82e2..274bee2a 100644
--- a/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md
+++ b/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md
@@ -223,15 +223,44 @@ For every toolbar or indicator detail page:
register page with gmPanelUI panel id
when shown:
hide the current child
- move the requested child to the shared main-panel placement
+ apply the shared main-panel rectangle to the requested child
show only the requested child
when a page with bool property 0x10000049 closes:
restore the deferred ordinary child
```
-Helpful/Harmful, Link Status, Vitae, and the Mini Game shell therefore share
-the same retained host position as Inventory, Character, and Magic. They do
-not remember independent positions.
+Character Information, Helpful/Harmful, Link Status, Vitae, and the Mini Game
+shell therefore share the same retained host size and position as Inventory,
+Character/Skills, and Magic. They do not remember independent rectangles.
+
+The burden/backpack indicator sends panel id `3`, whose production child is
+root `0x10000183` under LayoutDesc `0x2100006E`. Panel `11` is the separate
+Attributes/Skills child and is not the burden indicator destination.
+
+### Helpful and harmful effect rows
+
+```text
+gmEffectsUI::RebuildList @ 0x004B8350:
+ sort visible enchantment tokens alphabetically by spell name
+ AddItemFromTemplateList(template 0)
+
+EffectInfoRegion row template 0x10000128 (LayoutDesc 0x2100001B):
+ icon 0x10000129 at x=0, 32 x 32
+ name 0x1000012A at x=37, 188 x 32
+ time left 0x1000012B at x=225, 50 x 32
+
+gmEffectsUI::UpdateSelection @ 0x004B7F90:
+ no selected spell -> display ID_Effects_Info_SelectASpell
+ selected spell -> display name + "\n\n" + description
+ matching rows use Highlight state 6; other rows use Normal state 1
+
+gmEffectsUI::SetSelectedSpell @ 0x004B8290:
+ clicking the selected spell again clears selection
+```
+
+The installed local.dat resolves `ID_Effects_Info_SelectASpell` to
+`SELECT A SPELL`. Remaining finite durations use `m:ss` below one hour and
+`h:mm:ss` at one hour or more.
### End character session
diff --git a/docs/research/2026-07-17-retail-shared-main-panel-pseudocode.md b/docs/research/2026-07-17-retail-shared-main-panel-pseudocode.md
index d71b51f2..f3060e3a 100644
--- a/docs/research/2026-07-17-retail-shared-main-panel-pseudocode.md
+++ b/docs/research/2026-07-17-retail-shared-main-panel-pseudocode.md
@@ -1,4 +1,4 @@
-# Retail shared main-panel placement — pseudocode
+# Retail shared main-panel geometry — pseudocode
**Date:** 2026-07-17
**Symptom:** moving Inventory and then opening Character/Skills or Magic opens
@@ -73,10 +73,11 @@ SetPanelVisibility(panelId, show):
requested.child.SetVisible(false)
```
-The decisive placement fact is structural: Inventory, Character/Skills, and
-Magic are children of one `gmPanelUI`. The switch changes child visibility;
-there is no child move and no per-panel top-level position. Dragging the parent
-therefore moves the one position subsequently used by every child.
+The decisive geometry fact is structural: Inventory, Character Information,
+Character/Skills, Magic, Effects, and the other detail pages are children of
+one `gmPanelUI`. The switch changes child visibility; there is no child move,
+resize, or per-panel top-level rectangle. Moving or resizing the parent
+therefore changes the one rectangle subsequently used by every child.
## Resize follow-up
@@ -86,9 +87,12 @@ The production end-of-retail LayoutDesc supplies the missing geometry oracle:
LayoutDesc 0x2100006E
gmPanelUI host 0x100005FE 310 x 372
content parent 0x10000180 300 x 362, anchored on all edges
- Inventory child 0x1000018B 300 x 362, anchored on all edges
- Character child 0x1000018E 300 x 362, anchored on all edges
- Magic child 0x10000190 300 x 362, anchored on all edges
+ Character Information 0x10000183 panel 3, 300 x 362
+ Helpful Effects 0x10000184 panel 4, 300 x 362
+ Harmful Effects 0x10000185 panel 5, 300 x 362
+ Inventory child 0x1000018B panel 7, 300 x 362
+ Character/Skills child 0x1000018E panel 11, 300 x 362
+ Magic child 0x10000190 panel 13, 300 x 362
top-center 0x1000065C Type 2 Dragbar, cursor 0x06006119
bottom-center 0x10000660 Type 9 Resizebar, cursor 0x06005E66
@@ -102,7 +106,8 @@ properties `0x3C..0x3F`. There is no retail 760-pixel Character limit and no
maximum captured from the host's original screen position.
acdream retains independent wrappers under IA-12, so the faithful adaptation
-is to give each primary wrapper the same bottom-only vertical resize policy,
+is to give each registered child wrapper the same bottom-only vertical resize
+policy,
leave its maximum unbounded by an authored substitute, and calculate the
available desktop extent from its current position during the resize. The
viewport remains the physical limit; an old static maximum must not become a
@@ -113,31 +118,31 @@ smaller limit after the shared panel moves.
acdream imports these child LayoutDesc roots independently, so each currently
has its own retained wrapper. `RetailPanelUiController` already ports retail's
one-active-child and restore-previous lifecycle. Complete that logical-parent
-ownership by adding one canonical placement for the primary toolbar children
-(Inventory `7`, Character `11`, Magic `13`):
+ownership by adding one canonical rectangle for all registered main-panel
+children, including Character Information `3`, Helpful/Harmful `4`/`5`,
+Inventory `7`, Character/Skills `11`, and Magic `13`:
```text
-on primary child frame moved:
- canonicalPosition = moved frame position
- move every other primary child frame to canonicalPosition
+on registered child frame moved or resized:
+ canonicalGeometry = frame left, top, width, height
+ apply canonicalGeometry to every hidden sibling
-before showing a primary child:
- if canonicalPosition exists:
- move requested child frame to canonicalPosition
+before showing a registered child:
+ if canonicalGeometry exists:
+ apply canonicalGeometry to requested child frame
else:
- canonicalPosition = requested child frame position
+ canonicalGeometry = requested child frame geometry
-before hiding the active primary child:
- capture its current position as canonicalPosition
+before hiding the active child:
+ capture its current geometry as canonicalGeometry
```
The controller owns this synchronization and subscribes to typed
-`RetailWindowHandle.Moved` events. Toolbar commands, keyboard commands,
-restore-previous effects, persistence restore, and direct window dragging all
-therefore pass through the same owner. Panel-authored size/resize policy stays
-with the active child, but the three primary wrappers apply the shared host's
-same vertical resize contract; only placement is synchronized between the
-independent frames.
+`RetailWindowHandle.Moved` and `Resized` events. Toolbar, indicator, and
+keyboard commands, restore-previous effects, persistence restore, and direct
+window dragging all therefore pass through the same owner. Each wrapper
+retains its own imported content tree, but its outer rectangle and bottom-only
+resize contract mirror the shared retail host.
ACE contains no client UI implementation, and the in-tree WorldBuilder
reference contains no `gmPanelUI` port, so neither supplies a competing
diff --git a/src/AcDream.App/UI/Layout/CharacterController.cs b/src/AcDream.App/UI/Layout/CharacterController.cs
index 709c8262..b63b2708 100644
--- a/src/AcDream.App/UI/Layout/CharacterController.cs
+++ b/src/AcDream.App/UI/Layout/CharacterController.cs
@@ -6,7 +6,8 @@ using AcDream.App.UI;
namespace AcDream.App.UI.Layout;
///
-/// Per-window controller for the Character window (LayoutDesc 0x2100002E, gmCharacterInfoUI).
+/// Per-window controller for the Character Information child in the production
+/// gmPanelUI layout (LayoutDesc 0x2100006E, root 0x10000183).
///
/// Retail fills a single scrollable UIElement_Text element (id 0x1000011d,
/// m_pMainText) by calling a sequence of Update* methods that each
@@ -32,9 +33,14 @@ namespace AcDream.App.UI.Layout;
///
public static class CharacterController
{
+ public const uint LayoutId = 0x2100006Eu;
+ public const uint RootId = 0x10000183u;
+
/// Dat element id for the main report text (m_pMainText). Confirmed in
/// gmCharacterInfoUI::PostInit 0x004b86f0: GetChildRecursive(this, 0x1000011d).
public const uint MainTextId = 0x1000011du;
+ public const uint ScrollbarId = 0x1000011Eu;
+ public const uint CloseId = 0x100000FCu;
/// White body text — matches retail's default UIElement_Text foreground.
private static readonly Vector4 BodyColor = new(1f, 1f, 1f, 1f);
@@ -48,27 +54,27 @@ public static class CharacterController
/// so the Studio or a live session can push a fresh
/// without re-binding.
///
- /// The element must resolve as a (Type 12 in the dat).
- /// If 0x1000011d is absent from the layout the bind is a no-op — partial layouts
- /// (e.g. test fakes) don't cause errors.
+ /// Production data resolves the element as a
+ /// (Type 12). A synthetic fallback is created only for deliberately reduced
+ /// test layouts.
///
- /// Imported 0x2100002E layout tree.
+ /// Imported 0x2100006E / 0x10000183 layout tree.
/// Provider returning the current .
/// Retail dat font forwarded from the render stack. May be null
/// (falls back to the BitmapFont debug path).
public static void Bind(
ImportedLayout layout,
Func data,
- UiDatFont? datFont = null)
+ UiDatFont? datFont = null,
+ Action? close = null)
{
- // Retail's gmCharacterInfoUI CREATES m_pMainText (0x1000011d) at RUNTIME — it is NOT a static
- // element in any LayoutDesc (confirmed: acdream's dat-import of 0x2100002E AND 0x2100006E both
- // lack it; only a runtime UI-tree capture has it). So replicate that: build the report text
- // element ourselves and place it in the panel body, exactly as the gm*UI does at runtime.
+ // End-of-retail data authors m_pMainText under the Character Information
+ // child. The fallback exists only so focused controller tests can bind a
+ // deliberately reduced tree without fabricating production structure.
var root = layout.Root;
if (root is null) return;
- var text = new UiText
+ UiText text = layout.FindElement(MainTextId) as UiText ?? new UiText
{
EventId = MainTextId,
Name = "m_pMainText",
@@ -81,8 +87,17 @@ public static class CharacterController
DatFont = datFont,
ClickThrough = false,
};
+ if (text.Parent is null)
+ root.AddChild(text);
+ else if (text.DatFont is null && datFont is not null)
+ text.DatFont = datFont;
+ text.PreserveEndOnLayout = false;
+ if (layout.FindElement(ScrollbarId) is UiScrollbar scrollbar)
+ scrollbar.Model = text.Scroll;
text.LinesProvider = () => BuildReport(data());
- root.AddChild(text);
+
+ if (layout.FindElement(CloseId) is UiButton closeButton)
+ closeButton.OnClick = close;
}
// ── Report builder ────────────────────────────────────────────────────────
diff --git a/src/AcDream.App/UI/Layout/EffectRowTemplateFactory.cs b/src/AcDream.App/UI/Layout/EffectRowTemplateFactory.cs
new file mode 100644
index 00000000..d888cfd8
--- /dev/null
+++ b/src/AcDream.App/UI/Layout/EffectRowTemplateFactory.cs
@@ -0,0 +1,148 @@
+using DatReaderWriter;
+
+namespace AcDream.App.UI.Layout;
+
+///
+/// Instantiates retail's effect-token template 0x10000128. This is the
+/// retained equivalent of InfoRegion::InfoRegion @ 0x004F1450 calling
+/// UIElement_ListBox::AddItemFromTemplateList for
+/// EffectInfoRegion.
+///
+public sealed class EffectRowTemplateFactory
+{
+ private readonly ElementInfo _template;
+ private readonly Func _resolveSprite;
+ private readonly UiDatFont? _defaultFont;
+ private readonly IReadOnlyDictionary _fonts;
+
+ public EffectRowTemplateFactory(
+ ElementInfo template,
+ Func resolveSprite,
+ UiDatFont? defaultFont,
+ IReadOnlyDictionary? fonts = null)
+ {
+ _template = template ?? throw new ArgumentNullException(nameof(template));
+ _resolveSprite = resolveSprite ?? throw new ArgumentNullException(nameof(resolveSprite));
+ _defaultFont = defaultFont;
+ _fonts = fonts ?? new Dictionary();
+ }
+
+ public float Width => _template.Width;
+ public float Height => _template.Height;
+
+ public static EffectRowTemplateFactory? TryLoad(
+ DatCollection dats,
+ Func resolveSprite,
+ UiDatFont? defaultFont,
+ Func? resolveFont)
+ {
+ ElementInfo? template = LayoutImporter.ImportInfos(
+ dats,
+ EffectsUiController.LayoutId,
+ EffectsUiController.RowTemplateId);
+ if (template is null
+ || Find(template, EffectsUiController.RowIconId) is null
+ || Find(template, EffectsUiController.RowLabelId) is null
+ || Find(template, EffectsUiController.RowDurationId) is null
+ || template.Width <= 0f
+ || template.Height <= 0f)
+ return null;
+
+ var fonts = new Dictionary();
+ CaptureFonts(template, resolveFont, fonts);
+ return new EffectRowTemplateFactory(
+ template, resolveSprite, defaultFont, fonts);
+ }
+
+ public EffectRow Create(
+ uint spellId,
+ uint iconTexture,
+ string name,
+ string remaining)
+ {
+ ImportedLayout content = LayoutImporter.Build(
+ _template,
+ _resolveSprite,
+ _defaultFont,
+ did => _fonts.TryGetValue(did, out UiDatFont? font)
+ ? font
+ : _defaultFont);
+ UiElement iconHost = Required(content, EffectsUiController.RowIconId);
+ UiText label = Required(content, EffectsUiController.RowLabelId);
+ UiText duration = Required(content, EffectsUiController.RowDurationId);
+
+ iconHost.AddChild(new UiTextureElement
+ {
+ Width = iconHost.Width,
+ Height = iconHost.Height,
+ Anchors = AnchorEdges.Left | AnchorEdges.Top
+ | AnchorEdges.Right | AnchorEdges.Bottom,
+ Texture = iconTexture,
+ });
+ label.LinesProvider = () => [new UiText.Line(name, label.DefaultColor)];
+
+ var slot = new UiTemplateListSlot(
+ content,
+ spellId,
+ UiButtonStateMachine.Normal,
+ UiButtonStateMachine.Highlight);
+ return new EffectRow(slot, duration, remaining);
+ }
+
+ public sealed class EffectRow
+ {
+ private readonly UiText _duration;
+ private string _remaining;
+
+ internal EffectRow(
+ UiTemplateListSlot slot,
+ UiText duration,
+ string remaining)
+ {
+ Slot = slot;
+ _duration = duration;
+ _remaining = remaining;
+ _duration.LinesProvider = () =>
+ [new UiText.Line(_remaining, _duration.DefaultColor)];
+ }
+
+ public UiTemplateListSlot Slot { get; }
+
+ public string Remaining
+ {
+ get => _remaining;
+ set => _remaining = value;
+ }
+ }
+
+ private static T Required(ImportedLayout content, uint id)
+ where T : UiElement
+ => content.FindElement(id) as T
+ ?? throw new InvalidOperationException(
+ $"Retail effect template element 0x{id:X8} did not resolve to {typeof(T).Name}.");
+
+ private static UiElement Required(ImportedLayout content, uint id)
+ => content.FindElement(id)
+ ?? throw new InvalidOperationException(
+ $"Retail effect template element 0x{id:X8} is missing.");
+
+ private static ElementInfo? Find(ElementInfo root, uint id)
+ {
+ if (root.Id == id) return root;
+ foreach (ElementInfo child in root.Children)
+ if (Find(child, id) is { } found)
+ return found;
+ return null;
+ }
+
+ private static void CaptureFonts(
+ ElementInfo info,
+ Func? resolveFont,
+ Dictionary fonts)
+ {
+ if (info.FontDid != 0u && !fonts.ContainsKey(info.FontDid))
+ fonts[info.FontDid] = resolveFont?.Invoke(info.FontDid);
+ foreach (ElementInfo child in info.Children)
+ CaptureFonts(child, resolveFont, fonts);
+ }
+}
diff --git a/src/AcDream.App/UI/Layout/EffectsUiController.cs b/src/AcDream.App/UI/Layout/EffectsUiController.cs
index cc04b290..772b2af2 100644
--- a/src/AcDream.App/UI/Layout/EffectsUiController.cs
+++ b/src/AcDream.App/UI/Layout/EffectsUiController.cs
@@ -16,16 +16,24 @@ public sealed class EffectsUiController : IRetainedPanelController
public const uint NegativeRootId = 0x10000121u;
public const uint CloseId = 0x100000FCu;
public const uint ListId = 0x10000123u;
+ public const uint ScrollbarId = 0x10000124u;
public const uint InfoTextId = 0x10000126u;
+ public const uint RowTemplateId = 0x10000128u;
+ public const uint RowIconId = 0x10000129u;
+ public const uint RowLabelId = 0x1000012Au;
+ public const uint RowDurationId = 0x1000012Bu;
private readonly Spellbook _spellbook;
private readonly bool _positive;
private readonly Func _serverTime;
private readonly Func _resolveSpellIcon;
+ private readonly EffectRowTemplateFactory _templates;
+ private readonly string _selectPrompt;
private readonly UiItemList _list;
private readonly UiText? _info;
private readonly UiButton? _close;
- private readonly Dictionary _rows = new();
+ private readonly UiScrollbar? _scrollbar;
+ private readonly Dictionary _rows = new();
private uint? _selectedSpellId;
private double _lastDurationUpdate = double.NaN;
private bool _disposed;
@@ -38,6 +46,8 @@ public sealed class EffectsUiController : IRetainedPanelController
bool positive,
Func serverTime,
Func resolveSpellIcon,
+ EffectRowTemplateFactory templates,
+ string selectPrompt,
UiItemList list,
Action? close)
{
@@ -45,13 +55,18 @@ public sealed class EffectsUiController : IRetainedPanelController
_positive = positive;
_serverTime = serverTime;
_resolveSpellIcon = resolveSpellIcon;
+ _templates = templates;
+ _selectPrompt = selectPrompt;
_list = list;
_info = layout.FindElement(InfoTextId) as UiText;
_close = layout.FindElement(CloseId) as UiButton;
+ _scrollbar = layout.FindElement(ScrollbarId) as UiScrollbar;
if (_close is not null) _close.OnClick = close;
_list.Columns = 1;
- _list.CellWidth = Math.Max(1f, _list.Width);
- _list.CellHeight = 32f;
+ _list.CellWidth = templates.Width;
+ _list.CellHeight = templates.Height;
+ if (_scrollbar is not null)
+ _scrollbar.Model = _list.Scroll;
ConfigureInfo();
_spellbook.EnchantmentsChanged += Rebuild;
Rebuild();
@@ -64,6 +79,8 @@ public sealed class EffectsUiController : IRetainedPanelController
Func serverTime,
Func spriteResolve,
Func resolveSpellIcon,
+ EffectRowTemplateFactory templates,
+ string selectPrompt,
Action? close = null)
{
UiElement? host = layout.FindElement(ListId);
@@ -82,7 +99,15 @@ public sealed class EffectsUiController : IRetainedPanelController
host.AddChild(list);
}
return new EffectsUiController(
- layout, spellbook, positive, serverTime, resolveSpellIcon, list, close);
+ layout,
+ spellbook,
+ positive,
+ serverTime,
+ resolveSpellIcon,
+ templates,
+ selectPrompt,
+ list,
+ close);
}
public void Tick()
@@ -95,8 +120,10 @@ public sealed class EffectsUiController : IRetainedPanelController
return;
_lastDurationUpdate = now;
foreach (ActiveEnchantmentRecord enchantment in VisibleEnchantments())
- if (_rows.TryGetValue(enchantment.Identity, out UiCatalogSlot? row))
- row.Detail = FormatRemaining(enchantment, now);
+ if (_rows.TryGetValue(
+ enchantment.Identity,
+ out EffectRowTemplateFactory.EffectRow? row))
+ row.Remaining = FormatRemaining(enchantment, now);
}
private void Rebuild()
@@ -111,22 +138,18 @@ public sealed class EffectsUiController : IRetainedPanelController
{
_spellbook.TryGetMetadata(enchantment.SpellId, out SpellMetadata? metadata);
uint identity = enchantment.Identity;
- var row = new UiCatalogSlot
- {
- EntryId = enchantment.SpellId,
- CatalogIconTexture = metadata is null ? 0u : _resolveSpellIcon(enchantment.SpellId),
- Label = metadata?.Name ?? $"Spell {enchantment.SpellId}",
- Detail = FormatRemaining(enchantment, _serverTime()),
- ShowLabel = true,
- SpriteResolve = _list.SpriteResolve,
- };
- row.Clicked = () => Select(enchantment.SpellId);
+ EffectRowTemplateFactory.EffectRow row = _templates.Create(
+ enchantment.SpellId,
+ metadata is null ? 0u : _resolveSpellIcon(enchantment.SpellId),
+ metadata?.Name ?? $"Spell {enchantment.SpellId}",
+ FormatRemaining(enchantment, _serverTime()));
+ row.Slot.Clicked = () => Select(enchantment.SpellId);
_rows[identity] = row;
- _list.AddItem(row);
+ _list.AddItem(row.Slot);
}
}
if (_selectedSpellId is uint selected
- && !_rows.Values.Any(row => row.EntryId == selected))
+ && !_rows.Values.Any(row => row.Slot.EntryId == selected))
_selectedSpellId = null;
SyncSelection();
}
@@ -145,7 +168,9 @@ public sealed class EffectsUiController : IRetainedPanelController
private static string FormatRemaining(ActiveEnchantmentRecord enchantment, double now)
{
- if (enchantment.Duration < 0) return "Permanent";
+ // EffectInfoRegion::Update @ 0x004F1C00 starts from retail's empty
+ // PString and only formats it when the remaining duration is >= 0.
+ if (enchantment.Duration < 0) return string.Empty;
double remaining = Math.Max(0, enchantment.StartTime + enchantment.Duration - now);
if (!double.IsFinite(remaining)) return "--:--";
remaining = Math.Min(remaining, TimeSpan.MaxValue.TotalSeconds);
@@ -165,25 +190,27 @@ public sealed class EffectsUiController : IRetainedPanelController
private void SyncSelection()
{
- foreach (UiCatalogSlot row in _rows.Values)
- row.Selected = row.EntryId == _selectedSpellId;
+ foreach (EffectRowTemplateFactory.EffectRow row in _rows.Values)
+ row.Slot.SetSelected(row.Slot.EntryId == _selectedSpellId);
}
private void ConfigureInfo()
{
if (_info is null) return;
+ _info.PreserveEndOnLayout = false;
_info.LinesProvider = () =>
{
if (_selectedSpellId is not uint spellId)
- return Array.Empty();
+ return [new UiText.Line(_selectPrompt, _info.DefaultColor)];
ActiveEnchantmentRecord? selected = _spellbook.ActiveEnchantmentSnapshot
.Cast()
.FirstOrDefault(record => record?.SpellId == spellId);
if (selected is null || !_spellbook.TryGetMetadata(selected.Value.SpellId, out SpellMetadata metadata))
- return Array.Empty();
+ return [new UiText.Line(_selectPrompt, _info.DefaultColor)];
return
[
new UiText.Line(metadata.Name, _info.DefaultColor),
+ new UiText.Line(string.Empty, _info.DefaultColor),
new UiText.Line(metadata.Description, _info.DefaultColor),
];
};
@@ -197,5 +224,6 @@ public sealed class EffectsUiController : IRetainedPanelController
_disposed = true;
_spellbook.EnchantmentsChanged -= Rebuild;
if (_close is not null) _close.OnClick = null;
+ if (_scrollbar is not null) _scrollbar.Model = null;
}
}
diff --git a/src/AcDream.App/UI/Layout/IndicatorBarController.cs b/src/AcDream.App/UI/Layout/IndicatorBarController.cs
index 713e87f5..1cf19182 100644
--- a/src/AcDream.App/UI/Layout/IndicatorBarController.cs
+++ b/src/AcDream.App/UI/Layout/IndicatorBarController.cs
@@ -87,7 +87,7 @@ public sealed class IndicatorBarController : IRetainedPanelController
_harmful.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.NegativeEffects);
_link.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.LinkStatus);
_vitae.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.Vitae);
- _burden.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.Character);
+ _burden.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.CharacterInformation);
_miniGame.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.MiniGame);
_endCharacterSession.OnClick = bindings.RequestEndCharacterSession;
diff --git a/src/AcDream.App/UI/Layout/RetailPanelUiController.cs b/src/AcDream.App/UI/Layout/RetailPanelUiController.cs
index 50e9e251..c5e9c8d5 100644
--- a/src/AcDream.App/UI/Layout/RetailPanelUiController.cs
+++ b/src/AcDream.App/UI/Layout/RetailPanelUiController.cs
@@ -21,8 +21,8 @@ public sealed class RetailPanelUiController : IDisposable
private uint? _activePanel;
private uint? _deferredPanel;
private bool _applying;
- private bool _synchronizingPlacement;
- private PanelPlacement? _mainPanelPlacement;
+ private bool _synchronizingGeometry;
+ private PanelGeometry? _mainPanelGeometry;
private bool _disposed;
public RetailPanelUiController(
@@ -38,11 +38,11 @@ public sealed class RetailPanelUiController : IDisposable
public uint? ActivePanelId => _activePanel;
///
- /// Registers one of retail gmPanelUI's primary toolbar children.
- /// Inventory, Character, and Magic retain panel-specific content/resize
- /// policy but share the one parent placement. A drag of any registered
- /// child updates every hidden sibling through typed window handles, so
- /// persistence observes the same canonical position too.
+ /// Registers one of retail gmPanelUI's toolbar or detail children.
+ /// All primary and detail children retain panel-specific content but share
+ /// one parent geometry. A move or resize of any registered child updates
+ /// every hidden sibling through typed window handles, so persistence sees
+ /// the same canonical rectangle too.
///
public void RegisterMainPanel(
uint panelId,
@@ -61,7 +61,7 @@ public sealed class RetailPanelUiController : IDisposable
windowName,
restorePrevious,
window,
- sharesMainPanelPlacement: true);
+ sharesMainPanelGeometry: true);
}
///
@@ -75,14 +75,14 @@ public sealed class RetailPanelUiController : IDisposable
windowName,
restorePrevious,
window: null,
- sharesMainPanelPlacement: false);
+ sharesMainPanelGeometry: false);
private void RegisterCore(
uint panelId,
string windowName,
bool restorePrevious,
RetailWindowHandle? window,
- bool sharesMainPanelPlacement)
+ bool sharesMainPanelGeometry)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (panelId == 0) throw new ArgumentOutOfRangeException(nameof(panelId));
@@ -95,14 +95,17 @@ public sealed class RetailPanelUiController : IDisposable
windowName,
restorePrevious,
window,
- sharesMainPanelPlacement);
+ sharesMainPanelGeometry);
_byPanel.Add(panelId, entry);
_byWindow.Add(windowName, panelId);
if (window is not null)
+ {
window.Moved += OnWindowMoved;
+ window.Resized += OnWindowResized;
+ }
- if (sharesMainPanelPlacement && _mainPanelPlacement is { } placement)
- MoveWindowTo(entry, placement);
+ if (sharesMainPanelGeometry && _mainPanelGeometry is { } geometry)
+ ApplyWindowGeometry(entry, geometry);
if (_isVisible(windowName))
ObserveWindowVisibility(windowName, visible: true);
}
@@ -144,7 +147,7 @@ public sealed class RetailPanelUiController : IDisposable
{
if (_activePanel == panelId)
{
- PrepareMainPanelPlacement(requested);
+ PrepareMainPanelGeometry(requested);
return _show(requested.WindowName);
}
@@ -156,7 +159,7 @@ public sealed class RetailPanelUiController : IDisposable
: null;
if (previous is { } previousEntry)
- CaptureMainPanelPlacement(previousEntry, synchronizeSiblings: true);
+ CaptureMainPanelGeometry(previousEntry, synchronizeSiblings: true);
_deferredPanel = requested.RestorePrevious
&& previous is { RestorePrevious: false }
@@ -165,7 +168,7 @@ public sealed class RetailPanelUiController : IDisposable
_activePanel = panelId;
if (previous is not null)
_hide(previous.Value.WindowName);
- PrepareMainPanelPlacement(requested);
+ PrepareMainPanelGeometry(requested);
return _show(requested.WindowName);
}
@@ -175,7 +178,7 @@ public sealed class RetailPanelUiController : IDisposable
return _hide(requested.WindowName);
}
- CaptureMainPanelPlacement(requested, synchronizeSiblings: true);
+ CaptureMainPanelGeometry(requested, synchronizeSiblings: true);
bool hidden = _hide(requested.WindowName);
_activePanel = null;
if (_deferredPanel is not uint deferredId
@@ -184,7 +187,7 @@ public sealed class RetailPanelUiController : IDisposable
_deferredPanel = null;
_activePanel = deferredId;
- PrepareMainPanelPlacement(deferred);
+ PrepareMainPanelGeometry(deferred);
return _show(deferred.WindowName) || hidden;
}
finally
@@ -205,76 +208,109 @@ public sealed class RetailPanelUiController : IDisposable
private void OnWindowMoved(RetailWindowHandle window)
{
- if (_disposed || _synchronizingPlacement) return;
+ if (_disposed || _synchronizingGeometry) return;
foreach (PanelEntry entry in _byPanel.Values)
{
- if (!entry.SharesMainPanelPlacement
+ if (!entry.SharesMainPanelGeometry
|| !ReferenceEquals(entry.Window, window))
continue;
- _mainPanelPlacement = new PanelPlacement(window.Left, window.Top);
- SynchronizeMainPanelSiblings(window);
+ CaptureAndSynchronizeMainPanelGeometry(window);
return;
}
}
- private void CaptureMainPanelPlacement(
+ private void OnWindowResized(RetailWindowHandle window)
+ {
+ if (_disposed || _synchronizingGeometry) return;
+
+ foreach (PanelEntry entry in _byPanel.Values)
+ {
+ if (!entry.SharesMainPanelGeometry
+ || !ReferenceEquals(entry.Window, window))
+ continue;
+
+ CaptureAndSynchronizeMainPanelGeometry(window);
+ return;
+ }
+ }
+
+ private void CaptureAndSynchronizeMainPanelGeometry(RetailWindowHandle source)
+ {
+ _mainPanelGeometry = new PanelGeometry(
+ source.Left,
+ source.Top,
+ source.Width,
+ source.Height);
+ SynchronizeMainPanelSiblings(source);
+ }
+
+ private void CaptureMainPanelGeometry(
PanelEntry entry,
bool synchronizeSiblings)
{
- if (!entry.SharesMainPanelPlacement || entry.Window is not { } window)
+ if (!entry.SharesMainPanelGeometry || entry.Window is not { } window)
return;
- _mainPanelPlacement = new PanelPlacement(window.Left, window.Top);
+ _mainPanelGeometry = new PanelGeometry(
+ window.Left,
+ window.Top,
+ window.Width,
+ window.Height);
if (synchronizeSiblings)
SynchronizeMainPanelSiblings(window);
}
- private void PrepareMainPanelPlacement(PanelEntry entry)
+ private void PrepareMainPanelGeometry(PanelEntry entry)
{
- if (!entry.SharesMainPanelPlacement || entry.Window is not { } window)
+ if (!entry.SharesMainPanelGeometry || entry.Window is not { } window)
return;
- if (_mainPanelPlacement is not { } placement)
+ if (_mainPanelGeometry is not { } geometry)
{
- _mainPanelPlacement = new PanelPlacement(window.Left, window.Top);
+ _mainPanelGeometry = new PanelGeometry(
+ window.Left,
+ window.Top,
+ window.Width,
+ window.Height);
SynchronizeMainPanelSiblings(window);
return;
}
- MoveWindowTo(entry, placement);
+ ApplyWindowGeometry(entry, geometry);
}
private void SynchronizeMainPanelSiblings(RetailWindowHandle source)
{
- if (_mainPanelPlacement is not { } placement) return;
+ if (_mainPanelGeometry is not { } geometry) return;
- _synchronizingPlacement = true;
+ _synchronizingGeometry = true;
try
{
foreach (PanelEntry sibling in _byPanel.Values)
{
- if (!sibling.SharesMainPanelPlacement
+ if (!sibling.SharesMainPanelGeometry
|| sibling.Window is not { } window
|| ReferenceEquals(window, source))
continue;
- MoveWindowTo(sibling, placement);
+ ApplyWindowGeometry(sibling, geometry);
}
}
finally
{
- _synchronizingPlacement = false;
+ _synchronizingGeometry = false;
}
}
- private void MoveWindowTo(PanelEntry entry, PanelPlacement placement)
+ private static void ApplyWindowGeometry(PanelEntry entry, PanelGeometry geometry)
{
- if (entry.Window is not { } window
- || (window.Left == placement.Left && window.Top == placement.Top))
- return;
+ if (entry.Window is not { } window) return;
- window.MoveTo(placement.Left, placement.Top);
+ if (window.Left != geometry.Left || window.Top != geometry.Top)
+ window.MoveTo(geometry.Left, geometry.Top);
+ if (window.Width != geometry.Width || window.Height != geometry.Height)
+ window.ResizeTo(geometry.Width, geometry.Height);
}
public void Dispose()
@@ -283,14 +319,21 @@ public sealed class RetailPanelUiController : IDisposable
_disposed = true;
foreach (PanelEntry entry in _byPanel.Values)
if (entry.Window is { } window)
+ {
window.Moved -= OnWindowMoved;
+ window.Resized -= OnWindowResized;
+ }
}
- private readonly record struct PanelPlacement(float Left, float Top);
+ private readonly record struct PanelGeometry(
+ float Left,
+ float Top,
+ float Width,
+ float Height);
private readonly record struct PanelEntry(
string WindowName,
bool RestorePrevious,
RetailWindowHandle? Window,
- bool SharesMainPanelPlacement);
+ bool SharesMainPanelGeometry);
}
diff --git a/src/AcDream.App/UI/RetailPanelCatalog.cs b/src/AcDream.App/UI/RetailPanelCatalog.cs
index b2577b15..95dcae21 100644
--- a/src/AcDream.App/UI/RetailPanelCatalog.cs
+++ b/src/AcDream.App/UI/RetailPanelCatalog.cs
@@ -7,6 +7,7 @@ namespace AcDream.App.UI;
///
public static class RetailPanelCatalog
{
+ public const uint CharacterInformation = 3u;
public const uint PositiveEffects = 4u;
public const uint NegativeEffects = 5u;
public const uint Inventory = 7u;
@@ -18,6 +19,7 @@ public static class RetailPanelCatalog
private static readonly (uint PanelId, string WindowName)[] Mounted =
{
+ (CharacterInformation, WindowNames.CharacterInformation),
(PositiveEffects, WindowNames.PositiveEffects),
(NegativeEffects, WindowNames.NegativeEffects),
(Inventory, WindowNames.Inventory),
diff --git a/src/AcDream.App/UI/RetailUiRuntime.cs b/src/AcDream.App/UI/RetailUiRuntime.cs
index fcf8e198..e69ee869 100644
--- a/src/AcDream.App/UI/RetailUiRuntime.cs
+++ b/src/AcDream.App/UI/RetailUiRuntime.cs
@@ -847,6 +847,8 @@ public sealed class RetailUiRuntime : IDisposable
uint rootId = positive ? EffectsUiController.PositiveRootId : EffectsUiController.NegativeRootId;
ElementInfo? rootInfo;
ImportedLayout? layout;
+ EffectRowTemplateFactory? rowTemplates;
+ string selectPrompt;
lock (_bindings.Assets.DatLock)
{
rootInfo = LayoutImporter.ImportInfos(
@@ -861,10 +863,20 @@ public sealed class RetailUiRuntime : IDisposable
_bindings.Assets.DefaultFont,
_bindings.Assets.ResolveFont,
new DatStringResolver(_bindings.Assets.Dats).Resolve);
+ rowTemplates = EffectRowTemplateFactory.TryLoad(
+ _bindings.Assets.Dats,
+ _bindings.Assets.ResolveSprite,
+ _bindings.Assets.DefaultFont,
+ _bindings.Assets.ResolveFont);
+ var strings = new DatStringResolver(_bindings.Assets.Dats);
+ selectPrompt = strings.Resolve(
+ 0x23000001u,
+ DatStringResolver.ComputeHash("ID_Effects_Info_SelectASpell"))
+ ?? "SELECT A SPELL";
}
- if (rootInfo is null || layout is null)
+ if (rootInfo is null || layout is null || rowTemplates is null)
{
- Console.WriteLine($"[M3] effects: root 0x{rootId:X8} not found.");
+ Console.WriteLine($"[M3] effects: root or row template for 0x{rootId:X8} not found.");
return;
}
@@ -875,6 +887,8 @@ public sealed class RetailUiRuntime : IDisposable
_bindings.Magic.ServerTime,
_bindings.Assets.ResolveSprite,
_bindings.Magic.ResolveSpellIcon,
+ rowTemplates,
+ selectPrompt,
close: () => CloseWindow(
positive ? WindowNames.PositiveEffects : WindowNames.NegativeEffects));
if (controller is null)
@@ -893,14 +907,17 @@ public sealed class RetailUiRuntime : IDisposable
new RetailWindowFrame.Options
{
WindowName = positive ? WindowNames.PositiveEffects : WindowNames.NegativeEffects,
- Chrome = RetailWindowChrome.Imported,
+ Chrome = RetailWindowChrome.NineSlice,
Left = Math.Max(0f, Host.Root.Width - root.Width - 12f),
Top = 18f,
Visible = false,
- Resizable = false,
ResizeX = false,
- ResizeY = false,
+ ResizeY = true,
+ ResizableEdges = ResizeEdges.Bottom,
ConstrainDragToParent = true,
+ ConstrainResizeToParent = true,
+ ContentAnchors = AnchorEdges.Left | AnchorEdges.Top
+ | AnchorEdges.Right | AnchorEdges.Bottom,
ContentClickThrough = false,
Controller = controller,
});
@@ -919,6 +936,41 @@ public sealed class RetailUiRuntime : IDisposable
MountLinkStatusPanel();
MountVitaePanel();
MountMiniGamePanel();
+ MountCharacterInformationPanel();
+ }
+
+ private void MountCharacterInformationPanel()
+ {
+ ElementInfo? rootInfo;
+ ImportedLayout? layout;
+ lock (_bindings.Assets.DatLock)
+ {
+ rootInfo = LayoutImporter.ImportInfos(
+ _bindings.Assets.Dats,
+ CharacterController.LayoutId,
+ CharacterController.RootId);
+ layout = rootInfo is null
+ ? null
+ : LayoutImporter.Build(
+ rootInfo,
+ _bindings.Assets.ResolveSprite,
+ _bindings.Assets.DefaultFont,
+ _bindings.Assets.ResolveFont,
+ new DatStringResolver(_bindings.Assets.Dats).Resolve);
+ }
+ if (rootInfo is null || layout is null) return;
+
+ CharacterController.Bind(
+ layout,
+ _bindings.Character.Provider.BuildSheet,
+ _bindings.Assets.DefaultFont,
+ () => CloseWindow(WindowNames.CharacterInformation));
+ RegisterIndicatorDetailPanel(
+ RetailPanelCatalog.CharacterInformation,
+ WindowNames.CharacterInformation,
+ rootInfo,
+ layout.Root,
+ controller: null);
}
private void MountLinkStatusPanel()
@@ -1054,7 +1106,7 @@ public sealed class RetailUiRuntime : IDisposable
string windowName,
ElementInfo rootInfo,
UiElement root,
- IRetainedPanelController controller)
+ IRetainedPanelController? controller)
{
RetailWindowHandle handle = RetailWindowFrame.Mount(
Host.Root,
@@ -1063,14 +1115,17 @@ public sealed class RetailUiRuntime : IDisposable
new RetailWindowFrame.Options
{
WindowName = windowName,
- Chrome = RetailWindowChrome.Imported,
+ Chrome = RetailWindowChrome.NineSlice,
Left = 18f,
Top = 18f,
Visible = false,
- Resizable = false,
ResizeX = false,
- ResizeY = false,
+ ResizeY = true,
+ ResizableEdges = ResizeEdges.Bottom,
ConstrainDragToParent = true,
+ ConstrainResizeToParent = true,
+ ContentAnchors = AnchorEdges.Left | AnchorEdges.Top
+ | AnchorEdges.Right | AnchorEdges.Bottom,
ContentClickThrough = false,
Controller = controller,
});
diff --git a/src/AcDream.App/UI/UiText.cs b/src/AcDream.App/UI/UiText.cs
index fb54c08d..5fc596d0 100644
--- a/src/AcDream.App/UI/UiText.cs
+++ b/src/AcDream.App/UI/UiText.cs
@@ -152,6 +152,13 @@ public sealed class UiText : UiElement, IUiDatStateful
/// The scroll model — also read by the linked UiScrollbar.
public UiScrollable Scroll { get; } = new();
+ ///
+ /// Keeps a view that is already at the end pinned there when content or
+ /// geometry changes. Chat uses the default; top-oriented reports such as
+ /// Character Information disable it.
+ ///
+ public bool PreserveEndOnLayout { get; set; } = true;
+
private const float WheelLines = 1f; // lines advanced per wheel notch (retail = 1 line per notch)
// ── Cached layout from the last OnDraw, so OnEvent hit-tests the SAME geometry ──
@@ -448,7 +455,7 @@ public sealed class UiText : UiElement, IUiDatStateful
Scroll.SetExtents(
(int)MathF.Ceiling(contentH),
(int)MathF.Floor(innerH),
- preserveEnd: true);
+ preserveEnd: PreserveEndOnLayout);
// Overflow keeps the UiScrollable convention: ScrollY=0 is TOP/oldest and
// ScrollY=MaxScroll is BOTTOM/newest. Fitting DAT-authored content instead
diff --git a/src/AcDream.App/UI/WindowNames.cs b/src/AcDream.App/UI/WindowNames.cs
index 21069b3e..b5acacef 100644
--- a/src/AcDream.App/UI/WindowNames.cs
+++ b/src/AcDream.App/UI/WindowNames.cs
@@ -7,6 +7,7 @@ public static class WindowNames
public const string Vitals = "vitals";
public const string Toolbar = "toolbar";
public const string Character = "character";
+ public const string CharacterInformation = "character-information";
public const string Inventory = "inventory";
public const string Chat = "chat";
public const string Radar = "radar";
diff --git a/tests/AcDream.App.Tests/UI/Layout/CharacterControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/CharacterControllerTests.cs
index 0f92ca07..c137c7dd 100644
--- a/tests/AcDream.App.Tests/UI/Layout/CharacterControllerTests.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/CharacterControllerTests.cs
@@ -6,12 +6,37 @@ namespace AcDream.App.Tests.UI.Layout;
///
/// Unit tests for and .
-/// The controller CREATES the m_pMainText report element (0x1000011d) at bind time — retail's
-/// gm*UI builds it at runtime; it is not a static dat element — and attaches it to the layout root.
-/// No dats, no GL — pure data-wiring + report-content tests.
+/// Production Character Information data authors m_pMainText (0x1000011d);
+/// reduced unit-test layouts exercise the controller's fallback element.
+/// No GL — pure data-wiring + report-content tests.
///
public class CharacterControllerTests
{
+ [Fact]
+ public void AuthoredCharacterInformationPanel_BindsTextScrollbarAndClose()
+ {
+ ImportedLayout layout = FixtureLoader.LoadCharacterInformation();
+ int closes = 0;
+
+ CharacterController.Bind(
+ layout, SampleData.SampleCharacter, close: () => closes++);
+
+ Assert.Equal(CharacterController.RootId, layout.Root.DatElementId);
+ Assert.Equal((300f, 362f), (layout.Root.Width, layout.Root.Height));
+ UiText text = Assert.IsType(
+ layout.FindElement(CharacterController.MainTextId));
+ UiScrollbar scrollbar = Assert.IsType(
+ layout.FindElement(CharacterController.ScrollbarId));
+ Assert.Same(text.Scroll, scrollbar.Model);
+ Assert.False(text.PreserveEndOnLayout);
+ Assert.Contains("Studio Player", text.LinesProvider().Select(line => line.Text));
+
+ UiButton close = Assert.IsType(
+ layout.FindElement(CharacterController.CloseId));
+ close.OnEvent(new UiEvent(0, close, UiEventType.Click));
+ Assert.Equal(1, closes);
+ }
+
/// Bind on an empty layout, then return the created m_pMainText element.
private static UiText BindAndGetText(System.Func data)
{
diff --git a/tests/AcDream.App.Tests/UI/Layout/EffectsUiControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/EffectsUiControllerTests.cs
index bb7f7819..e1e83757 100644
--- a/tests/AcDream.App.Tests/UI/Layout/EffectsUiControllerTests.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/EffectsUiControllerTests.cs
@@ -7,6 +7,28 @@ namespace AcDream.App.Tests.UI.Layout;
public sealed class EffectsUiControllerTests
{
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
+ private static EffectRowTemplateFactory Templates()
+ => new(FixtureLoader.LoadEffectRowTemplateInfos(), NoTex, defaultFont: null);
+
+ [Fact]
+ public void AuthoredEffectRowTemplate_HasRetailIconNameDurationAndSelectionStates()
+ {
+ ElementInfo row = FixtureLoader.LoadEffectRowTemplateInfos();
+
+ Assert.Equal(EffectsUiController.RowTemplateId, row.Id);
+ Assert.Equal((300f, 32f), (row.Width, row.Height));
+ Assert.Contains(UiButtonStateMachine.Normal, row.States.Keys);
+ Assert.Contains(UiButtonStateMachine.Highlight, row.States.Keys);
+ Assert.Equal((0f, 32f),
+ (FindInfo(row, EffectsUiController.RowIconId)!.X,
+ FindInfo(row, EffectsUiController.RowIconId)!.Width));
+ Assert.Equal((37f, 188f),
+ (FindInfo(row, EffectsUiController.RowLabelId)!.X,
+ FindInfo(row, EffectsUiController.RowLabelId)!.Width));
+ Assert.Equal((225f, 50f),
+ (FindInfo(row, EffectsUiController.RowDurationId)!.X,
+ FindInfo(row, EffectsUiController.RowDurationId)!.Width));
+ }
[Fact]
public void Rebuild_DoesNotAutoSelectFirstEffect_AndClearsRemovedSelection()
@@ -25,8 +47,10 @@ public sealed class EffectsUiControllerTests
[EffectsUiController.InfoTextId] = info,
});
using EffectsUiController controller = EffectsUiController.Bind(
- layout, spellbook, positive: true, () => 0d, NoTex, id => id)!;
+ layout, spellbook, positive: true, () => 0d, NoTex, id => id,
+ Templates(), "SELECT A SPELL")!;
+ Assert.False(info.PreserveEndOnLayout);
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
42u, 1u, 60f, 1u, Bucket: 1u, SpellCategory: 42u));
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
@@ -34,10 +58,15 @@ public sealed class EffectsUiControllerTests
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
42u, 3u, 60f, 1u, Bucket: 8u, SpellCategory: 42u));
Assert.Null(controller.SelectedSpellId);
+ Assert.Equal("SELECT A SPELL", Assert.Single(info.LinesProvider()).Text);
Assert.Equal(1, list.GetNumUIItems());
- UiCatalogSlot row = Assert.IsType(list.GetItem(0));
+ UiTemplateListSlot row = Assert.IsType(list.GetItem(0));
+ UiText duration = Assert.IsType(
+ row.Content.FindElement(EffectsUiController.RowDurationId));
+ Assert.Equal("1:00", Assert.Single(duration.LinesProvider()).Text);
row.OnEvent(new UiEvent(0, row, UiEventType.Click));
Assert.Equal(42u, controller.SelectedSpellId);
+ Assert.Equal(["Boon", "", ""], info.LinesProvider().Select(line => line.Text));
spellbook.OnEnchantmentRemoved(1u, 42u);
Assert.Null(controller.SelectedSpellId);
@@ -57,14 +86,15 @@ public sealed class EffectsUiControllerTests
[EffectsUiController.ListId] = list,
});
using EffectsUiController controller = EffectsUiController.Bind(
- layout, spellbook, positive: true, () => 0d, NoTex, id => id)!;
+ layout, spellbook, positive: true, () => 0d, NoTex, id => id,
+ Templates(), "SELECT A SPELL")!;
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
42u, 1u, 60f, 1u, Bucket: 1u, SpellCategory: 100u));
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
42u, 2u, 60f, 2u, Bucket: 2u, SpellCategory: 101u));
- UiCatalogSlot first = Assert.IsType(list.GetItem(0));
- UiCatalogSlot second = Assert.IsType(list.GetItem(1));
+ UiTemplateListSlot first = Assert.IsType(list.GetItem(0));
+ UiTemplateListSlot second = Assert.IsType(list.GetItem(1));
first.OnEvent(new UiEvent(0, first, UiEventType.Click));
Assert.Equal(42u, controller.SelectedSpellId);
@@ -77,6 +107,32 @@ public sealed class EffectsUiControllerTests
Assert.False(second.Selected);
}
+ [Fact]
+ public void PermanentEffect_LeavesRetailDurationColumnEmpty()
+ {
+ var table = SpellTable.LoadFromReader(new StringReader(
+ "Spell ID,Name,Flags [Hex]\n42,Boon,0x4\n"));
+ var spellbook = new Spellbook(table);
+ var root = new UiPanel { Width = 160f, Height = 100f };
+ var list = new UiItemList(NoTex) { Width = 160f, Height = 64f };
+ root.AddChild(list);
+ var layout = new ImportedLayout(root, new Dictionary
+ {
+ [EffectsUiController.ListId] = list,
+ });
+ using EffectsUiController controller = EffectsUiController.Bind(
+ layout, spellbook, positive: true, () => 0d, NoTex, id => id,
+ Templates(), "SELECT A SPELL")!;
+
+ spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
+ 42u, 1u, -1f, 1u, Bucket: 1u, SpellCategory: 42u));
+
+ UiTemplateListSlot row = Assert.IsType(list.GetItem(0));
+ UiText duration = Assert.IsType(
+ row.Content.FindElement(EffectsUiController.RowDurationId));
+ Assert.Equal(string.Empty, Assert.Single(duration.LinesProvider()).Text);
+ }
+
[Theory]
[InlineData(true)]
[InlineData(false)]
@@ -117,11 +173,13 @@ public sealed class EffectsUiControllerTests
int closes = 0;
using EffectsUiController? controller = EffectsUiController.Bind(
- layout, spellbook, positive, () => 0d, NoTex, id => id, () => closes++);
+ layout, spellbook, positive, () => 0d, NoTex, id => id,
+ Templates(), "SELECT A SPELL", () => closes++);
Assert.NotNull(controller);
Assert.NotNull(layout.FindElement(EffectsUiController.ListId));
Assert.IsType(layout.FindElement(EffectsUiController.InfoTextId));
+ Assert.IsType(layout.FindElement(EffectsUiController.ScrollbarId));
Assert.Equal(expectedRoot, layout.Root.DatElementId);
UiButton close = Assert.IsType(layout.FindElement(EffectsUiController.CloseId));
close.OnEvent(new UiEvent(0, close, UiEventType.Click));
diff --git a/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs b/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs
index 5a18205b..214586bc 100644
--- a/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs
@@ -137,6 +137,16 @@ public static class FixtureLoader
public static ElementInfo LoadNegativeEffectsInfos()
=> LoadInfos("effects_negative_2100001B.json");
+ public static ElementInfo LoadEffectRowTemplateInfos()
+ => LoadInfos("effects_row_2100001B_10000128.json");
+
+ public static ImportedLayout LoadCharacterInformation()
+ => LayoutImporter.Build(
+ LoadCharacterInformationInfos(), _ => (0u, 0, 0), null);
+
+ public static ElementInfo LoadCharacterInformationInfos()
+ => LoadInfos("character_info_2100006E_10000183.json");
+
public static ImportedLayout LoadIndicators()
=> LayoutImporter.Build(LoadIndicatorsInfos(), _ => (0u, 0, 0), null);
diff --git a/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs
index caf419a7..810e85c9 100644
--- a/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs
@@ -98,7 +98,7 @@ public sealed class IndicatorBarControllerTests
burden.OnEvent(new UiEvent(0, burden, UiEventType.Click));
- Assert.Equal([RetailPanelCatalog.Character], h.ToggledPanels);
+ Assert.Equal([RetailPanelCatalog.CharacterInformation], h.ToggledPanels);
}
[Theory]
diff --git a/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs b/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs
index aa33afa5..ce38b102 100644
--- a/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs
@@ -64,6 +64,11 @@ public sealed class RetailLayoutFixtureGenerator
Assert.Equal(expected, localStrings.Resolve(
0x23000001u, DatStringResolver.ComputeHash(key)));
}
+ Assert.Equal(
+ "SELECT A SPELL",
+ localStrings.Resolve(
+ 0x23000001u,
+ DatStringResolver.ComputeHash("ID_Effects_Info_SelectASpell")));
uint masterDid = (uint)dats.Portal.Header.MasterMapId;
Assert.True(dats.Portal.TryGet(masterDid, out var master));
@@ -120,6 +125,25 @@ public sealed class RetailLayoutFixtureGenerator
File.WriteAllText(Path.Combine(FixtureDirectory(), fileName), effectsJson);
}
+ foreach ((uint layoutId, uint rootId, string fileName) in new[]
+ {
+ (EffectsUiController.LayoutId, EffectsUiController.RowTemplateId,
+ "effects_row_2100001B_10000128.json"),
+ (CharacterController.LayoutId, CharacterController.RootId,
+ "character_info_2100006E_10000183.json"),
+ })
+ {
+ ElementInfo? panelPart = LayoutImporter.ImportInfos(dats, layoutId, rootId);
+ Assert.NotNull(panelPart);
+ File.WriteAllText(
+ Path.Combine(FixtureDirectory(), fileName),
+ JsonSerializer.Serialize(panelPart, new JsonSerializerOptions
+ {
+ IncludeFields = true,
+ WriteIndented = true,
+ }));
+ }
+
ElementInfo? miniGame = LayoutImporter.ImportInfos(
dats, MiniGameUiController.LayoutId, MiniGameUiController.RootId);
Assert.NotNull(miniGame);
diff --git a/tests/AcDream.App.Tests/UI/Layout/RetailPanelUiControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/RetailPanelUiControllerTests.cs
index 23bd81c9..1ea87958 100644
--- a/tests/AcDream.App.Tests/UI/Layout/RetailPanelUiControllerTests.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/RetailPanelUiControllerTests.cs
@@ -79,9 +79,9 @@ public sealed class RetailPanelUiControllerTests
public void MainPanels_ShareOneCanonicalPlacementAcrossMoveCloseAndSwitch()
{
var root = new UiRoot { Width = 1280f, Height = 720f };
- RetailWindowHandle inventory = Mount(root, WindowNames.Inventory, 40f, 60f);
- RetailWindowHandle character = Mount(root, WindowNames.Character, 540f, 18f);
- RetailWindowHandle spellbook = Mount(root, WindowNames.Spellbook, 18f, 18f);
+ RetailWindowHandle inventory = Mount(root, WindowNames.Inventory, 40f, 60f, 360f);
+ RetailWindowHandle character = Mount(root, WindowNames.Character, 540f, 18f, 500f);
+ RetailWindowHandle spellbook = Mount(root, WindowNames.Spellbook, 18f, 18f, 420f);
using var controller = Create(root);
controller.RegisterMainPanel(7u, WindowNames.Inventory, inventory);
controller.RegisterMainPanel(11u, WindowNames.Character, character);
@@ -94,9 +94,12 @@ public sealed class RetailPanelUiControllerTests
controller.SetPanelVisibility(7u, visible: true);
inventory.MoveTo(312f, 147f);
+ inventory.ResizeTo(310f, 540f);
Assert.Equal((312f, 147f), (character.Left, character.Top));
Assert.Equal((312f, 147f), (spellbook.Left, spellbook.Top));
+ Assert.Equal((310f, 540f), (character.Width, character.Height));
+ Assert.Equal((310f, 540f), (spellbook.Width, spellbook.Height));
Assert.True(characterMoves > 0);
Assert.True(spellbookMoves > 0);
@@ -104,12 +107,14 @@ public sealed class RetailPanelUiControllerTests
Assert.False(inventory.IsVisible);
Assert.True(character.IsVisible);
Assert.Equal((312f, 147f), (character.Left, character.Top));
+ Assert.Equal((310f, 540f), (character.Width, character.Height));
controller.SetPanelVisibility(11u, visible: false);
controller.SetPanelVisibility(13u, visible: true);
Assert.False(character.IsVisible);
Assert.True(spellbook.IsVisible);
Assert.Equal((312f, 147f), (spellbook.Left, spellbook.Top));
+ Assert.Equal((310f, 540f), (spellbook.Width, spellbook.Height));
}
[Fact]
@@ -130,15 +135,18 @@ public sealed class RetailPanelUiControllerTests
controller.SetPanelVisibility(7u, visible: true);
inventory.MoveTo(280f, 120f);
+ inventory.ResizeTo(310f, 600f);
controller.SetPanelVisibility(4u, visible: true);
Assert.Equal((280f, 120f), (effect.Left, effect.Top));
+ Assert.Equal((310f, 600f), (effect.Width, effect.Height));
Assert.False(inventory.IsVisible);
Assert.True(effect.IsVisible);
controller.SetPanelVisibility(4u, visible: false);
Assert.True(inventory.IsVisible);
Assert.Equal((280f, 120f), (inventory.Left, inventory.Top));
+ Assert.Equal((310f, 600f), (inventory.Width, inventory.Height));
controller.SetPanelVisibility(11u, visible: true);
Assert.Equal((280f, 120f), (character.Left, character.Top));
@@ -165,9 +173,10 @@ public sealed class RetailPanelUiControllerTests
UiRoot root,
string name,
float left,
- float top)
+ float top,
+ float contentHeight = 500f)
{
- var content = new UiPanel { Width = 300f, Height = 500f };
+ var content = new UiPanel { Width = 300f, Height = contentHeight };
return RetailWindowFrame.Mount(
root,
content,
diff --git a/tests/AcDream.App.Tests/UI/Layout/fixtures/character_info_2100006E_10000183.json b/tests/AcDream.App.Tests/UI/Layout/fixtures/character_info_2100006E_10000183.json
new file mode 100644
index 00000000..50e23d7b
--- /dev/null
+++ b/tests/AcDream.App.Tests/UI/Layout/fixtures/character_info_2100006E_10000183.json
@@ -0,0 +1,2107 @@
+{
+ "Id": 268435843,
+ "Type": 268435482,
+ "X": 0,
+ "Y": 0,
+ "Width": 300,
+ "Height": 362,
+ "OriginalParentWidth": 0,
+ "OriginalParentHeight": 0,
+ "HasOriginalParentSize": false,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 3,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "88": {
+ "Kind": 0,
+ "MasterPropertyId": 88,
+ "UnsignedValue": 1,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "87": {
+ "Kind": 0,
+ "MasterPropertyId": 87,
+ "UnsignedValue": 268435461,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "268435529": {
+ "Kind": 1,
+ "MasterPropertyId": 268435529,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "268435497": {
+ "Kind": 0,
+ "MasterPropertyId": 268435497,
+ "UnsignedValue": 3,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {},
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": [
+ {
+ "Id": 268435740,
+ "Type": 3,
+ "X": 0,
+ "Y": 25,
+ "Width": 300,
+ "Height": 337,
+ "OriginalParentWidth": 300,
+ "OriginalParentHeight": 362,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 3,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": {
+ "File": 100682946,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "": {
+ "Item1": 100682946,
+ "Item2": 1
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": [
+ {
+ "Id": 268435741,
+ "Type": 12,
+ "X": 8,
+ "Y": 0,
+ "Width": 262,
+ "Height": 337,
+ "OriginalParentWidth": 300,
+ "OriginalParentHeight": 337,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 1,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "35": {
+ "Kind": 4,
+ "MasterPropertyId": 35,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "26": {
+ "Kind": 7,
+ "MasterPropertyId": 26,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 2,
+ "MasterPropertyId": 24,
+ "UnsignedValue": 1073741825,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ },
+ "37": {
+ "Kind": 4,
+ "MasterPropertyId": 37,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "27": {
+ "Kind": 7,
+ "MasterPropertyId": 27,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 6,
+ "MasterPropertyId": 25,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 255,
+ "Green": 255,
+ "Red": 255,
+ "Alpha": 255
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ },
+ "20": {
+ "Kind": 0,
+ "MasterPropertyId": 20,
+ "UnsignedValue": 1,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "21": {
+ "Kind": 0,
+ "MasterPropertyId": 21,
+ "UnsignedValue": 1,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "114": {
+ "Kind": 0,
+ "MasterPropertyId": 114,
+ "UnsignedValue": 268435742,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ },
+ "13": {
+ "Id": 13,
+ "Name": "Ghosted",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "27": {
+ "Kind": 7,
+ "MasterPropertyId": 27,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 6,
+ "MasterPropertyId": 25,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 76,
+ "Green": 76,
+ "Red": 76,
+ "Alpha": 255
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ }
+ }
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 1073741825,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": {
+ "X": 1,
+ "Y": 1,
+ "Z": 1,
+ "W": 1
+ },
+ "StateMedia": {},
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": []
+ },
+ {
+ "Id": 268435742,
+ "Type": 11,
+ "X": 280,
+ "Y": 0,
+ "Width": 16,
+ "Height": 337,
+ "OriginalParentWidth": 300,
+ "OriginalParentHeight": 337,
+ "HasOriginalParentSize": true,
+ "Left": 2,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 2,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": {
+ "File": 100682847,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "121": {
+ "Kind": 1,
+ "MasterPropertyId": 121,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "137": {
+ "Kind": 4,
+ "MasterPropertyId": 137,
+ "UnsignedValue": 0,
+ "IntegerValue": 16,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "130": {
+ "Kind": 1,
+ "MasterPropertyId": 130,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "119": {
+ "Kind": 0,
+ "MasterPropertyId": 119,
+ "UnsignedValue": 268435570,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "131": {
+ "Kind": 1,
+ "MasterPropertyId": 131,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "120": {
+ "Kind": 0,
+ "MasterPropertyId": 120,
+ "UnsignedValue": 268435569,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "": {
+ "Item1": 100682847,
+ "Item2": 3
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": [
+ {
+ "Id": 1,
+ "Type": 1,
+ "X": 0,
+ "Y": 0,
+ "Width": 16,
+ "Height": 16,
+ "OriginalParentWidth": 16,
+ "OriginalParentHeight": 48,
+ "HasOriginalParentSize": true,
+ "Left": 0,
+ "Top": 0,
+ "Right": 0,
+ "Bottom": 0,
+ "ReadOrder": 1,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 56,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "19": {
+ "Kind": 1,
+ "MasterPropertyId": 19,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ },
+ "1": {
+ "Id": 1,
+ "Name": "Normal",
+ "PassToChildren": true,
+ "IncorporationFlags": 1,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "2": {
+ "Id": 2,
+ "Name": "Normal_rollover",
+ "PassToChildren": true,
+ "IncorporationFlags": 1,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "3": {
+ "Id": 3,
+ "Name": "Normal_pressed",
+ "PassToChildren": true,
+ "IncorporationFlags": 1,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 1,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {},
+ "StateCursors": {},
+ "DefaultStateName": "Normal",
+ "Children": [
+ {
+ "Id": 268436324,
+ "Type": 3,
+ "X": 0,
+ "Y": 0,
+ "Width": 16,
+ "Height": 3,
+ "OriginalParentWidth": 16,
+ "OriginalParentHeight": 16,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 2,
+ "ReadOrder": 1,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 62,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "1": {
+ "Id": 1,
+ "Name": "Normal",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682848,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "2": {
+ "Id": 2,
+ "Name": "Normal_rollover",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682849,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "3": {
+ "Id": 3,
+ "Name": "Normal_pressed",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682850,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "Normal": {
+ "Item1": 100682848,
+ "Item2": 3
+ },
+ "Normal_rollover": {
+ "Item1": 100682849,
+ "Item2": 3
+ },
+ "Normal_pressed": {
+ "Item1": 100682850,
+ "Item2": 3
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": []
+ },
+ {
+ "Id": 268436325,
+ "Type": 3,
+ "X": 0,
+ "Y": 3,
+ "Width": 16,
+ "Height": 10,
+ "OriginalParentWidth": 16,
+ "OriginalParentHeight": 16,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 2,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 62,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "1": {
+ "Id": 1,
+ "Name": "Normal",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682851,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "2": {
+ "Id": 2,
+ "Name": "Normal_rollover",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682852,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "3": {
+ "Id": 3,
+ "Name": "Normal_pressed",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682853,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "Normal": {
+ "Item1": 100682851,
+ "Item2": 3
+ },
+ "Normal_rollover": {
+ "Item1": 100682852,
+ "Item2": 3
+ },
+ "Normal_pressed": {
+ "Item1": 100682853,
+ "Item2": 3
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": []
+ },
+ {
+ "Id": 268436326,
+ "Type": 3,
+ "X": 0,
+ "Y": 13,
+ "Width": 16,
+ "Height": 3,
+ "OriginalParentWidth": 16,
+ "OriginalParentHeight": 16,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 2,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 3,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 62,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "1": {
+ "Id": 1,
+ "Name": "Normal",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682854,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "2": {
+ "Id": 2,
+ "Name": "Normal_rollover",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682855,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "3": {
+ "Id": 3,
+ "Name": "Normal_pressed",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682856,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "Normal": {
+ "Item1": 100682854,
+ "Item2": 3
+ },
+ "Normal_rollover": {
+ "Item1": 100682855,
+ "Item2": 3
+ },
+ "Normal_pressed": {
+ "Item1": 100682856,
+ "Item2": 3
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": []
+ }
+ ]
+ },
+ {
+ "Id": 268435569,
+ "Type": 1,
+ "X": 0,
+ "Y": 0,
+ "Width": 16,
+ "Height": 16,
+ "OriginalParentWidth": 16,
+ "OriginalParentHeight": 48,
+ "HasOriginalParentSize": true,
+ "Left": 2,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 2,
+ "ReadOrder": 2,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 62,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "15": {
+ "Kind": 1,
+ "MasterPropertyId": 15,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "16": {
+ "Kind": 3,
+ "MasterPropertyId": 16,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0.5,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "17": {
+ "Kind": 3,
+ "MasterPropertyId": 17,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0.1,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "19": {
+ "Kind": 1,
+ "MasterPropertyId": 19,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ },
+ "3": {
+ "Id": 3,
+ "Name": "Normal_pressed",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682859,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "1": {
+ "Id": 1,
+ "Name": "Normal",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682857,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "2": {
+ "Id": 2,
+ "Name": "Normal_rollover",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682858,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 1,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "Normal": {
+ "Item1": 100682857,
+ "Item2": 1
+ },
+ "Normal_rollover": {
+ "Item1": 100682858,
+ "Item2": 1
+ },
+ "Normal_pressed": {
+ "Item1": 100682859,
+ "Item2": 1
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "Normal",
+ "Children": []
+ },
+ {
+ "Id": 268435570,
+ "Type": 1,
+ "X": 0,
+ "Y": 32,
+ "Width": 16,
+ "Height": 16,
+ "OriginalParentWidth": 16,
+ "OriginalParentHeight": 48,
+ "HasOriginalParentSize": true,
+ "Left": 2,
+ "Top": 2,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 3,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 62,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "15": {
+ "Kind": 1,
+ "MasterPropertyId": 15,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "16": {
+ "Kind": 3,
+ "MasterPropertyId": 16,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0.5,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "17": {
+ "Kind": 3,
+ "MasterPropertyId": 17,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0.1,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "19": {
+ "Kind": 1,
+ "MasterPropertyId": 19,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ },
+ "3": {
+ "Id": 3,
+ "Name": "Normal_pressed",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682862,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "1": {
+ "Id": 1,
+ "Name": "Normal",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682860,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "2": {
+ "Id": 2,
+ "Name": "Normal_rollover",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100682861,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 1,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "Normal": {
+ "Item1": 100682860,
+ "Item2": 1
+ },
+ "Normal_rollover": {
+ "Item1": 100682861,
+ "Item2": 1
+ },
+ "Normal_pressed": {
+ "Item1": 100682862,
+ "Item2": 1
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "Normal",
+ "Children": []
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Id": 268435708,
+ "Type": 1,
+ "X": 276,
+ "Y": 0,
+ "Width": 24,
+ "Height": 25,
+ "OriginalParentWidth": 300,
+ "OriginalParentHeight": 362,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 2,
+ "ReadOrder": 1,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "18": {
+ "Kind": 0,
+ "MasterPropertyId": 18,
+ "UnsignedValue": 268435461,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ },
+ "3": {
+ "Id": 3,
+ "Name": "Normal_pressed",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100668308,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "1": {
+ "Id": 1,
+ "Name": "Normal",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100668307,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 1,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "Normal": {
+ "Item1": 100668307,
+ "Item2": 1
+ },
+ "Normal_pressed": {
+ "Item1": 100668308,
+ "Item2": 1
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "Normal",
+ "Children": []
+ },
+ {
+ "Id": 268435710,
+ "Type": 12,
+ "X": 0,
+ "Y": 0,
+ "Width": 276,
+ "Height": 25,
+ "OriginalParentWidth": 300,
+ "OriginalParentHeight": 362,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 2,
+ "ReadOrder": 2,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": {
+ "File": 100683002,
+ "DrawMode": 3
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "35": {
+ "Kind": 4,
+ "MasterPropertyId": 35,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "26": {
+ "Kind": 7,
+ "MasterPropertyId": 26,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 2,
+ "MasterPropertyId": 24,
+ "UnsignedValue": 1073741825,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ },
+ "37": {
+ "Kind": 4,
+ "MasterPropertyId": 37,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "27": {
+ "Kind": 7,
+ "MasterPropertyId": 27,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 6,
+ "MasterPropertyId": 25,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 255,
+ "Green": 255,
+ "Red": 255,
+ "Alpha": 255
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ },
+ "20": {
+ "Kind": 0,
+ "MasterPropertyId": 20,
+ "UnsignedValue": 1,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "21": {
+ "Kind": 0,
+ "MasterPropertyId": 21,
+ "UnsignedValue": 1,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "23": {
+ "Kind": 5,
+ "MasterPropertyId": 23,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 189013650,
+ "TableId": 587202561,
+ "Override": 0,
+ "English": 1,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ },
+ "13": {
+ "Id": 13,
+ "Name": "Ghosted",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "27": {
+ "Kind": 7,
+ "MasterPropertyId": 27,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 6,
+ "MasterPropertyId": 25,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 76,
+ "Green": 76,
+ "Red": 76,
+ "Alpha": 255
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ }
+ }
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 1073741825,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": {
+ "X": 1,
+ "Y": 1,
+ "Z": 1,
+ "W": 1
+ },
+ "StateMedia": {
+ "": {
+ "Item1": 100683002,
+ "Item2": 3
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/AcDream.App.Tests/UI/Layout/fixtures/effects_row_2100001B_10000128.json b/tests/AcDream.App.Tests/UI/Layout/fixtures/effects_row_2100001B_10000128.json
new file mode 100644
index 00000000..048ebdff
--- /dev/null
+++ b/tests/AcDream.App.Tests/UI/Layout/fixtures/effects_row_2100001B_10000128.json
@@ -0,0 +1,961 @@
+{
+ "Id": 268435752,
+ "Type": 3,
+ "X": 0,
+ "Y": 0,
+ "Width": 300,
+ "Height": 32,
+ "OriginalParentWidth": 0,
+ "OriginalParentHeight": 0,
+ "HasOriginalParentSize": false,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 2,
+ "ReadOrder": 3,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "1": {
+ "Id": 1,
+ "Name": "Normal",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100668310,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ },
+ "6": {
+ "Id": 6,
+ "Name": "Highlight",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": {
+ "File": 100668311,
+ "DrawMode": 1
+ },
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 1,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {
+ "Normal": {
+ "Item1": 100668310,
+ "Item2": 1
+ },
+ "Highlight": {
+ "Item1": 100668311,
+ "Item2": 1
+ }
+ },
+ "StateCursors": {},
+ "DefaultStateName": "Normal",
+ "Children": [
+ {
+ "Id": 268435753,
+ "Type": 3,
+ "X": 0,
+ "Y": 0,
+ "Width": 32,
+ "Height": 32,
+ "OriginalParentWidth": 300,
+ "OriginalParentHeight": 32,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 2,
+ "Bottom": 2,
+ "ReadOrder": 1,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {}
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 0,
+ "HJustify": 1,
+ "VJustify": 1,
+ "FontColor": null,
+ "StateMedia": {},
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": []
+ },
+ {
+ "Id": 268435754,
+ "Type": 12,
+ "X": 37,
+ "Y": 0,
+ "Width": 188,
+ "Height": 32,
+ "OriginalParentWidth": 300,
+ "OriginalParentHeight": 32,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 2,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "35": {
+ "Kind": 4,
+ "MasterPropertyId": 35,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "26": {
+ "Kind": 7,
+ "MasterPropertyId": 26,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 2,
+ "MasterPropertyId": 24,
+ "UnsignedValue": 1073741825,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ },
+ "37": {
+ "Kind": 4,
+ "MasterPropertyId": 37,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "27": {
+ "Kind": 7,
+ "MasterPropertyId": 27,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 6,
+ "MasterPropertyId": 25,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 255,
+ "Green": 255,
+ "Red": 255,
+ "Alpha": 255
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ },
+ "20": {
+ "Kind": 0,
+ "MasterPropertyId": 20,
+ "UnsignedValue": 2,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "21": {
+ "Kind": 0,
+ "MasterPropertyId": 21,
+ "UnsignedValue": 1,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "199": {
+ "Kind": 5,
+ "MasterPropertyId": 199,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 58579907,
+ "TableId": 587202561,
+ "Override": 0,
+ "English": 1,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "71": {
+ "Kind": 0,
+ "MasterPropertyId": 71,
+ "UnsignedValue": 268436373,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "72": {
+ "Kind": 2,
+ "MasterPropertyId": 72,
+ "UnsignedValue": 553648193,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "32": {
+ "Kind": 1,
+ "MasterPropertyId": 32,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "208": {
+ "Kind": 1,
+ "MasterPropertyId": 208,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": true,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ },
+ "13": {
+ "Id": 13,
+ "Name": "Ghosted",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "27": {
+ "Kind": 7,
+ "MasterPropertyId": 27,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 6,
+ "MasterPropertyId": 25,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 76,
+ "Green": 76,
+ "Red": 76,
+ "Alpha": 255
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ }
+ }
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 1073741825,
+ "HJustify": 0,
+ "VJustify": 1,
+ "FontColor": {
+ "X": 1,
+ "Y": 1,
+ "Z": 1,
+ "W": 1
+ },
+ "StateMedia": {},
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": []
+ },
+ {
+ "Id": 268435755,
+ "Type": 12,
+ "X": 225,
+ "Y": 0,
+ "Width": 50,
+ "Height": 32,
+ "OriginalParentWidth": 300,
+ "OriginalParentHeight": 32,
+ "HasOriginalParentSize": true,
+ "Left": 1,
+ "Top": 1,
+ "Right": 1,
+ "Bottom": 1,
+ "ReadOrder": 3,
+ "ZLevel": 0,
+ "States": {
+ "4294967295": {
+ "Id": 4294967295,
+ "Name": "",
+ "PassToChildren": false,
+ "IncorporationFlags": 30,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "35": {
+ "Kind": 4,
+ "MasterPropertyId": 35,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "26": {
+ "Kind": 7,
+ "MasterPropertyId": 26,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 2,
+ "MasterPropertyId": 24,
+ "UnsignedValue": 1073741825,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ },
+ "37": {
+ "Kind": 4,
+ "MasterPropertyId": 37,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "27": {
+ "Kind": 7,
+ "MasterPropertyId": 27,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 6,
+ "MasterPropertyId": 25,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 255,
+ "Green": 255,
+ "Red": 255,
+ "Alpha": 255
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ },
+ "20": {
+ "Kind": 0,
+ "MasterPropertyId": 20,
+ "UnsignedValue": 3,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ },
+ "21": {
+ "Kind": 0,
+ "MasterPropertyId": 21,
+ "UnsignedValue": 1,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ }
+ }
+ },
+ "13": {
+ "Id": 13,
+ "Name": "Ghosted",
+ "PassToChildren": false,
+ "IncorporationFlags": 0,
+ "Image": null,
+ "Cursor": null,
+ "Properties": {
+ "Values": {
+ "27": {
+ "Kind": 7,
+ "MasterPropertyId": 27,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 0,
+ "Green": 0,
+ "Red": 0,
+ "Alpha": 0
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [
+ {
+ "Kind": 6,
+ "MasterPropertyId": 25,
+ "UnsignedValue": 0,
+ "IntegerValue": 0,
+ "FloatValue": 0,
+ "BoolValue": false,
+ "StringInfoValue": {
+ "Token": 0,
+ "StringId": 0,
+ "TableId": 0,
+ "Override": 0,
+ "English": 0,
+ "Comment": 0
+ },
+ "ColorValue": {
+ "Blue": 76,
+ "Green": 76,
+ "Red": 76,
+ "Alpha": 255
+ },
+ "VectorValue": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
+ "ArrayValue": [],
+ "StructValue": {}
+ }
+ ],
+ "StructValue": {}
+ }
+ }
+ }
+ }
+ },
+ "DefaultStateId": 0,
+ "FontDid": 1073741825,
+ "HJustify": 2,
+ "VJustify": 1,
+ "FontColor": {
+ "X": 1,
+ "Y": 1,
+ "Z": 1,
+ "W": 1
+ },
+ "StateMedia": {},
+ "StateCursors": {},
+ "DefaultStateName": "",
+ "Children": []
+ }
+ ]
+}
\ No newline at end of file