feat(render): Campaign V slice V4a - port TextRenderer/BitmapFont/DebugLineRenderer/TextureCache onto IGpuDevice

TextRenderer, BitmapFont, DebugLineRenderer, and TextureCache's UI-texture
upload path (GetOrUploadRenderSurface/UploadRgba8) now issue every draw and
resource creation through the pinned IGpuDevice/IGpuFrame/IGpuPassEncoder
RHI contract instead of raw GL. This is the RHI's first real production
consumer - V0-V3 only established the contract, GL backend skeleton, and a
shader-dialect migration with no live GL exercise. TextRenderer owns one
IGpuPipeline (ui_text shader, straight-alpha blend, depth disabled) and
allocates a per-bucket ring each Flush; BitmapFont's atlas texture is
created and uploaded via device.CreateTexture/.Upload; DebugLineRenderer
mirrors the same one-pipeline-per-Flush shape for its line-list draws.
World-path TextureCache methods (GetOrUpload, the raw-GL layer-array
upload) are untouched - still legacy GL, still out of scope.

Frame lifecycle: GpuDeviceFrameLifetime (RenderFrameOrchestrator.cs) wraps
IGpuDevice.BeginFrame()/IGpuFrame.End() inside the existing
IRenderFrameLifetime bracket HostInputCameraCompositionPhase already opens
per callback, additively - no frame-graph restructuring. Ported renderers
reach the frame via ICurrentGpuFrameSource, a plain interface (not a
delegate field) so WorldSceneDiagnosticsController keeps passing its
existing "no stored window/delegate" architectural-conformance test.

Two real bugs surfaced by actually exercising the RHI against a live GL
context (nothing here was previously reachable before this slice):

- GlGpuDevice.BeginFrame() now resets the render-state cache every frame.
  The cache assumes it is the sole writer of GL program/blend/depth/cull
  state, which was true while it had zero real consumers, but every
  still-legacy renderer (WbDrawDispatcher, terrain, particles, EnvCells)
  mutates that same GL state directly and never informs the cache. Once a
  legacy renderer ran between two RHI binds, the cache's belief about the
  current GL program went stale, so a later BindPipeline(text shader)
  skipped re-issuing glUseProgram and the following push-constant upload
  threw GL_INVALID_OPERATION against whatever program was actually bound.
  Reset() at the frame boundary is the same defensive move BeginPass
  already makes after a forced clear (see its comment); it costs one
  redundant state application on the frame's first bind.
- GL_MULTISAMPLE has no representation in the pinned contract. Added a
  GL-backend-internal Multisample field to GlRenderStateSnapshot/Changes,
  computed from GpuPipelineDescription.SampleCount at BindPipeline time -
  mirrors how Vulkan bakes MSAA into the pipeline instead of a separate
  toggle.

Collateral, scoped to keep the port real rather than a stub:

- GpuTextureSlot (Unassigned = uint.MaxValue, NOT 0) now flows through
  every consumer of TextureCache.GetOrUploadRenderSurface/UploadRgba8 and
  TextRenderer.DrawSprite - the entire retained UI layer, since a pervasive
  Func<uint,(uint,int,int)> sprite-resolve delegate threads through nearly
  every UI element/controller. Every prior `== 0` / `!= 0` "no texture"
  check became `.IsAssigned` / `!.IsAssigned`; slot 0 is a real assigned
  slot (the device's default white texture), so the old sentinel would
  have produced live visual regressions if left in place.
- GpuTextureSlot/IGpuDevice/IGpuFrame are internal, so ~270 previously
  public AcDream.App types that touched them (directly or transitively)
  are now internal too - safe, since AcDream.App is an exe with no
  external project references; only the two test projects consume it, via
  InternalsVisibleTo. A handful of unrelated types the sweep caught
  (ElementInfo/ImportedLayout's property-bag hierarchy, several enums used
  as public [Theory] parameters, CursorFeedbackSnapshot's DragAcceptState)
  were reverted back to public where making them internal would have
  either cascaded into unrelated files or broken xUnit's public-member
  discovery.
- ExternalViewportTextureBridge (new) registers the still-raw-GL FBO
  color textures PrivateEntityViewportRenderer/PaperdollViewportRenderer
  produce (V4g's scope) into the device's texture table for
  UiViewport.TextureHandle, via a temporary
  GlGpuDevice.RegisterExternalColorTexture escape hatch (internal, not
  part of IGpuDevice) deleted when V4g ports those viewports.
- TextRenderGlStateScope.cs and its test deleted: the pipeline description
  now bakes what it used to restore by hand.
- ResourceCleanupGroupTests/GlTextureOwnershipTests: the two source-text
  conformance tests keyed to TextRenderer's old multi-resource
  construction shape (Shader + per-flight FrameBufferSet array + white
  texture + tracked VAO/VBO, all via ResourceCleanupGroup) no longer apply
  - that shape is gone, replaced by one IGpuPipeline created through
    IGpuDevice. The construction-order test is deleted; the checked-commit
    texture-creation check now targets GlGpuTexture (which already used
    the same GlResourceCommand.CreateName primitive before this slice).

Gates:
- dotnet build -c Release: 0 warnings, 0 errors (AcDream.App has
  TreatWarningsAsErrors).
- dotnet test tests/AcDream.App.Tests -c Release: 3,840 passed / 3
  skipped (was 3,843/3 entering this slice - net 3 fewer tests:
  TextRendererFailureSafetyTests.cs deleted (2, tested the now-deleted
  TextRenderGlStateScope) plus the one retired ResourceCleanupGroupTests
  method). Full solution: 8,908 passed / 5 skipped across all nine test
  projects.
- Offline pixel gate (tools/run-offline-pixel-gate.ps1, parent ec414d60
  vs this commit): differing fraction 0.318% (1,791/563,200 compared
  pixels), above the 0.001 threshold. Investigated pixel-by-pixel rather
  than waved through: a diff heatmap plus 4x crops at the differing
  clusters show zero differences anywhere in the retained UI, terrain,
  scenery, or static meshes - every differing pixel sits on continuously-
  animated ambient content (flying-insect sprites over the swamp, foliage
  sparkle/dew glints) whose exact phase depends on elapsed wall-clock
  time, the same category the gate's own sky-masking rationale already
  documents and the campaign doc's coverage table explicitly excludes
  ("Not covered - particles"). Confirming evidence: two same-commit
  captures at HEAD compare clean against each other (0.0025%), and two
  same-commit captures at the parent compare clean against each other
  (0.0044%) - only base-vs-head is consistently elevated, which is what
  frame-pacing drift from genuinely new per-frame RHI work (BeginFrame,
  ring resets, the render-state reset above) would produce against a
  fixed wall-clock capture deadline, not a rendering defect. Recommend a
  quick user visual check of this capture pair alongside the automated
  result, matching how V2c's particle work was already handled in this
  campaign (flagged for user visual confirmation rather than blocked on
  an automated gate that cannot cover animated content).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-27 18:22:08 +02:00
parent ec414d60cd
commit ceec3bc440
334 changed files with 3660 additions and 3840 deletions

View file

@ -159,6 +159,7 @@ public sealed class HostInputCameraCompositionTests
{
public GpuFrameFlightController? GpuFrames { get; private set; }
public IGpuDevice? GpuDevice { get; private set; }
public GpuDeviceFrameLifetime? GpuFrameLifetime { get; private set; }
public SilkKeyboardSource? Keyboard { get; private set; }
public SilkMouseSource? Mouse { get; private set; }
public IMouseLookCursor? Cursor { get; private set; }
@ -172,6 +173,9 @@ public sealed class HostInputCameraCompositionTests
public void PublishGpuDevice(IGpuDevice value) =>
GpuDevice = PublishOnce(GpuDevice, value);
public void PublishGpuFrameLifetime(GpuDeviceFrameLifetime value) =>
GpuFrameLifetime = PublishOnce(GpuFrameLifetime, value);
public void PublishKeyboardSource(SilkKeyboardSource value) =>
Keyboard = PublishOnce(Keyboard, value);

View file

@ -189,9 +189,10 @@ public sealed class InteractionRetainedUiCompositionTests
Dependencies = new InteractionRetainedUiDependencies(
Options: options,
Gl: null!,
GpuDevice: null!,
CurrentGpuFrame: null!,
Window: null!,
Input: null!,
ShadersDirectory: "shaders",
Dats: null!,
DatLock: new object(),
TextureCache: null!,

View file

@ -218,6 +218,7 @@ public sealed class SettingsDevToolsCompositionTests
null!,
null!,
null!,
null!,
null,
null,
null,

View file

@ -2,8 +2,10 @@ using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using AcDream.App.Composition;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Gpu;
using AcDream.App.Rendering.Wb;
using AcDream.App.Rendering.Residency;
using AcDream.App.Tests.Rendering.Gpu;
using AcDream.App.World;
using AcDream.Content;
using AcDream.Core.Physics;
@ -159,6 +161,7 @@ public sealed class WorldRenderCompositionTests
Factory = new Factory(hasFont);
Publication = new Publication(publicationFailure);
Lifetime = new RenderLifetime(Factory.Atlas);
GpuDevice = new RecordingGpuDevice();
var content = (ContentEffectsAudioResult)
RuntimeHelpers.GetUninitializedObject(typeof(ContentEffectsAudioResult));
Content = content;
@ -167,6 +170,7 @@ public sealed class WorldRenderCompositionTests
public Factory Factory { get; }
public Publication Publication { get; }
public RenderLifetime Lifetime { get; }
public RecordingGpuDevice GpuDevice { get; }
public List<WorldRenderCompositionPoint> Points { get; } = [];
public ContentEffectsAudioResult Content { get; }
@ -176,6 +180,7 @@ public sealed class WorldRenderCompositionTests
new WorldEnvironmentController(),
Lifetime,
ImmediateGpuResourceRetirementQueue.Instance,
GpuDevice,
_budgets,
0xA9B4FFFFu,
Path.Combine(Path.GetTempPath(), "acdream-tests"),
@ -248,15 +253,15 @@ public sealed class WorldRenderCompositionTests
public SceneLightingUboBinding CreateSceneLighting(GL gl) =>
Resource<SceneLightingUboBinding>("scene lighting");
public DebugLineRenderer CreateDebugLines(GL gl, string shadersDirectory) =>
public DebugLineRenderer CreateDebugLines(IGpuDevice device) =>
Resource<DebugLineRenderer>("debug lines");
public byte[]? TryLoadDebugFont() => hasFont ? [1] : null;
public BitmapFont CreateDebugFont(GL gl, byte[] bytes) =>
public BitmapFont CreateDebugFont(IGpuDevice device, byte[] bytes) =>
Resource<BitmapFont>("debug font");
public TextRenderer CreateTextRenderer(GL gl, string shadersDirectory) =>
public TextRenderer CreateTextRenderer(IGpuDevice device) =>
Resource<TextRenderer>("text renderer");
public TerrainModernRenderer CreateTerrain(
@ -295,6 +300,7 @@ public sealed class WorldRenderCompositionTests
public TextureCache CreateTextureCache(
GL gl,
IGpuDevice device,
IDatReaderWriter dats,
BindlessSupport bindless,
IGpuResourceRetirementQueue retirement,

View file

@ -2,3 +2,4 @@ global using AcDream.Runtime.Gameplay;
global using AcDream.Runtime.Physics;
global using ILocalPlayerMotionSource =
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerMotionSource;
global using AcDream.App.Rendering.Gpu;

View file

@ -104,8 +104,13 @@ public sealed class GlTextureOwnershipTests
root, "src", "AcDream.App", "Rendering", "ShaderProgramConstruction.cs"));
string terrain = File.ReadAllText(Path.Combine(
root, "src", "AcDream.App", "Rendering", "TerrainAtlas.cs"));
string text = File.ReadAllText(Path.Combine(
root, "src", "AcDream.App", "Rendering", "TextRenderer.cs"));
// Campaign V slice V4a ported TextRenderer/BitmapFont off raw GL onto
// IGpuDevice: neither file creates a GL texture name directly anymore.
// The checked-commit-boundary invariant this test enforces now lives in
// the RHI's GlGpuTexture, which every ported (and legacy) texture path
// shares.
string gpuTexture = File.ReadAllText(Path.Combine(
root, "src", "AcDream.App", "Rendering", "Gpu", "Gl", "GlGpuTexture.cs"));
string bindless = File.ReadAllText(Path.Combine(
root, "src", "AcDream.App", "Rendering", "Wb", "BindlessSupport.cs"));
@ -116,8 +121,8 @@ public sealed class GlTextureOwnershipTests
Assert.Contains("GlResourceCommand.DeleteProgram", shaderPrograms, StringComparison.Ordinal);
Assert.Contains("_anisotropyBindingMutation.Execute", terrain, StringComparison.Ordinal);
Assert.Contains("GlResourceCommand.DeleteTexture", terrain, StringComparison.Ordinal);
Assert.Contains("GlResourceCommand.CreateTexture", text, StringComparison.Ordinal);
Assert.Contains("GlResourceCommand.Execute", text, StringComparison.Ordinal);
Assert.Contains("GlResourceCommand.CreateName", gpuTexture, StringComparison.Ordinal);
Assert.Contains("GLHelpers.ThrowOnResourceError", gpuTexture, StringComparison.Ordinal);
Assert.Contains("make bindless handle", bindless, StringComparison.Ordinal);
Assert.Contains("GlResourceCommand.Execute", bindless, StringComparison.Ordinal);
}

View file

@ -14,7 +14,8 @@ public sealed class GlRenderStateCacheTests
GpuCullMode.Back,
GpuFrontFace.CounterClockwise,
AlphaToCoverage: false,
ColorWrite: true);
ColorWrite: true,
Multisample: false);
[Fact]
public void FirstApplyReportsEveryDimensionChanged()

View file

@ -49,6 +49,12 @@ public sealed class RenderFrameResourceControllerTests
{
string source = ResourceSource();
// TextRenderer (_worldText/_uiText) moved onto the IGpuDevice/IGpuFrame
// RHI in Campaign V slice V4a: it no longer has a per-slot BeginFrame
// step, so those two needles are retired from this ordering check.
// The remaining GL-only renderers (WbDrawDispatcher, EnvCellRenderer,
// terrain, sky/lighting) are untouched by V4a and keep their slot-based
// BeginFrame(gpuSlot) order.
AssertAppearsInOrder(
source,
"_textures?.BeginCompositeTextureFrame();",
@ -56,8 +62,6 @@ public sealed class RenderFrameResourceControllerTests
"_dispatcher?.BeginFrame(gpuSlot);",
"_environmentCells?.BeginFrame(gpuSlot);",
"_portalDepth?.BeginFrame(gpuSlot);",
"_worldText?.BeginFrame(gpuSlot);",
"_uiText?.BeginFrame(gpuSlot);",
"_clip?.BeginFrame(gpuSlot);",
"_terrain?.BeginFrame(gpuSlot);",
"_lighting?.BeginFrame(gpuSlot);");

View file

@ -130,40 +130,13 @@ public sealed class ResourceCleanupGroupTests
StringComparison.Ordinal);
}
[Fact]
public void TextRendererPublishesEveryConstructorResourceBeforeLaterGlWork()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"TextRenderer.cs"));
AssertAppearsInOrder(
source,
"shader = new Shader(gl,",
"resources.Add(\"text shader\", shader.Dispose);",
"frameBuffers[i] = CreateFrameBufferSet(resources);",
"whiteTexture = GlResourceCommand.CreateTexture(",
"resources.Add(",
"\"white texture\"",
"GlResourceCommand.Execute(",
"initialize TextRenderer white texture",
"resources.RetryCleanup();",
"_resources = resources;");
AssertAppearsInOrder(
source,
"private FrameBufferSet CreateFrameBufferSet(ResourceCleanupGroup resources)",
"TrackedGlResource.CreateVertexArray(",
"resources.Add(\"frame VAO\", vaoRelease.Run);",
"TrackedGlResource.CreateBuffer(",
"resources.Add(",
"\"frame VBO\"");
Assert.Contains("_resources.RetryCleanup();", source, StringComparison.Ordinal);
Assert.DoesNotContain("private FrameBufferSet CreateFrameBufferSet()", source,
StringComparison.Ordinal);
}
// TextRendererPublishesEveryConstructorResourceBeforeLaterGlWork was retired
// by Campaign V slice V4a: TextRenderer no longer owns a Shader, per-flight
// FrameBufferSet array, white-texture fallback, or tracked VAO/VBO pair —
// it now borrows a shared IGpuDevice and owns exactly one IGpuPipeline
// created through it, so there is no multi-resource construction order left
// to assert. The equivalent GL-only invariant now lives in GlTextureOwnershipTests
// (GlGpuTexture's checked-commit texture creation).
[Fact]
public void ReturnedNameIsRetainedWhenPostconditionAndFirstRollbackFail()

View file

@ -1,217 +0,0 @@
using System.Reflection;
using AcDream.App.Rendering;
using Silk.NET.OpenGL;
namespace AcDream.App.Tests.Rendering;
public sealed class TextRendererFailureSafetyTests
{
[Fact]
public void Flush_CompilesCompleteGlStateScopeAsFinallyAroundBothDrawLayers()
{
MethodInfo flush = typeof(TextRenderer).GetMethod(nameof(TextRenderer.Flush))!;
MethodBody body = flush.GetMethodBody()!;
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"TextRenderer.cs"));
Assert.Contains(
body.ExceptionHandlingClauses,
clause => clause.Flags == ExceptionHandlingClauseOptions.Finally);
AssertAppearsInOrder(
source,
"using var stateScope = new TextRenderGlStateScope(_glState);",
"_gl.Disable(EnableCap.Multisample);",
"DrawLayer(_spriteSegs,",
"DrawLayer(_overlaySpriteSegs,");
}
[Fact]
public void FailedDraw_RestoresEveryGlValueMutatedByTheTextPass()
{
var gl = new RecordingGlState
{
DepthWrite = false,
BlendSourceRgb = BlendingFactor.DstAlpha,
BlendDestinationRgb = BlendingFactor.OneMinusDstAlpha,
BlendSourceAlpha = BlendingFactor.One,
BlendDestinationAlpha = BlendingFactor.Zero,
Program = 17,
VertexArray = 23,
ArrayBuffer = 31,
ActiveUnit = TextureUnit.Texture2,
};
gl.SetCapability(EnableCap.DepthTest, enabled: true);
gl.SetCapability(EnableCap.Blend, enabled: false);
gl.SetCapability(EnableCap.CullFace, enabled: true);
gl.SetCapability(EnableCap.SampleAlphaToCoverage, enabled: true);
gl.SetCapability(EnableCap.Multisample, enabled: false);
gl.TextureBindings[TextureUnit.Texture0] = 41;
gl.TextureBindings[TextureUnit.Texture2] = 43;
StateSnapshot expected = gl.Capture();
Action failedDraw = () =>
{
using var stateScope = new TextRenderGlStateScope(gl);
gl.SetCapability(EnableCap.DepthTest, enabled: false);
gl.SetCapability(EnableCap.Blend, enabled: true);
gl.SetCapability(EnableCap.CullFace, enabled: false);
gl.SetCapability(EnableCap.SampleAlphaToCoverage, enabled: false);
gl.SetCapability(EnableCap.Multisample, enabled: true);
gl.DepthMask(true);
gl.BlendFuncSeparate(
BlendingFactor.SrcAlpha,
BlendingFactor.OneMinusSrcAlpha,
BlendingFactor.SrcAlpha,
BlendingFactor.OneMinusSrcAlpha);
gl.UseProgram(101);
gl.BindVertexArray(103);
gl.BindBuffer(BufferTargetARB.ArrayBuffer, 107);
gl.ActiveTexture(TextureUnit.Texture0);
gl.BindTexture(TextureTarget.Texture2D, 109);
throw new InvalidOperationException("draw upload");
};
Assert.Throws<InvalidOperationException>(failedDraw);
Assert.Equal(expected, gl.Capture());
}
private readonly record struct StateSnapshot(
bool DepthTest,
bool Blend,
bool Cull,
bool AlphaToCoverage,
bool Multisample,
bool DepthWrite,
BlendingFactor BlendSourceRgb,
BlendingFactor BlendDestinationRgb,
BlendingFactor BlendSourceAlpha,
BlendingFactor BlendDestinationAlpha,
uint Program,
uint VertexArray,
uint ArrayBuffer,
TextureUnit ActiveUnit,
uint Texture0,
uint Texture2);
private sealed class RecordingGlState : ITextRenderGlStateApi
{
private readonly Dictionary<EnableCap, bool> _capabilities = [];
public bool DepthWrite { get; set; }
public BlendingFactor BlendSourceRgb { get; set; }
public BlendingFactor BlendDestinationRgb { get; set; }
public BlendingFactor BlendSourceAlpha { get; set; }
public BlendingFactor BlendDestinationAlpha { get; set; }
public uint Program { get; set; }
public uint VertexArray { get; set; }
public uint ArrayBuffer { get; set; }
public TextureUnit ActiveUnit { get; set; }
public Dictionary<TextureUnit, uint> TextureBindings { get; } = [];
public StateSnapshot Capture() => new(
IsEnabled(EnableCap.DepthTest),
IsEnabled(EnableCap.Blend),
IsEnabled(EnableCap.CullFace),
IsEnabled(EnableCap.SampleAlphaToCoverage),
IsEnabled(EnableCap.Multisample),
DepthWrite,
BlendSourceRgb,
BlendDestinationRgb,
BlendSourceAlpha,
BlendDestinationAlpha,
Program,
VertexArray,
ArrayBuffer,
ActiveUnit,
TextureBindings.GetValueOrDefault(TextureUnit.Texture0),
TextureBindings.GetValueOrDefault(TextureUnit.Texture2));
public bool IsEnabled(EnableCap capability) =>
_capabilities.GetValueOrDefault(capability);
public int GetInteger(GetPName parameter) => parameter switch
{
GetPName.BlendSrcRgb => (int)BlendSourceRgb,
GetPName.BlendDstRgb => (int)BlendDestinationRgb,
GetPName.BlendSrcAlpha => (int)BlendSourceAlpha,
GetPName.BlendDstAlpha => (int)BlendDestinationAlpha,
GetPName.CurrentProgram => (int)Program,
GetPName.VertexArrayBinding => (int)VertexArray,
GetPName.ArrayBufferBinding => (int)ArrayBuffer,
GetPName.ActiveTexture => (int)ActiveUnit,
GetPName.TextureBinding2D =>
(int)TextureBindings.GetValueOrDefault(ActiveUnit),
_ => throw new ArgumentOutOfRangeException(nameof(parameter)),
};
public bool GetBoolean(GetPName parameter) =>
parameter == GetPName.DepthWritemask
? DepthWrite
: throw new ArgumentOutOfRangeException(nameof(parameter));
public void SetCapability(EnableCap capability, bool enabled) =>
_capabilities[capability] = enabled;
public void DepthMask(bool enabled) => DepthWrite = enabled;
public void BlendFuncSeparate(
BlendingFactor sourceRgb,
BlendingFactor destinationRgb,
BlendingFactor sourceAlpha,
BlendingFactor destinationAlpha)
{
BlendSourceRgb = sourceRgb;
BlendDestinationRgb = destinationRgb;
BlendSourceAlpha = sourceAlpha;
BlendDestinationAlpha = destinationAlpha;
}
public void UseProgram(uint program) => Program = program;
public void BindVertexArray(uint vertexArray) => VertexArray = vertexArray;
public void BindBuffer(BufferTargetARB target, uint buffer)
{
Assert.Equal(BufferTargetARB.ArrayBuffer, target);
ArrayBuffer = buffer;
}
public void ActiveTexture(TextureUnit unit) => ActiveUnit = unit;
public void BindTexture(TextureTarget target, uint texture)
{
Assert.Equal(TextureTarget.Texture2D, target);
TextureBindings[ActiveUnit] = texture;
}
}
private static void AssertAppearsInOrder(string source, params string[] needles)
{
int cursor = -1;
foreach (string needle in needles)
{
int next = source.IndexOf(needle, cursor + 1, StringComparison.Ordinal);
Assert.True(next >= 0, $"Missing expected source fragment: {needle}");
Assert.True(next > cursor, $"Out-of-order source fragment: {needle}");
cursor = next;
}
}
private static string FindRepoRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
return directory.FullName;
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
}
}

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.Studio;
using AcDream.App.UI;
@ -31,7 +32,7 @@ public class DumpLayoutTests
"docs", "research", "2026-06-25-retail-ui-layout-dump.json");
}
private static (uint, int, int) NoTex(uint _) => (1u, 1, 1);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (new GpuTextureSlot(1), 1, 1);
// ── Helpers ──────────────────────────────────────────────────────────

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.Studio;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -12,7 +13,7 @@ namespace AcDream.App.Tests.Studio;
/// </summary>
public class LayoutSourceTests
{
private static (uint handle, int width, int height) NoTex(uint _) => (1u, 1, 1);
private static (GpuTextureSlot handle, int width, int height) NoTex(uint _) => (new GpuTextureSlot(1u), 1, 1);
/// <summary>Resolve the client dat directory, or null if unavailable (skip the test).</summary>
private static string? ResolveDatDir()

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.Core.Combat;
using AcDream.Core.Items;
@ -20,11 +21,11 @@ public sealed class CursorFeedbackControllerTests
var accept = c.Resolve(new CursorFeedbackSnapshot(
DragPayload: new object(),
DragAccept: UiItemSlot.DragAcceptState.Accept,
DragAccept: DragAcceptState.Accept,
HoverResizeEdges: ResizeEdges.Right));
var reject = c.Resolve(new CursorFeedbackSnapshot(
DragPayload: new object(),
DragAccept: UiItemSlot.DragAcceptState.Reject,
DragAccept: DragAcceptState.Reject,
HoverResizeEdges: ResizeEdges.Right));
Assert.Equal(CursorFeedbackKind.DragAccept, accept.Kind);
@ -211,7 +212,7 @@ public sealed class CursorFeedbackControllerTests
{
["Drag_rollover_accept"] = acceptCursor,
});
slot.SetItem(Target, iconTexture: 1u);
slot.SetItem(Target, iconTexture: new GpuTextureSlot(1u));
root.AddChild(slot);
root.OnMouseMove(20, 20);
var c = new CursorFeedbackController(interaction);

View file

@ -1,10 +1,11 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI;
public class DatWidgetFactoryZOrderTests
{
private static (uint, int, int) NoChrome(uint id) => (0u, 0, 0);
private static (GpuTextureSlot, int, int) NoChrome(uint id) => (GpuTextureSlot.Unassigned, 0, 0);
[Fact]
public void ZOrder_HigherZLevel_DrawsBehind()

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using Xunit;
@ -40,7 +41,7 @@ public class DragDropSpineTests
[Fact]
public void UiItemList_registerDragHandler_roundtrips()
{
var list = new UiItemList(_ => (0u, 0, 0));
var list = new UiItemList(_ => (GpuTextureSlot.Unassigned, 0, 0));
Assert.Null(list.DragHandler);
var h = new SpyHandler();
list.RegisterDragHandler(h);
@ -56,7 +57,7 @@ public class DragDropSpineTests
public void GetDragPayload_boundCell_snapshotsFields()
{
var cell = new UiItemSlot { SlotIndex = 4, SourceKind = ItemDragSource.ShortcutBar };
cell.SetItem(0x5001u, 0x99u);
cell.SetItem(0x5001u, new GpuTextureSlot(0x99u));
var p = Assert.IsType<ItemDragPayload>(cell.GetDragPayload());
Assert.Equal(0x5001u, p.ObjId);
Assert.Equal(ItemDragSource.ShortcutBar, p.SourceKind);
@ -72,10 +73,10 @@ public class DragDropSpineTests
public void GetDragGhost_boundCell_returnsIconTuple()
{
var cell = new UiItemSlot { Width = 32, Height = 32 };
cell.SetItem(0x5001u, 0x99u);
cell.SetItem(0x5001u, new GpuTextureSlot(0x99u));
var g = cell.GetDragGhost();
Assert.NotNull(g);
Assert.Equal(0x99u, g!.Value.tex);
Assert.Equal(new GpuTextureSlot(0x99u), g!.Value.tex);
Assert.Equal(32, g.Value.w);
Assert.Equal(32, g.Value.h);
}
@ -84,16 +85,16 @@ public class DragDropSpineTests
public void GetDragGhost_prefersDedicatedUnderlayFreeTexture()
{
var cell = new UiItemSlot { Width = 36, Height = 36 }; // retail bag cell is larger than its icon
cell.SetItem(0x5001u, 0x99u, dragIconTexture: 0x77u);
cell.SetItem(0x5001u, new GpuTextureSlot(0x99u), dragIconTexture: new GpuTextureSlot(0x77u));
Assert.Equal((0x77u, 32, 32), cell.GetDragGhost());
Assert.Equal(0x99u, cell.IconTexture); // source cell keeps the full m_pIcon
Assert.Equal((new GpuTextureSlot(0x77u), 32, 32), cell.GetDragGhost());
Assert.Equal(new GpuTextureSlot(0x99u), cell.IconTexture); // source cell keeps the full m_pIcon
}
// ── cell drop-target: DragEnter overlay + DropReleased dispatch ──────────
private static (UiItemList list, UiItemSlot cell, SpyHandler h) ListWithHandler()
{
var list = new UiItemList(_ => (1u, 1, 1)); // non-zero resolve so overlay draw is harmless
var list = new UiItemList(_ => (new GpuTextureSlot(1u), 1, 1)); // non-zero resolve so overlay draw is harmless
var h = new SpyHandler();
list.RegisterDragHandler(h);
return (list, list.Cell, h);
@ -108,7 +109,7 @@ public class DragDropSpineTests
var (_, cell, h) = ListWithHandler();
h.Acceptance = ItemDragAcceptance.Accept;
cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragEnter, Payload: SomePayload()));
Assert.Equal(UiItemSlot.DragAcceptState.Accept, cell.DragAcceptVisual);
Assert.Equal(DragAcceptState.Accept, cell.DragAcceptVisual);
}
[Fact]
@ -117,7 +118,7 @@ public class DragDropSpineTests
var (_, cell, h) = ListWithHandler();
h.Acceptance = ItemDragAcceptance.Reject;
cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragEnter, Payload: SomePayload()));
Assert.Equal(UiItemSlot.DragAcceptState.Reject, cell.DragAcceptVisual);
Assert.Equal(DragAcceptState.Reject, cell.DragAcceptVisual);
}
[Fact]
@ -126,7 +127,7 @@ public class DragDropSpineTests
var (_, cell, h) = ListWithHandler();
h.Acceptance = ItemDragAcceptance.None;
cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragEnter, Payload: SomePayload()));
Assert.Equal(UiItemSlot.DragAcceptState.None, cell.DragAcceptVisual);
Assert.Equal(DragAcceptState.None, cell.DragAcceptVisual);
}
[Fact]
@ -135,7 +136,7 @@ public class DragDropSpineTests
var (_, cell, h) = ListWithHandler();
cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragEnter, Payload: SomePayload()));
cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragOver, Payload: SomePayload()));
Assert.Equal(UiItemSlot.DragAcceptState.None, cell.DragAcceptVisual);
Assert.Equal(DragAcceptState.None, cell.DragAcceptVisual);
}
[Fact]
@ -179,16 +180,16 @@ public class DragDropSpineTests
root.OnMouseDown(UiMouseButton.Left, 10, 10);
root.OnMouseMove(20, 10); // BeginDrag → snapshot ghost
cell.Clear(); // simulate the lift emptying the source
Assert.Equal((0x99u, 32, 32), root.DragGhostForTest);
Assert.Equal((new GpuTextureSlot(0x99u), 32, 32), root.DragGhostForTest);
}
[Fact]
public void FinishDrag_overNothing_deliversNoDrop_butLiftStands()
{
var root = new UiRoot { Width = 800, Height = 600 };
var list = new UiItemList(_ => (1u, 1, 1)) { Left = 0, Top = 0, Width = 32, Height = 32 };
var list = new UiItemList(_ => (new GpuTextureSlot(1u), 1, 1)) { Left = 0, Top = 0, Width = 32, Height = 32 };
list.Cell.Width = 32; list.Cell.Height = 32;
list.Cell.SetItem(0x5001u, 0x99u);
list.Cell.SetItem(0x5001u, new GpuTextureSlot(0x99u));
var h = new SpyHandler();
list.RegisterDragHandler(h);
root.AddChild(list);
@ -237,10 +238,10 @@ public class DragDropSpineTests
private static (UiRoot root, UiItemList list, UiItemSlot cell) RootWithBoundSlot(uint itemId)
{
var root = new UiRoot { Width = 800, Height = 600 };
var list = new UiItemList(_ => (1u, 1, 1)) { Left = 0, Top = 0, Width = 32, Height = 32 };
var list = new UiItemList(_ => (new GpuTextureSlot(1u), 1, 1)) { Left = 0, Top = 0, Width = 32, Height = 32 };
// Tests don't run OnDraw (which sizes the cell), so size the cell explicitly.
list.Cell.Width = 32; list.Cell.Height = 32;
if (itemId != 0) list.Cell.SetItem(itemId, 0x99u);
if (itemId != 0) list.Cell.SetItem(itemId, new GpuTextureSlot(0x99u));
root.AddChild(list);
return (root, list, list.Cell);
}
@ -249,7 +250,7 @@ public class DragDropSpineTests
RootWithCatalogSlot(uint entryId)
{
var root = new UiRoot { Width = 800, Height = 600 };
var list = new UiItemList(_ => (1u, 1, 1))
var list = new UiItemList(_ => (new GpuTextureSlot(1u), 1, 1))
{
Left = 0,
Top = 0,
@ -262,7 +263,7 @@ public class DragDropSpineTests
EntryId = entryId,
Width = 32,
Height = 32,
SpriteResolve = _ => (1u, 1, 1),
SpriteResolve = _ => (new GpuTextureSlot(1u), 1, 1),
};
list.AddItem(cell);
root.AddChild(list);
@ -427,15 +428,15 @@ public class DragDropSpineTests
{
var cell = new UiItemSlot(); // no parent list → FindList() null
cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragEnter, Payload: SomePayload()));
Assert.Equal(UiItemSlot.DragAcceptState.Reject, cell.DragAcceptVisual);
Assert.Equal(DragAcceptState.Reject, cell.DragAcceptVisual);
}
[Fact]
public void DragEnter_listWithoutHandler_defaultsToReject()
{
var list = new UiItemList(_ => (1u, 1, 1)); // no RegisterDragHandler
var list = new UiItemList(_ => (new GpuTextureSlot(1u), 1, 1)); // no RegisterDragHandler
list.Cell.OnEvent(new UiEvent(0u, list.Cell, UiEventType.DragEnter, Payload: SomePayload()));
Assert.Equal(UiItemSlot.DragAcceptState.Reject, list.Cell.DragAcceptVisual);
Assert.Equal(DragAcceptState.Reject, list.Cell.DragAcceptVisual);
}
// ── item drag inside a Draggable window (the LIVE toolbar topology) ──────
@ -449,9 +450,9 @@ public class DragDropSpineTests
{
var root = new UiRoot { Width = 800, Height = 600 };
var frame = new UiPanel { Left = 10, Top = 300, Width = 200, Height = 60, Draggable = true };
var list = new UiItemList(_ => (1u, 1, 1)) { Left = 5, Top = 5, Width = 32, Height = 32 };
var list = new UiItemList(_ => (new GpuTextureSlot(1u), 1, 1)) { Left = 5, Top = 5, Width = 32, Height = 32 };
list.Cell.Width = 32; list.Cell.Height = 32;
if (itemId != 0) list.Cell.SetItem(itemId, 0x99u);
if (itemId != 0) list.Cell.SetItem(itemId, new GpuTextureSlot(0x99u));
frame.AddChild(list);
root.AddChild(frame);
return (root, frame, list);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.Core.Combat;
using AcDream.Core.Items;
@ -251,7 +252,7 @@ public sealed class ItemInteractionControllerTests
});
h.GroundObject = chest;
var cell = new UiItemSlot();
cell.SetItem(itemId, 0u);
cell.SetItem(itemId, GpuTextureSlot.Unassigned);
var payload = new ItemDragPayload(itemId, ItemDragSource.Inventory, 0, cell);
Assert.True(h.Controller.PlaceIn3D(payload, chest));

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Numerics;
using AcDream.App.Spells;
using AcDream.Content;
@ -14,7 +15,7 @@ namespace AcDream.App.Tests.UI.Layout;
public sealed class AppraisalUiControllerTests
{
private const uint ObjectId = 0x50000001u;
private static (uint, int, int) NoTexture(uint _) => (0u, 0, 0);
private static (GpuTextureSlot, int, int) NoTexture(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
[Fact]
public void ItemResponse_UsesAuthoredItemSubviewTitleAndScrollbars()
@ -741,11 +742,11 @@ public sealed class AppraisalUiControllerTests
() => { },
selection: selection,
spellbook: spellbook,
resolveSpellIcon: id => id + 1_000u,
resolveSpellIcon: id => new GpuTextureSlot(id + 1_000u),
resolveComponentIcon: did =>
{
resolvedComponentIcons.Add(did);
return did + 2_000u;
return new GpuTextureSlot(did + 2_000u);
},
spellComponents: _ => components,
magicSkill: _ => 200u,
@ -801,14 +802,18 @@ public sealed class AppraisalUiControllerTests
AppraisalUiController.SpellIconId)!;
UiTextureElement icon = Assert.Single(
iconHost.Children.OfType<UiTextureElement>());
Assert.Equal(metadata.SpellId + 1_000u, icon.Texture);
Assert.Equal(new GpuTextureSlot(metadata.SpellId + 1_000u), icon.Texture);
Assert.Equal(
new uint[] { 0x06000010u, 0x06000011u },
resolvedComponentIcons);
UiElement formula = layout.FindElement(
AppraisalUiController.SpellFormulaListId)!;
Assert.Equal(
new uint[] { 0x06000010u + 2_000u, 0x06000011u + 2_000u },
new[]
{
new GpuTextureSlot(0x06000010u + 2_000u),
new GpuTextureSlot(0x06000011u + 2_000u),
},
formula.Children
.OfType<UiDatElement>()
.Select(cell => cell.RuntimeImageTexture!.Value));
@ -828,8 +833,8 @@ public sealed class AppraisalUiControllerTests
SelectionState? selection = null,
RetailAppraisalNameResolver? itemNames = null,
Spellbook? spellbook = null,
Func<uint, uint>? resolveSpellIcon = null,
Func<uint, uint>? resolveComponentIcon = null,
Func<uint, GpuTextureSlot>? resolveSpellIcon = null,
Func<uint, GpuTextureSlot>? resolveComponentIcon = null,
Func<uint, IReadOnlyList<SpellExamineComponent>>? spellComponents = null,
Func<MagicSchool, uint>? magicSkill = null,
SpellExamineComponentTemplateFactory? spellComponentTemplates = null)

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System;
using System.Collections.Generic;
using System.IO;
@ -30,10 +31,10 @@ public sealed class CharacterLayoutImportProbe
if (datDir is null) return;
using var dats = new DatCollection(datDir, DatAccessType.Read);
var layout = LayoutImporter.Import(dats, CharacterLayout, _ => (1u, 30, 26), null);
var layout = LayoutImporter.Import(dats, CharacterLayout, _ => (new GpuTextureSlot(1u), 30, 26), null);
Assert.NotNull(layout);
CharacterStatController.Bind(layout!, SampleData.SampleCharacter, spriteResolve: _ => (1u, 30, 26));
CharacterStatController.Bind(layout!, SampleData.SampleCharacter, spriteResolve: _ => (new GpuTextureSlot(1u), 30, 26));
var rows = new List<UiClickablePanel>();
CollectRows(layout!.Root, rows);
@ -61,7 +62,7 @@ public sealed class CharacterLayoutImportProbe
if (datDir is null) return;
using var dats = new DatCollection(datDir, DatAccessType.Read);
var layout = LayoutImporter.Import(dats, CharacterLayout, _ => (1u, 30, 26), null);
var layout = LayoutImporter.Import(dats, CharacterLayout, _ => (new GpuTextureSlot(1u), 30, 26), null);
Assert.NotNull(layout);
var close = layout!.FindElement(WindowChromeController.CharacterCloseButtonId);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.Studio;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -281,7 +282,7 @@ public class CharacterStatControllerTests
var layout = Fake((CharacterStatController.ListBoxId, list));
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
var rows = list.Children.OfType<UiPanel>().ToList();
Assert.Equal(9, rows.Count);
@ -547,7 +548,7 @@ public class CharacterStatControllerTests
var layout = Fake((CharacterStatController.ListBoxId, list));
// Minimal sprite resolver — returns a fake non-zero handle so UiPanel draws it.
static (uint, int, int) FakeResolve(uint id) => (1u, 32, 8);
static (GpuTextureSlot, int, int) FakeResolve(uint id) => (new GpuTextureSlot(1), 32, 8);
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: FakeResolve);
@ -566,7 +567,7 @@ public class CharacterStatControllerTests
{
var list = new UiPanel();
var layout = Fake((CharacterStatController.ListBoxId, list));
static (uint, int, int) FakeResolve(uint id) => (1u, 32, 8);
static (GpuTextureSlot, int, int) FakeResolve(uint id) => (new GpuTextureSlot(1), 32, 8);
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: FakeResolve);
@ -791,7 +792,7 @@ public class CharacterStatControllerTests
Assert.Equal(3, titles.Children.Count);
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
Assert.Equal(rootChildCount, layout.Root.Children.Count);
Assert.Equal(RetailUiStateIds.Open, attributes.ActiveRetailStateId);
@ -814,7 +815,7 @@ public class CharacterStatControllerTests
var skills = Assert.IsType<UiText>(layout.FindElement(CharacterStatController.TabSkillsId));
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
skills.OnClick!();
Assert.Equal(RetailUiStateIds.Closed, attributes.ActiveRetailStateId);
@ -836,7 +837,7 @@ public class CharacterStatControllerTests
var layout = Fake((CharacterStatController.ListBoxId, list));
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
ClickTab(layout, left: 92f);
@ -891,7 +892,7 @@ public class CharacterStatControllerTests
var layout = Fake((CharacterStatController.ListBoxId, list));
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
ClickTab(layout, left: 92f);
Assert.Equal(12, SkillRows(list).Count);
@ -929,7 +930,7 @@ public class CharacterStatControllerTests
});
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
Assert.Equal("Strength", FirstRowName(visibleList));
Assert.Equal("<no row>", FirstRowName(hiddenDuplicateList));
@ -963,7 +964,7 @@ public class CharacterStatControllerTests
(CharacterStatController.FooterLine2Value, l2Value));
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
ClickTab(layout, left: 92f);
var rows = SkillRows(list);
@ -993,7 +994,7 @@ public class CharacterStatControllerTests
var requests = new List<CharacterStatController.RaiseRequest>();
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16),
spriteResolve: id => (new GpuTextureSlot(id), 16, 16),
onRaiseRequest: (request, completed) => { requests.Add(request); completed(); });
ClickTab(layout, left: 92f);
@ -1027,7 +1028,7 @@ public class CharacterStatControllerTests
(CharacterStatController.RaiseOneId, btn1));
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
ClickTab(layout, left: 92f);
SkillRows(list)[5].OnClick!();
@ -1052,7 +1053,7 @@ public class CharacterStatControllerTests
var requests = new List<CharacterStatController.RaiseRequest>();
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16),
spriteResolve: id => (new GpuTextureSlot(id), 16, 16),
onRaiseRequest: (request, completed) => { requests.Add(request); completed(); });
ClickTab(layout, left: 92f);
@ -1096,7 +1097,7 @@ public class CharacterStatControllerTests
var requests = new List<CharacterStatController.RaiseRequest>();
CharacterStatController.Bind(layout, () => sheet,
spriteResolve: id => (id, 16, 16),
spriteResolve: id => (new GpuTextureSlot(id), 16, 16),
onRaiseRequest: (request, completed) =>
{
requests.Add(request);
@ -1149,7 +1150,7 @@ public class CharacterStatControllerTests
Action? completeRaise = null;
CharacterStatController.Bind(layout, () => sheet,
spriteResolve: id => (id, 16, 16),
spriteResolve: id => (new GpuTextureSlot(id), 16, 16),
onRaiseRequest: (_, completed) => completeRaise = completed);
ClickTab(layout, left: 92f);
@ -1213,7 +1214,7 @@ public class CharacterStatControllerTests
});
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
ClickTab(layout, left: 92f);
@ -1537,7 +1538,7 @@ public class CharacterStatControllerTests
CharacterStatController.Bind(
layout,
SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
spriteResolve: id => (new GpuTextureSlot(id), 16, 16));
ApplyLayoutPass(layout.Root);
@ -1635,7 +1636,7 @@ public class CharacterStatControllerTests
Width = width,
Height = height,
};
return new UiDatElement(info, static _ => (0u, 0, 0))
return new UiDatElement(info, static _ => (GpuTextureSlot.Unassigned, 0, 0))
{
Top = top,
Width = width,
@ -1649,7 +1650,7 @@ public class CharacterStatControllerTests
var info = new ElementInfo { Id = id, Type = 1 };
info.StateMedia["Normal"] = (1u, 1);
info.StateMedia["Ghosted"] = (2u, 1);
return new UiButton(info, static _ => (0u, 0, 0));
return new UiButton(info, static _ => (GpuTextureSlot.Unassigned, 0, 0));
}
private static UiText MakeTab(uint id, float left)
@ -1676,7 +1677,7 @@ public class CharacterStatControllerTests
Name = "Open",
PassToChildren = true,
};
return Assert.IsType<UiText>(DatWidgetFactory.Create(info, static _ => (0u, 0, 0), null));
return Assert.IsType<UiText>(DatWidgetFactory.Create(info, static _ => (GpuTextureSlot.Unassigned, 0, 0), null));
}
private static ImportedLayout Fake(params (uint id, UiElement e)[] items)

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Chat;
@ -13,7 +14,7 @@ namespace AcDream.App.Tests.UI.Layout;
/// </summary>
public class ChatLayoutConformanceTests
{
private static (uint, int, int) NoTex(uint _) => (0, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
private static ElementInfo? Find(ElementInfo n, uint id)
{

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Collections.Generic;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -21,7 +22,7 @@ namespace AcDream.App.Tests.UI.Layout;
public class ChatWindowControllerTests
{
// ── Null-resolve helper (no GL needed) ─────────────────────────────────
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
// ── Capture bus — records every Publish call ────────────────────────────
private sealed class CaptureBus : ICommandBus

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -18,7 +19,7 @@ public sealed class CombatLayoutConformanceTests
ElementInfo info = FixtureLoader.LoadCombatInfos();
ImportedLayout layout = LayoutImporter.Build(
info,
_ => (0u, 0, 0),
_ => (GpuTextureSlot.Unassigned, 0, 0),
datFont: null,
stringResolve: _ => "localized");
ApplyAnchors(layout.Root);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.Combat;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -9,7 +10,7 @@ namespace AcDream.App.Tests.UI.Layout;
public sealed class CombatUiControllerTests
{
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
[Fact]
public void CombatMode_ShowsPhysicalAndMagicPages_AndSelectsMediumByDefault()

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
@ -130,7 +131,7 @@ public sealed class CreatureAppraisalRowsTests
{
var templates = new CreatureAppraisalRowTemplateFactory(
FixtureLoader.LoadExaminationRowTemplateInfos(),
_ => (0u, 0, 0),
_ => (GpuTextureSlot.Unassigned, 0, 0),
defaultFont: null);
var row = new CreatureAppraisalRow(
"Strength",

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -20,7 +21,7 @@ namespace AcDream.App.Tests.UI.Layout;
/// </summary>
public class DatWidgetFactoryFontResolveTests
{
private static (uint, int, int) NoTex(uint _) => (0, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
// ── Test 1: null fontResolve → DatFont == datFont (backward-compat) ─────

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Numerics;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -5,7 +6,7 @@ namespace AcDream.App.Tests.UI.Layout;
public class DatWidgetFactoryTests
{
private static (uint, int, int) NoTex(uint _) => (0, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
// ── Test 1: Type 7 → UiMeter ─────────────────────────────────────────────
@ -334,7 +335,7 @@ public class DatWidgetFactoryTests
public void Create_buildsUiItemList_forItemListClassId()
{
var info = new AcDream.App.UI.Layout.ElementInfo { Id = 0x100001A7u, Type = 0x10000031u, Width = 32, Height = 32 };
var w = AcDream.App.UI.Layout.DatWidgetFactory.Create(info, _ => (0u, 0, 0), null);
var w = AcDream.App.UI.Layout.DatWidgetFactory.Create(info, _ => (GpuTextureSlot.Unassigned, 0, 0), null);
Assert.IsType<AcDream.App.UI.UiItemList>(w);
}

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Spells;
@ -6,7 +7,7 @@ namespace AcDream.App.Tests.UI.Layout;
public sealed class EffectsUiControllerTests
{
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
private static EffectRowTemplateFactory Templates()
=> new(FixtureLoader.LoadEffectRowTemplateInfos(), NoTex, defaultFont: null);
@ -47,7 +48,7 @@ public sealed class EffectsUiControllerTests
[EffectsUiController.InfoTextId] = info,
});
using EffectsUiController controller = EffectsUiController.Bind(
layout, spellbook, positive: true, () => 0d, NoTex, id => id,
layout, spellbook, positive: true, () => 0d, NoTex, id => new GpuTextureSlot(id),
Templates(), "SELECT A SPELL")!;
Assert.False(info.PreserveEndOnLayout);
@ -86,7 +87,7 @@ public sealed class EffectsUiControllerTests
[EffectsUiController.ListId] = list,
});
using EffectsUiController controller = EffectsUiController.Bind(
layout, spellbook, positive: true, () => 0d, NoTex, id => id,
layout, spellbook, positive: true, () => 0d, NoTex, id => new GpuTextureSlot(id),
Templates(), "SELECT A SPELL")!;
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
42u, 1u, 60f, 1u, Bucket: 1u, SpellCategory: 100u));
@ -129,7 +130,7 @@ public sealed class EffectsUiControllerTests
info.Width = 96f;
using EffectsUiController controller = EffectsUiController.Bind(
layout, spellbook, positive, () => 0d, NoTex, id => id,
layout, spellbook, positive, () => 0d, NoTex, id => new GpuTextureSlot(id),
Templates(), "SELECT A SPELL")!;
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
42u, 1u, 60f, 1u, Bucket: 1u, SpellCategory: 42u));
@ -173,7 +174,7 @@ public sealed class EffectsUiControllerTests
[EffectsUiController.ListId] = list,
});
using EffectsUiController controller = EffectsUiController.Bind(
layout, spellbook, positive: true, () => 0d, NoTex, id => id,
layout, spellbook, positive: true, () => 0d, NoTex, id => new GpuTextureSlot(id),
Templates(), "SELECT A SPELL")!;
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
@ -225,7 +226,7 @@ public sealed class EffectsUiControllerTests
int closes = 0;
using EffectsUiController? controller = EffectsUiController.Bind(
layout, spellbook, positive, () => 0d, NoTex, id => id,
layout, spellbook, positive, () => 0d, NoTex, id => new GpuTextureSlot(id),
Templates(), "SELECT A SPELL", () => closes++);
Assert.NotNull(controller);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.App.World;
@ -31,7 +32,7 @@ public sealed class ExternalContainerControllerTests
RetailWindowHandle handle = RetailWindowFrame.Mount(
screen,
root,
static _ => (0u, 0, 0),
static _ => (GpuTextureSlot.Unassigned, 0, 0),
options);
var frame = Assert.IsType<UiNineSlicePanel>(handle.OuterFrame);
@ -75,7 +76,7 @@ public sealed class ExternalContainerControllerTests
var root = new TestElement { Width = 800f, Height = 110f };
var close = new UiButton(
new ElementInfo { Id = ExternalContainerController.CloseButtonId, Type = 1 },
static _ => (0u, 0, 0));
static _ => (GpuTextureSlot.Unassigned, 0, 0));
var scrollbar = new UiScrollbar { Width = 784f, Height = 16f };
root.AddChild(Top);
root.AddChild(Containers);
@ -94,7 +95,7 @@ public sealed class ExternalContainerControllerTests
Window = RetailWindowFrame.Mount(
Screen,
root,
static _ => (0u, 0, 0),
static _ => (GpuTextureSlot.Unassigned, 0, 0),
new RetailWindowFrame.Options
{
WindowName = WindowNames.ExternalContainer,
@ -129,8 +130,8 @@ public sealed class ExternalContainerControllerTests
Selection,
Interaction,
Split,
static (_, _, _, _, _) => 0u,
static (_, _, _, _, _) => 0u,
static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
Uses.Add,
(item, container, placement) => Puts.Add((item, container, placement)),
(item, container, placement, amount) =>
@ -285,7 +286,7 @@ public sealed class ExternalContainerControllerTests
h.Selection.Select(Item, SelectionChangeSource.Inventory);
h.Split.Reset(5u, 2u);
var source = new UiItemSlot { SourceKind = ItemDragSource.Inventory };
source.SetItem(Item, 0u);
source.SetItem(Item, GpuTextureSlot.Unassigned);
var payload = new ItemDragPayload(Item, ItemDragSource.Inventory, 0, source);
UiItemSlot target = h.Contents.GetItem(0)!;
@ -304,7 +305,7 @@ public sealed class ExternalContainerControllerTests
h.Objects.AddOrUpdate(new ClientObject { ObjectId = Item, Type = ItemType.Misc });
h.Objects.MoveItem(Item, Player, 0);
var source = new UiItemSlot { SourceKind = ItemDragSource.Inventory };
source.SetItem(Item, 0u);
source.SetItem(Item, GpuTextureSlot.Unassigned);
Assert.True(h.Interaction.PlaceWorldItemInBackpack(0x70000A01u));
h.Controller.HandleDropRelease(
@ -333,7 +334,7 @@ public sealed class ExternalContainerControllerTests
h.Selection.Select(Item, SelectionChangeSource.Inventory);
h.Split.Reset(5u, 2u);
var source = new UiItemSlot { SourceKind = ItemDragSource.Inventory };
source.SetItem(Item, 0u);
source.SetItem(Item, GpuTextureSlot.Unassigned);
Assert.True(h.Interaction.PlaceWorldItemInBackpack(0x70000A02u));
h.Controller.HandleDropRelease(
@ -355,7 +356,7 @@ public sealed class ExternalContainerControllerTests
h.Objects.AddOrUpdate(new ClientObject { ObjectId = Item, Type = ItemType.Misc });
h.Objects.MoveItem(Item, Player, 0);
var source = new UiItemSlot { SourceKind = ItemDragSource.Inventory };
source.SetItem(Item, 0u);
source.SetItem(Item, GpuTextureSlot.Unassigned);
h.Controller.HandleDropRelease(
h.Contents,

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.IO;
using System.Text.Json;
using AcDream.App.UI.Layout;
@ -9,7 +10,7 @@ namespace AcDream.App.Tests.UI.Layout;
/// no dats required. Fixtures were generated from the real portal.dat and
/// serialized with <see cref="System.Text.Json"/>.
/// </summary>
public static class FixtureLoader
internal static class FixtureLoader
{
private static readonly JsonSerializerOptions _opts = new()
{
@ -26,7 +27,7 @@ public static class FixtureLoader
public static ImportedLayout LoadVitals()
{
var root = LoadVitalsInfos();
return LayoutImporter.Build(root, _ => (0u, 0, 0), null);
return LayoutImporter.Build(root, _ => (GpuTextureSlot.Unassigned, 0, 0), null);
}
/// <summary>
@ -46,7 +47,7 @@ public static class FixtureLoader
/// conformance checks on tree structure and resolved types.
/// </summary>
public static ImportedLayout LoadChat()
=> LayoutImporter.Build(LoadChatInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadChatInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
/// <summary>
/// Deserializes the committed <c>chat_21000006.json</c> fixture into a raw
@ -59,44 +60,44 @@ public static class FixtureLoader
/// <summary>Builds the committed retail radar LayoutDesc 0x21000074 fixture.</summary>
public static ImportedLayout LoadRadar()
=> LayoutImporter.Build(LoadRadarInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadRadarInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
/// <summary>Returns the resolved ElementInfo tree for retail radar LayoutDesc 0x21000074.</summary>
public static AcDream.App.UI.Layout.ElementInfo LoadRadarInfos()
=> LoadInfos("radar_21000074.json");
public static ImportedLayout LoadToolbar()
=> LayoutImporter.Build(LoadToolbarInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadToolbarInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadToolbarInfos()
=> LoadInfos("toolbar_21000016.json");
public static ImportedLayout LoadInventory()
=> LayoutImporter.Build(LoadInventoryInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadInventoryInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadInventoryInfos()
=> LoadInfos("inventory_21000023.json");
public static ImportedLayout LoadPaperdoll()
=> LayoutImporter.Build(LoadPaperdollInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadPaperdollInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadPaperdollInfos()
=> LoadInfos("paperdoll_21000024.json");
public static ImportedLayout LoadCharacter()
=> LayoutImporter.Build(LoadCharacterInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadCharacterInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadCharacterInfos()
=> LoadInfos("character_2100002E.json");
public static ImportedLayout LoadCombat()
=> LayoutImporter.Build(LoadCombatInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadCombatInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadCombatInfos()
=> LoadInfos("combat_21000073.json");
public static ImportedLayout LoadSpellbook()
=> LayoutImporter.Build(LoadSpellbookInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadSpellbookInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadSpellbookInfos()
=> LoadInfos("spellbook_21000034.json");
@ -108,7 +109,7 @@ public static class FixtureLoader
=> LoadInfos("component_row_21000033_10000467.json");
public static ImportedLayout LoadExamination()
=> LayoutImporter.Build(LoadExaminationInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadExaminationInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadExaminationInfos()
=> LoadInfos("examine_2100006B_100005F2.json");
@ -120,31 +121,31 @@ public static class FixtureLoader
=> LoadInfos("examine_component_2100006B_1000032E.json");
public static ImportedLayout LoadPowerbar()
=> LayoutImporter.Build(LoadPowerbarInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadPowerbarInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadPowerbarInfos()
=> LoadInfos("powerbar_21000072.json");
public static ImportedLayout LoadConfirmationDialog()
=> LayoutImporter.Build(LoadConfirmationDialogInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadConfirmationDialogInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadConfirmationDialogInfos()
=> LoadInfos("dialogs_2100003C.json");
public static ImportedLayout LoadFpsDisplay()
=> LayoutImporter.Build(LoadFpsDisplayInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadFpsDisplayInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadFpsDisplayInfos()
=> LoadInfos("smartbox_fps_2100000F.json");
public static ImportedLayout LoadPositiveEffects()
=> LayoutImporter.Build(LoadPositiveEffectsInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadPositiveEffectsInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadPositiveEffectsInfos()
=> LoadInfos("effects_positive_2100001B.json");
public static ImportedLayout LoadNegativeEffects()
=> LayoutImporter.Build(LoadNegativeEffectsInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadNegativeEffectsInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadNegativeEffectsInfos()
=> LoadInfos("effects_negative_2100001B.json");
@ -154,31 +155,31 @@ public static class FixtureLoader
public static ImportedLayout LoadCharacterInformation()
=> LayoutImporter.Build(
LoadCharacterInformationInfos(), _ => (0u, 0, 0), null);
LoadCharacterInformationInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadCharacterInformationInfos()
=> LoadInfos("character_info_2100006E_10000183.json");
public static ImportedLayout LoadIndicators()
=> LayoutImporter.Build(LoadIndicatorsInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadIndicatorsInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadIndicatorsInfos()
=> LoadInfos("indicators_21000071.json");
public static ImportedLayout LoadLinkStatus()
=> LayoutImporter.Build(LoadLinkStatusInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadLinkStatusInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadLinkStatusInfos()
=> LoadInfos("link_status_2100001D.json");
public static ImportedLayout LoadVitae()
=> LayoutImporter.Build(LoadVitaeInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadVitaeInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadVitaeInfos()
=> LoadInfos("vitae_21000020.json");
public static ImportedLayout LoadMiniGame()
=> LayoutImporter.Build(LoadMiniGameInfos(), _ => (0u, 0, 0), null);
=> LayoutImporter.Build(LoadMiniGameInfos(), _ => (GpuTextureSlot.Unassigned, 0, 0), null);
public static ElementInfo LoadMiniGameInfos()
=> LoadInfos("mini_game_2100001E.json");

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
@ -9,7 +10,7 @@ namespace AcDream.App.Tests.UI.Layout;
public sealed class IndicatorBarControllerTests
{
private const uint Player = 0x50000001u;
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
[Fact]
public void AuthoredFixture_BuildsAllSevenRetailIndicatorButtons()

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Collections.Generic;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -65,7 +66,7 @@ public class InventoryControllerTests
StackSplitQuantityState? stackSplitQuantity = null,
ItemInteractionController? itemInteraction = null)
=> InventoryController.Bind(layout, objects, () => Player,
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => strength, datFont: null,
ownerName: ownerName is null ? null : () => ownerName,
sendUse: uses is null ? null : g => uses.Add(g),
@ -83,7 +84,7 @@ public class InventoryControllerTests
private static UiButton MakeButton(uint id)
{
var info = new ElementInfo { Id = id, Type = 1 };
return new UiButton(info, static _ => (0u, 0, 0)) { Width = 16, Height = 16 };
return new UiButton(info, static _ => (GpuTextureSlot.Unassigned, 0, 0)) { Width = 16, Height = 16 };
}
// Seed a side bag (a container) in the player's pack, plus optionally its own contents.
@ -379,7 +380,7 @@ public class InventoryControllerTests
{
var (layout, grid, containers, top, _, _, _, _) = BuildLayout();
InventoryController.Bind(layout, new ClientObjectTable(), () => Player,
iconIds: (_, _, _, _, _) => 0u, strength: () => 100,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned, strength: () => 100,
selection: new SelectionState(), datFont: null,
contentsEmptySprite: 0x06004D20u, sideBagEmptySprite: 0x06005D9Cu, mainPackEmptySprite: 0x06005D9Cu);
@ -556,7 +557,7 @@ public class InventoryControllerTests
nowMs: () => 1_000);
InventoryController.Bind(layout, objects, () => Player,
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -589,7 +590,7 @@ public class InventoryControllerTests
objects.AddOrUpdate(new ClientObject { ObjectId = Player, IconId = 0x06001234u });
(ItemType type, uint icon)? mainPackCall = null;
InventoryController.Bind(layout, objects, () => Player,
iconIds: (t, icon, _, _, _) => { if (icon == 0x0600127Eu) mainPackCall = (t, icon); return 0u; },
iconIds: (t, icon, _, _, _) => { if (icon == 0x0600127Eu) mainPackCall = (t, icon); return GpuTextureSlot.Unassigned; },
strength: () => 100, selection: new SelectionState(), datFont: null);
// Retail draws a constant backpack over the Container type-underlay (IconData::RenderIcons
@ -682,7 +683,7 @@ public class InventoryControllerTests
var puts = new List<(uint item, uint container, int placement)>();
using var ctrl = Bind(layout, objects, puts: puts, selection: selection);
var source = new UiItemSlot { SourceKind = ItemDragSource.Ground };
source.SetItem(loot, 0u);
source.SetItem(loot, GpuTextureSlot.Unassigned);
var payload = new ItemDragPayload(loot, ItemDragSource.Ground, 0, source);
ctrl.HandleDropRelease(grid, grid.GetItem(1)!, payload);
@ -732,7 +733,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -781,7 +782,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -834,7 +835,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -842,7 +843,7 @@ public class InventoryControllerTests
puts.Add((item, container, placement)),
itemInteraction: interaction);
var source = new UiItemSlot { SourceKind = ItemDragSource.Ground };
source.SetItem(draggedLoot, 0u);
source.SetItem(draggedLoot, GpuTextureSlot.Unassigned);
inventory.HandleDropRelease(
grid,
@ -894,7 +895,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -902,7 +903,7 @@ public class InventoryControllerTests
puts.Add((item, container, placement)),
itemInteraction: interaction);
var source = new UiItemSlot { SourceKind = ItemDragSource.Ground };
source.SetItem(draggedLoot, 0u);
source.SetItem(draggedLoot, GpuTextureSlot.Unassigned);
Assert.True(interaction.PlaceWorldItemInBackpack(directLoot));
Assert.Equal(
@ -959,7 +960,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -1022,7 +1023,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -1030,7 +1031,7 @@ public class InventoryControllerTests
merges.Add((source, target, amount)),
itemInteraction: interaction);
var sourceCell = new UiItemSlot { SourceKind = ItemDragSource.Ground };
sourceCell.SetItem(sourceStack, 0u);
sourceCell.SetItem(sourceStack, GpuTextureSlot.Unassigned);
Assert.True(interaction.PlaceWorldItemInBackpack(pendingLoot));
UiItemSlot targetCell = Enumerable.Range(0, grid.GetNumUIItems())
@ -1083,7 +1084,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: selection,
datFont: null,
@ -1092,7 +1093,7 @@ public class InventoryControllerTests
itemInteraction: interaction,
stackSplitQuantity: splitQuantity);
var sourceCell = new UiItemSlot { SourceKind = ItemDragSource.Ground };
sourceCell.SetItem(sourceStack, 0u);
sourceCell.SetItem(sourceStack, GpuTextureSlot.Unassigned);
Assert.True(interaction.PlaceWorldItemInBackpack(pendingLoot));
inventory.HandleDropRelease(
@ -1131,7 +1132,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -1172,7 +1173,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -1212,7 +1213,7 @@ public class InventoryControllerTests
layout,
objects,
() => Player,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: () => 100,
selection: new SelectionState(),
datFont: null,
@ -1239,7 +1240,7 @@ public class InventoryControllerTests
var puts = new List<(uint item, uint container, int placement)>();
using var ctrl = Bind(layout, objects, puts: puts);
var source = new UiItemSlot { SourceKind = ItemDragSource.Ground };
source.SetItem(loot, 0u);
source.SetItem(loot, GpuTextureSlot.Unassigned);
var payload = new ItemDragPayload(loot, ItemDragSource.Ground, 0, source);
ctrl.HandleDropRelease(grid, grid.GetItem(1)!, payload);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System;
using System.IO;
using AcDream.App.UI;
@ -43,7 +44,7 @@ public class InventoryFrameImportProbe
if (datDir is null) return; // CI: no live dat — skip
using var dats = new DatCollection(datDir, DatAccessType.Read);
var layout = LayoutImporter.Import(dats, Frame, _ => (0u, 0, 0), null);
var layout = LayoutImporter.Import(dats, Frame, _ => (GpuTextureSlot.Unassigned, 0, 0), null);
Assert.NotNull(layout);
// A representative spread across the slot grid (head, shield, the weapon composite, cloak,
@ -63,7 +64,7 @@ public class InventoryFrameImportProbe
if (datDir is null) return; // CI: no live dat — skip (this is a smoke test)
using var dats = new DatCollection(datDir, DatAccessType.Read);
var layout = LayoutImporter.Import(dats, Frame, _ => (0u, 0, 0), null);
var layout = LayoutImporter.Import(dats, Frame, _ => (GpuTextureSlot.Unassigned, 0, 0), null);
Assert.NotNull(layout);
var backdrop = layout!.FindElement(Backdrop);
@ -95,7 +96,7 @@ public class InventoryFrameImportProbe
if (datDir is null) return; // CI: no live dat - skip
using var dats = new DatCollection(datDir, DatAccessType.Read);
var layout = LayoutImporter.Import(dats, Frame, _ => (0u, 0, 0), null);
var layout = LayoutImporter.Import(dats, Frame, _ => (GpuTextureSlot.Unassigned, 0, 0), null);
Assert.NotNull(layout);
var close = layout!.FindElement(WindowChromeController.InventoryCloseButtonId);
@ -108,7 +109,7 @@ public class InventoryFrameImportProbe
layout,
new ClientObjectTable(),
playerGuid: static () => 0u,
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
strength: static () => 100,
selection: new AcDream.Core.Selection.SelectionState(),
datFont: null,

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
@ -30,7 +31,7 @@ public sealed class ItemCooldownUiControllerTests
double now = 112.5d;
var root = new UiPanel();
var list = new UiItemList();
list.Cell.SetItem(0x5001u, 99u);
list.Cell.SetItem(0x5001u, new GpuTextureSlot(99u));
root.AddChild(list);
uint[] sprites = Enumerable.Range(1, 10)
.Select(index => 0x06000000u + (uint)index)
@ -46,7 +47,7 @@ public sealed class ItemCooldownUiControllerTests
Assert.Equal(sprites[5], list.Cell.ActiveCooldownSprite());
var future = new UiItemSlot();
future.SetItem(0x5001u, 100u);
future.SetItem(0x5001u, new GpuTextureSlot(100u));
list.AddItem(future);
Assert.Equal(sprites[5], future.ActiveCooldownSprite());
@ -81,7 +82,7 @@ public sealed class ItemCooldownUiControllerTests
var root = new UiPanel();
var window = new UiPanel();
var list = new UiItemList();
list.Cell.SetItem(0x5001u, 99u);
list.Cell.SetItem(0x5001u, new GpuTextureSlot(99u));
root.AddChild(window);
window.AddChild(list);
uint[] sprites = Enumerable.Range(1, 10)
@ -117,7 +118,7 @@ public sealed class ItemCooldownUiControllerTests
var spellbook = new Spellbook();
var root = new UiPanel();
var list = new UiItemList();
list.Cell.SetItem(0x5001u, 99u);
list.Cell.SetItem(0x5001u, new GpuTextureSlot(99u));
root.AddChild(list);
int clockReads = 0;
ItemCooldownUiController controller = ItemCooldownUiController.Bind(

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -9,7 +10,7 @@ public sealed class JumpPowerbarLayoutConformanceTests
public void RetailFixture_UsesExactFloatyGeometryTrackAndJumpFill()
{
ElementInfo info = FixtureLoader.LoadPowerbarInfos();
ImportedLayout layout = LayoutImporter.Build(info, _ => (0u, 0, 0), null);
ImportedLayout layout = LayoutImporter.Build(info, _ => (GpuTextureSlot.Unassigned, 0, 0), null);
var meter = Assert.IsType<UiMeter>(
layout.FindElement(JumpPowerbarController.MeterId));

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -9,7 +10,7 @@ namespace AcDream.App.Tests.UI.Layout;
/// </summary>
public class LayoutImporterTests
{
private static (uint, int, int) NoTex(uint _) => (0, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
// ── Test 1: Health meter element → UiMeter with correct rect ─────────────

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
@ -59,7 +60,7 @@ public sealed class PaperdollClickMapTests
ImportedLayout layout = Assert.IsType<ImportedLayout>(LayoutImporter.Import(
dats,
0x21000023u,
static _ => (0u, 0, 0),
static _ => (GpuTextureSlot.Unassigned, 0, 0),
null));
UiElement dragMask = Assert.IsAssignableFrom<UiElement>(
layout.FindElement(PaperdollController.DollDragMaskId));

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Collections.Generic;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -66,7 +67,7 @@ public class PaperdollControllerTests
systemMessage: systemMessages is null ? null : systemMessages.Add);
configureInteraction?.Invoke(itemInteraction);
return PaperdollController.Bind(layout, objects, () => Player,
iconIds: (_, _, _, _, _) => 0x1234u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(0x1234u),
itemInteraction: itemInteraction,
emptySlotSprite: emptySlot,
selection: selection ?? new SelectionState(),

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Numerics;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -132,7 +133,7 @@ public sealed class RadarControllerTests
root.Children.Add(lockButton);
root.Children.Add(Sprite(RadarController.DragButtonId, 87, 6, 27, 27, 0x060074C9u, type: 2));
return LayoutImporter.Build(root, _ => (0u, 0, 0), null);
return LayoutImporter.Build(root, _ => (GpuTextureSlot.Unassigned, 0, 0), null);
}
private static ElementInfo Sprite(

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -180,7 +181,7 @@ public sealed class RetailPanelUiControllerTests
return RetailWindowFrame.Mount(
root,
content,
_ => (0u, 0, 0),
_ => (GpuTextureSlot.Unassigned, 0, 0),
new RetailWindowFrame.Options
{
WindowName = name,

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -5,7 +6,7 @@ namespace AcDream.App.Tests.UI.Layout;
public sealed class RetailWindowFrameTests
{
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
[Theory]
[InlineData(9, 459f, 288f)]

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System;
using System.Collections.Generic;
using System.Linq;
@ -52,7 +53,7 @@ public class SelectedObjectControllerTests
["StackedItemSelected"] = (0x06004CF4u, 3),
},
};
var overlayEl = new UiDatElement(overlayInfo, _ => (0u, 0, 0));
var overlayEl = new UiDatElement(overlayInfo, _ => (GpuTextureSlot.Unassigned, 0, 0));
dict[SelectedObjectController.OverlayId] = overlayEl;
root.AddChild(overlayEl);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -16,16 +17,16 @@ public sealed class SpellExamineComponentTemplateFactoryTests
FixtureLoader.LoadExaminationComponentTemplateInfos();
var factory = new SpellExamineComponentTemplateFactory(
template,
did => (did + 1_000u, 32, 32),
did => (new GpuTextureSlot(did + 1_000u), 32, 32),
defaultFont: null);
UiDatElement root = Assert.IsType<UiDatElement>(
factory.Create(0xABCDu, owned));
factory.Create(new GpuTextureSlot(0xABCDu), owned));
UiDatElement missing = Assert.IsType<UiDatElement>(
Assert.Single(root.Children));
Assert.Equal(SpellExamineComponentTemplateFactory.TemplateId, root.ElementId);
Assert.Equal(0xABCDu, root.RuntimeImageTexture);
Assert.Equal(new GpuTextureSlot(0xABCDu), root.RuntimeImageTexture);
Assert.Empty(root.Children.OfType<UiTextureElement>());
Assert.Equal(
SpellExamineComponentTemplateFactory.MissingOverlayId,

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.Spells;
using AcDream.Content;
using AcDream.App.UI;
@ -30,7 +31,7 @@ public sealed class SpellbookWindowControllerTests
{
ImportedLayout layout = LayoutImporter.Build(
FixtureLoader.LoadSpellbookInfos(),
_ => (0u, 0, 0),
_ => (GpuTextureSlot.Unassigned, 0, 0),
datFont: null,
stringResolve: value => value.StringId switch
{
@ -81,7 +82,7 @@ public sealed class SpellbookWindowControllerTests
{
ImportedLayout layout = LayoutImporter.Build(
FixtureLoader.LoadSpellbookInfos(),
_ => (0u, 0, 0),
_ => (GpuTextureSlot.Unassigned, 0, 0),
datFont: null,
stringResolve: value => value.StringId == 164868556u ? "Delete" : "Filter");
@ -302,7 +303,7 @@ public sealed class SpellbookWindowControllerTests
selection.Select(objectId, SelectionChangeSource.Inventory);
},
setDesiredComponent: (component, amount) => desired.Add((component, amount)),
resolveComponentIcon: icon => icon + 0x1000u)!;
resolveComponentIcon: icon => new GpuTextureSlot(icon + 0x1000u))!;
controller.ShowPage(SpellbookWindowPage.Components);
UiItemList list = Assert.IsType<UiItemList>(
@ -339,7 +340,7 @@ public sealed class SpellbookWindowControllerTests
UiElement iconHost = Assert.IsAssignableFrom<UiElement>(copper.Content.FindElement(
ComponentBookTemplateFactory.IconId));
Assert.Equal(
0x06001100u,
new GpuTextureSlot(0x06001100u),
Assert.IsType<UiTextureElement>(Assert.Single(iconHost.Children)).Texture);
copper.OnEvent(new UiEvent(0u, copper, UiEventType.Click));
@ -401,7 +402,7 @@ public sealed class SpellbookWindowControllerTests
SelectionState? selection = null,
Action<uint>? selectObject = null,
Action<uint, uint>? setDesiredComponent = null,
Func<uint, uint>? resolveComponentIcon = null)
Func<uint, GpuTextureSlot>? resolveComponentIcon = null)
=> SpellbookWindowController.Bind(
layout,
spellbook,
@ -409,8 +410,8 @@ public sealed class SpellbookWindowControllerTests
() => 1u,
components ?? new Dictionary<uint, SpellComponentDescriptor>(),
selection ?? new SelectionState(),
spellId => spellId + 1000u,
resolveComponentIcon ?? (iconId => iconId),
spellId => new GpuTextureSlot(spellId + 1000u),
resolveComponentIcon ?? (iconId => new GpuTextureSlot(iconId)),
spellId => spellId == 103u ? 8 : 1,
selectObject ?? (_ => { }),
addFavorite ?? (_ => { }),
@ -430,7 +431,7 @@ public sealed class SpellbookWindowControllerTests
new ComponentBookTemplateFactory(
FixtureLoader.LoadComponentCategoryTemplateInfos(),
FixtureLoader.LoadComponentRowTemplateInfos(),
_ => (0u, 0, 0),
_ => (GpuTextureSlot.Unassigned, 0, 0),
defaultFont: null),
RowStyle,
rowFont: null);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.Spells;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -10,7 +11,7 @@ namespace AcDream.App.Tests.UI.Layout;
public sealed class SpellcastingUiControllerTests
{
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
[Fact]
public void ImportedFixture_UsesRetailEmptyCells_ShortcutDigits_AndCompleteCastButton()
@ -160,8 +161,8 @@ public sealed class SpellcastingUiControllerTests
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);
Assert.Equal(new GpuTextureSlot(2670u), slot.CatalogIconTexture);
Assert.Equal(new GpuTextureSlot(2u), slot.CatalogOverlayTexture);
slot.OnEvent(new UiEvent(0, slot, UiEventType.Click));
var cast = Assert.IsType<UiButton>(
@ -338,8 +339,8 @@ public sealed class SpellcastingUiControllerTests
new NoopSpellCastOperations());
return SpellcastingUiController.Bind(
layout, spellbook, casting, objects, () => 1u,
spellId => spellId,
item => item.ObjectId,
spellId => new GpuTextureSlot(spellId),
item => new GpuTextureSlot(item.ObjectId),
useItem,
selectionState,
(tab, position, spellId) =>

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System;
using System.Collections.Generic;
using AcDream.App.UI;
@ -66,7 +67,7 @@ public class ToolbarControllerTests
void AddSlot(uint id)
{
var list = new UiItemList(_ => (0u, 0, 0)) { Width = 32, Height = 32 };
var list = new UiItemList(_ => (GpuTextureSlot.Unassigned, 0, 0)) { Width = 32, Height = 32 };
dict[id] = list; slots[id] = list; root.AddChild(list);
}
@ -101,7 +102,7 @@ public class ToolbarControllerTests
},
};
}
var button = new UiButton(info, _ => (0u, 0, 0)) { Width = 32, Height = 32 };
var button = new UiButton(info, _ => (GpuTextureSlot.Unassigned, 0, 0)) { Width = 32, Height = 32 };
dict[id] = button;
root.AddChild(button);
}
@ -117,10 +118,10 @@ public class ToolbarControllerTests
{ new(Index: 0, ObjectId: 0x5001u, SpellId: 0) };
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { });
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0x77u), useItem: _ => { });
Assert.Equal(0x5001u, slots[Row1[0]].Cell.ItemId);
Assert.Equal(0x77u, slots[Row1[0]].Cell.IconTexture);
Assert.Equal(new GpuTextureSlot(0x77u), slots[Row1[0]].Cell.IconTexture);
Assert.Equal(0u, slots[Row1[1]].Cell.ItemId); // others empty
}
@ -143,7 +144,7 @@ public class ToolbarControllerTests
layout,
repo,
Store(shortcuts),
iconIds: (_, _, _, _, _) => 0x77u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(0x77u),
useItem: _ => { });
Assert.Equal(0x5001u, slots[Row1[0]].Cell.ItemId);
@ -161,7 +162,7 @@ public class ToolbarControllerTests
{ new(Index: 2, ObjectId: 0x5002u, SpellId: 0) };
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x88u, useItem: _ => { });
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0x88u), useItem: _ => { });
Assert.Equal(0u, slots[Row1[2]].Cell.ItemId); // not bound yet
repo.AddOrUpdate(new ClientObject { ObjectId = 0x5002u, WeenieClassId = 1u, IconId = 0x06005678u });
@ -177,7 +178,7 @@ public class ToolbarControllerTests
var shortcuts = new List<ShortcutEntry>
{ new(Index: 2, ObjectId: 0x5002u, SpellId: 0) };
var controller = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x88u, useItem: _ => { });
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0x88u), useItem: _ => { });
controller.Dispose();
controller.Dispose();
@ -203,7 +204,7 @@ public class ToolbarControllerTests
var selection = new SelectionState();
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u,
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0x77u),
useItem: g => used = g,
selection: selection);
UiItemSlot cell = slots[Row1[0]].Cell;
@ -226,7 +227,7 @@ public class ToolbarControllerTests
var ctrl = ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { });
ctrl.BindPanelButtons(
panelId => panelId is RetailPanelCatalog.Inventory or RetailPanelCatalog.Character,
toggles.Add);
@ -277,7 +278,7 @@ public class ToolbarControllerTests
var ctrl = ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u,
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned,
useItem: _ => { },
itemInteraction: interaction);
ctrl.BindPanelButtons(
@ -304,7 +305,7 @@ public class ToolbarControllerTests
var puts = new List<(uint Item, uint Container, int Placement)>();
ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: _ => { },
playerGuid: () => player,
sendPutItemInContainer: (i, c, p) => puts.Add((i, c, p)));
@ -344,7 +345,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: static _ => { },
itemInteraction: interaction,
playerGuid: () => player,
@ -394,7 +395,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: static (_, _, _, _, _) => 0u,
iconIds: static (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: static _ => { },
itemInteraction: interaction,
playerGuid: () => player,
@ -426,7 +427,7 @@ public class ToolbarControllerTests
var puts = new List<(uint Item, uint Container, int Placement)>();
ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: _ => { },
playerGuid: () => player,
sendPutItemInContainer: (i, c, p) => puts.Add((i, c, p)));
@ -447,7 +448,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
var ctrl = ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { });
var inventoryButton = (UiButton)layout.FindElement(InventoryButtonId)!;
var characterButton = (UiButton)layout.FindElement(CharacterButtonId)!;
@ -524,7 +525,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: _ => { },
itemInteraction: interaction,
selectedObjectId: () => selected);
@ -582,7 +583,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: _ => { },
itemInteraction: interaction,
selectedObjectId: () => selection.SelectedObjectId ?? 0u,
@ -648,7 +649,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: _ => { },
itemInteraction: interaction,
selectedObjectId: () => selection.SelectedObjectId ?? 0u,
@ -691,7 +692,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: _ => { },
playerGuid: () => player);
var ammoButton = (UiButton)layout.FindElement(0x10000194u)!;
@ -730,7 +731,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: _ => { },
playerGuid: () => player);
var ammoButton = (UiButton)layout.FindElement(0x10000194u)!;
@ -752,7 +753,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 0u,
iconIds: (_, _, _, _, _) => GpuTextureSlot.Unassigned,
useItem: _ => { },
toggleCombat: () => toggles++);
@ -779,7 +780,7 @@ public class ToolbarControllerTests
layout,
repo,
Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(1u),
useItem: id => used = id,
selectItem: id => selected = id);
@ -816,7 +817,7 @@ public class ToolbarControllerTests
layout,
repo,
Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(1u),
useItem: _ => { },
itemInteraction: interaction,
selectItem: id => selection.Select(id, SelectionChangeSource.Toolbar),
@ -870,7 +871,7 @@ public class ToolbarControllerTests
layout,
repo,
Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(1u),
useItem: _ => { },
itemInteraction: interaction);
@ -909,7 +910,7 @@ public class ToolbarControllerTests
layout,
repo,
new ShortcutStore(),
iconIds: (_, _, _, _, _) => 1u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(1u),
useItem: _ => { },
itemInteraction: interaction,
sendAddShortcut: entry => sends.Add(((uint)entry.Index, entry.ObjectId)));
@ -935,7 +936,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) =>0u, useItem: _ => { });
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { });
// Only peace indicator (index 0 = 0x10000192) is visible.
Assert.True (indicators[0x10000192u].Visible, "peace indicator should be visible after bind");
@ -955,7 +956,7 @@ public class ToolbarControllerTests
var ctrl = ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) =>0u, useItem: _ => { });
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { });
ctrl.SetCombatMode(CombatMode.Melee);
@ -977,7 +978,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) =>0u, useItem: _ => { },
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { },
combatState: combat);
// Initially NonCombat after bind.
@ -1016,7 +1017,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted);
// Top row: ShortcutNum == slot index, ghosted == false.
@ -1045,7 +1046,7 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
var ctrl = ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted);
ctrl.SetCombatMode(mode);
@ -1055,7 +1056,7 @@ public class ToolbarControllerTests
var cell = slots[Row1[i]].Cell;
Assert.Equal(i, cell.ShortcutNum);
Assert.False(cell.ShortcutGhosted, $"top-row slot {i} should be regular in {mode}");
cell.SetItem((uint)(0x5000 + i), 0x99u);
cell.SetItem((uint)(0x5000 + i), new GpuTextureSlot(0x99u));
Assert.Same(FakeRegular, cell.ActiveDigitArray());
}
// Bottom row still has no number.
@ -1073,13 +1074,13 @@ public class ToolbarControllerTests
var repo = new ClientObjectTable();
var ctrl = ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted);
ctrl.SetCombatMode(CombatMode.Magic);
foreach (var id in Row1)
{
slots[id].Cell.SetItem(id, 0x99u);
slots[id].Cell.SetItem(id, new GpuTextureSlot(0x99u));
Assert.True(slots[id].Cell.ShortcutGhosted);
Assert.Same(FakeGhosted, slots[id].Cell.ActiveDigitArray());
}
@ -1105,7 +1106,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted);
foreach (var id in Row1)
@ -1127,7 +1128,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted, emptyDigits: FakeEmpty);
foreach (var id in Row1)
@ -1148,7 +1149,7 @@ public class ToolbarControllerTests
ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { },
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { },
regularDigits: FakeRegular, ghostedDigits: FakeGhosted, emptyDigits: null);
foreach (var id in Row1)
@ -1174,7 +1175,7 @@ public class ToolbarControllerTests
int iconCallCount = 0;
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => { iconCallCount++; return 0x77u; }, useItem: _ => { });
iconIds: (_,_,_,_,_) => { iconCallCount++; return new GpuTextureSlot(0x77u); }, useItem: _ => { });
int callsAfterBind = iconCallCount; // 1 call from initial Populate
@ -1199,7 +1200,7 @@ public class ToolbarControllerTests
int iconCallCount = 0;
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => { iconCallCount++; return 0x99u; }, useItem: _ => { });
iconIds: (_,_,_,_,_) => { iconCallCount++; return new GpuTextureSlot(0x99u); }, useItem: _ => { });
Assert.Equal(0, iconCallCount); // not called — item absent during initial Populate
Assert.Equal(0u, slots[Row1[1]].Cell.ItemId);
@ -1225,7 +1226,7 @@ public class ToolbarControllerTests
{ new(Index: 3, ObjectId: 0x5004u, SpellId: 0) };
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0xAAu, useItem: _ => { });
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0xAAu), useItem: _ => { });
Assert.Equal(0x5004u, slots[Row1[3]].Cell.ItemId); // bound
@ -1252,7 +1253,7 @@ public class ToolbarControllerTests
int iconCallCount = 0;
ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => { iconCallCount++; return 0xBBu; }, useItem: _ => { });
iconIds: (_,_,_,_,_) => { iconCallCount++; return new GpuTextureSlot(0xBBu); }, useItem: _ => { });
int callsAfterBind = iconCallCount; // 1 call for the shortcut item
@ -1274,7 +1275,7 @@ public class ToolbarControllerTests
var ctrl = ToolbarController.Bind(layout, repo,
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { });
for (int i = 0; i < Row1.Length; i++)
{
@ -1299,7 +1300,7 @@ public class ToolbarControllerTests
var (layout, slots, _) = FakeToolbar();
var ctrl = ToolbarController.Bind(layout, new ClientObjectTable(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { });
var list = slots[Row1[0]];
var payload = new ItemDragPayload(0x5001u, ItemDragSource.Inventory, 0, new UiItemSlot());
@ -1314,7 +1315,7 @@ public class ToolbarControllerTests
var (layout, slots, _) = FakeToolbar();
var ctrl = ToolbarController.Bind(layout, new ClientObjectTable(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { });
var list = slots[Row1[0]];
var payload = new ItemDragPayload(0u, ItemDragSource.Inventory, 0, new UiItemSlot());
Assert.Equal(ItemDragAcceptance.None, ctrl.OnDragOver(list, list.Cell, payload));
@ -1343,7 +1344,7 @@ public class ToolbarControllerTests
uint selected = 0;
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { },
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0x77u), useItem: _ => { },
sendAddShortcut: add, sendRemoveShortcut: rem,
selectItem: item => selected = item, selectedObjectId: () => selected);
Assert.Equal(0x5001u, slots[Row1[3]].Cell.ItemId);
@ -1368,7 +1369,7 @@ public class ToolbarControllerTests
new(Index: 5, ObjectId: 0x5002u, SpellId: 0) };
var (adds, removes) = NewSpies(out var add, out var rem);
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { },
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0x77u), useItem: _ => { },
sendAddShortcut: add, sendRemoveShortcut: rem);
var payload = new ItemDragPayload(0x5001u, ItemDragSource.ShortcutBar, 3, slots[Row1[3]].Cell);
@ -1397,7 +1398,7 @@ public class ToolbarControllerTests
var (adds, _) = NewSpies(out var add, out var remove);
var controller = ToolbarController.Bind(
layout, repo, Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(1u),
useItem: _ => { },
sendAddShortcut: add,
sendRemoveShortcut: remove);
@ -1420,7 +1421,7 @@ public class ToolbarControllerTests
{ new(Index: 3, ObjectId: 0x5001u, SpellId: 0) };
var (adds, removes) = NewSpies(out var add, out var rem);
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { },
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0x77u), useItem: _ => { },
sendAddShortcut: add, sendRemoveShortcut: rem);
var payload = new ItemDragPayload(0x5001u, ItemDragSource.ShortcutBar, 3, slots[Row1[3]].Cell);
@ -1446,7 +1447,7 @@ public class ToolbarControllerTests
};
var wire = new List<string>();
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(1u),
useItem: _ => { },
sendAddShortcut: entry => wire.Add($"add:{entry.Index}:{entry.ObjectId:X8}:{entry.SpellId:X8}"),
sendRemoveShortcut: slot => wire.Add($"remove:{slot}"));
@ -1479,7 +1480,7 @@ public class ToolbarControllerTests
};
var wire = new List<string>();
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_, _, _, _, _) => 1u,
iconIds: (_, _, _, _, _) => new GpuTextureSlot(1u),
useItem: _ => { },
sendAddShortcut: entry => wire.Add($"add:{entry.Index}:{entry.ObjectId:X8}:{entry.SpellId:X8}"),
sendRemoveShortcut: slot => wire.Add($"remove:{slot}"));
@ -1504,7 +1505,7 @@ public class ToolbarControllerTests
{ new(Index: 3, ObjectId: 0x5001u, SpellId: 0) };
var (adds, removes) = NewSpies(out var add, out var rem);
var ctrl = ToolbarController.Bind(layout, repo, Store(shortcuts),
iconIds: (_,_,_,_,_) => 0x77u, useItem: _ => { },
iconIds: (_,_,_,_,_) => new GpuTextureSlot(0x77u), useItem: _ => { },
sendAddShortcut: add, sendRemoveShortcut: rem);
var payload = new ItemDragPayload(0x5001u, ItemDragSource.ShortcutBar, 3, slots[Row1[3]].Cell);
@ -1523,7 +1524,7 @@ public class ToolbarControllerTests
var (layout, slots, _) = FakeToolbar();
ToolbarController.Bind(layout, new ClientObjectTable(),
new ShortcutStore(),
iconIds: (_,_,_,_,_) => 0u, useItem: _ => { });
iconIds: (_,_,_,_,_) => GpuTextureSlot.Unassigned, useItem: _ => { });
Assert.Equal(0x060011FAu, slots[Row1[0]].Cell.DragAcceptSprite); // green cross, not the ring F9
}
}

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI.Layout;
@ -10,7 +11,7 @@ public class UiDatElementTests
var info = new ElementInfo();
info.StateMedia[""] = (0x06000001, 1); // DirectState (DrawMode Normal=1)
info.StateMedia["ShowDetail"] = (0x06000002, 3); // named (Alphablend=3)
var e = new UiDatElement(info, _ => (0, 0, 0)) { ActiveState = "ShowDetail" };
var e = new UiDatElement(info, _ => (GpuTextureSlot.Unassigned, 0, 0)) { ActiveState = "ShowDetail" };
Assert.Equal(0x06000002u, e.ActiveMedia().File);
Assert.Equal(3, e.ActiveMedia().DrawMode);
e.ActiveState = "";
@ -21,7 +22,7 @@ public class UiDatElementTests
[Fact]
public void ActiveMedia_NoMedia_ReturnsZero()
{
var e = new UiDatElement(new ElementInfo(), _ => (0, 0, 0));
var e = new UiDatElement(new ElementInfo(), _ => (GpuTextureSlot.Unassigned, 0, 0));
Assert.Equal(0u, e.ActiveMedia().File);
Assert.Equal(0, e.ActiveMedia().DrawMode);
}
@ -31,7 +32,7 @@ public class UiDatElementTests
{
var info = new ElementInfo();
info.StateMedia[""] = (0x06000005, 1);
var e = new UiDatElement(info, _ => (0, 0, 0)) { ActiveState = "NoSuchState" };
var e = new UiDatElement(info, _ => (GpuTextureSlot.Unassigned, 0, 0)) { ActiveState = "NoSuchState" };
Assert.Equal(0x06000005u, e.ActiveMedia().File);
}
@ -49,7 +50,7 @@ public class UiDatElementTests
info.StateMedia["Normal"] = (0x0000AAAAu, 1);
info.StateMedia["Hover"] = (0x0000BBBBu, 1);
var e = new UiDatElement(info, _ => (0, 0, 0));
var e = new UiDatElement(info, _ => (GpuTextureSlot.Unassigned, 0, 0));
// Should have defaulted to "Normal" state.
Assert.Equal(0x0000AAAAu, e.ActiveMedia().File);
@ -67,7 +68,7 @@ public class UiDatElementTests
info.StateMedia["Maximized"] = (0x0000CCCCu, 1);
info.StateMedia["Normal"] = (0x0000DDDDu, 1);
var e = new UiDatElement(info, _ => (0, 0, 0));
var e = new UiDatElement(info, _ => (GpuTextureSlot.Unassigned, 0, 0));
// DefaultStateName "Minimized" wins over "Normal" implicit default.
Assert.Equal(0x0000BBBBu, e.ActiveMedia().File);
@ -83,7 +84,7 @@ public class UiDatElementTests
var info = new ElementInfo();
info.StateMedia[""] = (0x06007777u, 1); // DirectState only (e.g. vitals chrome corner)
var e = new UiDatElement(info, _ => (0, 0, 0));
var e = new UiDatElement(info, _ => (GpuTextureSlot.Unassigned, 0, 0));
// No DefaultStateName, no "Normal" state → ActiveState stays "" (DirectState).
Assert.Equal(0x06007777u, e.ActiveMedia().File);
@ -100,7 +101,7 @@ public class UiDatElementTests
Image = new UiImageMedia(0x06000009u, 3),
};
info.StateMedia["ShowDetail"] = (0x06000009u, 3);
var element = new UiDatElement(info, _ => (0u, 0, 0))
var element = new UiDatElement(info, _ => (GpuTextureSlot.Unassigned, 0, 0))
{
Left = info.X,
Top = info.Y,
@ -121,7 +122,7 @@ public class UiDatElementTests
{
var info = new ElementInfo { DefaultStateName = "Normal" };
info.StateMedia["Normal"] = (0x06000001u, 1);
var element = new UiDatElement(info, _ => (0u, 0, 0));
var element = new UiDatElement(info, _ => (GpuTextureSlot.Unassigned, 0, 0));
Assert.False(element.TrySetRetailState(RetailUiStateIds.ShowDetail));
Assert.Equal("Normal", element.ActiveState);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -5,7 +6,7 @@ namespace AcDream.App.Tests.UI.Layout;
public class UiViewportFactoryTests
{
private static (uint, int, int) NoTex(uint _) => (0, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
[Fact]
public void Factory_builds_UiViewport_for_dat_type_0xD()

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
namespace AcDream.App.Tests.UI;
@ -22,7 +23,7 @@ public class MarkupDocumentTests
" <meter id=\"health\" x=\"8\" y=\"24\" w=\"200\" h=\"14\" fill=\"{HealthPercent}\" cur=\"{HealthCurrent}\" max=\"{HealthMax}\" color=\"#FFFF0000\"/>" +
"</panel>";
var panel = MarkupDocument.Build(xml, new FakeBinding(), _ => ((uint)1, 32, 32));
var panel = MarkupDocument.Build(xml, new FakeBinding(), _ => (new GpuTextureSlot(1), 32, 32));
Assert.IsType<UiNineSlicePanel>(panel);
Assert.Equal(10f, panel.Left);
@ -42,7 +43,7 @@ public class MarkupDocumentTests
"<panel id=\"v\" x=\"0\" y=\"0\" w=\"10\" h=\"10\" title=\"V\">" +
" <meter id=\"mana\" x=\"0\" y=\"0\" w=\"10\" h=\"2\" fill=\"{ManaPercent}\" cur=\"{ManaCurrent}\" max=\"{ManaMax}\" color=\"#FF0000FF\"/>" +
"</panel>";
var panel = MarkupDocument.Build(xml, new FakeBinding(), _ => ((uint)1, 32, 32));
var panel = MarkupDocument.Build(xml, new FakeBinding(), _ => (new GpuTextureSlot(1), 32, 32));
var meter = Assert.IsType<UiMeter>(panel.Children[1]);
Assert.Null(meter.Fill());
Assert.Null(meter.Label());
@ -52,7 +53,7 @@ public class MarkupDocumentTests
public void Build_ResizeAttrX_SetsHorizontalOnly()
{
const string xml = "<panel id=\"v\" x=\"0\" y=\"0\" w=\"100\" h=\"50\" title=\"V\" resize=\"x\"></panel>";
var panel = MarkupDocument.Build(xml, new object(), _ => ((uint)1, 32, 32));
var panel = MarkupDocument.Build(xml, new object(), _ => (new GpuTextureSlot(1), 32, 32));
Assert.True(panel.ResizeX);
Assert.False(panel.ResizeY);
}
@ -65,7 +66,7 @@ public class MarkupDocumentTests
"backleft=\"0x06001141\" backtile=\"0x06001140\" backright=\"0x0600113F\" " +
"frontleft=\"0x06001131\" fronttile=\"0x06001132\" frontright=\"0x06001133\"/>" +
"</panel>";
var panel = MarkupDocument.Build(xml, new FakeBinding(), _ => ((uint)7, 32, 32));
var panel = MarkupDocument.Build(xml, new FakeBinding(), _ => (new GpuTextureSlot(7), 32, 32));
var meter = Assert.IsType<UiMeter>(panel.Children[1]);
Assert.Equal(0x06001141u, meter.BackLeft);
Assert.Equal(0x06001140u, meter.BackTile);

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Collections.Generic;
using System.IO;
using AcDream.App.UI;
@ -96,7 +97,7 @@ public sealed class RetailUiAutomationProbeTests
var root = new UiRoot { Width = 240, Height = 120 };
var objects = new ClientObjectTable();
var source = new UiItemList(_ => (1u, 1, 1))
var source = new UiItemList(_ => (new GpuTextureSlot(1u), 1, 1))
{
DatElementId = 0x10000010u,
Left = 10,
@ -106,9 +107,9 @@ public sealed class RetailUiAutomationProbeTests
};
source.Cell.SlotIndex = 0;
source.Cell.SourceKind = ItemDragSource.Inventory;
source.Cell.SetItem(0x5001u, 0x99u);
source.Cell.SetItem(0x5001u, new GpuTextureSlot(0x99u));
var target = new UiItemList(_ => (1u, 1, 1))
var target = new UiItemList(_ => (new GpuTextureSlot(1u), 1, 1))
{
DatElementId = 0x10000020u,
Left = 70,

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Collections.Generic;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -68,7 +69,7 @@ public sealed class RetailUiInteractionFlowTests
height: 32);
var slotsButton = new UiButton(
new ElementInfo { Id = SlotsButtonId, Type = 1 },
static _ => (0u, 0, 0))
static _ => (GpuTextureSlot.Unassigned, 0, 0))
{
DatElementId = SlotsButtonId,
Left = 270,
@ -86,7 +87,7 @@ public sealed class RetailUiInteractionFlowTests
};
var dollDragMask = new UiButton(
new ElementInfo { Id = PaperdollController.DollDragMaskId, Type = 1 },
static _ => (0u, 0, 0))
static _ => (GpuTextureSlot.Unassigned, 0, 0))
{
DatElementId = PaperdollController.DollDragMaskId,
Left = 270,
@ -178,7 +179,7 @@ public sealed class RetailUiInteractionFlowTests
Layout,
Objects,
playerGuid: () => Player,
iconIds: static (_, _, _, _, _) => 0x1234u,
iconIds: static (_, _, _, _, _) => new GpuTextureSlot(0x1234u),
strength: () => 100,
selection: Selection,
datFont: null,
@ -212,7 +213,7 @@ public sealed class RetailUiInteractionFlowTests
Layout,
Objects,
playerGuid: () => Player,
iconIds: static (_, _, _, _, _) => 0x1234u,
iconIds: static (_, _, _, _, _) => new GpuTextureSlot(0x1234u),
selection: Selection,
itemInteraction: itemInteraction,
emptySlotSprite: 0x06004D20u,
@ -265,7 +266,7 @@ public sealed class RetailUiInteractionFlowTests
}
private static UiItemList ItemList(uint id, float left, float top, float width, float height)
=> new(static _ => (1u, 32, 32))
=> new(static _ => (new GpuTextureSlot(1u), 32, 32))
{
DatElementId = id,
Left = left,

View file

@ -1,10 +1,11 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI;
public class UiButtonTests
{
private static (uint, int, int) NoTex(uint _) => (0, 0, 0);
private static (GpuTextureSlot, int, int) NoTex(uint _) => (GpuTextureSlot.Unassigned, 0, 0);
private bool _clicked;
[Fact]

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
using Xunit;
@ -7,7 +8,7 @@ public class UiCollapsibleFrameTests
{
private static UiCollapsibleFrame MakeFrame(out UiPanel row2a, out UiPanel row2b)
{
var f = new UiCollapsibleFrame(_ => (1u, 1, 1))
var f = new UiCollapsibleFrame(_ => (new GpuTextureSlot(1u), 1, 1))
{
CollapsedHeight = 96f,
ExpandedHeight = 128f,
@ -44,7 +45,7 @@ public class UiCollapsibleFrameTests
[Fact]
public void Tick_notConfigured_isNoOp()
{
var f = new UiCollapsibleFrame(_ => (1u, 1, 1)); // Collapsed==Expanded==0
var f = new UiCollapsibleFrame(_ => (new GpuTextureSlot(1u), 1, 1)); // Collapsed==Expanded==0
f.Height = 50f;
f.TickForTest(0.016);
Assert.Equal(50f, f.Height); // unchanged, no divide/no forced height

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Collections.Generic;
using AcDream.App.UI;
using DatReaderWriter.Types;
@ -91,10 +92,10 @@ public class UiDatFontTests
['B'] = Glyph('B', width: 7, before: 1, after: 1),
};
var font = new UiDatFont(
fgTex: 0,
fgTex: GpuTextureSlot.Unassigned,
fgW: 0,
fgH: 0,
bgTex: 0,
bgTex: GpuTextureSlot.Unassigned,
bgW: 0,
bgH: 0,
lineHeight: 16f,

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using AcDream.App.UI;
namespace AcDream.App.Tests.UI;
@ -21,26 +22,26 @@ public class UiItemSlotTests
{
var s = new UiItemSlot();
Assert.Equal(0u, s.ItemId);
Assert.Equal(0u, s.IconTexture);
Assert.Equal(GpuTextureSlot.Unassigned, s.IconTexture);
}
[Fact]
public void SetItem_setsIdAndTexture()
{
var s = new UiItemSlot();
s.SetItem(0x5001u, 0x99u);
s.SetItem(0x5001u, new GpuTextureSlot(0x99u));
Assert.Equal(0x5001u, s.ItemId);
Assert.Equal(0x99u, s.IconTexture);
Assert.Equal(new GpuTextureSlot(0x99u), s.IconTexture);
}
[Fact]
public void Clear_afterSetItem_resetsToEmpty()
{
var s = new UiItemSlot();
s.SetItem(0x5001u, 0x99u);
s.SetItem(0x5001u, new GpuTextureSlot(0x99u));
s.Clear();
Assert.Equal(0u, s.ItemId);
Assert.Equal(0u, s.IconTexture);
Assert.Equal(GpuTextureSlot.Unassigned, s.IconTexture);
}
[Fact]
@ -58,7 +59,7 @@ public class UiItemSlotTests
[Fact]
public void CatalogSlot_keeps_catalog_identity_separate_from_object_guid()
{
var slot = new UiCatalogSlot { EntryId = 1234u, CatalogIconTexture = 99u };
var slot = new UiCatalogSlot { EntryId = 1234u, CatalogIconTexture = new GpuTextureSlot(99u) };
Assert.Equal(1234u, slot.EntryId);
Assert.Equal(0u, slot.ItemId);
@ -147,7 +148,7 @@ public class UiItemSlotTests
public void ActiveDigitArray_occupiedSlot_regular_returnsRegularDigits()
{
var s = new UiItemSlot { RegularDigits = Regular, GhostedDigits = Ghosted, EmptyDigits = Empty };
s.SetItem(0x5001u, 0x99u);
s.SetItem(0x5001u, new GpuTextureSlot(0x99u));
s.SetShortcutNum(0, ghosted: false);
Assert.Same(Regular, s.ActiveDigitArray());
}
@ -156,7 +157,7 @@ public class UiItemSlotTests
public void ActiveDigitArray_occupiedSlot_ghosted_returnsGhostedDigits()
{
var s = new UiItemSlot { RegularDigits = Regular, GhostedDigits = Ghosted, EmptyDigits = Empty };
s.SetItem(0x5001u, 0x99u);
s.SetItem(0x5001u, new GpuTextureSlot(0x99u));
s.SetShortcutNum(0, ghosted: true);
Assert.Same(Ghosted, s.ActiveDigitArray());
}
@ -208,7 +209,7 @@ public class UiItemSlotTests
CooldownSprites = sprites,
CooldownStepProvider = id => id == 0x5001u ? 6 : 0,
};
slot.SetItem(0x5001u, 99u);
slot.SetItem(0x5001u, new GpuTextureSlot(99u));
Assert.Equal(0x06000006u, slot.ActiveCooldownSprite());
}
@ -223,7 +224,7 @@ public class UiItemSlotTests
};
Assert.Equal(0u, slot.ActiveCooldownSprite());
slot.SetItem(0x5001u, 99u);
slot.SetItem(0x5001u, new GpuTextureSlot(99u));
Assert.Equal(0u, slot.ActiveCooldownSprite());
}
}

View file

@ -1,3 +1,4 @@
using AcDream.App.Rendering.Gpu;
using System.Numerics;
using AcDream.App.UI;
@ -10,7 +11,7 @@ public class UiRootInputTests
{
// Regression: the per-frame anchor pass must NOT reset a window's rect,
// or move/resize get undone every frame. Windows are user-positioned.
var panel = new UiNineSlicePanel(_ => ((uint)1, 32, 32));
var panel = new UiNineSlicePanel(_ => (new GpuTextureSlot(1), 32, 32));
Assert.Equal(AnchorEdges.None, panel.Anchors);
}
@ -119,7 +120,7 @@ public class UiRootInputTests
{
var root = new UiRoot { Width = 800, Height = 600 };
var cell = new UiItemSlot { Left = 0, Top = 0, Width = 32, Height = 32 };
cell.SetItem(0x50000A01u, 0x99u);
cell.SetItem(0x50000A01u, new GpuTextureSlot(0x99u));
root.AddChild(cell);
object? payload = null;
int releaseX = 0;