feat: initial release of SplitOverlay (KeyOverlay 1.0.0)
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
Semi-transparent always-on-top Windows overlay that shows the keymap of a Vial/QMK split keyboard (Silakka54). - draws all layers from a Vial .vil layout, transparent keys fall through - detects the active layer from the keyboard via a Raw HID command (firmware/) - shows characters of the active Windows input language - can split the two halves into separate, independently movable windows - highlights pressed keys; tray menu for opacity, size and options - Inno Setup installer and portable build; GitHub-style CI workflow
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
using KeyOverlay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace KeyOverlay.Core.Tests;
|
||||
|
||||
public class KeyLocalizerTests
|
||||
{
|
||||
// Pretends to be a Russian layout: only a few keys are mapped.
|
||||
private static string? FakeRussian(string keycode, bool shift) => (keycode, shift) switch
|
||||
{
|
||||
("KC_Q", false) => "й",
|
||||
("KC_Q", true) => "Й",
|
||||
("KC_1", true) => "!",
|
||||
("KC_SLASH", true) => ",",
|
||||
("KC_COMMA", false) => "б",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Letter_IsShownUppercaseInTheActiveLanguage()
|
||||
{
|
||||
var localized = KeyLocalizer.Localize(Keycodes.Describe("KC_Q"), FakeRussian);
|
||||
Assert.Equal("Й", localized.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftWrappedKey_UsesShiftedCharacter()
|
||||
{
|
||||
var localized = KeyLocalizer.Localize(Keycodes.Describe("LSFT(KC_SLASH)"), FakeRussian);
|
||||
Assert.Equal(",", localized.Main);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Punctuation_IsReplacedWithLayoutCharacter()
|
||||
{
|
||||
var localized = KeyLocalizer.Localize(Keycodes.Describe("KC_COMMA"), FakeRussian);
|
||||
Assert.Equal("Б", localized.Main); // a letter on a punctuation key is still shown uppercase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Localizing_KeepsBaseKeycodeForHighlighting()
|
||||
{
|
||||
var localized = KeyLocalizer.Localize(Keycodes.Describe("KC_Q"), FakeRussian);
|
||||
Assert.Equal("KC_Q", localized.BaseKeycode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModTap_KeepsModifierHintAndLocalizesTapKey()
|
||||
{
|
||||
var localized = KeyLocalizer.Localize(Keycodes.Describe("LCTL_T(KC_Q)"), FakeRussian);
|
||||
Assert.Equal("Й", localized.Main);
|
||||
Assert.Equal("Ctrl", localized.Sub);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("KC_ESCAPE")]
|
||||
[InlineData("KC_SPACE")]
|
||||
[InlineData("KC_LSHIFT")]
|
||||
[InlineData("KC_RIGHT")]
|
||||
[InlineData("MO(2)")]
|
||||
[InlineData("LCTL(KC_Q)")]
|
||||
public void NonCharacterKeys_AreLeftUntouched(string code)
|
||||
{
|
||||
var original = Keycodes.Describe(code);
|
||||
Assert.Equal(original, KeyLocalizer.Localize(original, FakeRussian));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownCharacter_FallsBackToDefaultLabel()
|
||||
{
|
||||
var original = Keycodes.Describe("KC_W"); // FakeRussian has no mapping for W
|
||||
Assert.Equal(original, KeyLocalizer.Localize(original, FakeRussian));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ControlCharacterResult_FallsBackToDefaultLabel()
|
||||
{
|
||||
var original = Keycodes.Describe("KC_Q");
|
||||
var localized = KeyLocalizer.Localize(original, (_, _) => "\u0011");
|
||||
Assert.Equal(original, localized);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("KC_A", true)]
|
||||
[InlineData("KC_5", true)]
|
||||
[InlineData("KC_GRAVE", true)]
|
||||
[InlineData("KC_SCOLON", true)]
|
||||
[InlineData("KC_F1", false)]
|
||||
[InlineData("KC_ENTER", false)]
|
||||
public void IsCharacterKey_CoversLettersDigitsAndPunctuation(string code, bool expected) =>
|
||||
Assert.Equal(expected, Keycodes.IsCharacterKey(code));
|
||||
|
||||
[Theory]
|
||||
[InlineData("KC_A", 0x1E)]
|
||||
[InlineData("KC_1", 0x02)]
|
||||
[InlineData("KC_SLASH", 0x35)]
|
||||
public void ToScanCode_ReturnsPhysicalKeyCode(string code, int expected) =>
|
||||
Assert.Equal(expected, ScanCodes.ToScanCode(code));
|
||||
|
||||
[Fact]
|
||||
public void ToScanCode_ReturnsNullForUnknownKeycode() =>
|
||||
Assert.Null(ScanCodes.ToScanCode("KC_NOPE"));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\KeyOverlay.Core\KeyOverlay.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,102 @@
|
||||
using KeyOverlay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace KeyOverlay.Core.Tests;
|
||||
|
||||
public class KeycodesTests
|
||||
{
|
||||
[Fact]
|
||||
public void Letter_IsUppercaseWithBaseKeycode()
|
||||
{
|
||||
var d = Keycodes.Describe("KC_A");
|
||||
Assert.Equal("A", d.Main);
|
||||
Assert.Equal("KC_A", d.BaseKeycode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShortQmkAlias_MatchesLongVialName()
|
||||
{
|
||||
Assert.Equal(Keycodes.Describe("KC_BSPACE").Main, Keycodes.Describe("KC_BSPC").Main);
|
||||
Assert.Equal("KC_BSPACE", Keycodes.Describe("KC_BSPC").BaseKeycode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftWrapper_ShowsShiftedSymbol()
|
||||
{
|
||||
var d = Keycodes.Describe("LSFT(KC_1)");
|
||||
Assert.Equal("!", d.Main);
|
||||
Assert.True(d.RequiresShift);
|
||||
Assert.Equal("KC_1", d.BaseKeycode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("LSFT(KC_MINUS)", "_")]
|
||||
[InlineData("LSFT(KC_BSLASH)", "|")]
|
||||
[InlineData("LSFT(KC_GRAVE)", "~")]
|
||||
[InlineData("LSFT(KC_QUOTE)", "\"")]
|
||||
public void ShiftWrapper_Symbols(string code, string expected) =>
|
||||
Assert.Equal(expected, Keycodes.Describe(code).Main);
|
||||
|
||||
[Fact]
|
||||
public void CtrlWrapper_ShowsPrefix()
|
||||
{
|
||||
var d = Keycodes.Describe("LCTL(KC_C)");
|
||||
Assert.Equal("Ctrl+C", d.Main);
|
||||
Assert.False(d.RequiresShift);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("MO(2)", "MO 2")]
|
||||
[InlineData("TG(1)", "TG 1")]
|
||||
[InlineData("TO(0)", "TO 0")]
|
||||
public void LayerSwitch_IsFlagged(string code, string main)
|
||||
{
|
||||
var d = Keycodes.Describe(code);
|
||||
Assert.Equal(main, d.Main);
|
||||
Assert.True(d.IsLayerSwitch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerTap_ShowsTapKeyAndLayer()
|
||||
{
|
||||
var d = Keycodes.Describe("LT(1, KC_SPACE)");
|
||||
Assert.Equal("Space", d.Main);
|
||||
Assert.Equal("L1", d.Sub);
|
||||
Assert.Equal("KC_SPACE", d.BaseKeycode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("LCTL_T(KC_A)")]
|
||||
[InlineData("MT(MOD_LCTL, KC_A)")]
|
||||
public void ModTap_ShowsTapKeyAndModifier(string code)
|
||||
{
|
||||
var d = Keycodes.Describe(code);
|
||||
Assert.Equal("A", d.Main);
|
||||
Assert.Equal("Ctrl", d.Sub);
|
||||
Assert.Equal("KC_A", d.BaseKeycode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("KC_TRNS")]
|
||||
[InlineData("KC_TRANSPARENT")]
|
||||
public void Transparent_IsFlagged(string code) =>
|
||||
Assert.True(Keycodes.Describe(code).IsTransparent);
|
||||
|
||||
[Fact]
|
||||
public void No_IsEmpty() => Assert.True(Keycodes.Describe("KC_NO").IsEmpty);
|
||||
|
||||
[Fact]
|
||||
public void UnknownKeycode_FallsBackToTrimmedName()
|
||||
{
|
||||
var d = Keycodes.Describe("QK_BOOT");
|
||||
Assert.Equal("QK_BOOT", d.Main);
|
||||
Assert.Null(d.BaseKeycode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Canonicalize_ResolvesAliases()
|
||||
{
|
||||
Assert.Equal("KC_ESCAPE", Keycodes.Canonicalize("KC_ESC"));
|
||||
Assert.Equal("KC_LSHIFT", Keycodes.Canonicalize("KC_LSFT"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using KeyOverlay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace KeyOverlay.Core.Tests;
|
||||
|
||||
public class KeymapTests
|
||||
{
|
||||
// 2 layers, 10 matrix rows x 6 cols. Layer 1 is mostly transparent.
|
||||
private static string Vil(string layer0Row0, string layer1Row0, string layer0Row5 = "[\"KC_BSPACE\",\"KC_0\",\"KC_9\",\"KC_8\",\"KC_7\",\"KC_6\"]")
|
||||
{
|
||||
string Layer(string row0, string row5)
|
||||
{
|
||||
var rows = new List<string>();
|
||||
for (var r = 0; r < 10; r++)
|
||||
{
|
||||
rows.Add(r switch
|
||||
{
|
||||
0 => row0,
|
||||
5 => row5,
|
||||
4 or 9 => "[-1,-1,-1,\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\"]",
|
||||
_ => "[\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\"]",
|
||||
});
|
||||
}
|
||||
return "[" + string.Join(",", rows) + "]";
|
||||
}
|
||||
var tr = "[\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\"]";
|
||||
return "{\"layout\":[" + Layer(layer0Row0, layer0Row5) + "," + Layer(layer1Row0, tr) + "]}";
|
||||
}
|
||||
|
||||
private static readonly string Row0L0 = "[\"KC_ESCAPE\",\"KC_1\",\"KC_2\",\"KC_3\",\"KC_4\",\"KC_5\"]";
|
||||
private static readonly string Row0L1 = "[\"KC_GRAVE\",\"KC_F1\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"MO(2)\"]";
|
||||
|
||||
[Fact]
|
||||
public void Parse_ReadsLayerCount()
|
||||
{
|
||||
var km = Keymap.Parse(Vil(Row0L0, Row0L1));
|
||||
Assert.Equal(2, km.LayerCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_MapsLeftHalfRowsAndCols()
|
||||
{
|
||||
var km = Keymap.Parse(Vil(Row0L0, Row0L1));
|
||||
Assert.Equal("KC_ESCAPE", km.GetRaw(0, new KeyId(Side.Left, 0, 0)));
|
||||
Assert.Equal("KC_5", km.GetRaw(0, new KeyId(Side.Left, 0, 5)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_MapsRightHalfFromMatrixRows5To9()
|
||||
{
|
||||
var km = Keymap.Parse(Vil(Row0L0, Row0L1));
|
||||
Assert.Equal("KC_BSPACE", km.GetRaw(0, new KeyId(Side.Right, 0, 0)));
|
||||
Assert.Equal("KC_6", km.GetRaw(0, new KeyId(Side.Right, 0, 5)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_SkipsMinusOnePositions()
|
||||
{
|
||||
var km = Keymap.Parse(Vil(Row0L0, Row0L1));
|
||||
Assert.Null(km.GetRaw(0, new KeyId(Side.Left, 4, 0)));
|
||||
Assert.NotNull(km.GetRaw(0, new KeyId(Side.Left, 4, 3)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Describe_TransparentFallsThroughToLowerLayer()
|
||||
{
|
||||
var km = Keymap.Parse(Vil(Row0L0, Row0L1));
|
||||
var d = km.Describe(1, new KeyId(Side.Left, 0, 2));
|
||||
Assert.Equal("2", d.Main);
|
||||
Assert.True(d.FromLowerLayer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Describe_OwnKeyIsNotFromLowerLayer()
|
||||
{
|
||||
var km = Keymap.Parse(Vil(Row0L0, Row0L1));
|
||||
var d = km.Describe(1, new KeyId(Side.Left, 0, 1));
|
||||
Assert.Equal("F1", d.Main);
|
||||
Assert.False(d.FromLowerLayer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsedLayers_SkipsLayersWithoutOwnKeys()
|
||||
{
|
||||
var allTransparent = "[\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\",\"KC_TRNS\"]";
|
||||
var km = Keymap.Parse(Vil(Row0L0, allTransparent));
|
||||
Assert.Equal(new[] { 0 }, km.UsedLayers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsedLayers_IncludesLayersWithOwnKeys()
|
||||
{
|
||||
var km = Keymap.Parse(Vil(Row0L0, Row0L1));
|
||||
Assert.Equal(new[] { 0, 1 }, km.UsedLayers);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{}")]
|
||||
[InlineData("{\"layout\":[]}")]
|
||||
[InlineData("{\"layout\":[[[\"KC_A\"]]]}")]
|
||||
[InlineData("not json")]
|
||||
public void Parse_RejectsInvalidInput(string json) =>
|
||||
Assert.Throws<FormatException>(() => Keymap.Parse(json));
|
||||
|
||||
[Fact]
|
||||
public void IsLit_RequiresBaseKeyDown()
|
||||
{
|
||||
var pressed = new PressedKeys();
|
||||
var a = Keycodes.Describe("KC_A");
|
||||
Assert.False(Keymap.IsLit(a, pressed));
|
||||
pressed.Apply(0x1E, false, true);
|
||||
Assert.True(Keymap.IsLit(a, pressed));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLit_ShiftWrappedKeyNeedsShift()
|
||||
{
|
||||
var pressed = new PressedKeys();
|
||||
var bang = Keycodes.Describe("LSFT(KC_1)");
|
||||
pressed.Apply(0x02, false, true);
|
||||
Assert.False(Keymap.IsLit(bang, pressed));
|
||||
pressed.Apply(0x2A, false, true);
|
||||
Assert.True(Keymap.IsLit(bang, pressed));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using KeyOverlay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace KeyOverlay.Core.Tests;
|
||||
|
||||
public class LayerProtocolTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildRequest_Is32BytesWithCommandAndVersion()
|
||||
{
|
||||
var request = LayerProtocol.BuildRequest();
|
||||
Assert.Equal(32, request.Length);
|
||||
Assert.Equal(0xE0, request[0]);
|
||||
Assert.Equal(0x01, request[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ReadsLayerAndMasks()
|
||||
{
|
||||
var payload = new byte[32];
|
||||
payload[0] = 0xE0;
|
||||
payload[1] = 0x01;
|
||||
payload[2] = 2;
|
||||
payload[3] = 0b0000_0101; // layer_state = layers 0 and 2
|
||||
payload[7] = 0b0000_0001; // default_layer_state = layer 0
|
||||
|
||||
Assert.True(LayerProtocol.TryParse(payload, out var report));
|
||||
Assert.Equal(2, report.HighestLayer);
|
||||
Assert.Equal(5u, report.LayerState);
|
||||
Assert.Equal(1u, report.DefaultLayerState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ReadsLittleEndianMask()
|
||||
{
|
||||
var payload = new byte[32];
|
||||
payload[0] = 0xE0;
|
||||
payload[1] = 0x01;
|
||||
payload[4] = 0x01; // bit 8 of layer_state
|
||||
|
||||
Assert.True(LayerProtocol.TryParse(payload, out var report));
|
||||
Assert.Equal(0x100u, report.LayerState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_RejectsUnhandledResponse()
|
||||
{
|
||||
var payload = new byte[32];
|
||||
payload[0] = 0xFF; // old firmware: "unhandled"
|
||||
Assert.False(LayerProtocol.TryParse(payload, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_RejectsUnknownProtocolVersion()
|
||||
{
|
||||
var payload = new byte[32];
|
||||
payload[0] = 0xE0;
|
||||
payload[1] = 0x7F;
|
||||
Assert.False(LayerProtocol.TryParse(payload, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_RejectsShortPayload() =>
|
||||
Assert.False(LayerProtocol.TryParse(new byte[5], out _));
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using KeyOverlay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace KeyOverlay.Core.Tests;
|
||||
|
||||
public class ScanCodesTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0x01, false, "KC_ESCAPE")]
|
||||
[InlineData(0x1E, false, "KC_A")]
|
||||
[InlineData(0x02, false, "KC_1")]
|
||||
[InlineData(0x0B, false, "KC_0")]
|
||||
[InlineData(0x2A, false, "KC_LSHIFT")]
|
||||
[InlineData(0x1D, false, "KC_LCTRL")]
|
||||
[InlineData(0x1D, true, "KC_RCTRL")]
|
||||
[InlineData(0x38, true, "KC_RALT")]
|
||||
[InlineData(0x4B, true, "KC_LEFT")]
|
||||
[InlineData(0x3B, false, "KC_F1")]
|
||||
[InlineData(0x58, false, "KC_F12")]
|
||||
[InlineData(0x5B, true, "KC_LGUI")]
|
||||
public void ToKeycode_MapsKnownScanCodes(int scan, bool ext, string expected) =>
|
||||
Assert.Equal(expected, ScanCodes.ToKeycode(scan, ext));
|
||||
|
||||
[Fact]
|
||||
public void ToKeycode_ReturnsNullForUnknown() =>
|
||||
Assert.Null(ScanCodes.ToKeycode(0x7F, false));
|
||||
}
|
||||
|
||||
public class PressedKeysTests
|
||||
{
|
||||
[Fact]
|
||||
public void Apply_TracksDownAndUp()
|
||||
{
|
||||
var p = new PressedKeys();
|
||||
p.Apply(0x1E, false, true);
|
||||
Assert.True(p.IsDown("KC_A"));
|
||||
p.Apply(0x1E, false, false);
|
||||
Assert.False(p.IsDown("KC_A"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShiftDown_TrueForEitherShift()
|
||||
{
|
||||
var p = new PressedKeys();
|
||||
p.Apply(0x36, false, true);
|
||||
Assert.True(p.ShiftDown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CtrlAndLeftAlt_DetectsChord()
|
||||
{
|
||||
var p = new PressedKeys();
|
||||
Assert.False(p.CtrlAndLeftAltDown);
|
||||
p.Apply(0x1D, false, true);
|
||||
p.Apply(0x38, false, true);
|
||||
Assert.True(p.CtrlAndLeftAltDown);
|
||||
}
|
||||
}
|
||||
|
||||
public class BoardGeometryTests
|
||||
{
|
||||
[Fact]
|
||||
public void RightHalf_IsMirroredSoOuterColumnIsRightmost()
|
||||
{
|
||||
var outer = BoardGeometry.Position(new KeyId(Side.Right, 0, 0));
|
||||
var inner = BoardGeometry.Position(new KeyId(Side.Right, 0, 5));
|
||||
Assert.True(outer.X > inner.X);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RightHalf_IsToTheRightOfLeftHalf()
|
||||
{
|
||||
var leftInner = BoardGeometry.Position(new KeyId(Side.Left, 0, 5));
|
||||
var rightInner = BoardGeometry.Position(new KeyId(Side.Right, 0, 5));
|
||||
Assert.True(rightInner.X > leftInner.X + 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleHalf_WidthIsSixColumns() =>
|
||||
Assert.Equal(6, BoardGeometry.WidthOf(Side.Right));
|
||||
|
||||
[Fact]
|
||||
public void SingleHalf_BothWidthMatchesFullBoard() =>
|
||||
Assert.Equal(BoardGeometry.Width, BoardGeometry.WidthOf(null));
|
||||
|
||||
[Theory]
|
||||
[InlineData(Side.Left)]
|
||||
[InlineData(Side.Right)]
|
||||
public void SingleHalf_KeysStartAtOriginAndFitWidth(Side side)
|
||||
{
|
||||
for (var col = 0; col < 6; col++)
|
||||
{
|
||||
var p = BoardGeometry.Position(new KeyId(side, 0, col), side);
|
||||
Assert.InRange(p.X, 0, BoardGeometry.WidthOf(side) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleHalf_RightOuterColumnIsStillRightmost()
|
||||
{
|
||||
var outer = BoardGeometry.Position(new KeyId(Side.Right, 0, 0), Side.Right);
|
||||
var inner = BoardGeometry.Position(new KeyId(Side.Right, 0, 5), Side.Right);
|
||||
Assert.True(outer.X > inner.X);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleHalf_KeepsSameRowPositionAsFullBoard()
|
||||
{
|
||||
var key = new KeyId(Side.Right, 2, 1);
|
||||
Assert.Equal(BoardGeometry.Position(key).Y, BoardGeometry.Position(key, Side.Right).Y);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllKeys_FitInsideBoardBounds()
|
||||
{
|
||||
foreach (var side in new[] { Side.Left, Side.Right })
|
||||
{
|
||||
for (var row = 0; row < 5; row++)
|
||||
{
|
||||
for (var col = 0; col < 6; col++)
|
||||
{
|
||||
var p = BoardGeometry.Position(new KeyId(side, row, col));
|
||||
Assert.InRange(p.X, 0, BoardGeometry.Width - 1);
|
||||
Assert.InRange(p.Y, 0, BoardGeometry.Height - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user