Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ready for Review - [Mouse Jump] Customisable appearance - borders, margins, colours, etc - final part #35521

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6dcbd29
[MouseJump] move Mouse Jump settings into separate control (#27511)
mikeclayton Oct 21, 2024
1cfb30b
[MouseJump] added Mouse Jump style controls to Settings UI (#27511)
mikeclayton Oct 21, 2024
cc99c8e
[MouseJump] added Mouse Jump style controls to Settings UI (#27511)
mikeclayton Oct 21, 2024
02314a9
[MouseJump] removing unused MouseJumpUI code (#27511)
mikeclayton Oct 21, 2024
3eecd9c
[MouseJump] whitespace (#27511)
mikeclayton Oct 21, 2024
ee0d950
Merge branch 'dev/mikeclayton/mousejump-styles-part-3' of https://git…
mikeclayton Oct 21, 2024
489052f
[MouseJump] fix spellcheck (#27511)
mikeclayton Oct 21, 2024
da50b5e
[MouseJump] enabled "Copy to custom style" (#27511)
mikeclayton Oct 21, 2024
fad8c95
[MouseJump] fixing build (internal members -> public) (#27511)
mikeclayton Oct 22, 2024
6f8e1a9
[MouseJump] remove unused "using"s (#27511)
mikeclayton Oct 22, 2024
1a0d741
[MouseJump] use custom styles in preview image (#27511)
mikeclayton Oct 22, 2024
b10f475
[MouseJump] fixing failing test (#27511)
mikeclayton Oct 22, 2024
5f8b957
[MouseJump] fixing failing test (#27511)
mikeclayton Oct 22, 2024
f434eff
[MouseJump] fixing failing test (#27511)
mikeclayton Oct 22, 2024
5938448
[MouseJump] fixing failing test (#27511)
mikeclayton Oct 22, 2024
c3cc1bf
[MouseJump] delinting to trigger a build (#27511)
mikeclayton Oct 22, 2024
5d4b971
[MouseJump] updated settings preview image ("browser" header) (#27511)
mikeclayton Oct 23, 2024
5dcde18
[MouseJump] upgrade default "custom" style settings in config (#27511)
mikeclayton Oct 23, 2024
8dce052
[MouseJump] fixed a glitch in settings upgrade (#27511)
mikeclayton Oct 23, 2024
d6af62a
[MouseJump] fixed spell checker (#27511)
mikeclayton Oct 23, 2024
093f0d4
[MouseJump] typo in resource strings (image -> images) (#27511)
mikeclayton Oct 24, 2024
c30b992
Merge branch 'main' into dev/mikeclayton/mousejump-styles-part-3
mikeclayton Oct 27, 2024
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
2 changes: 2 additions & 0 deletions .github/actions/spell-check/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ backtracer
bbwe
bck
BESTEFFORT
bezelled
bhid
BIF
bigbar
Expand Down Expand Up @@ -730,6 +731,7 @@ ISSEPARATOR
ITask
ith
ITHUMBNAIL
ITwoWayPipeMessageIPCManaged
IUI
IUnknown
IWbem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Reflection;

using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJump.Common.Helpers;
using MouseJump.Common.Imaging;
Expand All @@ -16,7 +17,7 @@ namespace MouseJump.Common.UnitTests.Helpers;
public static class DrawingHelperTests
{
[TestClass]
public sealed class GetPreviewLayoutTests
public sealed class RenderPreviewTests
{
public sealed class TestCase
{
Expand Down Expand Up @@ -46,7 +47,7 @@ public static IEnumerable<object[]> GetTestCases()
yield return new object[]
{
new TestCase(
previewStyle: StyleHelper.DefaultPreviewStyle,
previewStyle: StyleHelper.BezelledPreviewStyle,
screens: new List<RectangleInfo>()
{
new(0, 0, 500, 500),
Expand All @@ -62,7 +63,7 @@ public static IEnumerable<object[]> GetTestCases()
yield return new object[]
{
new TestCase(
previewStyle: StyleHelper.DefaultPreviewStyle,
previewStyle: StyleHelper.BezelledPreviewStyle,
screens: new List<RectangleInfo>()
{
new(5120, 349, 1920, 1080),
Expand All @@ -79,7 +80,7 @@ public static IEnumerable<object[]> GetTestCases()
public void RunTestCases(TestCase data)
{
// load the fake desktop image
using var desktopImage = GetPreviewLayoutTests.LoadImageResource(data.DesktopImageFilename);
using var desktopImage = RenderPreviewTests.LoadImageResource(data.DesktopImageFilename);

// draw the preview image
var previewLayout = LayoutHelper.GetPreviewLayout(
Expand All @@ -90,28 +91,29 @@ public void RunTestCases(TestCase data)
using var actual = DrawingHelper.RenderPreview(previewLayout, imageCopyService);

// load the expected image
var expected = GetPreviewLayoutTests.LoadImageResource(data.ExpectedImageFilename);
var expected = RenderPreviewTests.LoadImageResource(data.ExpectedImageFilename);

// compare the images
var screens = System.Windows.Forms.Screen.AllScreens;
AssertImagesEqual(expected, actual);
}

private static Bitmap LoadImageResource(string filename)
{
// assume embedded resources are in the same source folder as this
// class, and the namespace hierarchy matches the folder structure.
// that way we can build resource names from the current namespace
var resourcePrefix = typeof(DrawingHelperTests).Namespace;
var resourceName = $"{resourcePrefix}.{filename}";

var assembly = Assembly.GetExecutingAssembly();
var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException());
var resourceName = $"{typeof(DrawingHelperTests).Namespace}.{filename.Replace("/", ".")}";
var resourceNames = assembly.GetManifestResourceNames();
if (!resourceNames.Contains(resourceName))
{
var message = $"Embedded resource '{resourceName}' does not exist. " +
"Valid resource names are: \r\n" + string.Join("\r\n", resourceNames);
throw new InvalidOperationException(message);
var message = new StringBuilder();
message.AppendLine(CultureInfo.InvariantCulture, $"Embedded resource '{resourceName}' does not exist.");
message.AppendLine($"Known resources:");
foreach (var name in resourceNames)
{
message.AppendLine(name);
}

throw new InvalidOperationException(message.ToString());
}

var stream = assembly.GetManifestResourceStream(resourceName)
Expand All @@ -121,7 +123,7 @@ private static Bitmap LoadImageResource(string filename)
}

/// <summary>
/// Naive / brute force image comparison - we can optimise this later :-)
/// Naive / brute force image comparison - we can optimize this later :-)
/// </summary>
private static void AssertImagesEqual(Bitmap expected, Bitmap actual)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public TestCase(PreviewStyle previewStyle, List<RectangleInfo> screens, PointInf
public static IEnumerable<object[]> GetTestCases()
{
// happy path - single screen with 50% scaling,
// *has* a preview borders but *no* screenshot borders
// *has* a preview border but *no* screenshot borders
//
// +----------------+
// | |
Expand Down Expand Up @@ -160,7 +160,7 @@ public static IEnumerable<object[]> GetTestCases()
new(0, 0, 1024, 768),
};
var activatedLocation = new PointInfo(512, 384);
var previewLayout = new PreviewLayout(
var expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 1024, 768),
screens: screens,
activatedScreenIndex: 0,
Expand All @@ -183,7 +183,7 @@ public static IEnumerable<object[]> GetTestCases()
contentBounds: new(6, 6, 512, 384)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };

// happy path - single screen with 50% scaling,
// *no* preview borders but *has* screenshot borders
Expand Down Expand Up @@ -217,7 +217,7 @@ public static IEnumerable<object[]> GetTestCases()
new(0, 0, 1024, 768),
};
activatedLocation = new PointInfo(512, 384);
previewLayout = new PreviewLayout(
expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 1024, 768),
screens: screens,
activatedScreenIndex: 0,
Expand All @@ -240,7 +240,59 @@ public static IEnumerable<object[]> GetTestCases()
contentBounds: new(6, 6, 500, 372)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };

// rounding error check - single screen with 33% scaling,
// no borders, check to make sure form scales to exactly
// fill the canvas size with no rounding errors.
//
// in this test the preview width is 300 and the desktop is
// 900, so the scaling factor is 1/3, but this gets rounded
// to 0.3333333333333333333333333333, and 900 times this value
// is 299.99999999999999999999999997. if we don't scale correctly
// the resulting form width might only be 299 pixels instead of 300
//
// +----------------+
// | |
// | 0 |
// | |
// +----------------+
previewStyle = new PreviewStyle(
canvasSize: new(
width: 300,
height: 200
),
canvasStyle: BoxStyle.Empty,
screenStyle: BoxStyle.Empty);
screens = new List<RectangleInfo>
{
new(0, 0, 900, 200),
};
activatedLocation = new PointInfo(450, 100);
expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 900, 200),
screens: screens,
activatedScreenIndex: 0,
formBounds: new(300, 66.5m, 300, 67),
previewStyle: previewStyle,
previewBounds: new(
outerBounds: new(0, 0, 300, 67),
marginBounds: new(0, 0, 300, 67),
borderBounds: new(0, 0, 300, 67),
paddingBounds: new(0, 0, 300, 67),
contentBounds: new(0, 0, 300, 67)
),
screenshotBounds: new()
{
new(
outerBounds: new(0, 0, 300, 67),
marginBounds: new(0, 0, 300, 67),
borderBounds: new(0, 0, 300, 67),
paddingBounds: new(0, 0, 300, 67),
contentBounds: new(0, 0, 300, 67)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };

// primary monitor not topmost / leftmost - if there are screens
// that are further left or higher up than the primary monitor
Expand Down Expand Up @@ -291,7 +343,7 @@ public static IEnumerable<object[]> GetTestCases()
new(0, 0, 5120, 1440),
};
activatedLocation = new(-960, 60);
previewLayout = new PreviewLayout(
expectedResult = new PreviewLayout(
virtualScreen: new(-1920, -480, 7040, 1920),
screens: screens,
activatedScreenIndex: 0,
Expand Down Expand Up @@ -321,7 +373,7 @@ public static IEnumerable<object[]> GetTestCases()
contentBounds: new(204, 60, 500, 132)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
}

[TestMethod]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,49 @@ public sealed class ScaleToFitTests
{
public sealed class TestCase
{
public TestCase(SizeInfo obj, SizeInfo bounds, SizeInfo expectedResult)
public TestCase(SizeInfo source, SizeInfo bounds, SizeInfo expectedResult, decimal scalingRatio)
{
this.Obj = obj;
this.Source = source;
this.Bounds = bounds;
this.ExpectedResult = expectedResult;
this.ScalingRatio = scalingRatio;
}

public SizeInfo Obj { get; }
public SizeInfo Source { get; }

public SizeInfo Bounds { get; }

public SizeInfo ExpectedResult { get; }

public decimal ScalingRatio { get; }
}

public static IEnumerable<object[]> GetTestCases()
{
// identity tests
yield return new object[] { new TestCase(new(512, 384), new(512, 384), new(512, 384)), };
yield return new object[] { new TestCase(new(1024, 768), new(1024, 768), new(1024, 768)), };
yield return new object[] { new TestCase(new(512, 384), new(512, 384), new(512, 384), 1), };
yield return new object[] { new TestCase(new(1024, 768), new(1024, 768), new(1024, 768), 1), };

// general tests
yield return new object[] { new TestCase(new(512, 384), new(2048, 1536), new(2048, 1536)), };
yield return new object[] { new TestCase(new(2048, 1536), new(1024, 768), new(1024, 768)), };
yield return new object[] { new TestCase(new(512, 384), new(2048, 1536), new(2048, 1536), 4), };
yield return new object[] { new TestCase(new(2048, 1536), new(1024, 768), new(1024, 768), 0.5m), };

// scale to fit width
yield return new object[] { new TestCase(new(512, 384), new(2048, 3072), new(2048, 1536)), };
yield return new object[] { new TestCase(new(512, 384), new(2048, 3072), new(2048, 1536), 4), };

// scale to fit height
yield return new object[] { new TestCase(new(512, 384), new(4096, 1536), new(2048, 1536)), };
yield return new object[] { new TestCase(new(512, 384), new(4096, 1536), new(2048, 1536), 4), };
}

[TestMethod]
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
public void RunTestCases(TestCase data)
{
var actual = data.Obj.ScaleToFit(data.Bounds);
var actual = data.Source.ScaleToFit(data.Bounds, out var scalingRatio);
var expected = data.ExpectedResult;
Assert.AreEqual(expected.Width, actual.Width);
Assert.AreEqual(expected.Height, actual.Height);
Assert.AreEqual(scalingRatio, data.ScalingRatio);
}
}

Expand Down
91 changes: 91 additions & 0 deletions src/modules/MouseUtils/MouseJump.Common/Helpers/ConfigHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;

namespace MouseJump.Common.Helpers;

public static class ConfigHelper
{
public static Color? ToUnnamedColor(Color? value)
{
if (!value.HasValue)
{
return null;
}

var color = value.Value;
return Color.FromArgb(color.A, color.R, color.G, color.B);
}

public static string? SerializeToConfigColorString(Color? value)
{
if (!value.HasValue)
{
return null;
}

var color = value.Value;
return color switch
{
Color { IsNamedColor: true } =>
$"{nameof(Color)}.{color.Name}",
Color { IsSystemColor: true } =>
$"{nameof(SystemColors)}.{color.Name}",
_ =>
$"#{color.R:X2}{color.G:X2}{color.B:X2}",
};
}

public static Color? DeserializeFromConfigColorString(string? value)
{
if (string.IsNullOrEmpty(value))
{
return null;
}

// e.g. "#AABBCC"
if (value.StartsWith('#'))
{
var culture = CultureInfo.InvariantCulture;
if ((value.Length == 7)
&& int.TryParse(value[1..3], NumberStyles.HexNumber, culture, out var r)
&& int.TryParse(value[3..5], NumberStyles.HexNumber, culture, out var g)
&& int.TryParse(value[5..7], NumberStyles.HexNumber, culture, out var b))
{
return Color.FromArgb(0xFF, r, g, b);
}
}

const StringComparison comparison = StringComparison.InvariantCulture;

// e.g. "Color.Red"
const string colorPrefix = $"{nameof(Color)}.";
if (value.StartsWith(colorPrefix, comparison))
{
var colorName = value[colorPrefix.Length..];
var property = typeof(Color).GetProperties()
.SingleOrDefault(property => property.Name == colorName);
if (property is not null)
{
return (Color?)property.GetValue(null, null);
}
}

// e.g. "SystemColors.Highlight"
const string systemColorPrefix = $"{nameof(SystemColors)}.";
if (value.StartsWith(systemColorPrefix, comparison))
{
var colorName = value[systemColorPrefix.Length..];
var property = typeof(SystemColors).GetProperties()
.SingleOrDefault(property => property.Name == colorName);
if (property is not null)
{
return (Color?)property.GetValue(null, null);
}
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,13 @@ private static void DrawRaisedBorder(
return;
}

if (borderStyle.Color is null)
{
return;
}

// draw the main box border
using var borderBrush = new SolidBrush(borderStyle.Color);
using var borderBrush = new SolidBrush(borderStyle.Color.Value);
var borderRegion = new Region(boxBounds.BorderBounds.ToRectangle());
borderRegion.Exclude(boxBounds.PaddingBounds.ToRectangle());
graphics.FillRegion(borderBrush, borderRegion);
Expand Down
Loading
Loading