acdream/src/AcDream.Cli/LayoutIndexDump.cs
Erik 26cb34f126 @
docs(D.2b): chat-window re-drive design spec + list-ui-layouts research tool

Plan-2 chat piece of the LayoutDesc importer. Identifies the chat window as
LayoutDesc 0x21000006 (gmMainChatUI, element class 0x10000041) and grounds a
faithful, data-driven re-drive in the named retail decomp (ChatInterface +
gmMainChatUI + UIElement_Text/_Scrollable/_Scrollbar/_Menu) plus a user-provided
retail screenshot.

Design (full-faithful scope, user-approved):
- transcript = UIElement_Text 0x10000011 (dat font, bottom-pinned, 10k behead cap,
  pixel scroll, 1 line/wheel-notch)
- scrollbar = right-side track 0x10000012 + thumb 0x1000048c + up/down
- input = editable UIElement_Text 0x10000016 (caret, 100-entry history, Enter/Send)
- channel menu = UIElement_Menu 0x10000014 ("Chat" selector -> active channel)
- shared ChatCommandRouter extracted from ChatPanel
- screenshot correction: the four 0x10000522-525 left-edge elements are the
  numbered CHAT TABS (1-4), not scroll buttons (a research-agent inference the
  retail screenshot refutes)
- deferred (need non-UI plumbing, each gets a divergence row): tab switching/
  filtering, squelch, clickable name-tags, in-element word-wrap, styled runs,
  font config, opacity transition

Tooling: AcDream.Cli `list-ui-layouts <datdir> [0xRootType]` — read-only index of
every UI LayoutDesc by root element class + size + element-Type histogram; how the
chat layout was located (root type 0x10000041). Reusable for future panel re-drives.

Spec: docs/superpowers/specs/2026-06-15-chat-window-redrive-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-06-15 19:38:27 +02:00

101 lines
3.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Reflection;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Options;
using DatReaderWriter.Types;
namespace AcDream.Cli;
/// <summary>
/// Read-only research diagnostic: index EVERY UI <see cref="LayoutDesc"/> in the
/// dat by its root element's <c>Type</c> + size + an element-Type histogram, so a
/// panel re-drive can locate its layout from the decomp-registered class id
/// (e.g. <c>gmMainChatUI</c> registers type <c>0x10000041</c> → the chat window
/// is the layout whose root element has Type 0x10000041). Optionally filter to a
/// single root Type. No writes; purely a console dump used during brainstorming.
/// </summary>
public static class LayoutIndexDump
{
public static int Run(string datDir, string? rootTypeText)
{
if (!Directory.Exists(datDir)) { Console.Error.WriteLine($"error: dir not found: {datDir}"); return 2; }
using var dats = new DatCollection(datDir, DatAccessType.Read);
uint? filter = null;
if (!string.IsNullOrWhiteSpace(rootTypeText))
{
var t = rootTypeText.Trim();
if (t.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) t = t[2..];
if (uint.TryParse(t, System.Globalization.NumberStyles.HexNumber, null, out var f)) filter = f;
}
Console.WriteLine(filter is { } ff
? $"=== LayoutDescs with a root element of Type 0x{ff:X8} ==="
: "=== All LayoutDescs (id : root element Type : size : #elements : type histogram) ===");
int total = 0, shown = 0;
foreach (var id in dats.GetAllIdsOfType<LayoutDesc>().OrderBy(x => x))
{
var l = dats.Get<LayoutDesc>(id);
if (l is null) continue;
total++;
// The root is the single top-level element (or, if several, the largest).
ElementDesc? root = null;
foreach (var kv in l.Elements)
if (root is null || Area(kv.Value) > Area(root)) root = kv.Value;
if (root is null) continue;
if (filter is { } want && root.Type != want) continue;
shown++;
var hist = new SortedDictionary<uint, int>();
int count = 0;
CountTypes(root, hist, ref count);
string h = string.Join(" ", hist.Select(kv => $"{TypeName(kv.Key)}×{kv.Value}"));
Console.WriteLine(
$" 0x{id:X8} root=0x{root.ElementId:X8} type=0x{root.Type:X8}({TypeName(root.Type)}) " +
$"{root.Width}x{root.Height} n={count} [{h}]");
}
Console.WriteLine();
Console.WriteLine($"shown {shown} / {total} LayoutDescs.");
return 0;
}
private static long Area(ElementDesc e) => (long)e.Width * e.Height;
private static void CountTypes(ElementDesc e, SortedDictionary<uint, int> hist, ref int count)
{
count++;
hist[e.Type] = hist.TryGetValue(e.Type, out var c) ? c + 1 : 1;
foreach (var kv in e.Children)
CountTypes(kv.Value, hist, ref count);
}
private static string TypeName(uint t) => t switch
{
0 => "Text0",
1 => "Button",
2 => "Dragbar",
3 => "Field",
5 => "ListBox",
6 => "Menu",
7 => "Meter",
8 => "Panel",
9 => "Resizebar",
0xB => "Scrollbar",
0xC => "Text",
0xD => "Viewport",
0xE => "Browser",
0x10 => "ColorPicker",
0x11 => "GroupBox",
0x12 => "Proto",
0x10000041 => "gmMainChatUI",
0x10000040 => "gmFloatyChatUI",
0x10000050 => "gmFloatyMainChatUI",
0x10000042 => "gmChatOptionsUI",
0x10000009 => "gmVitalsUI",
_ => $"0x{t:X}",
};
}