mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02:00
[Mouse Jump] Customisable appearance - borders, margins, colours, etc - final part (#35521)
* [MouseJump] move Mouse Jump settings into separate control (#27511) * [MouseJump] added Mouse Jump style controls to Settings UI (#27511) * [MouseJump] added Mouse Jump style controls to Settings UI (#27511) * [MouseJump] removing unused MouseJumpUI code (#27511) * [MouseJump] whitespace (#27511) * [MouseJump] fix spellcheck (#27511) * [MouseJump] enabled "Copy to custom style" (#27511) * [MouseJump] fixing build (internal members -> public) (#27511) * [MouseJump] remove unused "using"s (#27511) * [MouseJump] use custom styles in preview image (#27511) * [MouseJump] fixing failing test (#27511) * [MouseJump] fixing failing test (#27511) * [MouseJump] fixing failing test (#27511) * [MouseJump] fixing failing test (#27511) * [MouseJump] delinting to trigger a build (#27511) * [MouseJump] updated settings preview image ("browser" header) (#27511) * [MouseJump] upgrade default "custom" style settings in config (#27511) * [MouseJump] fixed a glitch in settings upgrade (#27511) * [MouseJump] fixed spell checker (#27511) * [MouseJump] typo in resource strings (image -> images) (#27511) * Remove unused include
This commit is contained in:
@@ -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;
|
||||
@@ -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
|
||||
{
|
||||
@@ -46,7 +47,7 @@ public static class DrawingHelperTests
|
||||
yield return new object[]
|
||||
{
|
||||
new TestCase(
|
||||
previewStyle: StyleHelper.DefaultPreviewStyle,
|
||||
previewStyle: StyleHelper.BezelledPreviewStyle,
|
||||
screens: new List<RectangleInfo>()
|
||||
{
|
||||
new(0, 0, 500, 500),
|
||||
@@ -62,7 +63,7 @@ public static class DrawingHelperTests
|
||||
yield return new object[]
|
||||
{
|
||||
new TestCase(
|
||||
previewStyle: StyleHelper.DefaultPreviewStyle,
|
||||
previewStyle: StyleHelper.BezelledPreviewStyle,
|
||||
screens: new List<RectangleInfo>()
|
||||
{
|
||||
new(5120, 349, 1920, 1080),
|
||||
@@ -79,7 +80,7 @@ public static class DrawingHelperTests
|
||||
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(
|
||||
@@ -90,28 +91,29 @@ public static class DrawingHelperTests
|
||||
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)
|
||||
@@ -121,7 +123,7 @@ public static class DrawingHelperTests
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
|
||||
@@ -129,7 +129,7 @@ public static class LayoutHelperTests
|
||||
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
|
||||
//
|
||||
// +----------------+
|
||||
// | |
|
||||
@@ -160,7 +160,7 @@ public static class LayoutHelperTests
|
||||
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,
|
||||
@@ -183,7 +183,7 @@ public static class LayoutHelperTests
|
||||
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
|
||||
@@ -217,7 +217,7 @@ public static class LayoutHelperTests
|
||||
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,
|
||||
@@ -240,7 +240,59 @@ public static class LayoutHelperTests
|
||||
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
|
||||
@@ -291,7 +343,7 @@ public static class LayoutHelperTests
|
||||
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,
|
||||
@@ -321,7 +373,7 @@ public static class LayoutHelperTests
|
||||
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]
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 202 KiB |
@@ -15,45 +15,49 @@ public static class SizeInfoTests
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -102,8 +102,13 @@ public static class DrawingHelper
|
||||
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);
|
||||
|
||||
@@ -46,16 +46,13 @@ public static class LayoutHelper
|
||||
.Shrink(previewStyle.CanvasStyle.BorderStyle)
|
||||
.Shrink(previewStyle.CanvasStyle.PaddingStyle);
|
||||
|
||||
// scale the virtual screen to fit inside the content area
|
||||
var screenScalingRatio = builder.VirtualScreen.Size
|
||||
.ScaleToFitRatio(maxContentSize);
|
||||
|
||||
// work out the actual size of the "content area" by scaling the virtual screen
|
||||
// to fit inside the maximum content area while maintaining its aspect ration.
|
||||
// we'll also offset it to allow for any margins, borders and padding
|
||||
var contentBounds = builder.VirtualScreen.Size
|
||||
.Scale(screenScalingRatio)
|
||||
.Floor()
|
||||
.ScaleToFit(maxContentSize, out var scalingRatio)
|
||||
.Round()
|
||||
.Clamp(maxContentSize)
|
||||
.PlaceAt(0, 0)
|
||||
.Offset(previewStyle.CanvasStyle.MarginStyle.Left, previewStyle.CanvasStyle.MarginStyle.Top)
|
||||
.Offset(previewStyle.CanvasStyle.BorderStyle.Left, previewStyle.CanvasStyle.BorderStyle.Top)
|
||||
@@ -82,16 +79,16 @@ public static class LayoutHelper
|
||||
screen => LayoutHelper.GetBoxBoundsFromOuterBounds(
|
||||
screen
|
||||
.Offset(builder.VirtualScreen.Location.ToSize().Invert())
|
||||
.Scale(screenScalingRatio)
|
||||
.Scale(scalingRatio)
|
||||
.Offset(builder.PreviewBounds.ContentBounds.Location.ToSize())
|
||||
.Truncate(),
|
||||
.Round(),
|
||||
previewStyle.ScreenStyle))
|
||||
.ToList();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
internal static RectangleInfo GetCombinedScreenBounds(List<RectangleInfo> screens)
|
||||
public static RectangleInfo GetCombinedScreenBounds(List<RectangleInfo> screens)
|
||||
{
|
||||
return screens.Skip(1).Aggregate(
|
||||
seed: screens.First(),
|
||||
|
||||
@@ -102,7 +102,7 @@ public static class MouseHelper
|
||||
/// See https://github.com/microsoft/PowerToys/issues/24523
|
||||
/// https://github.com/microsoft/PowerToys/pull/24527
|
||||
/// </remarks>
|
||||
internal static void SimulateMouseMovementEvent(PointInfo location)
|
||||
private static void SimulateMouseMovementEvent(PointInfo location)
|
||||
{
|
||||
var inputs = new User32.INPUT[]
|
||||
{
|
||||
|
||||
@@ -10,49 +10,9 @@ namespace MouseJump.Common.Helpers;
|
||||
public static class StyleHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Default v2 preview style
|
||||
/// Compact (legacy) preview style
|
||||
/// </summary>
|
||||
public static readonly PreviewStyle DefaultPreviewStyle = new(
|
||||
canvasSize: new(
|
||||
width: 1600,
|
||||
height: 1200
|
||||
),
|
||||
canvasStyle: new(
|
||||
marginStyle: MarginStyle.Empty,
|
||||
borderStyle: new(
|
||||
color: SystemColors.Highlight,
|
||||
all: 6,
|
||||
depth: 0
|
||||
),
|
||||
paddingStyle: new(
|
||||
all: 4
|
||||
),
|
||||
backgroundStyle: new(
|
||||
color1: Color.FromArgb(0xFF, 0x0D, 0x57, 0xD2),
|
||||
color2: Color.FromArgb(0xFF, 0x03, 0x44, 0xC0)
|
||||
)
|
||||
),
|
||||
screenStyle: new(
|
||||
marginStyle: new(
|
||||
all: 4
|
||||
),
|
||||
borderStyle: new(
|
||||
color: Color.FromArgb(0xFF, 0x22, 0x22, 0x22),
|
||||
all: 12,
|
||||
depth: 4
|
||||
),
|
||||
paddingStyle: PaddingStyle.Empty,
|
||||
backgroundStyle: new(
|
||||
color1: Color.MidnightBlue,
|
||||
color2: Color.MidnightBlue
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Legacy preview style
|
||||
/// </summary>
|
||||
public static readonly PreviewStyle LegacyPreviewStyle = new(
|
||||
public static readonly PreviewStyle CompactPreviewStyle = new(
|
||||
canvasSize: new(
|
||||
width: 1600,
|
||||
height: 1200
|
||||
@@ -89,6 +49,46 @@ public static class StyleHelper
|
||||
)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Bezelled preview style
|
||||
/// </summary>
|
||||
public static readonly PreviewStyle BezelledPreviewStyle = new(
|
||||
canvasSize: new(
|
||||
width: 1600,
|
||||
height: 1200
|
||||
),
|
||||
canvasStyle: new(
|
||||
marginStyle: MarginStyle.Empty,
|
||||
borderStyle: new(
|
||||
color: SystemColors.Highlight,
|
||||
all: 6,
|
||||
depth: 0
|
||||
),
|
||||
paddingStyle: new(
|
||||
all: 4
|
||||
),
|
||||
backgroundStyle: new(
|
||||
color1: Color.FromArgb(0xFF, 0x0D, 0x57, 0xD2),
|
||||
color2: Color.FromArgb(0xFF, 0x03, 0x44, 0xC0)
|
||||
)
|
||||
),
|
||||
screenStyle: new(
|
||||
marginStyle: new(
|
||||
all: 4
|
||||
),
|
||||
borderStyle: new(
|
||||
color: Color.FromArgb(0xFF, 0x22, 0x22, 0x22),
|
||||
all: 12,
|
||||
depth: 4
|
||||
),
|
||||
paddingStyle: PaddingStyle.Empty,
|
||||
backgroundStyle: new(
|
||||
color1: Color.MidnightBlue,
|
||||
color2: Color.MidnightBlue
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public static PreviewStyle WithCanvasSize(this PreviewStyle previewStyle, SizeInfo canvasSize)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(previewStyle);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// 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.Drawing.Drawing2D;
|
||||
|
||||
using MouseJump.Common.Models.Drawing;
|
||||
|
||||
namespace MouseJump.Common.Imaging;
|
||||
@@ -31,6 +33,11 @@ public sealed class StaticImageRegionCopyService : IImageRegionCopyService
|
||||
RectangleInfo sourceBounds,
|
||||
RectangleInfo targetBounds)
|
||||
{
|
||||
// prevent the background bleeding through into screen images
|
||||
// (see https://github.com/mikeclayton/FancyMouse/issues/44)
|
||||
targetGraphics.PixelOffsetMode = PixelOffsetMode.Half;
|
||||
targetGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||
|
||||
targetGraphics.DrawImage(
|
||||
image: this.SourceImage,
|
||||
destRect: targetBounds.ToRectangle(),
|
||||
|
||||
@@ -203,6 +203,15 @@ public sealed class RectangleInfo
|
||||
public RectangleInfo Offset(decimal dx, decimal dy) =>
|
||||
new(this.X + dx, this.Y + dy, this.Width, this.Height);
|
||||
|
||||
public RectangleInfo Round() =>
|
||||
this.Round(0);
|
||||
|
||||
public RectangleInfo Round(int decimals) => new(
|
||||
Math.Round(this.X, decimals),
|
||||
Math.Round(this.Y, decimals),
|
||||
Math.Round(this.Width, decimals),
|
||||
Math.Round(this.Height, decimals));
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="RectangleInfo"/> that is a scaled version of the current rectangle.
|
||||
/// The dimensions of the new rectangle are calculated by multiplying the current rectangle's dimensions by the scaling factor.
|
||||
|
||||
@@ -12,6 +12,8 @@ public sealed class ScreenInfo
|
||||
{
|
||||
public ScreenInfo(int handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea)
|
||||
{
|
||||
// this.Handle is a HMONITOR that has been cast to an int because we don't want
|
||||
// to expose the HMONITOR type outside the current assembly.
|
||||
this.Handle = handle;
|
||||
this.Primary = primary;
|
||||
this.DisplayArea = displayArea ?? throw new ArgumentNullException(nameof(displayArea));
|
||||
|
||||
@@ -33,6 +33,20 @@ public sealed class SizeInfo
|
||||
get;
|
||||
}
|
||||
|
||||
public SizeInfo Clamp(SizeInfo max)
|
||||
{
|
||||
return new(
|
||||
width: Math.Clamp(this.Width, 0, max.Width),
|
||||
height: Math.Clamp(this.Height, 0, max.Height));
|
||||
}
|
||||
|
||||
public SizeInfo Clamp(decimal maxWidth, decimal maxHeight)
|
||||
{
|
||||
return new(
|
||||
width: Math.Clamp(this.Width, 0, maxWidth),
|
||||
height: Math.Clamp(this.Height, 0, maxHeight));
|
||||
}
|
||||
|
||||
public SizeInfo Enlarge(BorderStyle border) =>
|
||||
new(
|
||||
this.Width + border.Horizontal,
|
||||
@@ -43,6 +57,17 @@ public sealed class SizeInfo
|
||||
this.Width + padding.Horizontal,
|
||||
this.Height + padding.Vertical);
|
||||
|
||||
/// <summary>
|
||||
/// Rounds down the width and height of this size to the nearest whole number.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="SizeInfo"/> instance with floored dimensions.</returns>
|
||||
public SizeInfo Floor()
|
||||
{
|
||||
return new SizeInfo(
|
||||
Math.Floor(this.Width),
|
||||
Math.Floor(this.Height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the intersection of this size with another size, resulting in a size that represents
|
||||
/// the overlapping dimensions. Both sizes must be non-negative.
|
||||
@@ -69,19 +94,6 @@ public sealed class SizeInfo
|
||||
public SizeInfo Invert() =>
|
||||
new(-this.Width, -this.Height);
|
||||
|
||||
public SizeInfo Scale(decimal scalingFactor) => new(
|
||||
this.Width * scalingFactor,
|
||||
this.Height * scalingFactor);
|
||||
|
||||
public SizeInfo Shrink(BorderStyle border) =>
|
||||
new(this.Width - border.Horizontal, this.Height - border.Vertical);
|
||||
|
||||
public SizeInfo Shrink(MarginStyle margin) =>
|
||||
new(this.Width - margin.Horizontal, this.Height - margin.Vertical);
|
||||
|
||||
public SizeInfo Shrink(PaddingStyle padding) =>
|
||||
new(this.Width - padding.Horizontal, this.Height - padding.Vertical);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RectangleInfo"/> instance representing a rectangle with this size,
|
||||
/// positioned at the specified coordinates.
|
||||
@@ -92,32 +104,39 @@ public sealed class SizeInfo
|
||||
public RectangleInfo PlaceAt(decimal x, decimal y) =>
|
||||
new(x, y, this.Width, this.Height);
|
||||
|
||||
public SizeInfo Round() =>
|
||||
this.Round(0);
|
||||
|
||||
public SizeInfo Round(int decimals) => new(
|
||||
Math.Round(this.Width, decimals),
|
||||
Math.Round(this.Height, decimals));
|
||||
|
||||
public SizeInfo Scale(decimal scalingFactor) => new(
|
||||
this.Width * scalingFactor,
|
||||
this.Height * scalingFactor);
|
||||
|
||||
/// <summary>
|
||||
/// Scales this size to fit within the bounds of another size, while maintaining the aspect ratio.
|
||||
/// </summary>
|
||||
/// <param name="bounds">The size to fit this size into.</param>
|
||||
/// <returns>A new <see cref="SizeInfo"/> instance representing the scaled size.</returns>
|
||||
public SizeInfo ScaleToFit(SizeInfo bounds)
|
||||
public SizeInfo ScaleToFit(SizeInfo bounds, out decimal scalingRatio)
|
||||
{
|
||||
var widthRatio = bounds.Width / this.Width;
|
||||
var heightRatio = bounds.Height / this.Height;
|
||||
return widthRatio.CompareTo(heightRatio) switch
|
||||
switch (widthRatio.CompareTo(heightRatio))
|
||||
{
|
||||
< 0 => new(bounds.Width, this.Height * widthRatio),
|
||||
0 => bounds,
|
||||
> 0 => new(this.Width * heightRatio, bounds.Height),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds down the width and height of this size to the nearest whole number.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="SizeInfo"/> instance with floored dimensions.</returns>
|
||||
public SizeInfo Floor()
|
||||
{
|
||||
return new SizeInfo(
|
||||
Math.Floor(this.Width),
|
||||
Math.Floor(this.Height));
|
||||
case < 0:
|
||||
scalingRatio = widthRatio;
|
||||
return new(bounds.Width, this.Height * widthRatio);
|
||||
case 0:
|
||||
// widthRatio and heightRatio are the same, so just pick one
|
||||
scalingRatio = widthRatio;
|
||||
return bounds;
|
||||
case > 0:
|
||||
scalingRatio = heightRatio;
|
||||
return new(this.Width * heightRatio, bounds.Height);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,6 +159,15 @@ public sealed class SizeInfo
|
||||
return scalingRatio;
|
||||
}
|
||||
|
||||
public SizeInfo Shrink(BorderStyle border) =>
|
||||
new(this.Width - border.Horizontal, this.Height - border.Vertical);
|
||||
|
||||
public SizeInfo Shrink(MarginStyle margin) =>
|
||||
new(this.Width - margin.Horizontal, this.Height - margin.Vertical);
|
||||
|
||||
public SizeInfo Shrink(PaddingStyle padding) =>
|
||||
new(this.Width - padding.Horizontal, this.Height - padding.Vertical);
|
||||
|
||||
public Size ToSize() => new((int)this.Width, (int)this.Height);
|
||||
|
||||
public Point ToPoint() => new((int)this.Width, (int)this.Height);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
|
||||
namespace MouseJump.Common.Models.Settings;
|
||||
|
||||
public enum PreviewType
|
||||
{
|
||||
Custom = 0,
|
||||
Compact = 1,
|
||||
Bezelled = 2,
|
||||
}
|
||||
@@ -9,14 +9,14 @@ namespace MouseJump.Common.Models.Styles;
|
||||
/// </summary>
|
||||
public sealed class BorderStyle
|
||||
{
|
||||
public static readonly BorderStyle Empty = new(Color.Transparent, 0, 0);
|
||||
public static readonly BorderStyle Empty = new(null, 0, 0);
|
||||
|
||||
public BorderStyle(Color color, decimal all, decimal depth)
|
||||
public BorderStyle(Color? color, decimal all, decimal depth)
|
||||
: this(color, all, all, all, all, depth)
|
||||
{
|
||||
}
|
||||
|
||||
public BorderStyle(Color color, decimal left, decimal top, decimal right, decimal bottom, decimal depth)
|
||||
public BorderStyle(Color? color, decimal left, decimal top, decimal right, decimal bottom, decimal depth)
|
||||
{
|
||||
this.Color = color;
|
||||
this.Left = left;
|
||||
@@ -26,7 +26,7 @@ public sealed class BorderStyle
|
||||
this.Depth = depth;
|
||||
}
|
||||
|
||||
public Color Color
|
||||
public Color? Color
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ using System.Threading;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using MouseJump.Common.Helpers;
|
||||
using MouseJump.Common.Models.Drawing;
|
||||
using MouseJump.Common.Models.Settings;
|
||||
using MouseJump.Common.Models.Styles;
|
||||
|
||||
namespace MouseJumpUI.Helpers;
|
||||
|
||||
@@ -93,4 +97,65 @@ internal sealed class SettingsHelper
|
||||
{
|
||||
this.CurrentSettings = this.LoadSettings();
|
||||
}
|
||||
|
||||
public static PreviewStyle GetActivePreviewStyle(MouseJumpSettings settings)
|
||||
{
|
||||
var previewType = Enum.TryParse<PreviewType>(settings.Properties.PreviewType, true, out var previewTypeResult)
|
||||
? previewTypeResult
|
||||
: PreviewType.Bezelled;
|
||||
|
||||
var canvasSize = new SizeInfo(
|
||||
settings.Properties.ThumbnailSize.Width,
|
||||
settings.Properties.ThumbnailSize.Height);
|
||||
|
||||
var properties = settings.Properties;
|
||||
|
||||
var previewStyle = previewType switch
|
||||
{
|
||||
PreviewType.Compact => StyleHelper.CompactPreviewStyle.WithCanvasSize(canvasSize),
|
||||
PreviewType.Bezelled => StyleHelper.BezelledPreviewStyle.WithCanvasSize(canvasSize),
|
||||
PreviewType.Custom => new PreviewStyle(
|
||||
canvasSize: canvasSize,
|
||||
canvasStyle: new(
|
||||
marginStyle: new(0),
|
||||
borderStyle: new(
|
||||
color: ConfigHelper.DeserializeFromConfigColorString(
|
||||
properties.BorderColor),
|
||||
all: properties.BorderThickness,
|
||||
depth: properties.Border3dDepth
|
||||
),
|
||||
paddingStyle: new(
|
||||
all: properties.BorderPadding
|
||||
),
|
||||
backgroundStyle: new(
|
||||
color1: ConfigHelper.DeserializeFromConfigColorString(
|
||||
properties.BackgroundColor1),
|
||||
color2: ConfigHelper.DeserializeFromConfigColorString(
|
||||
properties.BackgroundColor2)
|
||||
)
|
||||
),
|
||||
screenStyle: new(
|
||||
marginStyle: new(
|
||||
all: properties.ScreenMargin
|
||||
),
|
||||
borderStyle: new(
|
||||
color: ConfigHelper.DeserializeFromConfigColorString(
|
||||
properties.BezelColor),
|
||||
all: properties.BezelThickness,
|
||||
depth: properties.Bezel3dDepth
|
||||
),
|
||||
paddingStyle: new(0),
|
||||
backgroundStyle: new(
|
||||
color1: ConfigHelper.DeserializeFromConfigColorString(
|
||||
properties.ScreenColor1),
|
||||
color2: ConfigHelper.DeserializeFromConfigColorString(
|
||||
properties.ScreenColor2)
|
||||
)
|
||||
)),
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Unhandled {nameof(PreviewType)} '{previewType}'"),
|
||||
};
|
||||
|
||||
return previewStyle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,12 +183,9 @@ internal sealed partial class MainForm : Form
|
||||
var appSettings = this.SettingsHelper.CurrentSettings ?? throw new InvalidOperationException();
|
||||
var screens = ScreenHelper.GetAllScreens().Select(screen => screen.DisplayArea).ToList();
|
||||
var activatedLocation = MouseHelper.GetCursorPosition();
|
||||
|
||||
this.PreviewLayout = LayoutHelper.GetPreviewLayout(
|
||||
previewStyle: StyleHelper.LegacyPreviewStyle.WithCanvasSize(
|
||||
new(
|
||||
appSettings.Properties.ThumbnailSize.Width,
|
||||
appSettings.Properties.ThumbnailSize.Height
|
||||
)),
|
||||
previewStyle: SettingsHelper.GetActivePreviewStyle(appSettings),
|
||||
screens: screens,
|
||||
activatedLocation: activatedLocation);
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// 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.Windows.Forms;
|
||||
|
||||
namespace MouseJumpUI.Models.Drawing;
|
||||
|
||||
/// <summary>
|
||||
/// Immutable version of a System.Windows.Forms.Padding object with some extra utility methods.
|
||||
/// </summary>
|
||||
public sealed class PaddingInfo
|
||||
{
|
||||
public PaddingInfo(decimal all)
|
||||
: this(all, all, all, all)
|
||||
{
|
||||
}
|
||||
|
||||
public PaddingInfo(decimal left, decimal top, decimal right, decimal bottom)
|
||||
{
|
||||
this.Left = left;
|
||||
this.Top = top;
|
||||
this.Right = right;
|
||||
this.Bottom = bottom;
|
||||
}
|
||||
|
||||
public decimal Left
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Top
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Right
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Bottom
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Horizontal => this.Left + this.Right;
|
||||
|
||||
public decimal Vertical => this.Top + this.Bottom;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{" +
|
||||
$"{nameof(this.Left)}={this.Left}," +
|
||||
$"{nameof(this.Top)}={this.Top}," +
|
||||
$"{nameof(this.Right)}={this.Right}," +
|
||||
$"{nameof(this.Bottom)}={this.Bottom}" +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// 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.Drawing;
|
||||
|
||||
namespace MouseJumpUI.Models.Drawing;
|
||||
|
||||
/// <summary>
|
||||
/// Immutable version of a System.Drawing.Point object with some extra utility methods.
|
||||
/// </summary>
|
||||
public sealed class PointInfo
|
||||
{
|
||||
public PointInfo(decimal x, decimal y)
|
||||
{
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
}
|
||||
|
||||
public PointInfo(Point point)
|
||||
: this(point.X, point.Y)
|
||||
{
|
||||
}
|
||||
|
||||
public decimal X
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Y
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public SizeInfo ToSize()
|
||||
{
|
||||
return new((int)this.X, (int)this.Y);
|
||||
}
|
||||
|
||||
public PointInfo Scale(decimal scalingFactor) => new(this.X * scalingFactor, this.Y * scalingFactor);
|
||||
|
||||
public PointInfo Offset(PointInfo amount) => new(this.X + amount.X, this.Y + amount.Y);
|
||||
|
||||
public Point ToPoint() => new((int)this.X, (int)this.Y);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{" +
|
||||
$"{nameof(this.X)}={this.X}," +
|
||||
$"{nameof(this.Y)}={this.Y}" +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
// 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;
|
||||
using System.Drawing;
|
||||
|
||||
namespace MouseJumpUI.Models.Drawing;
|
||||
|
||||
/// <summary>
|
||||
/// Immutable version of a System.Drawing.Rectangle object with some extra utility methods.
|
||||
/// </summary>
|
||||
public sealed class RectangleInfo
|
||||
{
|
||||
public RectangleInfo(decimal x, decimal y, decimal width, decimal height)
|
||||
{
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
this.Width = width;
|
||||
this.Height = height;
|
||||
}
|
||||
|
||||
public RectangleInfo(Rectangle rectangle)
|
||||
: this(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height)
|
||||
{
|
||||
}
|
||||
|
||||
public RectangleInfo(Point location, SizeInfo size)
|
||||
: this(location.X, location.Y, size.Width, size.Height)
|
||||
{
|
||||
}
|
||||
|
||||
public RectangleInfo(SizeInfo size)
|
||||
: this(0, 0, size.Width, size.Height)
|
||||
{
|
||||
}
|
||||
|
||||
public decimal X
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Y
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Left => this.X;
|
||||
|
||||
public decimal Top => this.Y;
|
||||
|
||||
public decimal Right => this.X + this.Width;
|
||||
|
||||
public decimal Bottom => this.Y + this.Height;
|
||||
|
||||
public SizeInfo Size => new(this.Width, this.Height);
|
||||
|
||||
public PointInfo Location => new(this.X, this.Y);
|
||||
|
||||
public decimal Area => this.Width * this.Height;
|
||||
|
||||
/// <remarks>
|
||||
/// Adapted from https://github.com/dotnet/runtime
|
||||
/// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs
|
||||
/// </remarks>
|
||||
public bool Contains(RectangleInfo rect) =>
|
||||
(this.X <= rect.X) && (rect.X + rect.Width <= this.X + this.Width) &&
|
||||
(this.Y <= rect.Y) && (rect.Y + rect.Height <= this.Y + this.Height);
|
||||
|
||||
public RectangleInfo Enlarge(PaddingInfo padding) => new(
|
||||
this.X + padding.Left,
|
||||
this.Y + padding.Top,
|
||||
this.Width + padding.Horizontal,
|
||||
this.Height + padding.Vertical);
|
||||
|
||||
public RectangleInfo Offset(SizeInfo amount) => this.Offset(amount.Width, amount.Height);
|
||||
|
||||
public RectangleInfo Offset(decimal dx, decimal dy) => new(this.X + dx, this.Y + dy, this.Width, this.Height);
|
||||
|
||||
public RectangleInfo Scale(decimal scalingFactor) => new(
|
||||
this.X * scalingFactor,
|
||||
this.Y * scalingFactor,
|
||||
this.Width * scalingFactor,
|
||||
this.Height * scalingFactor);
|
||||
|
||||
public RectangleInfo Center(PointInfo point) => new(
|
||||
x: point.X - (this.Width / 2),
|
||||
y: point.Y - (this.Height / 2),
|
||||
width: this.Width,
|
||||
height: this.Height);
|
||||
|
||||
public PointInfo Midpoint => new(
|
||||
x: this.X + (this.Width / 2),
|
||||
y: this.Y + (this.Height / 2));
|
||||
|
||||
public RectangleInfo Clamp(RectangleInfo outer)
|
||||
{
|
||||
if ((this.Width > outer.Width) || (this.Height > outer.Height))
|
||||
{
|
||||
throw new ArgumentException($"Value cannot be larger than {nameof(outer)}.");
|
||||
}
|
||||
|
||||
return new(
|
||||
x: Math.Clamp(this.X, outer.X, outer.Right - this.Width),
|
||||
y: Math.Clamp(this.Y, outer.Y, outer.Bottom - this.Height),
|
||||
width: this.Width,
|
||||
height: this.Height);
|
||||
}
|
||||
|
||||
public Rectangle ToRectangle() => new((int)this.X, (int)this.Y, (int)this.Width, (int)this.Height);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{" +
|
||||
$"{nameof(this.Left)}={this.Left}," +
|
||||
$"{nameof(this.Top)}={this.Top}," +
|
||||
$"{nameof(this.Width)}={this.Width}," +
|
||||
$"{nameof(this.Height)}={this.Height}" +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// 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;
|
||||
using System.Drawing;
|
||||
|
||||
namespace MouseJumpUI.Models.Drawing;
|
||||
|
||||
/// <summary>
|
||||
/// Immutable version of a System.Drawing.Size object with some extra utility methods.
|
||||
/// </summary>
|
||||
public sealed class SizeInfo
|
||||
{
|
||||
public SizeInfo(decimal width, decimal height)
|
||||
{
|
||||
this.Width = width;
|
||||
this.Height = height;
|
||||
}
|
||||
|
||||
public SizeInfo(Size size)
|
||||
: this(size.Width, size.Height)
|
||||
{
|
||||
}
|
||||
|
||||
public decimal Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public decimal Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public SizeInfo Negate() => new(-this.Width, -this.Height);
|
||||
|
||||
public SizeInfo Shrink(PaddingInfo padding) => new(this.Width - padding.Horizontal, this.Height - padding.Vertical);
|
||||
|
||||
public SizeInfo Intersect(SizeInfo size) => new(
|
||||
Math.Min(this.Width, size.Width),
|
||||
Math.Min(this.Height, size.Height));
|
||||
|
||||
public RectangleInfo PlaceAt(decimal x, decimal y) => new(x, y, this.Width, this.Height);
|
||||
|
||||
public SizeInfo ScaleToFit(SizeInfo bounds)
|
||||
{
|
||||
var widthRatio = bounds.Width / this.Width;
|
||||
var heightRatio = bounds.Height / this.Height;
|
||||
return widthRatio.CompareTo(heightRatio) switch
|
||||
{
|
||||
< 0 => new(bounds.Width, this.Height * widthRatio),
|
||||
0 => bounds,
|
||||
> 0 => new(this.Width * heightRatio, bounds.Height),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the scaling ratio to scale obj by so that it fits inside the specified bounds
|
||||
/// without distorting the aspect ratio.
|
||||
/// </summary>
|
||||
public decimal ScaleToFitRatio(SizeInfo bounds)
|
||||
{
|
||||
if (bounds.Width == 0 || bounds.Height == 0)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(bounds.Width)} or {nameof(bounds.Height)} cannot be zero", nameof(bounds));
|
||||
}
|
||||
|
||||
var widthRatio = bounds.Width / this.Width;
|
||||
var heightRatio = bounds.Height / this.Height;
|
||||
var scalingRatio = Math.Min(widthRatio, heightRatio);
|
||||
|
||||
return scalingRatio;
|
||||
}
|
||||
|
||||
public Size ToSize() => new((int)this.Width, (int)this.Height);
|
||||
|
||||
public Point ToPoint() => new((int)this.Width, (int)this.Height);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{" +
|
||||
$"{nameof(this.Width)}={this.Width}," +
|
||||
$"{nameof(this.Height)}={this.Height}" +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using MouseJumpUI.Models.Drawing;
|
||||
using MouseJumpUI.Models.Screen;
|
||||
|
||||
namespace MouseJumpUI.Models.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of values needed for calculating the MainForm layout.
|
||||
/// </summary>
|
||||
public sealed class LayoutConfig
|
||||
{
|
||||
public LayoutConfig(
|
||||
RectangleInfo virtualScreenBounds,
|
||||
List<ScreenInfo> screens,
|
||||
PointInfo activatedLocation,
|
||||
int activatedScreenIndex,
|
||||
int activatedScreenNumber,
|
||||
SizeInfo maximumFormSize,
|
||||
PaddingInfo formPadding,
|
||||
PaddingInfo previewPadding)
|
||||
{
|
||||
// make sure the virtual screen entirely contains all of the individual screen bounds
|
||||
ArgumentNullException.ThrowIfNull(virtualScreenBounds);
|
||||
ArgumentNullException.ThrowIfNull(screens);
|
||||
if (screens.Any(screen => !virtualScreenBounds.Contains(screen.Bounds)))
|
||||
{
|
||||
throw new ArgumentException($"'{nameof(virtualScreenBounds)}' must contain all of the screens in '{nameof(screens)}'", nameof(virtualScreenBounds));
|
||||
}
|
||||
|
||||
this.VirtualScreenBounds = virtualScreenBounds;
|
||||
this.Screens = new(screens.ToList());
|
||||
this.ActivatedLocation = activatedLocation;
|
||||
this.ActivatedScreenIndex = activatedScreenIndex;
|
||||
this.ActivatedScreenNumber = activatedScreenNumber;
|
||||
this.MaximumFormSize = maximumFormSize;
|
||||
this.FormPadding = formPadding;
|
||||
this.PreviewPadding = previewPadding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the coordinates of the entire virtual screen.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Virtual Screen is the bounding rectangle of all the monitors.
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/gdi/the-virtual-screen
|
||||
/// </remarks>
|
||||
public RectangleInfo VirtualScreenBounds
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection containing the individual screens connected to the system.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ScreenInfo> Screens
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point where the cursor was located when the form was activated.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// The preview form will be centered on this location unless there are any
|
||||
/// constraints such as being too close to edge of a screen, in which case
|
||||
/// the form will be displayed centered as close as possible to this location.
|
||||
/// </summary>
|
||||
public PointInfo ActivatedLocation
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the screen the cursor was on when the form was activated.
|
||||
/// The value is an index into the ScreenBounds array and is 0-indexed as a result.
|
||||
/// </summary>
|
||||
public int ActivatedScreenIndex
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen number the cursor was on when the form was activated.
|
||||
/// The value matches the screen numbering scheme in the "Display Settings" dialog
|
||||
/// and is 1-indexed as a result.
|
||||
/// </summary>
|
||||
public int ActivatedScreenNumber
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum size of the screen preview form.
|
||||
/// </summary>
|
||||
public SizeInfo MaximumFormSize
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the padding border around the screen preview form.
|
||||
/// </summary>
|
||||
public PaddingInfo FormPadding
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the padding border inside the screen preview image.
|
||||
/// </summary>
|
||||
public PaddingInfo PreviewPadding
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using MouseJumpUI.Models.Drawing;
|
||||
|
||||
namespace MouseJumpUI.Models.Layout;
|
||||
|
||||
public sealed class LayoutInfo
|
||||
{
|
||||
public sealed class Builder
|
||||
{
|
||||
public Builder()
|
||||
{
|
||||
this.ScreenBounds = new();
|
||||
}
|
||||
|
||||
public LayoutConfig? LayoutConfig
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public RectangleInfo? FormBounds
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public RectangleInfo? PreviewBounds
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public List<RectangleInfo> ScreenBounds
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public RectangleInfo? ActivatedScreenBounds
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public LayoutInfo Build()
|
||||
{
|
||||
return new LayoutInfo(
|
||||
layoutConfig: this.LayoutConfig ?? throw new InvalidOperationException(),
|
||||
formBounds: this.FormBounds ?? throw new InvalidOperationException(),
|
||||
previewBounds: this.PreviewBounds ?? throw new InvalidOperationException(),
|
||||
screenBounds: this.ScreenBounds ?? throw new InvalidOperationException(),
|
||||
activatedScreenBounds: this.ActivatedScreenBounds ?? throw new InvalidOperationException());
|
||||
}
|
||||
}
|
||||
|
||||
public LayoutInfo(
|
||||
LayoutConfig layoutConfig,
|
||||
RectangleInfo formBounds,
|
||||
RectangleInfo previewBounds,
|
||||
IEnumerable<RectangleInfo> screenBounds,
|
||||
RectangleInfo activatedScreenBounds)
|
||||
{
|
||||
this.LayoutConfig = layoutConfig ?? throw new ArgumentNullException(nameof(layoutConfig));
|
||||
this.FormBounds = formBounds ?? throw new ArgumentNullException(nameof(formBounds));
|
||||
this.PreviewBounds = previewBounds ?? throw new ArgumentNullException(nameof(previewBounds));
|
||||
this.ScreenBounds = new(
|
||||
(screenBounds ?? throw new ArgumentNullException(nameof(screenBounds)))
|
||||
.ToList());
|
||||
this.ActivatedScreenBounds = activatedScreenBounds ?? throw new ArgumentNullException(nameof(activatedScreenBounds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original LayoutConfig settings used to calculate coordinates.
|
||||
/// </summary>
|
||||
public LayoutConfig LayoutConfig
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size and location of the preview form.
|
||||
/// </summary>
|
||||
public RectangleInfo FormBounds
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size and location of the preview image.
|
||||
/// </summary>
|
||||
public RectangleInfo PreviewBounds
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<RectangleInfo> ScreenBounds
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public RectangleInfo ActivatedScreenBounds
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// 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;
|
||||
|
||||
using MouseJumpUI.Models.Drawing;
|
||||
|
||||
namespace MouseJumpUI.Models.Screen;
|
||||
|
||||
/// <summary>
|
||||
/// Immutable version of a System.Windows.Forms.Screen object so we don't need to
|
||||
/// take a dependency on WinForms just for screen info.
|
||||
/// </summary>
|
||||
public sealed class ScreenInfo
|
||||
{
|
||||
internal ScreenInfo(int handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea)
|
||||
{
|
||||
this.Handle = handle;
|
||||
this.Primary = primary;
|
||||
this.DisplayArea = displayArea ?? throw new ArgumentNullException(nameof(displayArea));
|
||||
this.WorkingArea = workingArea ?? throw new ArgumentNullException(nameof(workingArea));
|
||||
}
|
||||
|
||||
public int Handle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public bool Primary
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public RectangleInfo DisplayArea
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public RectangleInfo Bounds =>
|
||||
this.DisplayArea;
|
||||
|
||||
public RectangleInfo WorkingArea
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Threading;
|
||||
@@ -97,29 +95,4 @@ internal static class Program
|
||||
cancellationTokenSource.Cancel();
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
private static MouseJumpSettings ReadSettings()
|
||||
{
|
||||
var settingsUtils = new SettingsUtils();
|
||||
var settingsPath = settingsUtils.GetSettingsFilePath(MouseJumpSettings.ModuleName);
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
var scaffoldSettings = new MouseJumpSettings();
|
||||
settingsUtils.SaveSettings(JsonSerializer.Serialize(scaffoldSettings), MouseJumpSettings.ModuleName);
|
||||
}
|
||||
|
||||
var settings = new MouseJumpSettings();
|
||||
try
|
||||
{
|
||||
settings = settingsUtils.GetSettings<MouseJumpSettings>(MouseJumpSettings.ModuleName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}";
|
||||
Logger.LogInfo(errorMessage);
|
||||
Logger.LogDebug(errorMessage);
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user