Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions src/CodexPetLimitWin/ApplicationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,15 @@ private void UpdatePetFrame()

lastDetectedPetOverlayFrame = petWindowProbe.TryFindVisiblePetOverlay(lastPetState.OverlayFrame);
lastPetWindowFound = lastDetectedPetOverlayFrame is not null;
if (lastDetectedPetOverlayFrame is null)
{
HandleMissingPet("pet-window-missing");
return;
}

consecutivePetMisses = 0;
lastOverlayStatus = "shown";
overlayWindow.ShowAroundPet(RebasePetFrame(lastPetState.PetFrame, lastPetState.OverlayFrame, lastDetectedPetOverlayFrame));
lastOverlayStatus = lastDetectedPetOverlayFrame is null
? "shown: state-bounds"
: "shown: native-window-bounds";
overlayWindow.ShowAroundPet(PetFramePlacement.Resolve(
lastPetState.PetFrame,
lastPetState.OverlayFrame,
lastDetectedPetOverlayFrame));
}
finally
{
Expand Down Expand Up @@ -316,15 +316,6 @@ private TrayMenuState CreateTrayState(bool canRefresh)
return new TrayMenuState(CreateStatusText(), settings.RingsVisible, settings.LiveUsageEnabled, canRefresh);
}

private static PetFrame RebasePetFrame(PetFrame petFrame, PetFrame stateOverlayFrame, PetFrame detectedOverlayFrame)
{
return new PetFrame(
detectedOverlayFrame.X + (petFrame.X - stateOverlayFrame.X),
detectedOverlayFrame.Y + (petFrame.Y - stateOverlayFrame.Y),
petFrame.Width,
petFrame.Height);
}

private ControlsState CreateControlsState()
{
return new ControlsState(
Expand Down
27 changes: 27 additions & 0 deletions src/CodexPetLimitWin/Codex/PetFramePlacement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace CodexPetLimitWin.Codex;

/// <summary>
/// Chooses the frame used to place the companion overlay.
/// The Codex global state is authoritative for whether the pet is open and
/// supplies a usable frame on current Codex versions. A detected native window
/// is used only when available to compensate for a transient position change.
/// </summary>
public static class PetFramePlacement
{
public static PetFrame Resolve(PetFrame petFrame, PetFrame stateOverlayFrame, PetFrame? detectedOverlayFrame)
{
ArgumentNullException.ThrowIfNull(petFrame);
ArgumentNullException.ThrowIfNull(stateOverlayFrame);

if (detectedOverlayFrame is null)
{
return petFrame;
}

return new PetFrame(
detectedOverlayFrame.X + (petFrame.X - stateOverlayFrame.X),
detectedOverlayFrame.Y + (petFrame.Y - stateOverlayFrame.Y),
petFrame.Width,
petFrame.Height);
}
}
33 changes: 33 additions & 0 deletions tests/CodexPetLimitWin.Tests/PetFramePlacementTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using CodexPetLimitWin.Codex;
using Xunit;

namespace CodexPetLimitWin.Tests;

public sealed class PetFramePlacementTests
{
[Fact]
public void ResolveUsesStateFrameWhenNoVisibleNativePetWindowIsFound()
{
var pet = new PetFrame(1784, 584, 80, 87);
var overlay = new PetFrame(1536, 486, 356, 320);

var resolved = PetFramePlacement.Resolve(pet, overlay, detectedOverlayFrame: null);

Assert.Equal(pet, resolved);
}

[Fact]
public void ResolveRebasesStateFrameWhenNativePetWindowIsFound()
{
var pet = new PetFrame(1784, 584, 80, 87);
var stateOverlay = new PetFrame(1536, 486, 356, 320);
var detectedOverlay = new PetFrame(1600, 520, 356, 320);

var resolved = PetFramePlacement.Resolve(pet, stateOverlay, detectedOverlay);

Assert.Equal(1848, resolved.X);
Assert.Equal(618, resolved.Y);
Assert.Equal(80, resolved.Width);
Assert.Equal(87, resolved.Height);
}
}