Phase 7: cockpit overlay generator, region extraction, and profile editor
Overlay generator (pure, tested) — RioJoy.Core/Overlay:
- FontFitter ports the legacy calc-fontsize auto-fit search (validated against a
brute-force oracle); OverlayLayoutEngine ports create-data-layer's fit + h/v
justification and adds per-region 90 deg CCW rotation; OverlayTemplate/Region +
OverlayTemplateStore hold cell geometry/colour/rotation (regions.json).
- GoobieDataImporter parses the legacy .data label sheet into label rows.
- src/RioJoy.Overlay: SkiaSharp rasterizer (SkiaTextMeasurer shared with the engine
so measured layout == drawn output; SkiaOverlayRenderer -> PNG;
ProfileWallpaperGenerator). Verified end-to-end on the real cockpit art
(regions.json + riojoy.png + TEST.data) by OverlayRenderIntegrationTests.
- RioJoy.Tray/WallpaperApplier applies via SystemParametersInfo; RioCoordinator
generates+applies on profile activation (opt-in via AppConfig.OverlayTemplatePath).
Region geometry — tools/XcfRegionExtract parses riojoy.xcf (a GIMP-format binary
reader, no GIMP needed) into the 119-cell regions.json: per-layer offsets/size/
font/colour, with 90 deg CCW rotation on a-00 + b-10..b-1F. Base image riojoy.png.
NOTE: the wallpaper positions are a VGA chroma-split display artifact (6 displays),
not the cockpit's logical layout.
Mapping editor (first cut) — RioJoy.Tray/Editor/ProfileEditorForm renders the
config sheet's logical grid (SheetLayout parses the sheet CSV export). The iRIO
word is edited via ButtonBinding <-> RioMapEntry.Create (no hex bit-twiddling), with
a context-sensitive picker: keyboard keys by name (KeyCatalog VK names), joystick
Button N, hat direction, mouse/RIO-command enum names; modifiers only for keyboard.
Opened from the tray ("Edit profile..."). The sheet-grid layout is a starting point
that needs rework from a better-formatted source.
~220 xUnit tests green. Docs (PLAN.md, README) updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
using RioJoy.Core.Editing;
|
||||
using Xunit;
|
||||
|
||||
namespace RioJoy.Core.Tests.Editing;
|
||||
|
||||
public class SheetLayoutTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("0F", 0x0F)]
|
||||
[InlineData("50", 0x50)]
|
||||
[InlineData("6F", 0x6F)]
|
||||
[InlineData("00", 0x00)]
|
||||
public void AddressFromText_ParsesHexCells(string text, int expected)
|
||||
{
|
||||
Assert.Equal(expected, SheetLayout.AddressFromText(text));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("HOME")]
|
||||
[InlineData("RIO_0")]
|
||||
[InlineData("70")] // > 0x6F
|
||||
[InlineData("M U")]
|
||||
[InlineData("0")] // single digit
|
||||
public void AddressFromText_RejectsNonAddresses(string text)
|
||||
{
|
||||
Assert.Null(SheetLayout.AddressFromText(text));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x00, SheetSection.Contacts)]
|
||||
[InlineData(0x10, SheetSection.Secondary)]
|
||||
[InlineData(0x18, SheetSection.Screen)]
|
||||
[InlineData(0x2F, SheetSection.Letters)]
|
||||
[InlineData(0x38, SheetSection.Throttle)]
|
||||
[InlineData(0x40, SheetSection.Joystick)]
|
||||
[InlineData(0x50, SheetSection.Keypad)]
|
||||
[InlineData(0x6F, SheetSection.ExtKeypad)]
|
||||
public void SectionForAddress_Classifies(int address, SheetSection expected)
|
||||
{
|
||||
Assert.Equal(expected, SheetLayout.SectionForAddress(address));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BuildsCellsWithGridPositions()
|
||||
{
|
||||
const string csv =
|
||||
"\"\",\"IsLit\",\"\",\"\",\"0F\"\n" +
|
||||
"\"\",\"\",\"\",\"\",\"HOME\"";
|
||||
|
||||
IReadOnlyList<SheetCell> cells = SheetLayout.Parse(csv);
|
||||
|
||||
SheetCell addr = Assert.Single(cells, c => c.Text == "0F");
|
||||
Assert.Equal(0x0F, addr.Address);
|
||||
Assert.Equal(SheetSection.Contacts, addr.Section);
|
||||
Assert.Equal(0, addr.Row);
|
||||
Assert.Equal(4, addr.Col);
|
||||
|
||||
SheetCell key = Assert.Single(cells, c => c.Text == "HOME");
|
||||
Assert.Null(key.Address);
|
||||
Assert.Equal(1, key.Row);
|
||||
Assert.Equal(4, key.Col);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_RealSheet_HasFullAddressMap()
|
||||
{
|
||||
IReadOnlyList<SheetCell> cells = SheetLayout.Load(
|
||||
TestRepo.CustomBackground("config-sheet.csv"));
|
||||
|
||||
// Every RIO address 0x00..0x6F that exists should appear exactly once as a cell.
|
||||
var addresses = cells.Where(c => c.Address is not null).Select(c => c.Address!.Value).ToHashSet();
|
||||
Assert.Contains(0x00, addresses);
|
||||
Assert.Contains(0x2F, addresses); // a letter button
|
||||
Assert.Contains(0x50, addresses); // keypad
|
||||
Assert.Contains(0x6F, addresses); // ext keypad
|
||||
Assert.True(addresses.Count >= 100, $"expected most of the address map, got {addresses.Count}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user