From e603a08d45db852bb4a0956a5023283fc243a743 Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Sun, 9 Aug 2026 21:21:31 -0500 Subject: [PATCH] Nudge the clock with the arrow keys Arrow keys move the focused clock one pixel at a time, or ten with Shift, giving a precise, mouse-free way to position it. Respects the "Drag to move" lock like the mouse does. --- DesktopClock.Tests/WindowUtilTests.cs | 30 +++++++++++++++++++++++++++ DesktopClock/MainWindow.xaml.cs | 24 +++++++++++++++++++++ DesktopClock/Utilities/WindowUtil.cs | 28 +++++++++++++++++++++++++ 3 files changed, 82 insertions(+) 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;