using System.Text.Json; using System.Text.Json.Serialization; namespace AcDream.App.Studio; // ───────────────────────────────────────────────────────────────────────────── // UiDumpModel — POCOs for docs/research/2026-06-25-retail-ui-layout-dump.json // // Schema (v1): // { "version":1, "panels":[ { "id":int, "slug":string, "title":string, // "bucket":string, "parent_slug":string|null, // "width":int, "height":int, // "nodes":[ { "traversal_index":int, "element_id":int, // "layout_id":int, "parent_layout_id":int|null, // "parent_traversal_index":int|null, "base_layout_id":int, // "rect":{x,y,width,height}, // "widget_kind":"Group"|"Sprite"|"Button"|"Scrollbar"|"Slider", // "state_set":{ "default_image":{image_id,alpha_image_id}|null, // "states":[{state_id,image:{...}}] } // } ] } ] } // // All ids in the dump are DECIMAL ints (e.g. element_id=268435925 = 0x100001D5, // image_id=100693194 = 0x060074CA). Cast to uint before use in dat/GL APIs. // // Rect coordinates are ABSOLUTE (screen-space origin = panel's design position // in retail layout, NOT relative to the parent). DumpLayout.Load converts them // to parent-relative when building the UiElement tree. // ───────────────────────────────────────────────────────────────────────────── /// Top-level container for the retail UI layout dump. public sealed class UiDump { [JsonPropertyName("version")] public int Version { get; set; } [JsonPropertyName("panels")] public List Panels { get; set; } = new(); } /// One panel (window) exported from the retail UI. public sealed class DumpPanel { [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("slug")] public string Slug { get; set; } = ""; [JsonPropertyName("title")] public string Title { get; set; } = ""; [JsonPropertyName("bucket")] public string Bucket { get; set; } = ""; [JsonPropertyName("parent_slug")] public string? ParentSlug { get; set; } [JsonPropertyName("width")] public float Width { get; set; } [JsonPropertyName("height")] public float Height { get; set; } [JsonPropertyName("nodes")] public List Nodes { get; set; } = new(); } /// One element node within a panel's traversal list. public sealed class DumpNode { [JsonPropertyName("traversal_index")] public int TraversalIndex { get; set; } [JsonPropertyName("element_id")] public long ElementId { get; set; } [JsonPropertyName("layout_id")] public long LayoutId { get; set; } [JsonPropertyName("parent_layout_id")] public long? ParentLayoutId { get; set; } [JsonPropertyName("parent_traversal_index")] public int? ParentTraversalIndex { get; set; } [JsonPropertyName("base_layout_id")] public long BaseLayoutId { get; set; } [JsonPropertyName("rect")] public DumpRect Rect { get; set; } = new(); [JsonPropertyName("widget_kind")] public string WidgetKind { get; set; } = "Group"; [JsonPropertyName("state_set")] public DumpStateSet StateSet { get; set; } = new(); } /// Absolute screen-space rect (see comment above — must subtract parent rect for UiElement). public sealed class DumpRect { [JsonPropertyName("x")] public float X { get; set; } [JsonPropertyName("y")] public float Y { get; set; } [JsonPropertyName("width")] public float Width { get; set; } [JsonPropertyName("height")] public float Height { get; set; } } /// State set for a node — default image plus per-state overrides. public sealed class DumpStateSet { [JsonPropertyName("default_image")] public DumpImage? DefaultImage { get; set; } [JsonPropertyName("states")] public List States { get; set; } = new(); } /// Image reference (RenderSurface dat id + optional separate alpha surface). public sealed class DumpImage { [JsonPropertyName("image_id")] public long ImageId { get; set; } [JsonPropertyName("alpha_image_id")] public long? AlphaImageId { get; set; } } /// A named state override. public sealed class DumpState { [JsonPropertyName("state_id")] public int StateId { get; set; } [JsonPropertyName("image")] public DumpImage Image { get; set; } = new(); } // ───────────────────────────────────────────────────────────────────────────── // Helper statics // ───────────────────────────────────────────────────────────────────────────── /// /// Parsing helpers for the retail UI dump JSON. /// public static class UiDumpModel { private static readonly JsonSerializerOptions _opts = new() { PropertyNameCaseInsensitive = true, AllowTrailingCommas = true, ReadCommentHandling = JsonCommentHandling.Skip, }; /// Parse the full dump from a file path. Returns null on failure. public static UiDump? Parse(string path) { try { using var stream = File.OpenRead(path); return JsonSerializer.Deserialize(stream, _opts); } catch { return null; } } /// /// Return the list of slugs in the dump (for smoke-testing every panel). /// Returns an empty list if the file cannot be parsed. /// public static IReadOnlyList ListSlugs(string path) { var dump = Parse(path); if (dump is null) return Array.Empty(); return dump.Panels.Select(p => p.Slug).ToList(); } /// /// Pick the sprite id to use for a node: prefer default_image.image_id; /// fall back to states[0].image.image_id; return 0 if neither exists. /// public static uint PickImageId(DumpNode node) { if (node.StateSet.DefaultImage is { ImageId: > 0 } di) return (uint)di.ImageId; if (node.StateSet.States.Count > 0 && node.StateSet.States[0].Image.ImageId > 0) return (uint)node.StateSet.States[0].Image.ImageId; return 0u; } }