feat: port retail magic lifecycle and retained spell UI
Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
7b7ffcd278
commit
07be994d97
84 changed files with 17822 additions and 1051 deletions
|
|
@ -11,12 +11,12 @@ public sealed class CombatUiControllerTests
|
|||
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
|
||||
|
||||
[Fact]
|
||||
public void CombatMode_ShowsOnlyTargetedModes_AndSelectsMediumByDefault()
|
||||
public void CombatMode_ShowsPhysicalAndMagicPages_AndSelectsMediumByDefault()
|
||||
{
|
||||
double now = 0d;
|
||||
var combat = new CombatState();
|
||||
using var attacks = CreateAttacks(combat, () => now, []);
|
||||
var (layout, _, _, power, high, medium, low) = BuildLayout();
|
||||
var (layout, basic, spellcasting, power, high, medium, low) = BuildLayout();
|
||||
var visibility = new List<bool>();
|
||||
GameplaySettings gameplay = GameplaySettings.Default;
|
||||
using var controller = CombatUiController.Bind(
|
||||
|
|
@ -27,7 +27,9 @@ public sealed class CombatUiControllerTests
|
|||
combat.SetCombatMode(CombatMode.Melee);
|
||||
combat.SetCombatMode(CombatMode.Magic);
|
||||
|
||||
Assert.Equal([false, true, false], visibility);
|
||||
Assert.Equal([false, true, true], visibility);
|
||||
Assert.False(basic.Visible);
|
||||
Assert.True(spellcasting.Visible);
|
||||
Assert.False(high.Selected);
|
||||
Assert.True(medium.Selected);
|
||||
Assert.False(low.Selected);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class EffectsIndicatorControllerTests
|
||||
{
|
||||
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
|
||||
|
||||
[Fact]
|
||||
public void AuthoredFixture_BuildsRetailIndicatorButtons()
|
||||
{
|
||||
ElementInfo info = FixtureLoader.LoadIndicatorsInfos();
|
||||
Assert.Equal(0x10000610u, info.Id);
|
||||
Assert.Equal(150f, info.Width);
|
||||
Assert.Equal(30f, info.Height);
|
||||
|
||||
ImportedLayout layout = LayoutImporter.Build(info, NoTex, datFont: null);
|
||||
Assert.IsType<UiButton>(layout.FindElement(EffectsIndicatorController.HelpfulButtonId));
|
||||
Assert.IsType<UiButton>(layout.FindElement(EffectsIndicatorController.HarmfulButtonId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Enchantments_GhostAndEnableMatchingIndicator_ThenClickCorrectPanel()
|
||||
{
|
||||
var table = SpellTable.LoadFromReader(new StringReader(
|
||||
"Spell ID,Name,Flags [Hex]\n42,Boon,0x4\n43,Bane,0x0\n"));
|
||||
var spellbook = new Spellbook(table);
|
||||
ImportedLayout layout = BuildSyntheticLayout();
|
||||
var toggled = new List<uint>();
|
||||
using EffectsIndicatorController controller = EffectsIndicatorController.Bind(
|
||||
layout, spellbook, toggled.Add)!;
|
||||
UiButton helpful = Assert.IsType<UiButton>(
|
||||
layout.FindElement(EffectsIndicatorController.HelpfulButtonId));
|
||||
UiButton harmful = Assert.IsType<UiButton>(
|
||||
layout.FindElement(EffectsIndicatorController.HarmfulButtonId));
|
||||
|
||||
Assert.False(helpful.Enabled);
|
||||
Assert.False(harmful.Enabled);
|
||||
|
||||
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
42u, 10u, 60f, 1u, Bucket: 4u, SpellCategory: 42u));
|
||||
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
43u, 11u, 60f, 2u, Bucket: 8u, SpellCategory: 43u));
|
||||
Assert.False(helpful.Enabled);
|
||||
Assert.False(harmful.Enabled);
|
||||
|
||||
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
42u, 1u, 60f, 1u, Bucket: 1u, SpellCategory: 99u, PowerLevel: 1u));
|
||||
Assert.True(helpful.Enabled);
|
||||
Assert.False(harmful.Enabled);
|
||||
helpful.OnEvent(new UiEvent(0, helpful, UiEventType.Click));
|
||||
Assert.Equal([RetailPanelCatalog.PositiveEffects], toggled);
|
||||
|
||||
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
43u, 1u, 60f, 2u, Bucket: 2u, SpellCategory: 99u, PowerLevel: 7u));
|
||||
// The harmful spell wins the shared category in the effects list, but
|
||||
// retail's raw registry counters keep both indicator types enabled.
|
||||
Assert.True(helpful.Enabled);
|
||||
Assert.True(harmful.Enabled);
|
||||
harmful.OnEvent(new UiEvent(0, harmful, UiEventType.Click));
|
||||
Assert.Equal(
|
||||
[RetailPanelCatalog.PositiveEffects, RetailPanelCatalog.NegativeEffects],
|
||||
toggled);
|
||||
|
||||
spellbook.OnEnchantmentRemoved(1u, 42u);
|
||||
Assert.False(helpful.Enabled);
|
||||
Assert.True(harmful.Enabled);
|
||||
}
|
||||
|
||||
private static ImportedLayout BuildSyntheticLayout()
|
||||
{
|
||||
var root = new ElementInfo { Id = 1u, Type = 3u, Width = 150f, Height = 30f };
|
||||
var helpful = ButtonInfo(EffectsIndicatorController.HelpfulButtonId);
|
||||
var harmful = ButtonInfo(EffectsIndicatorController.HarmfulButtonId);
|
||||
return LayoutImporter.BuildFromInfos(root, [helpful, harmful], NoTex, null);
|
||||
}
|
||||
|
||||
private static ElementInfo ButtonInfo(uint id)
|
||||
{
|
||||
var info = new ElementInfo
|
||||
{
|
||||
Id = id,
|
||||
Type = EffectsIndicatorController.RetailClassId,
|
||||
Width = 20f,
|
||||
Height = 20f,
|
||||
DefaultStateId = UiButtonStateMachine.Normal,
|
||||
};
|
||||
info.States[UiButtonStateMachine.Normal] = new UiStateInfo
|
||||
{
|
||||
Id = UiButtonStateMachine.Normal,
|
||||
Name = "Normal",
|
||||
};
|
||||
info.States[UiButtonStateMachine.Ghosted] = new UiStateInfo
|
||||
{
|
||||
Id = UiButtonStateMachine.Ghosted,
|
||||
Name = "Ghosted",
|
||||
};
|
||||
return info;
|
||||
}
|
||||
}
|
||||
198
tests/AcDream.App.Tests/UI/Layout/EffectsUiControllerTests.cs
Normal file
198
tests/AcDream.App.Tests/UI/Layout/EffectsUiControllerTests.cs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class EffectsUiControllerTests
|
||||
{
|
||||
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
|
||||
|
||||
[Fact]
|
||||
public void Rebuild_DoesNotAutoSelectFirstEffect_AndClearsRemovedSelection()
|
||||
{
|
||||
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 };
|
||||
var info = new UiText { Width = 160f, Height = 32f };
|
||||
root.AddChild(list);
|
||||
root.AddChild(info);
|
||||
var layout = new ImportedLayout(root, new Dictionary<uint, UiElement>
|
||||
{
|
||||
[EffectsUiController.ListId] = list,
|
||||
[EffectsUiController.InfoTextId] = info,
|
||||
});
|
||||
using EffectsUiController controller = EffectsUiController.Bind(
|
||||
layout, spellbook, positive: true, () => 0d, NoTex, id => id)!;
|
||||
|
||||
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
42u, 1u, 60f, 1u, Bucket: 1u, SpellCategory: 42u));
|
||||
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
42u, 2u, 60f, 1u, Bucket: 4u, SpellCategory: 42u));
|
||||
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
42u, 3u, 60f, 1u, Bucket: 8u, SpellCategory: 42u));
|
||||
Assert.Null(controller.SelectedSpellId);
|
||||
Assert.Equal(1, list.GetNumUIItems());
|
||||
UiCatalogSlot row = Assert.IsType<UiCatalogSlot>(list.GetItem(0));
|
||||
row.OnEvent(new UiEvent(0, row, UiEventType.Click));
|
||||
Assert.Equal(42u, controller.SelectedSpellId);
|
||||
|
||||
spellbook.OnEnchantmentRemoved(1u, 42u);
|
||||
Assert.Null(controller.SelectedSpellId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuplicateLayersOfSameSpell_ShareRetailSelection()
|
||||
{
|
||||
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<uint, UiElement>
|
||||
{
|
||||
[EffectsUiController.ListId] = list,
|
||||
});
|
||||
using EffectsUiController controller = EffectsUiController.Bind(
|
||||
layout, spellbook, positive: true, () => 0d, NoTex, id => id)!;
|
||||
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<UiCatalogSlot>(list.GetItem(0));
|
||||
UiCatalogSlot second = Assert.IsType<UiCatalogSlot>(list.GetItem(1));
|
||||
first.OnEvent(new UiEvent(0, first, UiEventType.Click));
|
||||
|
||||
Assert.Equal(42u, controller.SelectedSpellId);
|
||||
Assert.True(first.Selected);
|
||||
Assert.True(second.Selected);
|
||||
|
||||
second.OnEvent(new UiEvent(0, second, UiEventType.Click));
|
||||
Assert.Null(controller.SelectedSpellId);
|
||||
Assert.False(first.Selected);
|
||||
Assert.False(second.Selected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void AuthoredEffectsFixture_InheritsListAndInfo_AndBinds(bool positive)
|
||||
{
|
||||
ElementInfo info = positive
|
||||
? FixtureLoader.LoadPositiveEffectsInfos()
|
||||
: FixtureLoader.LoadNegativeEffectsInfos();
|
||||
uint expectedRoot = positive
|
||||
? EffectsUiController.PositiveRootId
|
||||
: EffectsUiController.NegativeRootId;
|
||||
Assert.Equal(expectedRoot, info.Id);
|
||||
Assert.Equal(0x1000001Bu, info.Type);
|
||||
Assert.Equal(300f, info.Width);
|
||||
Assert.Equal(362f, info.Height);
|
||||
Assert.True(info.TryGetEffectiveProperty(0x1000000Cu, out UiPropertyValue effectType));
|
||||
Assert.Equal(UiPropertyKind.Enum, effectType.Kind);
|
||||
Assert.Equal(positive ? 1u : 2u, effectType.UnsignedValue);
|
||||
Assert.Equal(
|
||||
positive,
|
||||
info.TryGetEffectiveBool(0x10000049u, out bool restorePrevious)
|
||||
&& restorePrevious);
|
||||
|
||||
ElementInfo[] children = info.Children.OrderBy(child => child.ReadOrder).ToArray();
|
||||
Assert.Equal(
|
||||
[0x100000FCu, 0x10000120u, 0x10000123u, 0x10000124u, 0x10000125u],
|
||||
children.Select(child => child.Id));
|
||||
Assert.Equal([1u, 2u, 3u, 4u, 5u], children.Select(child => child.ReadOrder));
|
||||
Assert.Equal(children.Length, children.Select(child => child.Id).Distinct().Count());
|
||||
ElementInfo listInfo = Assert.Single(children, child => child.Id == EffectsUiController.ListId);
|
||||
Assert.Equal(5u, listInfo.Type);
|
||||
Assert.Equal(300f, listInfo.Width);
|
||||
Assert.Equal(249f, listInfo.Height);
|
||||
Assert.Equal(12u, FindInfo(info, EffectsUiController.InfoTextId)?.Type);
|
||||
|
||||
ImportedLayout layout = LayoutImporter.Build(info, NoTex, datFont: null);
|
||||
var spellbook = new Spellbook();
|
||||
int closes = 0;
|
||||
|
||||
using EffectsUiController? controller = EffectsUiController.Bind(
|
||||
layout, spellbook, positive, () => 0d, NoTex, id => id, () => closes++);
|
||||
|
||||
Assert.NotNull(controller);
|
||||
Assert.NotNull(layout.FindElement(EffectsUiController.ListId));
|
||||
Assert.IsType<UiText>(layout.FindElement(EffectsUiController.InfoTextId));
|
||||
Assert.Equal(expectedRoot, layout.Root.DatElementId);
|
||||
UiButton close = Assert.IsType<UiButton>(layout.FindElement(EffectsUiController.CloseId));
|
||||
close.OnEvent(new UiEvent(0, close, UiEventType.Click));
|
||||
Assert.Equal(1, closes);
|
||||
|
||||
uint panelId = positive
|
||||
? RetailPanelCatalog.PositiveEffects
|
||||
: RetailPanelCatalog.NegativeEffects;
|
||||
Assert.True(RetailPanelCatalog.TryGetWindowName(panelId, out string windowName));
|
||||
Assert.Equal(
|
||||
positive ? WindowNames.PositiveEffects : WindowNames.NegativeEffects,
|
||||
windowName);
|
||||
Assert.DoesNotContain(
|
||||
RetailPanelCatalog.ToolbarPanels,
|
||||
entry => entry.PanelId == panelId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void AuthoredRestorePreviousProperty_DrivesRetailPanelLifecycle(bool positive)
|
||||
{
|
||||
ElementInfo info = positive
|
||||
? FixtureLoader.LoadPositiveEffectsInfos()
|
||||
: FixtureLoader.LoadNegativeEffectsInfos();
|
||||
var visible = new Dictionary<string, bool>(StringComparer.Ordinal)
|
||||
{
|
||||
[WindowNames.Inventory] = false,
|
||||
[positive ? WindowNames.PositiveEffects : WindowNames.NegativeEffects] = false,
|
||||
};
|
||||
var panelUi = new RetailPanelUiController(
|
||||
name => visible[name],
|
||||
name =>
|
||||
{
|
||||
visible[name] = true;
|
||||
return true;
|
||||
},
|
||||
name =>
|
||||
{
|
||||
visible[name] = false;
|
||||
return true;
|
||||
});
|
||||
uint effectsPanel = positive
|
||||
? RetailPanelCatalog.PositiveEffects
|
||||
: RetailPanelCatalog.NegativeEffects;
|
||||
string effectsWindow = positive
|
||||
? WindowNames.PositiveEffects
|
||||
: WindowNames.NegativeEffects;
|
||||
panelUi.Register(RetailPanelCatalog.Inventory, WindowNames.Inventory);
|
||||
panelUi.Register(
|
||||
effectsPanel,
|
||||
effectsWindow,
|
||||
info);
|
||||
|
||||
panelUi.SetPanelVisibility(RetailPanelCatalog.Inventory, visible: true);
|
||||
panelUi.SetPanelVisibility(effectsPanel, visible: true);
|
||||
panelUi.SetPanelVisibility(effectsPanel, visible: false);
|
||||
|
||||
Assert.Equal(positive, visible[WindowNames.Inventory]);
|
||||
Assert.False(visible[effectsWindow]);
|
||||
Assert.Equal(
|
||||
positive ? RetailPanelCatalog.Inventory : null,
|
||||
panelUi.ActivePanelId);
|
||||
}
|
||||
|
||||
private static ElementInfo? FindInfo(ElementInfo root, uint id)
|
||||
{
|
||||
if (root.Id == id) return root;
|
||||
foreach (ElementInfo child in root.Children)
|
||||
if (FindInfo(child, id) is { } found)
|
||||
return found;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -113,6 +113,24 @@ public static class FixtureLoader
|
|||
public static ElementInfo LoadFpsDisplayInfos()
|
||||
=> LoadInfos("smartbox_fps_2100000F.json");
|
||||
|
||||
public static ImportedLayout LoadPositiveEffects()
|
||||
=> LayoutImporter.Build(LoadPositiveEffectsInfos(), _ => (0u, 0, 0), null);
|
||||
|
||||
public static ElementInfo LoadPositiveEffectsInfos()
|
||||
=> LoadInfos("effects_positive_2100001B.json");
|
||||
|
||||
public static ImportedLayout LoadNegativeEffects()
|
||||
=> LayoutImporter.Build(LoadNegativeEffectsInfos(), _ => (0u, 0, 0), null);
|
||||
|
||||
public static ElementInfo LoadNegativeEffectsInfos()
|
||||
=> LoadInfos("effects_negative_2100001B.json");
|
||||
|
||||
public static ImportedLayout LoadIndicators()
|
||||
=> LayoutImporter.Build(LoadIndicatorsInfos(), _ => (0u, 0, 0), null);
|
||||
|
||||
public static ElementInfo LoadIndicatorsInfos()
|
||||
=> LoadInfos("indicators_21000071.json");
|
||||
|
||||
// ── Shared loader ────────────────────────────────────────────────────────
|
||||
|
||||
private static AcDream.App.UI.Layout.ElementInfo LoadInfos(string fileName)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
(0x21000024u, "paperdoll_21000024.json"),
|
||||
(0x2100002Eu, "character_2100002E.json"),
|
||||
(0x2100006Cu, "vitals_2100006C.json"),
|
||||
(EffectsIndicatorController.LayoutId, "indicators_21000071.json"),
|
||||
(0x21000072u, "powerbar_21000072.json"),
|
||||
(0x21000073u, "combat_21000073.json"),
|
||||
(0x21000074u, "radar_21000074.json"),
|
||||
|
|
@ -73,7 +74,7 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
Assert.Equal(0x4000001Au, fps!.FontDid);
|
||||
var fpsStrings = new DatStringResolver(dats);
|
||||
Assert.Equal("FPS: ", fpsStrings.Resolve(0x23000001u, 0x0DCFFF73u, 0));
|
||||
Assert.Equal("\nDEG: ", fpsStrings.Resolve(0x23000001u, 0x0DCFFF73u, 1));
|
||||
Assert.Equal(@"\nDEG: ", fpsStrings.Resolve(0x23000001u, 0x0DCFFF73u, 1));
|
||||
var fpsJson = JsonSerializer.Serialize(fps, new JsonSerializerOptions
|
||||
{
|
||||
IncludeFields = true,
|
||||
|
|
@ -83,6 +84,23 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
Path.Combine(FixtureDirectory(), $"smartbox_fps_{smartboxLayoutDid:X8}.json"),
|
||||
fpsJson);
|
||||
|
||||
foreach ((uint rootId, string fileName) in new[]
|
||||
{
|
||||
(EffectsUiController.PositiveRootId, "effects_positive_2100001B.json"),
|
||||
(EffectsUiController.NegativeRootId, "effects_negative_2100001B.json"),
|
||||
})
|
||||
{
|
||||
ElementInfo? effects = LayoutImporter.ImportInfos(
|
||||
dats, EffectsUiController.LayoutId, rootId);
|
||||
Assert.NotNull(effects);
|
||||
var effectsJson = JsonSerializer.Serialize(effects, new JsonSerializerOptions
|
||||
{
|
||||
IncludeFields = true,
|
||||
WriteIndented = true,
|
||||
});
|
||||
File.WriteAllText(Path.Combine(FixtureDirectory(), fileName), effectsJson);
|
||||
}
|
||||
|
||||
foreach (var (layoutId, fileName) in Layouts)
|
||||
{
|
||||
var info = LayoutImporter.ImportInfos(dats, layoutId);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class RetailPanelUiControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ShowingPanel_HidesCurrent_AndClosingLeavesHostEmpty()
|
||||
{
|
||||
var visible = new Dictionary<string, bool>(StringComparer.Ordinal)
|
||||
{
|
||||
["inventory"] = false,
|
||||
["helpful"] = false,
|
||||
};
|
||||
var controller = Create(visible);
|
||||
controller.Register(7u, "inventory");
|
||||
controller.Register(4u, "helpful");
|
||||
|
||||
Assert.True(controller.SetPanelVisibility(7u, true));
|
||||
Assert.True(visible["inventory"]);
|
||||
Assert.Equal(7u, controller.ActivePanelId);
|
||||
|
||||
Assert.True(controller.SetPanelVisibility(4u, true));
|
||||
Assert.False(visible["inventory"]);
|
||||
Assert.True(visible["helpful"]);
|
||||
Assert.Equal(4u, controller.ActivePanelId);
|
||||
|
||||
Assert.False(controller.TogglePanel(4u));
|
||||
Assert.False(visible["helpful"]);
|
||||
Assert.Null(controller.ActivePanelId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RestorePreviousProperty_ReopensOnlyNonRestoringPreviousPanel()
|
||||
{
|
||||
var visible = new Dictionary<string, bool>(StringComparer.Ordinal)
|
||||
{
|
||||
["ordinary"] = false,
|
||||
["transient"] = false,
|
||||
};
|
||||
var controller = Create(visible);
|
||||
controller.Register(1u, "ordinary", restorePrevious: false);
|
||||
controller.Register(2u, "transient", restorePrevious: true);
|
||||
|
||||
controller.SetPanelVisibility(1u, true);
|
||||
controller.SetPanelVisibility(2u, true);
|
||||
Assert.False(visible["ordinary"]);
|
||||
Assert.True(visible["transient"]);
|
||||
|
||||
controller.SetPanelVisibility(2u, false);
|
||||
Assert.True(visible["ordinary"]);
|
||||
Assert.False(visible["transient"]);
|
||||
Assert.Equal(1u, controller.ActivePanelId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObservedPersistenceShow_UsesSameExclusiveLifecycle()
|
||||
{
|
||||
var visible = new Dictionary<string, bool>(StringComparer.Ordinal)
|
||||
{
|
||||
["first"] = false,
|
||||
["second"] = false,
|
||||
};
|
||||
var controller = Create(visible);
|
||||
controller.Register(1u, "first");
|
||||
controller.Register(2u, "second");
|
||||
controller.SetPanelVisibility(1u, true);
|
||||
|
||||
visible["second"] = true;
|
||||
controller.ObserveWindowVisibility("second", visible: true);
|
||||
|
||||
Assert.False(visible["first"]);
|
||||
Assert.True(visible["second"]);
|
||||
Assert.Equal(2u, controller.ActivePanelId);
|
||||
}
|
||||
|
||||
private static RetailPanelUiController Create(Dictionary<string, bool> visible)
|
||||
=> new(
|
||||
name => visible[name],
|
||||
name =>
|
||||
{
|
||||
visible[name] = true;
|
||||
return true;
|
||||
},
|
||||
name =>
|
||||
{
|
||||
visible[name] = false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
using AcDream.App.Spells;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class SpellcastingUiControllerTests
|
||||
{
|
||||
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
|
||||
|
||||
[Fact]
|
||||
public void ImportedFixture_BindsAllTabs_AndTracksEquippedEndowment()
|
||||
{
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
var spellbook = new Spellbook();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
|
||||
var used = new List<uint>();
|
||||
using SpellcastingUiController? controller = Bind(
|
||||
layout, spellbook, objects, used.Add);
|
||||
|
||||
Assert.True(controller is not null, DescribeBinding(layout));
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 2u,
|
||||
Name = "Orb",
|
||||
Type = ItemType.Caster,
|
||||
WielderId = 1u,
|
||||
CurrentlyEquippedLocation = EquipMask.Held,
|
||||
SpellId = 2670u,
|
||||
IconId = 0x06001234u,
|
||||
});
|
||||
controller.Tick();
|
||||
|
||||
UiElement host = Assert.IsAssignableFrom<UiElement>(
|
||||
layout.FindElement(SpellcastingUiController.EndowmentId));
|
||||
Assert.True(host.Visible);
|
||||
UiCatalogSlot slot = Assert.IsType<UiCatalogSlot>(host.Children[^1]);
|
||||
Assert.Equal(2u, slot.EntryId);
|
||||
Assert.Equal(2670u, slot.CatalogIconTexture);
|
||||
Assert.Equal(2u, slot.CatalogOverlayTexture);
|
||||
|
||||
slot.OnEvent(new UiEvent(0, slot, UiEventType.Click));
|
||||
var cast = Assert.IsType<UiButton>(
|
||||
layout.FindElement(SpellcastingUiController.CastButtonId));
|
||||
cast.OnEvent(new UiEvent(0, cast, UiEventType.Click));
|
||||
Assert.Equal([2u], used);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FavoriteDrop_IgnoresForeignInventoryPayload()
|
||||
{
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
var spellbook = new Spellbook();
|
||||
spellbook.OnSpellLearned(42u, 1f);
|
||||
spellbook.SetFavorite(0, 0, 42u);
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
|
||||
using SpellcastingUiController? controller = Bind(
|
||||
layout, spellbook, objects, _ => { });
|
||||
|
||||
Assert.True(controller is not null, DescribeBinding(layout));
|
||||
controller.Tick();
|
||||
UiElement group = Assert.IsAssignableFrom<UiElement>(layout.FindElement(0x100000AAu));
|
||||
UiItemList list = Descendants(group).OfType<UiItemList>().First();
|
||||
UiCatalogSlot slot = Assert.IsType<UiCatalogSlot>(list.GetItem(0));
|
||||
var foreign = new ItemDragPayload(9u, ItemDragSource.Inventory, 0, new UiItemSlot(), null);
|
||||
|
||||
bool handled = slot.OnEvent(new UiEvent(
|
||||
0, slot, UiEventType.DropReleased, Payload: foreign));
|
||||
|
||||
Assert.True(handled);
|
||||
Assert.Equal([42u], spellbook.GetFavorites(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnrelatedObjectUpdate_DoesNotRecreateFavoriteSlots()
|
||||
{
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
var spellbook = new Spellbook();
|
||||
spellbook.OnSpellLearned(42u, 1f);
|
||||
spellbook.SetFavorite(0, 0, 42u);
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
|
||||
using SpellcastingUiController controller = Bind(
|
||||
layout, spellbook, objects, _ => { })!;
|
||||
UiElement group = layout.FindElement(0x100000AAu)!;
|
||||
UiItemList list = Descendants(group).OfType<UiItemList>().First();
|
||||
UiItemSlot before = list.GetItem(0)!;
|
||||
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 99u, Name = "Rock" });
|
||||
controller.Tick();
|
||||
|
||||
Assert.Same(before, list.GetItem(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectTableClear_RemovesEquippedEndowmentAndCannotUseStaleGuid()
|
||||
{
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
var spellbook = new Spellbook();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 2u,
|
||||
Name = "Orb",
|
||||
Type = ItemType.Caster,
|
||||
WielderId = 1u,
|
||||
CurrentlyEquippedLocation = EquipMask.Held,
|
||||
SpellId = 2670u,
|
||||
});
|
||||
var used = new List<uint>();
|
||||
using SpellcastingUiController controller = Bind(
|
||||
layout, spellbook, objects, used.Add)!;
|
||||
UiElement host = layout.FindElement(SpellcastingUiController.EndowmentId)!;
|
||||
UiCatalogSlot slot = Assert.IsType<UiCatalogSlot>(host.Children[^1]);
|
||||
slot.OnEvent(new UiEvent(0, slot, UiEventType.Click));
|
||||
|
||||
objects.Clear();
|
||||
controller.Tick();
|
||||
|
||||
Assert.False(host.Visible);
|
||||
Assert.Equal(0u, slot.EntryId);
|
||||
slot.OnEvent(new UiEvent(0, slot, UiEventType.DoubleClick));
|
||||
Assert.Empty(used);
|
||||
}
|
||||
|
||||
private static SpellcastingUiController? Bind(
|
||||
ImportedLayout layout,
|
||||
Spellbook spellbook,
|
||||
ClientObjectTable objects,
|
||||
Action<uint> useItem)
|
||||
{
|
||||
var casting = new SpellCastingController(
|
||||
spellbook, () => null, () => 1u, () => { }, _ => { }, (_, _) => { }, _ => { });
|
||||
return SpellcastingUiController.Bind(
|
||||
layout, spellbook, casting, objects, () => 1u,
|
||||
spellId => spellId,
|
||||
item => item.ObjectId,
|
||||
useItem,
|
||||
new SelectionState(),
|
||||
(_, _, _) => { },
|
||||
(_, _) => { });
|
||||
}
|
||||
|
||||
private static IEnumerable<UiElement> Descendants(UiElement root)
|
||||
{
|
||||
foreach (UiElement child in root.Children)
|
||||
{
|
||||
yield return child;
|
||||
foreach (UiElement nested in Descendants(child)) yield return nested;
|
||||
}
|
||||
}
|
||||
|
||||
private static string DescribeBinding(ImportedLayout layout)
|
||||
{
|
||||
uint[] ids =
|
||||
[
|
||||
SpellcastingUiController.CastButtonId, SpellcastingUiController.EndowmentId,
|
||||
0x100000A3u, 0x100000AAu, 0x100005C2u, 0x100005C3u,
|
||||
];
|
||||
return string.Join(", ", ids.Select(id =>
|
||||
$"{id:X8}={layout.FindElement(id)?.GetType().Name ?? "missing"}"));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1097,7 +1097,7 @@
|
|||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741825,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
@ -3320,7 +3320,7 @@
|
|||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741824,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 2,
|
||||
"FontColor": {
|
||||
"X": 0.8,
|
||||
|
|
@ -6248,7 +6248,7 @@
|
|||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741824,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
|
|||
|
|
@ -2035,7 +2035,7 @@
|
|||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741825,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
@ -19338,7 +19338,7 @@
|
|||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741826,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
@ -20278,7 +20278,7 @@
|
|||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741826,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
@ -20859,7 +20859,7 @@
|
|||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741826,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
@ -21469,7 +21469,7 @@
|
|||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741826,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
|
|||
|
|
@ -287,6 +287,382 @@
|
|||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": [
|
||||
{
|
||||
"Id": 268435977,
|
||||
"Type": 3,
|
||||
"X": 0,
|
||||
"Y": 13,
|
||||
"Width": 4,
|
||||
"Height": 53,
|
||||
"OriginalParentWidth": 78,
|
||||
"OriginalParentHeight": 79,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 1,
|
||||
"Top": 1,
|
||||
"Right": 2,
|
||||
"Bottom": 1,
|
||||
"ReadOrder": 6,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100687166,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100687166,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
},
|
||||
{
|
||||
"Id": 268435978,
|
||||
"Type": 3,
|
||||
"X": 74,
|
||||
"Y": 13,
|
||||
"Width": 4,
|
||||
"Height": 53,
|
||||
"OriginalParentWidth": 78,
|
||||
"OriginalParentHeight": 79,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 2,
|
||||
"Top": 1,
|
||||
"Right": 1,
|
||||
"Bottom": 1,
|
||||
"ReadOrder": 7,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100687166,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100687166,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
},
|
||||
{
|
||||
"Id": 268435979,
|
||||
"Type": 3,
|
||||
"X": 13,
|
||||
"Y": 73,
|
||||
"Width": 52,
|
||||
"Height": 6,
|
||||
"OriginalParentWidth": 78,
|
||||
"OriginalParentHeight": 79,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 1,
|
||||
"Top": 2,
|
||||
"Right": 1,
|
||||
"Bottom": 1,
|
||||
"ReadOrder": 8,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100687165,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100687165,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
},
|
||||
{
|
||||
"Id": 268436145,
|
||||
"Type": 3,
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Width": 13,
|
||||
"Height": 13,
|
||||
"OriginalParentWidth": 78,
|
||||
"OriginalParentHeight": 79,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 1,
|
||||
"Top": 1,
|
||||
"Right": 2,
|
||||
"Bottom": 2,
|
||||
"ReadOrder": 1,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100687161,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100687161,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
},
|
||||
{
|
||||
"Id": 268436146,
|
||||
"Type": 3,
|
||||
"X": 65,
|
||||
"Y": 0,
|
||||
"Width": 13,
|
||||
"Height": 13,
|
||||
"OriginalParentWidth": 78,
|
||||
"OriginalParentHeight": 79,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 2,
|
||||
"Top": 1,
|
||||
"Right": 1,
|
||||
"Bottom": 2,
|
||||
"ReadOrder": 2,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100687162,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100687162,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
},
|
||||
{
|
||||
"Id": 268436147,
|
||||
"Type": 3,
|
||||
"X": 0,
|
||||
"Y": 65,
|
||||
"Width": 13,
|
||||
"Height": 14,
|
||||
"OriginalParentWidth": 78,
|
||||
"OriginalParentHeight": 79,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 1,
|
||||
"Top": 2,
|
||||
"Right": 2,
|
||||
"Bottom": 1,
|
||||
"ReadOrder": 3,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100687163,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100687163,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
},
|
||||
{
|
||||
"Id": 268436148,
|
||||
"Type": 3,
|
||||
"X": 65,
|
||||
"Y": 65,
|
||||
"Width": 13,
|
||||
"Height": 14,
|
||||
"OriginalParentWidth": 78,
|
||||
"OriginalParentHeight": 79,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 2,
|
||||
"Top": 2,
|
||||
"Right": 1,
|
||||
"Bottom": 1,
|
||||
"ReadOrder": 4,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100687164,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100687164,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
},
|
||||
{
|
||||
"Id": 268436149,
|
||||
"Type": 3,
|
||||
"X": 13,
|
||||
"Y": 0,
|
||||
"Width": 52,
|
||||
"Height": 6,
|
||||
"OriginalParentWidth": 78,
|
||||
"OriginalParentHeight": 79,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 1,
|
||||
"Top": 1,
|
||||
"Right": 1,
|
||||
"Bottom": 2,
|
||||
"ReadOrder": 5,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100687165,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100687165,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
},
|
||||
{
|
||||
"Id": 268436271,
|
||||
"Type": 3,
|
||||
|
|
@ -301,7 +677,7 @@
|
|||
"Top": 2,
|
||||
"Right": 3,
|
||||
"Bottom": 1,
|
||||
"ReadOrder": 2,
|
||||
"ReadOrder": 10,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
|
|
@ -2069,7 +2445,7 @@
|
|||
"Top": 1,
|
||||
"Right": 1,
|
||||
"Bottom": 1,
|
||||
"ReadOrder": 1,
|
||||
"ReadOrder": 9,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2719
tests/AcDream.App.Tests/UI/Layout/fixtures/indicators_21000071.json
Normal file
2719
tests/AcDream.App.Tests/UI/Layout/fixtures/indicators_21000071.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -8940,7 +8940,7 @@
|
|||
},
|
||||
"DefaultStateId": 1,
|
||||
"FontDid": 1073741826,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
|
|||
|
|
@ -8151,7 +8151,7 @@
|
|||
},
|
||||
"DefaultStateId": 1,
|
||||
"FontDid": 1073741826,
|
||||
"HJustify": 1,
|
||||
"HJustify": 0,
|
||||
"VJustify": 1,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue