diff --git a/DesktopClock.Tests/WindowUtilTests.cs b/DesktopClock.Tests/WindowUtilTests.cs
index 2135c9c..cba42ed 100644
--- a/DesktopClock.Tests/WindowUtilTests.cs
+++ b/DesktopClock.Tests/WindowUtilTests.cs
@@ -1,7 +1,37 @@
+using System.Windows.Input;
+
namespace DesktopClock.Tests;
public class WindowUtilTests
{
+ [Theory]
+ [InlineData(Key.Left, ModifierKeys.None, -1, 0)]
+ [InlineData(Key.Right, ModifierKeys.None, 1, 0)]
+ [InlineData(Key.Up, ModifierKeys.None, 0, -1)]
+ [InlineData(Key.Down, ModifierKeys.None, 0, 1)]
+ [InlineData(Key.Left, ModifierKeys.Shift, -10, 0)]
+ [InlineData(Key.Right, ModifierKeys.Shift, 10, 0)]
+ [InlineData(Key.Up, ModifierKeys.Shift, 0, -10)]
+ [InlineData(Key.Down, ModifierKeys.Shift, 0, 10)]
+ public void GetKeyboardNudge_ArrowKeys_MoveByStep(Key key, ModifierKeys modifiers, double expectedX, double expectedY)
+ {
+ var nudge = WindowUtil.GetKeyboardNudge(key, modifiers);
+
+ Assert.Equal(expectedX, nudge.X);
+ Assert.Equal(expectedY, nudge.Y);
+ }
+
+ [Theory]
+ [InlineData(Key.Enter)]
+ [InlineData(Key.A)]
+ [InlineData(Key.Space)]
+ public void GetKeyboardNudge_OtherKeys_DoNotMove(Key key)
+ {
+ var nudge = WindowUtil.GetKeyboardNudge(key, ModifierKeys.None);
+
+ Assert.Equal(default, nudge);
+ }
+
private const int WsExTransparent = 0x00000020;
private const int WsExToolWindow = 0x00000080;
private const int WsExAppWindow = 0x00040000;
diff --git a/DesktopClock/MainWindow.xaml.cs b/DesktopClock/MainWindow.xaml.cs
index 4bbdd93..a76cafc 100644
--- a/DesktopClock/MainWindow.xaml.cs
+++ b/DesktopClock/MainWindow.xaml.cs
@@ -429,6 +429,30 @@ private void Window_KeyDown(object sender, KeyEventArgs e)
break;
}
}
+ else if (Keyboard.Modifiers is ModifierKeys.None or ModifierKeys.Shift)
+ {
+ NudgeWindow(e);
+ }
+ }
+
+ ///
+ /// Moves the clock with the arrow keys as a precise, mouse-free alternative to dragging.
+ ///
+ private void NudgeWindow(KeyEventArgs e)
+ {
+ // The keyboard respects the same lock as the mouse.
+ if (!Settings.Default.DragToMove)
+ return;
+
+ var nudge = WindowUtil.GetKeyboardNudge(e.Key, Keyboard.Modifiers);
+ if (nudge == default)
+ return;
+
+ Left += nudge.X;
+ Top += nudge.Y;
+
+ PixelShifter?.UpdateBasePosition(this);
+ e.Handled = true;
}
private void OpenUrl(string url)
diff --git a/DesktopClock/Utilities/WindowUtil.cs b/DesktopClock/Utilities/WindowUtil.cs
index 76cc5e0..a97f411 100644
--- a/DesktopClock/Utilities/WindowUtil.cs
+++ b/DesktopClock/Utilities/WindowUtil.cs
@@ -1,12 +1,40 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
+using System.Windows.Input;
using System.Windows.Interop;
namespace DesktopClock;
public static class WindowUtil
{
+ ///
+ /// How far one arrow key press moves the window.
+ ///
+ public const double NudgeStep = 1;
+
+ ///
+ /// How far an arrow key press moves the window while Shift is held.
+ ///
+ public const double LargeNudgeStep = 10;
+
+ ///
+ /// Returns how far an arrow key should move the window, or zero for other keys.
+ ///
+ public static Vector GetKeyboardNudge(Key key, ModifierKeys modifiers)
+ {
+ var step = modifiers == ModifierKeys.Shift ? LargeNudgeStep : NudgeStep;
+
+ return key switch
+ {
+ Key.Left => new Vector(-step, 0),
+ Key.Right => new Vector(step, 0),
+ Key.Up => new Vector(0, -step),
+ Key.Down => new Vector(0, step),
+ _ => default,
+ };
+ }
+
private const int GWL_EXSTYLE = -20;
private const int WS_EX_TRANSPARENT = 0x00000020;
private const int WS_EX_TOOLWINDOW = 0x00000080;