mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 02:36:19 +02:00
[New Utility]Mouse Jump(#23566)
* #23216 - initial MouseJump commit * #23216 - Mouse Jump - fix spelling, removing Interop folder * #23216 - Mouse Jump - removed orphaned project guids from PowerToys.sln * #23216 - Mouse Jump - removed orphaned project guids from PowerToys.sln * #23216 - Mouse Jump - switch MS Logger to NLog for nuget package allow-listing * #23216 added MouseJumpUI.UnitTests.dll to "MS Tests" step in build-powertoys-steps.yml * [MouseJump] fixed screenshot coords (x & y were transposed) (#23216) * [MouseJump] close form rather than hide on deactivate (#23216) * [MouseJump] added UI dll for signing (#23216) * [MouseJump] close form rather than hide on deactivate (#23216) * [MouseJump] removed redundant line * [MouseJump] configure dpi awareness, add NLog.config (microsoft#23216) * [MouseJump] fix spellchecker errors (microsoft#23216) * [MouseJump] fixing comment style warning (microsoft#23216) * [MouseJump] simplified dpi config (microsoft#23216) * [MouseJump] fixed edge case issue with moving cursor (microsoft#23216) * [MouseJump] fixed typo (microsoft#23216) * [MouseJump] added attribution (microsoft#23216) * [Mouse Jump] fix attribution link and spelling (microsoft#23216) * Add MouseJump to installer * Fix centralized version control * Add Quick Access enable/disable entry * Fix analyzer error in GPO * Fix botched merge * Disabled by default and remove boilerplate code * Add GPO definitions * Add GPO implications when starting standalone * Update hotkey when it's changed in Settings * Use standard Logger * Add OOBE strings for Mouse Jump * Add telemetry * Update installer * Add signing * Add to bug report tool * Address PR feedback
This commit is contained in:
191
src/modules/MouseUtils/MouseJumpUI/Helpers/LayoutHelper.cs
Normal file
191
src/modules/MouseUtils/MouseJumpUI/Helpers/LayoutHelper.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
// 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.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace MouseJumpUI.Helpers;
|
||||
|
||||
internal static class LayoutHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Center an object on the given origin.
|
||||
/// </summary>
|
||||
public static Point CenterObject(Size obj, Point origin)
|
||||
{
|
||||
return new Point(
|
||||
x: (int)(origin.X - ((float)obj.Width / 2)),
|
||||
y: (int)(origin.Y - ((float)obj.Height / 2)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the specified regions and returns the smallest rectangle that contains them.
|
||||
/// </summary>
|
||||
/// <param name="regions">The regions to combine.</param>
|
||||
/// <returns>
|
||||
/// Returns the smallest rectangle that contains all the specified regions.
|
||||
/// </returns>
|
||||
public static Rectangle CombineRegions(List<Rectangle> regions)
|
||||
{
|
||||
if (regions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(regions));
|
||||
}
|
||||
|
||||
if (regions.Count == 0)
|
||||
{
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
|
||||
var combined = regions.Aggregate(
|
||||
seed: regions[0],
|
||||
func: Rectangle.Union);
|
||||
return combined;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the midpoint of the given region.
|
||||
/// </summary>
|
||||
public static Point GetMidpoint(Rectangle region)
|
||||
{
|
||||
return new Point(
|
||||
(region.Left + region.Right) / 2,
|
||||
(region.Top + region.Bottom) / 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the largest Size object that can fit inside
|
||||
/// all of the given sizes. (Equivalent to a Size
|
||||
/// object with the smallest Width and smallest Height from
|
||||
/// all of the specified sizes).
|
||||
/// </summary>
|
||||
public static Size IntersectSizes(params Size[] sizes)
|
||||
{
|
||||
return new Size(
|
||||
sizes.Min(s => s.Width),
|
||||
sizes.Min(s => s.Height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the location to move the inner rectangle so that it sits entirely inside
|
||||
/// the outer rectangle. Returns the inner rectangle's current position if it is
|
||||
/// already inside the outer rectangle.
|
||||
/// </summary>
|
||||
public static Rectangle MoveInside(Rectangle inner, Rectangle outer)
|
||||
{
|
||||
if ((inner.Width > outer.Width) || (inner.Height > outer.Height))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(inner)} cannot be larger than {nameof(outer)}.");
|
||||
}
|
||||
|
||||
return inner with
|
||||
{
|
||||
X = Math.Clamp(inner.X, outer.X, outer.Right - inner.Width),
|
||||
Y = Math.Clamp(inner.Y, outer.Y, outer.Bottom - inner.Height),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales a location within a reference region onto a new region
|
||||
/// so that it's proportionally in the same position in the new region.
|
||||
/// </summary>
|
||||
public static Point ScaleLocation(Rectangle originalBounds, Point originalLocation, Rectangle scaledBounds)
|
||||
{
|
||||
return new Point(
|
||||
(int)(originalLocation.X / (double)originalBounds.Width * scaledBounds.Width) + scaledBounds.Left,
|
||||
(int)(originalLocation.Y / (double)originalBounds.Height * scaledBounds.Height) + scaledBounds.Top);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale an object to fit inside the specified bounds while maintaining aspect ratio.
|
||||
/// </summary>
|
||||
public static Size ScaleToFit(Size obj, Size bounds)
|
||||
{
|
||||
if (bounds.Width == 0 || bounds.Height == 0)
|
||||
{
|
||||
return Size.Empty;
|
||||
}
|
||||
|
||||
var widthRatio = (double)obj.Width / bounds.Width;
|
||||
var heightRatio = (double)obj.Height / bounds.Height;
|
||||
var scaledSize = (widthRatio > heightRatio)
|
||||
? bounds with
|
||||
{
|
||||
Height = (int)(obj.Height / widthRatio),
|
||||
}
|
||||
: bounds with
|
||||
{
|
||||
Width = (int)(obj.Width / heightRatio),
|
||||
};
|
||||
return scaledSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the position to show the preview form based on a number of factors.
|
||||
/// </summary>
|
||||
/// <param name="desktopBounds">
|
||||
/// The bounds of the entire desktop / virtual screen. Might start at a negative
|
||||
/// x, y if a non-primary screen is located left of or above the primary screen.
|
||||
/// </param>
|
||||
/// <param name="activatedPosition">
|
||||
/// The current position of the cursor on the virtual desktop.
|
||||
/// </param>
|
||||
/// <param name="activatedMonitorBounds">
|
||||
/// The bounds of the screen the cursor is currently on. Might start at a negative
|
||||
/// x, y if a non-primary screen is located left of or above the primary screen.
|
||||
/// </param>
|
||||
/// <param name="maximumThumbnailImageSize">
|
||||
/// The largest allowable size of the preview image. This is literally the just
|
||||
/// image itself, not including padding around the image.
|
||||
/// </param>
|
||||
/// <param name="thumbnailImagePadding">
|
||||
/// The total width and height of padding around the preview image.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The size and location to use when showing the preview image form.
|
||||
/// </returns>
|
||||
public static Rectangle GetPreviewFormBounds(
|
||||
Rectangle desktopBounds,
|
||||
Point activatedPosition,
|
||||
Rectangle activatedMonitorBounds,
|
||||
Size maximumThumbnailImageSize,
|
||||
Size thumbnailImagePadding)
|
||||
{
|
||||
// see https://learn.microsoft.com/en-gb/windows/win32/gdi/the-virtual-screen
|
||||
// calculate the maximum size the form is allowed to be
|
||||
var maxFormSize = LayoutHelper.IntersectSizes(
|
||||
new[]
|
||||
{
|
||||
// can't be bigger than the current screen
|
||||
activatedMonitorBounds.Size,
|
||||
|
||||
// can't be bigger than the max preview image
|
||||
// *plus* the padding around the preview image
|
||||
// (max thumbnail image size doesn't include the padding)
|
||||
maximumThumbnailImageSize + thumbnailImagePadding,
|
||||
});
|
||||
|
||||
// calculate the actual form size by scaling the entire
|
||||
// desktop bounds into the max thumbnail size while accounting
|
||||
// for the size of the padding around the preview
|
||||
var thumbnailImageSize = LayoutHelper.ScaleToFit(
|
||||
obj: desktopBounds.Size,
|
||||
bounds: maxFormSize - thumbnailImagePadding);
|
||||
var formSize = thumbnailImageSize + thumbnailImagePadding;
|
||||
|
||||
// center the form to the activated position, but nudge it back
|
||||
// inside the visible area of the screen if it falls outside
|
||||
var formBounds = LayoutHelper.MoveInside(
|
||||
inner: new Rectangle(
|
||||
LayoutHelper.CenterObject(
|
||||
obj: formSize,
|
||||
origin: activatedPosition),
|
||||
formSize),
|
||||
outer: activatedMonitorBounds);
|
||||
|
||||
return formBounds;
|
||||
}
|
||||
}
|
||||
77
src/modules/MouseUtils/MouseJumpUI/Helpers/Logger.cs
Normal file
77
src/modules/MouseUtils/MouseJumpUI/Helpers/Logger.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace MouseJumpUI.Helpers
|
||||
{
|
||||
// TODO: use centralized logger https://github.com/microsoft/PowerToys/issues/19650
|
||||
public static class Logger
|
||||
{
|
||||
private static readonly string ApplicationLogPath = Path.Combine(interop.Constants.AppDataPath(), "MouseJump\\Logs");
|
||||
|
||||
static Logger()
|
||||
{
|
||||
if (!Directory.Exists(ApplicationLogPath))
|
||||
{
|
||||
Directory.CreateDirectory(ApplicationLogPath);
|
||||
}
|
||||
|
||||
// Using InvariantCulture since this is used for a log file name
|
||||
var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
|
||||
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
|
||||
|
||||
Trace.AutoFlush = true;
|
||||
}
|
||||
|
||||
public static void LogError(string message)
|
||||
{
|
||||
Log(message, "ERROR");
|
||||
}
|
||||
|
||||
public static void LogError(string message, Exception ex)
|
||||
{
|
||||
Log(
|
||||
message + Environment.NewLine +
|
||||
ex?.Message + Environment.NewLine +
|
||||
"Inner exception: " + Environment.NewLine +
|
||||
ex?.InnerException?.Message + Environment.NewLine +
|
||||
"Stack trace: " + Environment.NewLine +
|
||||
ex?.StackTrace,
|
||||
"ERROR");
|
||||
}
|
||||
|
||||
public static void LogWarning(string message)
|
||||
{
|
||||
Log(message, "WARNING");
|
||||
}
|
||||
|
||||
public static void LogInfo(string message)
|
||||
{
|
||||
Log(message, "INFO");
|
||||
}
|
||||
|
||||
private static void Log(string message, string type)
|
||||
{
|
||||
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
|
||||
Trace.Indent();
|
||||
Trace.WriteLine(GetCallerInfo());
|
||||
Trace.WriteLine(message);
|
||||
Trace.Unindent();
|
||||
}
|
||||
|
||||
private static string GetCallerInfo()
|
||||
{
|
||||
StackTrace stackTrace = new StackTrace();
|
||||
|
||||
var methodName = stackTrace.GetFrame(3)?.GetMethod();
|
||||
var className = methodName?.DeclaringType.Name;
|
||||
return "[Method]: " + methodName?.Name + " [Class]: " + className;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user