# HG changeset patch # User Tess Snider # Date 2021-07-19 09:49:18 # Node ID 7117d2e703c8a82874a8fcb191bfff1452d854c9 # Parent 40eaee10ae5631b9de08ca27f4ccfeefe62a5923 Updated scanner for Tale 10, and fixed several bugs. diff --git a/Models/InterfaceSize.cs b/Models/InterfaceSize.cs deleted file mode 100644 --- a/Models/InterfaceSize.cs +++ /dev/null @@ -1,13 +0,0 @@ - -namespace DesertPaintCodex.Models -{ - public enum InterfaceSize - { - Tiny = 0, - Small = 1, - Medium = 2, - Large = 3, - Huge = 4, - Count - } -} diff --git a/Services/ReactionScannerService.cs b/Services/ReactionScannerService.cs --- a/Services/ReactionScannerService.cs +++ b/Services/ReactionScannerService.cs @@ -19,90 +19,37 @@ namespace DesertPaintCodex.Services High, Excessive } - - // Swatch is 301x20 solid color, 1px darker left/top, 1px black outer left/top, 1px light right/bottom, 1px bright right/bottom - // top-right and bottom-left are bright instead of black - - // Color bars are 4x302 solid color, 2px border on all sides (darker left/top, lighter bottom/right - - private static readonly int[] SWATCH_HEIGHT = - { - 24, // tiny - 24, // small - 24, // medium - 24, // large - 24 // huge - }; - - private static readonly int[] SWATCH_WIDTH = - { - 306, // tiny - 306, // small - 306, // medium - 320, // large - 350 // huge - }; - - private static readonly int[] COLOR_BAR_WIDTH = - { - 306, // tiny - 306, // small -- includes left and right borders - 306, // medium - 320, // large - 350 // huge - }; - private static readonly int[] RED_BAR_SPACING = - { - 32, // tiny - 32, // small - 32, // medium - 32, // large - 32 // huge - }; - - private static readonly int[] GREEN_BAR_SPACING = - { - 42, // tiny - 42, // small - 42, // medium - 42, // large - 42 // huge - }; + private const int SwatchHeight = 24; + private const int RedBarSpacing = 32; + private const int GreenBarSpacing = 42; + private const int BlueBarSpacing = 52; - private static readonly int[] BLUE_BAR_SPACING = - { - 52, // tiny - 52, // small - 52, // medium - 52, // large - 52 // huge - }; - // width to test on ends of swatch (10% on either end) - private static readonly int[] SWATCH_TEST_WIDTH = - { - 26, // tiny - 26, // small - 26, // medium - 28, // large - 31 // huge - }; + private const int MinSwatchWidth = 306; + private const int MaxSwatchWidth = 411; private static ReactionScannerService? s_instance; public static ReactionScannerService Instance => s_instance ??= new ReactionScannerService(); - private int _swatchHeight = SWATCH_HEIGHT[(int)Constants.DefaultInterfaceSize]; - private int _swatchWidth = SWATCH_WIDTH[(int)Constants.DefaultInterfaceSize]; - private int _swatchTestWidth = SWATCH_TEST_WIDTH[(int)Constants.DefaultInterfaceSize]; - private int _colorBarWidth = COLOR_BAR_WIDTH[(int)Constants.DefaultInterfaceSize]; - private int _redBarSpacing = RED_BAR_SPACING[(int)Constants.DefaultInterfaceSize]; - private int _greenBarSpacing = GREEN_BAR_SPACING[(int)Constants.DefaultInterfaceSize]; - private int _blueBarSpacing = BLUE_BAR_SPACING[(int)Constants.DefaultInterfaceSize]; + private int _swatchHeight = 0; + private int _minSwatchWidth = 0; + private int _maxSwatchWidth = 0; + private int _swatchWidth = 0; + private int _redBarSpacing = 0; + private int _greenBarSpacing = 0; + private int _blueBarSpacing = 0; + private int _swatchTopBorder = 0; + private int _swatchBottomBorder = 0; + private int _swatchLeftBorder = 0; + private int _swatchRightBorder = 0; + private int _swatchTestWidth = 0; + + private bool _lockedSwatchWidth = false; // Current Status public bool IsCaptured { get; private set; } - private PaintColor _recordedColor = new PaintColor(); + private readonly PaintColor _recordedColor = new(); public PaintColor RecordedColor { get => _recordedColor; private set => _recordedColor.Set(value); @@ -110,8 +57,6 @@ namespace DesertPaintCodex.Services private int _pixelMultiplier = 1; - private InterfaceSize _interfaceSize = Constants.DefaultInterfaceSize; - private bool _firstRun = true; private int _lastSwatchX = -1; private int _lastSwatchY = -1; @@ -151,11 +96,6 @@ namespace DesertPaintCodex.Services public void RefreshFromSettings() { SettingsService.Get(SettingKey.PixelMultiplier, out _pixelMultiplier, Constants.DefaultPixelMultiplier); - SettingsService.Get(SettingKey.InterfaceSize, out int interfaceSize, (int)Constants.DefaultInterfaceSize); - if (Enum.IsDefined(typeof(InterfaceSize), interfaceSize)) - { - _interfaceSize = (InterfaceSize)interfaceSize; - } SettingsService.Get(SettingKey.ScreenX, out _screenX, Constants.DefaultScreenX); SettingsService.Get(SettingKey.ScreenY, out _screenY, Constants.DefaultScreenY); @@ -171,13 +111,18 @@ namespace DesertPaintCodex.Services private void UpdateSwatchSizes() { - _swatchHeight = SWATCH_HEIGHT[(int)_interfaceSize] * _pixelMultiplier; - _swatchWidth = SWATCH_WIDTH[(int)_interfaceSize] * _pixelMultiplier; - _colorBarWidth = COLOR_BAR_WIDTH[(int)_interfaceSize] * _pixelMultiplier; - _redBarSpacing = RED_BAR_SPACING[(int)_interfaceSize] * _pixelMultiplier; - _greenBarSpacing = GREEN_BAR_SPACING[(int)_interfaceSize] * _pixelMultiplier; - _blueBarSpacing = BLUE_BAR_SPACING[(int)_interfaceSize] * _pixelMultiplier; - _swatchTestWidth = SWATCH_TEST_WIDTH[(int)_interfaceSize] * _pixelMultiplier; + _swatchWidth = MinSwatchWidth * _pixelMultiplier; + _swatchHeight = SwatchHeight * _pixelMultiplier; + _redBarSpacing = RedBarSpacing * _pixelMultiplier; + _greenBarSpacing = GreenBarSpacing * _pixelMultiplier; + _blueBarSpacing = BlueBarSpacing * _pixelMultiplier; + _swatchTopBorder = 2 * _pixelMultiplier; + _swatchBottomBorder = 2 * _pixelMultiplier; + _swatchLeftBorder = 2 * _pixelMultiplier; + _swatchRightBorder = 2 * _pixelMultiplier; + _minSwatchWidth = MinSwatchWidth * _pixelMultiplier; + _maxSwatchWidth = MaxSwatchWidth * _pixelMultiplier; + _swatchTestWidth = (int)(0.1f * MinSwatchWidth * _pixelMultiplier); } public void CancelScan() @@ -201,10 +146,10 @@ namespace DesertPaintCodex.Services if (!PixelColor.IsDark(pixels.ColorAt(x, y))) return false; // 2.) grab the swatch color 2 down from top border - ColorMatcher.Color = pixels.ColorAt(x, y + 2); + ColorMatcher.Color = pixels.ColorAt(x, y + _swatchTopBorder); // Scan the column from 2 below the top to 3 above the bottom to ensure the color matches - for (int i = 2; i < (_swatchHeight-3); ++i) + for (int i = 2; i < (_swatchHeight - 3); ++i) { if (!pixels.DoesPixelMatch(x, y + i, ColorMatcher.IsMatch)) return false; } @@ -216,11 +161,11 @@ namespace DesertPaintCodex.Services { bool result = true; - int swatchSolidWidth = _swatchWidth - 4; - int swatchSolidHeight = _swatchHeight - 5; // 2 top and 3 bottom pixels are slightly different colors - int swatchSolidLeftX = x + 2; - int swatchSolidTopY = y + 2; - int swatchSolidRightX = swatchSolidLeftX + swatchSolidWidth - 1; + int swatchSolidWidth = _swatchWidth - _swatchLeftBorder - _swatchRightBorder; + int swatchSolidHeight = _swatchHeight - _swatchTopBorder - _swatchBottomBorder; + int swatchSolidLeftX = x + _swatchLeftBorder; + int swatchSolidTopY = y + _swatchTopBorder; + int swatchSolidRightX = swatchSolidLeftX + swatchSolidWidth - 1; int swatchSolidBottomY = swatchSolidTopY + swatchSolidHeight - 1; Color swatchColor = pixels.ColorAt(swatchSolidLeftX, swatchSolidTopY); @@ -256,9 +201,29 @@ namespace DesertPaintCodex.Services WriteLog(LogVerbosity.High, "Failed to find left edge for potential swatch of color {2}, {3}, {4} at {0}, {1}", x, y, swatchColor.R, swatchColor.G, swatchColor.B); return false; } + + // Find the right side of the swatch, if necessary. + if (!_lockedSwatchWidth) + { + int xCutoff = Math.Min(x + _maxSwatchWidth - _swatchRightBorder, _targetBitmap.Width); + int newRight = swatchSolidRightX; + for (int px = swatchSolidRightX; px < xCutoff; px++) + { + if (pixels.DoesPixelMatch(px, swatchSolidTopY, ColorMatcher.IsMatch) && + pixels.DoesPixelMatch(px, swatchSolidBottomY, ColorMatcher.IsMatch)) + { + newRight = px; + } + else break; + } + + swatchSolidRightX = newRight; + _swatchWidth = swatchSolidRightX + _swatchRightBorder - x + _pixelMultiplier; + WriteLog(LogVerbosity.High, "Setting new swatch width of {0}.", _swatchWidth); + } // scan down the right and left sides - for (int yOff = 1; yOff < (swatchSolidHeight - 1); ++yOff) + for (int yOff = _pixelMultiplier; yOff < (swatchSolidHeight - _pixelMultiplier); ++yOff) { result &= pixels.DoesPixelMatch(swatchSolidLeftX, swatchSolidTopY + yOff, ColorMatcher.IsMatch); if (!result) @@ -275,10 +240,10 @@ namespace DesertPaintCodex.Services } if (!result) { - WriteLog(LogVerbosity.Normal, "Failed to find left/right edges for potential swatch of color {2}, {3}, {4} at {0}, {1} [failed color = {5},{6},{7}]", x, y, swatchColor.R, swatchColor.G, swatchColor.B, testColor.R, testColor.G, testColor.B); + WriteLog(LogVerbosity.Normal, "Failed to find left/right edges for potential swatch of color {2}, {3}, {4} at ({0}, {1}) [failed color = {5},{6},{7}]", x, y, swatchColor.R, swatchColor.G, swatchColor.B, testColor.R, testColor.G, testColor.B); return false; } - for (int xOff = 1; xOff < (swatchSolidWidth - 1); ++xOff) + for (int xOff = _pixelMultiplier; xOff < (swatchSolidWidth - _pixelMultiplier); ++xOff) { result &= pixels.DoesPixelMatch(swatchSolidLeftX + xOff, swatchSolidTopY, ColorMatcher.IsMatch); if (!result) @@ -295,16 +260,17 @@ namespace DesertPaintCodex.Services } if (!result) { - WriteLog(LogVerbosity.Normal, "Failed to match upper/lower edges for potential swatch of color {2}, {3}, {4} at {0}, {1} [failed color = {5},{6},{7}]", x, y, swatchColor.R, swatchColor.G, swatchColor.B, testColor.R, testColor.G, testColor.B); + WriteLog(LogVerbosity.Normal, "Failed to match upper/lower edges for potential swatch of color {2}, {3}, {4} at ({0}, {1}) [failed color = {5},{6},{7}]", x, y, swatchColor.R, swatchColor.G, swatchColor.B, testColor.R, testColor.G, testColor.B); return false; } // test the left edge for dark pixels -- the bottom-most pixel is bright now int i = 0; - for (i = 1; result && i < _swatchHeight - 1; ++i) + for (i = _pixelMultiplier; result && i < _swatchHeight - _pixelMultiplier; ++i) { result &= pixels.DoesPixelMatch(x, y + i, PixelColor.IsDark); + if (!result) break; } if (!result) { @@ -313,10 +279,10 @@ namespace DesertPaintCodex.Services return false; } - // test the dark top border and for papyrus above and below the swatch + // test the dark top border and for background above and below the swatch bool borderError = false; - int papyErrorCount = 0; - for (i = 0; result && (i < _swatchWidth - 1); ++i) + int bgErrorCount = 0; + for (i = 0; result && (i < _swatchWidth - _pixelMultiplier); ++i) { bool isBorder = pixels.DoesPixelMatch(x + i, y, PixelColor.IsDark); result &= isBorder; @@ -326,37 +292,37 @@ namespace DesertPaintCodex.Services borderError = true; } - // Checking along the top of the swatch for papyrus + // Checking along the top of the swatch for background // The row just above is shaded, so check 2 above if (y > 1) { - bool isPapyrus = pixels.DoesPixelMatch(x + i, y - 2, PixelColor.IsPapyrus); - papyErrorCount += isPapyrus ? 0 : 1; + bool isUiBackground = pixels.DoesPixelMatch(x + i, y - _swatchTopBorder, PixelColor.IsUiBackground); + bgErrorCount += isUiBackground ? 0 : 1; } else { - ++papyErrorCount; + ++bgErrorCount; } - // Checking along the bottom of the swatch for papyrus + // Checking along the bottom of the swatch for background if (y < pixels.Height) { - bool isPapyrus = pixels.DoesPixelMatch(x + i, y + _swatchHeight, PixelColor.IsPapyrus); - papyErrorCount += isPapyrus ? 0 : 1; + bool isUiBackground = pixels.DoesPixelMatch(x + i, y + _swatchHeight, PixelColor.IsUiBackground); + bgErrorCount += isUiBackground ? 0 : 1; } else { - ++papyErrorCount; + ++bgErrorCount; } } - result &= (papyErrorCount < (_swatchWidth / 20)); // allow up to 5% error rate checking for papy texture, because this seems to be inconsistent - if (!result && ((i > (_swatchWidth*0.8)) || (papyErrorCount >= (_swatchWidth/20)))) + result &= (bgErrorCount < (_swatchWidth / 20)); // allow up to 5% error rate checking for background texture, because this seems to be inconsistent + if (!result && ((i > (_swatchWidth * 0.8)) || (bgErrorCount >= (_swatchWidth / 20)))) { - if (!borderError && (papyErrorCount < _swatchWidth)) + if (!borderError && (bgErrorCount < _swatchWidth)) { - WriteLog(LogVerbosity.Normal, "Found a potential swatch candidate of width {0} at {1},{2} that had {3} failures matching papyrus texture", i, x, y, papyErrorCount); + WriteLog(LogVerbosity.Normal, "Found a potential swatch candidate of width {0} at {1},{2} that had {3} failures matching background color", i, x, y, bgErrorCount); } } @@ -366,12 +332,12 @@ namespace DesertPaintCodex.Services private bool TestPosition(int x, int y, Pixels pixels, ref PaintColor reactedColor) { // Check 4 corners of solid area and left/right solid bar areas - bool foundSwatch = IsPossibleSwatchUpperLeft(pixels, x, y); // ((pixel_r < 0x46) && (pixel_g < 0x46) && (pixel_b < 0x46)); + bool foundSwatch = IsPossibleSwatchUpperLeft(pixels, x, y); if (foundSwatch) { WriteLog(LogVerbosity.Normal, "Found probable swatch at {0},{1} - checking border slices", x, y); int borderXOffset = 0; - for (borderXOffset = 2; foundSwatch && (borderXOffset < _swatchTestWidth); ++borderXOffset) + for (borderXOffset = _swatchLeftBorder; foundSwatch && (borderXOffset < _swatchTestWidth); ++borderXOffset) { foundSwatch &= IsPossibleSwatchSlice(pixels, x + borderXOffset, y); if (!foundSwatch) @@ -388,17 +354,27 @@ namespace DesertPaintCodex.Services } } - if (!foundSwatch) return false; + if (!foundSwatch) + { + if (!_lockedSwatchWidth) + { + // Reset our guess at swatch width. + _swatchWidth = MinSwatchWidth; + } + return false; + } + // WE FOUND THE SWATCH! // Now we know where the color bars are. + _lockedSwatchWidth = true; int redPixelCount = pixels.LengthOfColorAt(x, y + _redBarSpacing, PixelColor.IsRed); - reactedColor.Red = (byte)Math.Round((float)redPixelCount * 255f / (float)_colorBarWidth); + reactedColor.Red = (byte)Math.Round(redPixelCount * 255f / _swatchWidth); int greenPixelCount = pixels.LengthOfColorAt(x, y + _greenBarSpacing, PixelColor.IsGreen); - reactedColor.Green = (byte)Math.Round((float)greenPixelCount * 255f / (float)_colorBarWidth); + reactedColor.Green = (byte)Math.Round(greenPixelCount * 255f / _swatchWidth); int bluePixelCount = pixels.LengthOfColorAt(x, y + _blueBarSpacing, PixelColor.IsBlue); - reactedColor.Blue = (byte)Math.Round((float)bluePixelCount * 255f / (float)_colorBarWidth); + reactedColor.Blue = (byte)Math.Round(bluePixelCount * 255f / _swatchWidth); WriteLog(LogVerbosity.Low, "Found the color swatch at {0}, {1}. Color={2} Red={3}px Green={4}px Blue={5}px", x, y, reactedColor, redPixelCount, greenPixelCount, bluePixelCount); return true; } @@ -421,7 +397,7 @@ namespace DesertPaintCodex.Services g.CopyFromScreen(_screenX, _screenY, 0, 0, _targetBitmap.Size, CopyPixelOperation.SourceCopy); } - _targetBitmap.Save(Path.Combine(ProfileManager.CurrentProfile?.Directory ?? "", "screenshot.png"), ImageFormat.Png); + // _targetBitmap.Save(Path.Combine(ProfileManager.CurrentProfile?.Directory ?? "", "screenshot.png"), ImageFormat.Png); Pixels pixels = new(_targetBitmap, _pixelMultiplier); @@ -442,7 +418,7 @@ namespace DesertPaintCodex.Services SettingsService.Get("Log.Verbosity", out var verbosityIdx, 1); _logVerbosity = (LogVerbosity)verbosityIdx; - int patchTestSize = ((_swatchHeight - 5) / 2) - 1; + int patchTestSize = ((_swatchHeight - _swatchTopBorder - _swatchBottomBorder) / 2) - 1; SettingsService.Get("ScanArea.Min.X", out var startX, 0); SettingsService.Get("ScanArea.Min.Y", out var startY, 0); @@ -451,10 +427,10 @@ namespace DesertPaintCodex.Services startX = Math.Max(2, Math.Min(startX, _targetBitmap.Width - 2)); startY = Math.Max(2, Math.Min(startY, _targetBitmap.Height - 2)); - endX = Math.Min(_targetBitmap.Width - 2, Math.Max(2, endX)) - _colorBarWidth; // + patchTestSize; + endX = Math.Min(_targetBitmap.Width - 2, Math.Max(2, endX)) - _minSwatchWidth; // + patchTestSize; endY = Math.Min(_targetBitmap.Height - 2, Math.Max(2, endY)) - (_blueBarSpacing + 10); // + patchTestSize; - Debug.WriteLine("startX: " + startX + " endX: " + endX + " startY: " + startY + " endY: " + endY); + Debug.WriteLine("startX: " + startX + " endX: " + endX + " startY: " + startY + " endY: " + endY + " with patch test size of " + patchTestSize); int xSpan = endX - startX; int ySpan = endY - startY; @@ -474,9 +450,9 @@ namespace DesertPaintCodex.Services Color patchColor = pixels.ColorAt(roughX, roughY); WriteLog(LogVerbosity.Excessive, "Found a solid patch of {2},{3},{4} at {0}, {1}", roughX, roughY, patchColor.R, patchColor.G, patchColor.B); - for (int x = Math.Max(0, roughX - patchTestSize); x < roughX; ++x) + for (int x = Math.Max(0, roughX - patchTestSize - _swatchLeftBorder + _pixelMultiplier); x < roughX; ++x) { - for (int y = Math.Max(0, roughY - patchTestSize); y < roughY; ++y) + for (int y = Math.Max(0, roughY - patchTestSize - _swatchTopBorder + _pixelMultiplier); y < roughY; ++y) { WriteLog(LogVerbosity.Excessive, "Searching for potential swatch at {0},{1} after found square at {2},{3}", x, y, roughX, roughY); @@ -512,8 +488,7 @@ namespace DesertPaintCodex.Services return new Reaction(r, g, b); } - - + public static Reaction CalculateReaction(PaintColor expectedColor, PaintColor reactedColor) { // A 2-reagent reaction. diff --git a/Services/SettingKey.cs b/Services/SettingKey.cs --- a/Services/SettingKey.cs +++ b/Services/SettingKey.cs @@ -2,7 +2,6 @@ namespace DesertPaintCodex.Services { public static class SettingKey { - public const string InterfaceSize = "interfacesize"; public const string PixelMultiplier = "pixelmultiplier"; public const string ScreenIndex = "screenindex"; public const string ScreenX = "screenx"; diff --git a/Util/Constants.cs b/Util/Constants.cs --- a/Util/Constants.cs +++ b/Util/Constants.cs @@ -4,7 +4,6 @@ namespace DesertPaintCodex.Util { public static class Constants { - public const InterfaceSize DefaultInterfaceSize = InterfaceSize.Small; public static int DefaultPixelMultiplier = 1; public static int DefaultScreenWidth = 1920; public static int DefaultScreenHeight = 1080; @@ -29,9 +28,5 @@ namespace DesertPaintCodex.Util } } - - - - } } \ No newline at end of file diff --git a/Util/PixelColor.cs b/Util/PixelColor.cs --- a/Util/PixelColor.cs +++ b/Util/PixelColor.cs @@ -6,7 +6,7 @@ namespace DesertPaintCodex.Util { public class PixelColor { - private const int ColorTolerance = 3; + private const int ColorTolerance = 2; public static bool IsMatch(Color colorA, Color colorB) { @@ -20,30 +20,22 @@ namespace DesertPaintCodex.Util return (color.R < 0x47) && (color.G < 0x47) && (color.B < 0x47); } - public static bool IsPapyrus(Color color) + public static bool IsUiBackground(Color color) { - // red between 208 and 244 - // 240 and 255 - // green between 192 and 237 - // 223 and 250 - // blue between 145 and 205 - // 178 and 232 - //return ((r > 0xD0) && (g >= 0xC0) && (b >= 0x91)) && - // ((r < 0xF4) && (g <= 0xED) && (b <= 0xCD)); - return ((color.R >= 0xD0) && (color.R <= 0xFF) && (color.G >= 0xC0) && (color.G <= 0xFA) && (color.B >= 0x91) && (color.B <= 0xE8)); + return (color.R is >= 0x27 and <= 0x2F) && (color.G is >= 0x3B and <= 0x42) && (color.B is >= 0x41 and <= 0x48); } public static bool IsRed(Color color) { - return (color.R > 0x9F) && (color.G < 0x70) && (color.B < 0x70); + return (color.R > 0x9D) && (color.G < 0x60) && (color.B < 0x60); } public static bool IsGreen(Color color) { - return (color.R < 0x70) && (color.G > 0x9F) && (color.B < 0x70); + return (color.R < 0x60) && (color.G > 0x9D) && (color.B < 0x60); } public static bool IsBlue(Color color) { - return (color.R < 0x70) && (color.G < 0x70) && (color.B > 0x9F); + return (color.R < 0x60) && (color.G < 0x60) && (color.B > 0x9D); } } diff --git a/ViewModels/ExperimentLogViewModel.cs b/ViewModels/ExperimentLogViewModel.cs --- a/ViewModels/ExperimentLogViewModel.cs +++ b/ViewModels/ExperimentLogViewModel.cs @@ -60,6 +60,7 @@ namespace DesertPaintCodex.ViewModels TestView.SaveReaction.Subscribe(_ => OnSaveReaction()); TestView.ClearReaction.Subscribe(_ => OnClearReaction()); + TestView.FinalizeTestResults.Subscribe(_ => OnReactionResults()); } private ReactionTest GetSelectedReactionTest() @@ -69,6 +70,8 @@ namespace DesertPaintCodex.ViewModels var list = _testLists[SelectedList]; return (itemIndex >= list.Count) ? Constants.StubReactionTest : list[itemIndex]; } + + #region Command Handlers private void OnSaveReaction() { @@ -112,6 +115,24 @@ namespace DesertPaintCodex.ViewModels } } + private void OnReactionResults() + { + ReactionTest? test = null; + int selectedTest = SelectedRemainingTest; + if (selectedTest >= 0) + { + test = RemainingTests[selectedTest]; + } + ReactionTestService.PopulateRemainingTests(RemainingTests); + if (test != null) + { + int newIndex = RemainingTests.IndexOf(test); + SelectedRemainingTest = newIndex; + } + } + + #endregion + private static void InsertTestIntoList(ReactionTest test, IList list) { int i; diff --git a/ViewModels/ReactionTestViewModel.cs b/ViewModels/ReactionTestViewModel.cs --- a/ViewModels/ReactionTestViewModel.cs +++ b/ViewModels/ReactionTestViewModel.cs @@ -42,6 +42,7 @@ namespace DesertPaintCodex.ViewModels ShowScreenSettingsDialog = new Interaction(); SaveReaction = ReactiveCommand.Create(() => ReactionTest.SaveReaction()); ClearReaction = ReactiveCommand.Create(() => ReactionTest.ClearReaction()); + FinalizeTestResults = ReactiveCommand.Create(Test); } private void UpdateDerivedState() @@ -75,6 +76,7 @@ namespace DesertPaintCodex.ViewModels try { await ReactionTest.StartScan(); + await FinalizeTestResults.Execute(); } catch (OperationCanceledException) { @@ -101,10 +103,16 @@ namespace DesertPaintCodex.ViewModels { await ShowInfoBox("Clipped Reactions", "The Pigment Lab is only capable of displaying color channel values in the 0-255 range. However, sometimes, your reactions will push one or more channels outside of that range. When that happens, the value will be clamped either to 0 or 255. In this case, we say that the reaction was \"Clipped.\"\n\nThis is nothing to panic about, though. We solve this issue by using a third (buffer) reagent to offset the extreme value, so that it falls within measurable range, similar to how we test Catalyst+Catalyst reactions. Desert Paint Codex will automatically do the math for this, but it does move clipped reactions towards the end of your test list, because you'll want any reactions between the buffer reagent and your two test reagents already known, prior to running your buffered test."); } + + private void Test() + { + Debug.WriteLine("Test complete"); + } public Interaction ShowScreenSettingsDialog { get; } public ReactiveCommand ClearReaction { get; } public ReactiveCommand SaveReaction { get; } + public ReactiveCommand FinalizeTestResults { get; } } } \ No newline at end of file diff --git a/ViewModels/ScreenSettingsViewModel.cs b/ViewModels/ScreenSettingsViewModel.cs --- a/ViewModels/ScreenSettingsViewModel.cs +++ b/ViewModels/ScreenSettingsViewModel.cs @@ -1,12 +1,10 @@  -using Avalonia.Controls; using Avalonia.Platform; using DesertPaintCodex.Models; using DesertPaintCodex.Services; using ReactiveUI; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Reactive; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; @@ -16,12 +14,8 @@ namespace DesertPaintCodex.ViewModels { public class ScreenSettingsViewModel : ViewModelBase { - public ObservableCollection InterfaceSizes { get; } = new(); public ObservableCollection Screens { get; } = new(); - - private int _selectedInterfaceSize; - public int SelectedInterfaceSize { get => _selectedInterfaceSize; private set => this.RaiseAndSetIfChanged(ref _selectedInterfaceSize, value); } - + private int _pixeMultiplier; public int PixelMultiplier { get => _pixeMultiplier; private set => this.RaiseAndSetIfChanged(ref _pixeMultiplier, value); } @@ -33,26 +27,19 @@ namespace DesertPaintCodex.ViewModels if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { IScreenImpl screen = desktop.MainWindow.PlatformImpl.Screen; - for (int i = 0; i < (int)InterfaceSize.Count; i++) - { - InterfaceSizes.Add(((InterfaceSize)i).ToString()); - } - IReadOnlyList screens = screen.AllScreens; for (int i = 0; i < screens.Count; i++) { Screens.Add(new ScreenMetrics(screens[i].Bounds, screens[i].Primary)); } - - SettingsService.Get(SettingKey.InterfaceSize, out _selectedInterfaceSize, (int)Constants.DefaultInterfaceSize); + SettingsService.Get(SettingKey.PixelMultiplier, out _pixeMultiplier, Constants.DefaultPixelMultiplier); SettingsService.Get(SettingKey.ScreenIndex, out _screenIndex, 0); _screenIndex = System.Math.Min(_screenIndex, screens.Count - 1); // In case the user has fewer screens than last time. Save = ReactiveCommand.Create(() => { - SettingsService.Set(SettingKey.InterfaceSize, _selectedInterfaceSize); SettingsService.Set(SettingKey.PixelMultiplier, _pixeMultiplier); SettingsService.Set(SettingKey.ScreenIndex, _screenIndex); SettingsService.Set(SettingKey.ScreenX, screens[_screenIndex].Bounds.X); @@ -68,16 +55,9 @@ namespace DesertPaintCodex.ViewModels else { // Placeholder stuff. - - for (int i = 0; i < (int)InterfaceSize.Count; i++) - { - InterfaceSizes.Add(((InterfaceSize)i).ToString()); - } - Screens.Add(new ScreenMetrics(new Avalonia.PixelRect(0, 0, 1920, 1080), true)); Screens.Add(new ScreenMetrics(new Avalonia.PixelRect(1920, 0, 1920, 1080), false)); - - SelectedInterfaceSize = 0; + PixelMultiplier = 1; ScreenIndex = 0; diff --git a/ViewModels/SelectProfileViewModel.cs b/ViewModels/SelectProfileViewModel.cs --- a/ViewModels/SelectProfileViewModel.cs +++ b/ViewModels/SelectProfileViewModel.cs @@ -1,21 +1,24 @@ using System.Reactive; using ReactiveUI; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; +using DesertPaintCodex.Models; using DesertPaintCodex.Services; +using DynamicData; namespace DesertPaintCodex.ViewModels { public class SelectProfileViewModel : ViewModelBase { - public List Profiles { get; private set; } + public ObservableCollection Profiles { get; } = new(); private string? _selectedItem; public string? SelectedItem { get => _selectedItem; set => this.RaiseAndSetIfChanged(ref _selectedItem, value); } public SelectProfileViewModel() { - Profiles = ProfileManager.GetProfileList(); + Profiles.AddRange(ProfileManager.GetProfileList()); var okEnabled = this.WhenAnyValue( x => x.SelectedItem, diff --git a/ViewModels/WelcomeViewModel.cs b/ViewModels/WelcomeViewModel.cs --- a/ViewModels/WelcomeViewModel.cs +++ b/ViewModels/WelcomeViewModel.cs @@ -30,25 +30,20 @@ namespace DesertPaintCodex.ViewModels private void ShowSelectProfile() { - if (_selectProfileVM == null) - { - _selectProfileVM = new SelectProfileViewModel(); - _selectProfileVM.NewProfile.Subscribe(_ => ShowCreateProfile()); - Observable.Merge(_selectProfileVM.Ok, _selectProfileVM.Cancel) - .Take(1) - .InvokeCommand(FinishWelcome); - } + _selectProfileVM = new SelectProfileViewModel(); + _selectProfileVM.NewProfile.Subscribe(_ => ShowCreateProfile()); + Observable.Merge(_selectProfileVM.Ok, _selectProfileVM.Cancel) + .Take(1) + .InvokeCommand(FinishWelcome); + ProfileActivity = _selectProfileVM; } private void ShowCreateProfile() { - if (_createProfileVM == null) - { - _createProfileVM = new CreateProfileViewModel(); - Observable.Merge(_createProfileVM.Ok, _createProfileVM.Cancel).Subscribe(_ => ShowSelectProfile()); - } + _createProfileVM = new CreateProfileViewModel(); + Observable.Merge(_createProfileVM.Ok, _createProfileVM.Cancel).Subscribe(_ => ShowSelectProfile()); ProfileActivity = _createProfileVM; } diff --git a/Views/ExperimentLogView.axaml b/Views/ExperimentLogView.axaml --- a/Views/ExperimentLogView.axaml +++ b/Views/ExperimentLogView.axaml @@ -63,10 +63,14 @@ - + - + diff --git a/Views/ExperimentLogView.axaml.cs b/Views/ExperimentLogView.axaml.cs --- a/Views/ExperimentLogView.axaml.cs +++ b/Views/ExperimentLogView.axaml.cs @@ -1,4 +1,5 @@ -using Avalonia.Markup.Xaml; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; using DesertPaintCodex.ViewModels; @@ -7,14 +8,29 @@ namespace DesertPaintCodex.Views { public class ExperimentLogView : ReactiveUserControl { + private readonly ListBox? _remainingList; + public ExperimentLogView() { InitializeComponent(); + + _remainingList = this.FindControl("RemainingList"); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + private void RemainingSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (_remainingList == null) return; + + foreach (var item in e.AddedItems) + { + _remainingList.ScrollIntoView(item); + return; + } + } } } diff --git a/Views/ScreenSettingsView.axaml b/Views/ScreenSettingsView.axaml --- a/Views/ScreenSettingsView.axaml +++ b/Views/ScreenSettingsView.axaml @@ -24,18 +24,6 @@ - Game UI Size - - - ? - - - Game UI Size - [Coming Soon] - - - - Screen pixels to game pixels: @@ -45,10 +33,11 @@ Screen Pixels to Game Pixels - [Coming Soon] + If your screen is scaled at the system-level, you may need to adjust this number to measure UI elements correctly. - + +