feat(D.2b): UiText (Type 12) -- generic text + Type-12 flip; transcript factory-built (widget-generalization Task 5)
Rename UiChatView -> UiText (the retail UIElement_Text class, RegisterElementClass(0xc) @ acclient_2013_pseudo_c.txt:115655). Factory changes (DatWidgetFactory.cs): - Remove the Type-12 skip (was: no-media -> null, with-media -> UiDatElement). - Add Type 12 -> BuildText() -> UiText in the switch. - BuildText extracts the element's Direct/Normal sprite as BackgroundSprite so any dat-media the element carried keeps rendering under the text. UiText changes (renamed from UiChatView.cs): - BackgroundColor default: (0,0,0,0.35) -> (0,0,0,0) (transparent). An unbound UiText draws nothing; the controller opts in to the translucent bg. - New BackgroundSprite + SpriteResolve: optional dat state-sprite background drawn UNDER DrawFill+text (faithful UIElement_Text media support). ChatWindowController.cs (Task 5 Step 8): - Transcript property: UiChatView -> UiText. - Bind() now uses layout.FindElement(TranscriptId) as UiText (factory-built) instead of manually constructing + AddChild-ing a new UiChatView. - Sets BackgroundColor = (0,0,0,0.35) on the found widget (retail translucent bg). - Removes the tInfo null-check from the early guard (transcript is factory-built; iInfo lookup kept for the input widget which is still manually constructed). - BuildLines: UiChatView.Line -> UiText.Line throughout. Vitals frozen: the Type-12 vitals number elements are meter children and are never recursed by BuildWidget (the `if (w is not UiMeter)` gate), so they are not built as widgets and keep rendering via UiMeter.Label. Vitals fixture vitals_2100006C.json unchanged; LayoutConformanceTests + VitalsBindingTests green. Tests: - UiChatViewTests.cs -> UiTextTests.cs (class: UiTextTests, all UiChatView.* -> UiText.*) - UiChatViewDatFontTests.cs -> UiTextDatFontTests.cs (same) - DatWidgetFactoryTests: delete Type12_StylePrototype_ReturnsNull + DatWidgetFactory_Type12WithMedia_Renders; add Type12_Text_MakesUiText + DatWidgetFactory_Type12_AlwaysMakesUiText. - LayoutImporterTests: BuildFromInfos_Type12Child_IsSkipped_Type3Present updated to assert IsType<UiText> (element is now in tree, transparent, not skipped). Divergence register: AP-37 amended -- removed the "standalone Type-0 text elements skipped / dat-text widget is Plan 2" clause (now shipped as UiText); kept the meter-collapse clause and the vitals-numbers-via-UiMeter.Label clause. AP-38/AP-39/AD-28 file references updated UiChatView.cs -> UiText.cs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
67e5b8cff2
commit
cb082b59e4
10 changed files with 127 additions and 118 deletions
|
|
@ -24,13 +24,13 @@ public class DatWidgetFactoryTests
|
|||
Assert.IsType<UiDatElement>(e);
|
||||
}
|
||||
|
||||
// ── Test 3: Type 12 → null (style prototype, never rendered) ─────────────
|
||||
// ── Test 3: Type 12 → UiText (behavioral text widget) ────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Type12_StylePrototype_ReturnsNull()
|
||||
public void Type12_Text_MakesUiText()
|
||||
{
|
||||
var e = DatWidgetFactory.Create(new ElementInfo { Type = 12 }, NoTex, null);
|
||||
Assert.Null(e);
|
||||
var e = DatWidgetFactory.Create(new ElementInfo { Type = 12, Width = 100, Height = 40 }, NoTex, null);
|
||||
Assert.IsType<UiText>(e);
|
||||
}
|
||||
|
||||
// ── Test 4: Rect + anchors set from ElementInfo ───────────────────────────
|
||||
|
|
@ -71,30 +71,15 @@ public class DatWidgetFactoryTests
|
|||
Assert.Equal(7, e!.ZOrder);
|
||||
}
|
||||
|
||||
// ── Test G1a: Type 12 with own sprites renders; without sprites is skipped ──
|
||||
// ── Test G1a: Type 12 always produces UiText (with or without own sprites) ──
|
||||
|
||||
/// <summary>
|
||||
/// Task G1 change 1: only PURE Type-12 prototypes (no state media) are skipped.
|
||||
/// A Type-12 element that carries its own state media must return a non-null widget.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DatWidgetFactory_Type12WithMedia_Renders()
|
||||
public void DatWidgetFactory_Type12_AlwaysMakesUiText()
|
||||
{
|
||||
// Type 12 with a "Normal" state sprite — must render (NOT skipped).
|
||||
var withMedia = new ElementInfo
|
||||
{
|
||||
Type = 12,
|
||||
Width = 32,
|
||||
Height = 16,
|
||||
StateMedia = { ["Normal"] = (0x00001234u, 1) },
|
||||
};
|
||||
var e = DatWidgetFactory.Create(withMedia, NoTex, null);
|
||||
Assert.NotNull(e);
|
||||
Assert.IsType<UiDatElement>(e);
|
||||
|
||||
// Type 12 with NO state media — must still be skipped (pure prototype).
|
||||
var noMedia = new ElementInfo { Type = 12 };
|
||||
Assert.Null(DatWidgetFactory.Create(noMedia, NoTex, null));
|
||||
var withMedia = new ElementInfo { Type = 12, Width = 32, Height = 16,
|
||||
StateMedia = { ["Normal"] = (0x00001234u, 1) } };
|
||||
Assert.IsType<UiText>(DatWidgetFactory.Create(withMedia, NoTex, null));
|
||||
Assert.IsType<UiText>(DatWidgetFactory.Create(new ElementInfo { Type = 12 }, NoTex, null));
|
||||
}
|
||||
|
||||
// ── Test 5c: Type 1 → UiButton ──────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -32,12 +32,13 @@ public class LayoutImporterTests
|
|||
Assert.Equal(150f, found.Width);
|
||||
}
|
||||
|
||||
// ── Test 2: Type-12 child is skipped; Type-3 sibling is present ──────────
|
||||
// ── Test 2: Type-12 child builds a UiText; Type-3 sibling is also present ──
|
||||
|
||||
/// <summary>
|
||||
/// A root with two children: one Type-12 style prototype and one Type-3 container.
|
||||
/// The Type-12 must be absent from the tree (FindElement returns null);
|
||||
/// the Type-3 must be present.
|
||||
/// A root with two children: one Type-12 UIElement_Text and one Type-3 container.
|
||||
/// The Type-12 must appear as a <see cref="UiText"/> in the tree (transparent,
|
||||
/// draws nothing until a controller binds its <c>LinesProvider</c>);
|
||||
/// the Type-3 must also be present.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BuildFromInfos_Type12Child_IsSkipped_Type3Present()
|
||||
|
|
@ -48,9 +49,9 @@ public class LayoutImporterTests
|
|||
|
||||
var tree = LayoutImporter.BuildFromInfos(root, new[] { prototype, container }, NoTex, null);
|
||||
|
||||
// Type-12 must be absent.
|
||||
Assert.Null(tree.FindElement(0x20000001));
|
||||
// Type-3 must be present.
|
||||
// Type-12 is now a UiText (transparent, no lines) — present in the tree.
|
||||
Assert.IsType<UiText>(tree.FindElement(0x20000001));
|
||||
// Type-3 must also be present.
|
||||
Assert.NotNull(tree.FindElement(0x20000002));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using Xunit;
|
|||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
public class UiChatViewDatFontTests
|
||||
public class UiTextDatFontTests
|
||||
{
|
||||
// Synthetic per-char advance: each glyph 10px (Before=2,Width=6,After=2).
|
||||
private static FontCharDesc Glyph(char c) => new()
|
||||
|
|
@ -17,9 +17,9 @@ public class UiChatViewDatFontTests
|
|||
public void CharIndexAt_UsesDatGlyphAdvance()
|
||||
{
|
||||
float Adv(char c) => UiDatFont.GlyphAdvance(Glyph(c));
|
||||
Assert.Equal(0, UiChatView.CharIndexAt("abc", Adv, 4f));
|
||||
Assert.Equal(1, UiChatView.CharIndexAt("abc", Adv, 12f));
|
||||
Assert.Equal(3, UiChatView.CharIndexAt("abc", Adv, 100f));
|
||||
Assert.Equal(0, UiText.CharIndexAt("abc", Adv, 4f));
|
||||
Assert.Equal(1, UiText.CharIndexAt("abc", Adv, 12f));
|
||||
Assert.Equal(3, UiText.CharIndexAt("abc", Adv, 100f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -5,28 +5,28 @@ using AcDream.App.UI;
|
|||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
public class UiChatViewTests
|
||||
public class UiTextTests
|
||||
{
|
||||
[Fact]
|
||||
public void ClampScroll_PinsToZero_WhenContentFitsView()
|
||||
{
|
||||
// 5 lines of content in a taller view → nothing to scroll, pinned at 0.
|
||||
Assert.Equal(0f, UiChatView.ClampScroll(50f, contentHeight: 80f, viewHeight: 200f));
|
||||
Assert.Equal(0f, UiChatView.ClampScroll(0f, contentHeight: 80f, viewHeight: 200f));
|
||||
Assert.Equal(0f, UiText.ClampScroll(50f, contentHeight: 80f, viewHeight: 200f));
|
||||
Assert.Equal(0f, UiText.ClampScroll(0f, contentHeight: 80f, viewHeight: 200f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClampScroll_CapsAtContentMinusView_WhenOverflowing()
|
||||
{
|
||||
// Content 500, view 200 → max scrollback is 300px (oldest line at top).
|
||||
Assert.Equal(300f, UiChatView.ClampScroll(1000f, contentHeight: 500f, viewHeight: 200f));
|
||||
Assert.Equal(120f, UiChatView.ClampScroll(120f, contentHeight: 500f, viewHeight: 200f));
|
||||
Assert.Equal(300f, UiText.ClampScroll(1000f, contentHeight: 500f, viewHeight: 200f));
|
||||
Assert.Equal(120f, UiText.ClampScroll(120f, contentHeight: 500f, viewHeight: 200f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClampScroll_NeverNegative()
|
||||
{
|
||||
Assert.Equal(0f, UiChatView.ClampScroll(-50f, contentHeight: 500f, viewHeight: 200f));
|
||||
Assert.Equal(0f, UiText.ClampScroll(-50f, contentHeight: 500f, viewHeight: 200f));
|
||||
}
|
||||
|
||||
// ── Char-index hit-testing (x → col) with a synthetic 10px monospace advance ──
|
||||
|
|
@ -36,39 +36,39 @@ public class UiChatViewTests
|
|||
[Fact]
|
||||
public void CharIndexAt_ZeroOrNegative_IsColumnZero()
|
||||
{
|
||||
Assert.Equal(0, UiChatView.CharIndexAt("hello", Mono10, 0f));
|
||||
Assert.Equal(0, UiChatView.CharIndexAt("hello", Mono10, -5f));
|
||||
Assert.Equal(0, UiText.CharIndexAt("hello", Mono10, 0f));
|
||||
Assert.Equal(0, UiText.CharIndexAt("hello", Mono10, -5f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CharIndexAt_SnapsToGlyphMidpoint()
|
||||
{
|
||||
// glyph[0] spans 0..10 (midpoint 5), glyph[1] 10..20 (midpoint 15), ...
|
||||
Assert.Equal(0, UiChatView.CharIndexAt("hello", Mono10, 4f)); // before mid of glyph 0
|
||||
Assert.Equal(1, UiChatView.CharIndexAt("hello", Mono10, 6f)); // past mid of glyph 0
|
||||
Assert.Equal(1, UiChatView.CharIndexAt("hello", Mono10, 14f)); // before mid of glyph 1
|
||||
Assert.Equal(2, UiChatView.CharIndexAt("hello", Mono10, 16f)); // past mid of glyph 1
|
||||
Assert.Equal(0, UiText.CharIndexAt("hello", Mono10, 4f)); // before mid of glyph 0
|
||||
Assert.Equal(1, UiText.CharIndexAt("hello", Mono10, 6f)); // past mid of glyph 0
|
||||
Assert.Equal(1, UiText.CharIndexAt("hello", Mono10, 14f)); // before mid of glyph 1
|
||||
Assert.Equal(2, UiText.CharIndexAt("hello", Mono10, 16f)); // past mid of glyph 1
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CharIndexAt_PastEnd_IsLength()
|
||||
{
|
||||
Assert.Equal(5, UiChatView.CharIndexAt("hello", Mono10, 1000f));
|
||||
Assert.Equal(5, UiText.CharIndexAt("hello", Mono10, 1000f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CharIndexAt_EmptyString_IsZero()
|
||||
{
|
||||
Assert.Equal(0, UiChatView.CharIndexAt("", Mono10, 50f));
|
||||
Assert.Equal(0, UiText.CharIndexAt("", Mono10, 50f));
|
||||
}
|
||||
|
||||
// ── SelectedText assembly ────────────────────────────────────────────
|
||||
|
||||
private static IReadOnlyList<UiChatView.Line> Lines(params string[] texts)
|
||||
private static IReadOnlyList<UiText.Line> Lines(params string[] texts)
|
||||
{
|
||||
var list = new List<UiChatView.Line>(texts.Length);
|
||||
var list = new List<UiText.Line>(texts.Length);
|
||||
foreach (var t in texts)
|
||||
list.Add(new UiChatView.Line(t, new Vector4(1, 1, 1, 1)));
|
||||
list.Add(new UiText.Line(t, new Vector4(1, 1, 1, 1)));
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public class UiChatViewTests
|
|||
public void SelectedText_SingleLine_Substring()
|
||||
{
|
||||
var lines = Lines("hello world");
|
||||
var s = UiChatView.SelectedText(lines, new UiChatView.Pos(0, 6), new UiChatView.Pos(0, 11));
|
||||
var s = UiText.SelectedText(lines, new UiText.Pos(0, 6), new UiText.Pos(0, 11));
|
||||
Assert.Equal("world", s);
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ public class UiChatViewTests
|
|||
{
|
||||
var lines = Lines("hello world");
|
||||
// caret BEFORE anchor — Order() must normalise.
|
||||
var s = UiChatView.SelectedText(lines, new UiChatView.Pos(0, 11), new UiChatView.Pos(0, 6));
|
||||
var s = UiText.SelectedText(lines, new UiText.Pos(0, 11), new UiText.Pos(0, 6));
|
||||
Assert.Equal("world", s);
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ public class UiChatViewTests
|
|||
public void SelectedText_SamePosition_IsEmpty()
|
||||
{
|
||||
var lines = Lines("hello");
|
||||
Assert.Equal("", UiChatView.SelectedText(lines, new UiChatView.Pos(0, 3), new UiChatView.Pos(0, 3)));
|
||||
Assert.Equal("", UiText.SelectedText(lines, new UiText.Pos(0, 3), new UiText.Pos(0, 3)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -101,7 +101,7 @@ public class UiChatViewTests
|
|||
{
|
||||
var lines = Lines("first line", "second line", "third line");
|
||||
// from col 6 of line 0 ("line") through col 5 of line 2 ("third")
|
||||
var s = UiChatView.SelectedText(lines, new UiChatView.Pos(0, 6), new UiChatView.Pos(2, 5));
|
||||
var s = UiText.SelectedText(lines, new UiText.Pos(0, 6), new UiText.Pos(2, 5));
|
||||
Assert.Equal("line\nsecond line\nthird", s);
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ public class UiChatViewTests
|
|||
public void SelectedText_MultiLine_TwoLines_NoMiddle()
|
||||
{
|
||||
var lines = Lines("alpha", "bravo");
|
||||
var s = UiChatView.SelectedText(lines, new UiChatView.Pos(0, 2), new UiChatView.Pos(1, 3));
|
||||
var s = UiText.SelectedText(lines, new UiText.Pos(0, 2), new UiText.Pos(1, 3));
|
||||
Assert.Equal("pha\nbra", s);
|
||||
}
|
||||
|
||||
|
|
@ -118,26 +118,26 @@ public class UiChatViewTests
|
|||
{
|
||||
var lines = Lines("alpha", "bravo");
|
||||
// end before start → Order() swaps them.
|
||||
var s = UiChatView.SelectedText(lines, new UiChatView.Pos(1, 3), new UiChatView.Pos(0, 2));
|
||||
var s = UiText.SelectedText(lines, new UiText.Pos(1, 3), new UiText.Pos(0, 2));
|
||||
Assert.Equal("pha\nbra", s);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedText_EmptyLineList_IsEmpty()
|
||||
{
|
||||
Assert.Equal("", UiChatView.SelectedText(Array.Empty<UiChatView.Line>(),
|
||||
new UiChatView.Pos(0, 0), new UiChatView.Pos(0, 0)));
|
||||
Assert.Equal("", UiText.SelectedText(Array.Empty<UiText.Line>(),
|
||||
new UiText.Pos(0, 0), new UiText.Pos(0, 0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Order_SortsByLineThenColumn()
|
||||
{
|
||||
var (s1, e1) = UiChatView.Order(new UiChatView.Pos(2, 1), new UiChatView.Pos(0, 5));
|
||||
Assert.Equal(new UiChatView.Pos(0, 5), s1);
|
||||
Assert.Equal(new UiChatView.Pos(2, 1), e1);
|
||||
var (s1, e1) = UiText.Order(new UiText.Pos(2, 1), new UiText.Pos(0, 5));
|
||||
Assert.Equal(new UiText.Pos(0, 5), s1);
|
||||
Assert.Equal(new UiText.Pos(2, 1), e1);
|
||||
|
||||
var (s2, e2) = UiChatView.Order(new UiChatView.Pos(1, 8), new UiChatView.Pos(1, 2));
|
||||
Assert.Equal(new UiChatView.Pos(1, 2), s2);
|
||||
Assert.Equal(new UiChatView.Pos(1, 8), e2);
|
||||
var (s2, e2) = UiText.Order(new UiText.Pos(1, 8), new UiText.Pos(1, 2));
|
||||
Assert.Equal(new UiText.Pos(1, 2), s2);
|
||||
Assert.Equal(new UiText.Pos(1, 8), e2);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue