fix(ui): keep spellbook tab captions above chrome
This commit is contained in:
parent
c918ccea06
commit
16eb4844c1
4 changed files with 91 additions and 1 deletions
|
|
@ -202,6 +202,9 @@ The two top-level tabs are not `UIElement_Button` instances. They resolve from
|
||||||
base `0x1000043A` as `UIElement_Text` (type 12), with state 11 `Closed` and
|
base `0x1000043A` as `UIElement_Text` (type 12), with state 11 `Closed` and
|
||||||
state 12 `Open`. Both states set `PassToChildren`; the three child chrome
|
state 12 `Open`. Both states set `PassToChildren`; the three child chrome
|
||||||
elements carry the left/center/right Closed/Open RenderSurface media.
|
elements carry the left/center/right Closed/Open RenderSurface media.
|
||||||
|
Their direct string property `0x17` resolves through the installed string
|
||||||
|
tables to `Spellbook` (string id `49867858`) and `Components` (string id
|
||||||
|
`79360818`).
|
||||||
|
|
||||||
Retail state and text construction used by these tabs:
|
Retail state and text construction used by these tabs:
|
||||||
|
|
||||||
|
|
@ -227,8 +230,21 @@ UIElement::SetState @ 0x00464E70
|
||||||
child.SetState(stateId)
|
child.SetState(stateId)
|
||||||
diff old/new effective properties
|
diff old/new effective properties
|
||||||
call OnSetAttribute for changed properties
|
call OnSetAttribute for changed properties
|
||||||
|
|
||||||
|
Retained rendering reduction:
|
||||||
|
draw the UIElement_Text background
|
||||||
|
draw its three stateful chrome children
|
||||||
|
draw the authored glyph content as the parent's foreground
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Retail `UIElement_Text::DrawSelf @ 0x00467AA0` owns the glyphs while the child
|
||||||
|
regions own the state chrome. acdream's sprites and DAT glyphs share one
|
||||||
|
submission-ordered batch, so submitting the glyphs in the parent's foreground
|
||||||
|
pass preserves that same visible composition; submitting the entire parent
|
||||||
|
before its retained children lets the center chrome overpaint the caption.
|
||||||
|
This rule is derived structurally from `PassToChildren`, not from the two
|
||||||
|
spellbook element ids.
|
||||||
|
|
||||||
The Magic-combat favorite tab captions are authored text (`I` through `VIII`)
|
The Magic-combat favorite tab captions are authored text (`I` through `VIII`)
|
||||||
and intentionally omit property `0x20`. Their small rectangles still render
|
and intentionally omit property `0x20`. Their small rectangles still render
|
||||||
because retail begins with zero text margins. A generic four-pixel inset on
|
because retail begins with zero text margins. A generic four-pixel inset on
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ public sealed class UiText : UiElement, IUiDatStateful
|
||||||
private ElementInfo? _datInfo;
|
private ElementInfo? _datInfo;
|
||||||
private uint _activeRetailStateId = UiStateInfo.DirectStateId;
|
private uint _activeRetailStateId = UiStateInfo.DirectStateId;
|
||||||
private string _activeDatStateName = "";
|
private string _activeDatStateName = "";
|
||||||
|
private bool _drawTextAfterChildren;
|
||||||
|
|
||||||
// ── Selection state ──────────────────────────────────────────────────
|
// ── Selection state ──────────────────────────────────────────────────
|
||||||
private Pos? _selAnchor; // where the drag started
|
private Pos? _selAnchor; // where the drag started
|
||||||
|
|
@ -205,10 +206,25 @@ public sealed class UiText : UiElement, IUiDatStateful
|
||||||
internal void ConfigureDatState(ElementInfo info)
|
internal void ConfigureDatState(ElementInfo info)
|
||||||
{
|
{
|
||||||
_datInfo = info;
|
_datInfo = info;
|
||||||
|
// Retail spellbook tabs are UIElement_Text parents whose Open/Closed
|
||||||
|
// PassToChildren states drive three authored chrome pieces. In retail's
|
||||||
|
// software surface those pieces form the tab background while the text
|
||||||
|
// remains the foreground. Our retained renderer submits parent and child
|
||||||
|
// sprites into one painter-ordered batch, so submit only the glyph content
|
||||||
|
// in the parent's foreground pass after those chrome children.
|
||||||
|
_drawTextAfterChildren = false;
|
||||||
|
foreach (UiStateInfo state in info.States.Values)
|
||||||
|
{
|
||||||
|
if (!state.PassToChildren) continue;
|
||||||
|
_drawTextAfterChildren = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
_activeRetailStateId = info.EffectiveDefaultStateId();
|
_activeRetailStateId = info.EffectiveDefaultStateId();
|
||||||
ApplyDatState(_activeRetailStateId, propagate: false);
|
ApplyDatState(_activeRetailStateId, propagate: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool DrawTextAfterChildren => _drawTextAfterChildren;
|
||||||
|
|
||||||
public bool TrySetRetailState(uint stateId)
|
public bool TrySetRetailState(uint stateId)
|
||||||
=> ApplyDatState(stateId, propagate: true);
|
=> ApplyDatState(stateId, propagate: true);
|
||||||
|
|
||||||
|
|
@ -307,6 +323,18 @@ public sealed class UiText : UiElement, IUiDatStateful
|
||||||
// submitted first → text on top.
|
// submitted first → text on top.
|
||||||
ctx.DrawFill(0, 0, Width, Height, BackgroundColor);
|
ctx.DrawFill(0, 0, Width, Height, BackgroundColor);
|
||||||
|
|
||||||
|
if (!_drawTextAfterChildren)
|
||||||
|
DrawText(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDrawAfterChildren(UiRenderContext ctx)
|
||||||
|
{
|
||||||
|
if (_drawTextAfterChildren)
|
||||||
|
DrawText(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawText(UiRenderContext ctx)
|
||||||
|
{
|
||||||
// Static centered single-line mode (vitals cur/max numbers etc.): draw the first
|
// Static centered single-line mode (vitals cur/max numbers etc.): draw the first
|
||||||
// line centered H+V (or H+Top/Bottom per VerticalJustify) with the SAME formula
|
// line centered H+V (or H+Top/Bottom per VerticalJustify) with the SAME formula
|
||||||
// UIElement_Meter used for its label, then skip the scroll/selection machinery entirely.
|
// UIElement_Meter used for its label, then skip the scroll/selection machinery entirely.
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,36 @@ public sealed class SpellbookRowStyleTests
|
||||||
Assert.Equal(0x06001396u, style.BackgroundSprite);
|
Assert.Equal(0x06001396u, style.BackgroundSprite);
|
||||||
Assert.Equal(0x06001397u, style.SelectedSprite);
|
Assert.Equal(0x06001397u, style.SelectedSprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InstalledDat_ResolvesAuthoredSpellbookTabCaptions()
|
||||||
|
{
|
||||||
|
string datDir = System.Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
|
||||||
|
?? Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile),
|
||||||
|
"Documents", "Asheron's Call");
|
||||||
|
if (!Directory.Exists(datDir)) return;
|
||||||
|
|
||||||
|
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||||
|
ElementInfo root = Assert.IsType<ElementInfo>(LayoutImporter.ImportInfos(
|
||||||
|
dats, SpellbookWindowController.LayoutId, SpellbookWindowController.RootId));
|
||||||
|
var strings = new DatStringResolver(dats);
|
||||||
|
|
||||||
|
Assert.Equal("Spellbook", ResolveCaption(SpellbookWindowController.SpellTabId));
|
||||||
|
Assert.Equal("Components", ResolveCaption(SpellbookWindowController.ComponentTabId));
|
||||||
|
|
||||||
|
string? ResolveCaption(uint id)
|
||||||
|
{
|
||||||
|
ElementInfo node = Assert.IsType<ElementInfo>(Find(root, id));
|
||||||
|
Assert.True(node.TryGetEffectiveProperty(0x17u, out UiPropertyValue label));
|
||||||
|
return strings.Resolve(label.StringInfoValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,16 @@ public sealed class SpellbookWindowControllerTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RetailFixture_BindsTextTabs_AndPropagatesOpenClosedState()
|
public void RetailFixture_BindsTextTabs_AndPropagatesOpenClosedState()
|
||||||
{
|
{
|
||||||
ImportedLayout layout = FixtureLoader.LoadSpellbook();
|
ImportedLayout layout = LayoutImporter.Build(
|
||||||
|
FixtureLoader.LoadSpellbookInfos(),
|
||||||
|
_ => (0u, 0, 0),
|
||||||
|
datFont: null,
|
||||||
|
stringResolve: value => value.StringId switch
|
||||||
|
{
|
||||||
|
49867858u => "Spellbook",
|
||||||
|
79360818u => "Components",
|
||||||
|
_ => null,
|
||||||
|
});
|
||||||
var closed = new List<uint>();
|
var closed = new List<uint>();
|
||||||
using SpellbookWindowController? controller = Bind(
|
using SpellbookWindowController? controller = Bind(
|
||||||
layout, new Spellbook(), close: () => closed.Add(1u));
|
layout, new Spellbook(), close: () => closed.Add(1u));
|
||||||
|
|
@ -37,6 +46,11 @@ public sealed class SpellbookWindowControllerTests
|
||||||
UiElement spellPage = Assert.IsAssignableFrom<UiElement>(layout.FindElement(SpellbookWindowController.SpellPageId));
|
UiElement spellPage = Assert.IsAssignableFrom<UiElement>(layout.FindElement(SpellbookWindowController.SpellPageId));
|
||||||
UiElement componentPage = Assert.IsAssignableFrom<UiElement>(layout.FindElement(SpellbookWindowController.ComponentPageId));
|
UiElement componentPage = Assert.IsAssignableFrom<UiElement>(layout.FindElement(SpellbookWindowController.ComponentPageId));
|
||||||
|
|
||||||
|
Assert.Equal("Spellbook", Assert.Single(spellTab.LinesProvider()).Text);
|
||||||
|
Assert.Equal("Components", Assert.Single(componentTab.LinesProvider()).Text);
|
||||||
|
Assert.True(spellTab.DrawTextAfterChildren);
|
||||||
|
Assert.True(componentTab.DrawTextAfterChildren);
|
||||||
|
|
||||||
Assert.Equal(RetailUiStateIds.Open, spellTab.ActiveRetailStateId);
|
Assert.Equal(RetailUiStateIds.Open, spellTab.ActiveRetailStateId);
|
||||||
Assert.Equal(RetailUiStateIds.Closed, componentTab.ActiveRetailStateId);
|
Assert.Equal(RetailUiStateIds.Closed, componentTab.ActiveRetailStateId);
|
||||||
Assert.True(spellPage.Visible);
|
Assert.True(spellPage.Visible);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue