diff --git a/CHANGELOG.md b/CHANGELOG.md index 327d005..bfed864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.6.0] - 2026-07-16 + +### Added + +- A configurable **Ctrl+Shift+K** hotkey that toggles the live keyboard-shortcut display. +- An on-screen badge that announces the current mode (color-coded) when you use the + toggle hotkey. +- **Screen zoom** (default **Ctrl+Shift+Z**): a ZoomIt-style full-screen zoom that + freezes the monitor under the cursor and lets you scroll to zoom (up to 6×) and move + the mouse to pan, keeping the point under the cursor fixed. Scrolling all the way + back out, Esc, right-click, or the hotkey again exits. The hotkey is configurable. + +### Changed + +- The toggle hotkey now **cycles Off → ClickLight → Laser Pointer** instead of a plain + on/off toggle. + ## [1.5.0] - 2026-07-16 ### Added @@ -134,7 +151,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release: fading click pulses drawn at the cursor, a system-tray presence, a transparent click-through overlay, and a system-wide mouse hook. -[Unreleased]: https://github.com/dvdstelt/ClickLightWin/compare/v1.5.0...HEAD +[Unreleased]: https://github.com/dvdstelt/ClickLightWin/compare/v1.6.0...HEAD +[1.6.0]: https://github.com/dvdstelt/ClickLightWin/compare/v1.5.0...v1.6.0 [1.5.0]: https://github.com/dvdstelt/ClickLightWin/compare/v1.4.0...v1.5.0 [1.4.0]: https://github.com/dvdstelt/ClickLightWin/compare/v1.3.0...v1.4.0 [1.3.0]: https://github.com/dvdstelt/ClickLightWin/compare/v1.2.0...v1.3.0 diff --git a/README.md b/README.md index af0f0b0..93953be 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ On each click, a short animated pulse is drawn at the cursor and fades out. The - Click highlights across macOS apps - Separate visuals for press, release, right-click, and drag - Optional laser pointer mode with drawing mode +- Screen zoom: freeze and magnify the display ZoomIt-style (scroll to zoom, move to pan) - Optional keyboard shortcuts - Local daily click activity chart with a resettable seven-day history - Dedicated settings window with presets, profiles, and a sidebar preview pad with Randomize @@ -38,7 +39,9 @@ ClickLight includes one default global shortcut for quick toggles during demos. | Shortcut | Action | | --- | --- | -| `Control + Shift + L` | Toggle ClickLight on/off | +| `Control + Shift + L` | Cycle mode: off -> ClickLight -> laser pointer | +| `Control + Shift + Z` | Screen zoom (scroll to zoom, move to pan; scroll out, Esc, or right-click to exit) | +| `Control + Shift + K` | Toggle the keyboard-shortcut display | | `Control + Shift + D` | Toggle drawing mode, cleared by exiting drawing mode. | | `Control + Shift + left-click` & drag | Draw an arrow | | `Control + Shift + right-click` & drag | Draw a box | diff --git a/src/ClickLightWin/AppController.cs b/src/ClickLightWin/AppController.cs index 782b47c..063b66b 100644 --- a/src/ClickLightWin/AppController.cs +++ b/src/ClickLightWin/AppController.cs @@ -23,6 +23,7 @@ public sealed class AppController : IDisposable private readonly LaunchAtLoginController _launchAtLogin = new(); private readonly ProfileStore _profileStore = new(); private readonly ActivityStore _activity = new(); + private readonly ZoomController _zoom = new(); private readonly UpdateService _updates = new(); private Settings _settings = new(); private TrayIcon? _tray; @@ -63,9 +64,11 @@ public void Start() _overlays = new OverlayManager(_settings); // Global hotkeys (defaults Ctrl+Shift+L/C/D; user-configurable in settings). - _hotKeys.TogglePressed += ToggleEnabled; + _hotKeys.TogglePressed += CycleMode; _hotKeys.ClearPressed += ClearAnnotations; _hotKeys.DrawModePressed += ToggleDrawMode; + _hotKeys.ShortcutsPressed += ToggleShortcuts; + _hotKeys.ZoomPressed += () => _zoom.Toggle(); _hotKeys.Start(); ConfigureHotkeys(); @@ -147,7 +150,8 @@ private void UpdateKeyboardHook() private void ConfigureHotkeys() { - _hotKeys.Configure(_settings.ToggleHotKey, _settings.ClearHotKey, _settings.DrawModeHotKey); + _hotKeys.Configure(_settings.ToggleHotKey, _settings.ClearHotKey, _settings.DrawModeHotKey, + _settings.ShortcutsHotKey, _settings.ZoomHotKey); WarnAboutUnavailableHotkeys(); } @@ -159,6 +163,8 @@ private void WarnAboutUnavailableHotkeys() if (!_hotKeys.ToggleRegistered) taken.Add($"{_settings.ToggleHotKey.Display} (toggle)"); if (!_hotKeys.ClearRegistered) taken.Add($"{_settings.ClearHotKey.Display} (clear annotations)"); if (!_hotKeys.DrawModeRegistered) taken.Add($"{_settings.DrawModeHotKey.Display} (drawing mode)"); + if (!_hotKeys.ShortcutsRegistered) taken.Add($"{_settings.ShortcutsHotKey.Display} (keyboard shortcuts)"); + if (!_hotKeys.ZoomRegistered) taken.Add($"{_settings.ZoomHotKey.Display} (zoom)"); if (taken.Count == 0) return; _tray?.ShowWarning("ClickLight shortcut unavailable", $"{string.Join(" and ", taken)} could not be registered (already in use or invalid)."); @@ -239,12 +245,50 @@ private void RecordActivity(ClickEvent click) } } - // The global hotkey path. The tray menu toggles Enabled on its own; both just - // mutate the shared settings, and the tray refreshes its checkmarks on open. - private void ToggleEnabled() + // The toggle hotkey cycles through three modes: Off -> ClickLight (pulses only) + // -> Laser Pointer (pulses + laser glow) -> Off. The tray still toggles Enabled and + // the laser independently; the tray refreshes its checkmarks on open. + private void CycleMode() { - _settings.Enabled = !_settings.Enabled; + if (!_settings.Enabled) + { + _settings.ShowLaserPointer = false; // Off -> ClickLight + _settings.Enabled = true; + } + else if (!_settings.ShowLaserPointer) + { + _settings.ShowLaserPointer = true; // ClickLight -> Laser Pointer + } + else + { + _settings.Enabled = false; // Laser Pointer -> Off + } + _settingsStore.Save(_settings); + AnnounceMode(); + } + + // Show a transient badge naming the mode we just switched to, color-coded so it + // stands apart from the live keyboard-shortcut pills. + private void AnnounceMode() + { + var (text, color) = !_settings.Enabled + ? ("Off", System.Windows.Media.Color.FromRgb(0x9A, 0x9A, 0xA2)) + : !_settings.ShowLaserPointer + ? ("ClickLight", System.Windows.Media.Color.FromRgb(0x3B, 0x82, 0xF6)) + : ("Laser Pointer", System.Windows.Media.Color.FromRgb(0xFF, 0x29, 0x05)); + _overlays?.ShowMode(text, color); + } + + // Toggle the live keyboard-shortcut display. Since it is otherwise invisible until + // the next shortcut is pressed, announce the new state with a toast. + private void ToggleShortcuts() + { + _settings.ShowShortcuts = !_settings.ShowShortcuts; _settingsStore.Save(_settings); + var (text, color) = _settings.ShowShortcuts + ? ("Keyboard Shortcuts On", System.Windows.Media.Color.FromRgb(0x3B, 0x82, 0xF6)) + : ("Keyboard Shortcuts Off", System.Windows.Media.Color.FromRgb(0x9A, 0x9A, 0xA2)); + _overlays?.ShowMode(text, color); } private void ShowSettings() @@ -287,6 +331,7 @@ public void Dispose() _keyboardHook.Dispose(); _hotKeys.Dispose(); _overlays?.Dispose(); + _zoom.Dispose(); _tray?.Dispose(); } } diff --git a/src/ClickLightWin/HotKeyBinding.cs b/src/ClickLightWin/HotKeyBinding.cs index 8df3ebb..13653ff 100644 --- a/src/ClickLightWin/HotKeyBinding.cs +++ b/src/ClickLightWin/HotKeyBinding.cs @@ -52,6 +52,8 @@ public string Display public static readonly HotKeyBinding DefaultToggle = new(ModifierKeys.Control | ModifierKeys.Shift, Key.L); public static readonly HotKeyBinding DefaultClear = new(ModifierKeys.Control | ModifierKeys.Shift, Key.C); public static readonly HotKeyBinding DefaultDrawMode = new(ModifierKeys.Control | ModifierKeys.Shift, Key.D); + public static readonly HotKeyBinding DefaultShortcuts = new(ModifierKeys.Control | ModifierKeys.Shift, Key.K); + public static readonly HotKeyBinding DefaultZoom = new(ModifierKeys.Control | ModifierKeys.Shift, Key.Z); private static string KeyName(Key key) => key switch { diff --git a/src/ClickLightWin/Interop/HotKeyManager.cs b/src/ClickLightWin/Interop/HotKeyManager.cs index 6a1d821..8237d69 100644 --- a/src/ClickLightWin/Interop/HotKeyManager.cs +++ b/src/ClickLightWin/Interop/HotKeyManager.cs @@ -14,18 +14,24 @@ public sealed class HotKeyManager : IDisposable private const int ToggleHotKeyId = 1; private const int ClearHotKeyId = 2; private const int DrawModeHotKeyId = 3; + private const int ShortcutsHotKeyId = 4; + private const int ZoomHotKeyId = 5; private HwndSource? _source; - private HotKeyBinding? _toggle, _clear, _drawMode; + private HotKeyBinding? _toggle, _clear, _drawMode, _shortcuts, _zoom; public event Action? TogglePressed; public event Action? ClearPressed; public event Action? DrawModePressed; + public event Action? ShortcutsPressed; + public event Action? ZoomPressed; /// False when the binding is invalid or another application already owns it. public bool ToggleRegistered { get; private set; } public bool ClearRegistered { get; private set; } public bool DrawModeRegistered { get; private set; } + public bool ShortcutsRegistered { get; private set; } + public bool ZoomRegistered { get; private set; } /// Create the message-only window that receives WM_HOTKEY. public void Start() @@ -42,11 +48,14 @@ public void Start() } /// Apply a new set of bindings and (re)register them. - public void Configure(HotKeyBinding toggle, HotKeyBinding clear, HotKeyBinding drawMode) + public void Configure(HotKeyBinding toggle, HotKeyBinding clear, HotKeyBinding drawMode, + HotKeyBinding shortcuts, HotKeyBinding zoom) { _toggle = toggle; _clear = clear; _drawMode = drawMode; + _shortcuts = shortcuts; + _zoom = zoom; Reregister(); } @@ -60,6 +69,8 @@ private void Reregister() ToggleRegistered = TryRegister(ToggleHotKeyId, _toggle); ClearRegistered = TryRegister(ClearHotKeyId, _clear); DrawModeRegistered = TryRegister(DrawModeHotKeyId, _drawMode); + ShortcutsRegistered = TryRegister(ShortcutsHotKeyId, _shortcuts); + ZoomRegistered = TryRegister(ZoomHotKeyId, _zoom); } private bool TryRegister(int id, HotKeyBinding? binding) => @@ -72,7 +83,9 @@ private void UnregisterAll() NativeMethods.UnregisterHotKey(_source.Handle, ToggleHotKeyId); NativeMethods.UnregisterHotKey(_source.Handle, ClearHotKeyId); NativeMethods.UnregisterHotKey(_source.Handle, DrawModeHotKeyId); - ToggleRegistered = ClearRegistered = DrawModeRegistered = false; + NativeMethods.UnregisterHotKey(_source.Handle, ShortcutsHotKeyId); + NativeMethods.UnregisterHotKey(_source.Handle, ZoomHotKeyId); + ToggleRegistered = ClearRegistered = DrawModeRegistered = ShortcutsRegistered = ZoomRegistered = false; } private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled) @@ -83,6 +96,8 @@ private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool hand case ToggleHotKeyId: TogglePressed?.Invoke(); handled = true; break; case ClearHotKeyId: ClearPressed?.Invoke(); handled = true; break; case DrawModeHotKeyId: DrawModePressed?.Invoke(); handled = true; break; + case ShortcutsHotKeyId: ShortcutsPressed?.Invoke(); handled = true; break; + case ZoomHotKeyId: ZoomPressed?.Invoke(); handled = true; break; } return 0; } diff --git a/src/ClickLightWin/Interop/NativeMethods.cs b/src/ClickLightWin/Interop/NativeMethods.cs index 4b57a3a..fc684ae 100644 --- a/src/ClickLightWin/Interop/NativeMethods.cs +++ b/src/ClickLightWin/Interop/NativeMethods.cs @@ -89,6 +89,11 @@ public static partial bool SetWindowPos( [return: MarshalAs(UnmanagedType.Bool)] public static partial bool DestroyIcon(nint hIcon); + // Free a GDI object (e.g. the HBITMAP from a screen capture). + [LibraryImport("gdi32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool DeleteObject(nint hObject); + // ---- Foreground activation ----------------------------------------------- // A tray context menu shown by hand never gets foreground activation, so a diff --git a/src/ClickLightWin/Overlay/OverlayManager.cs b/src/ClickLightWin/Overlay/OverlayManager.cs index 6651a45..2da9dbb 100644 --- a/src/ClickLightWin/Overlay/OverlayManager.cs +++ b/src/ClickLightWin/Overlay/OverlayManager.cs @@ -1,4 +1,5 @@ using Microsoft.Win32; // SystemEvents +using Color = System.Windows.Media.Color; using Screen = System.Windows.Forms.Screen; namespace ClickLightWin.Overlay; @@ -106,6 +107,14 @@ public void DispatchShortcut(IReadOnlyList keys) overlay?.ShowShortcut(keys, _settings, pt.X, pt.Y); } + /// Announce a mode switch on the monitor holding the cursor. + public void ShowMode(string text, Color accent) + { + Interop.NativeMethods.GetCursorPos(out var pt); + var overlay = FindByPoint(pt.X, pt.Y) ?? (_overlays.Count > 0 ? _overlays[0] : null); + overlay?.ShowMode(text, accent); + } + private OverlayWindow? Find(ClickEvent click) => FindByPoint(click.ScreenX, click.ScreenY); private OverlayWindow? FindByPoint(int x, int y) diff --git a/src/ClickLightWin/Overlay/OverlayWindow.xaml b/src/ClickLightWin/Overlay/OverlayWindow.xaml index 5fd89fd..91d8245 100644 --- a/src/ClickLightWin/Overlay/OverlayWindow.xaml +++ b/src/ClickLightWin/Overlay/OverlayWindow.xaml @@ -16,6 +16,9 @@ + + diff --git a/src/ClickLightWin/Overlay/OverlayWindow.xaml.cs b/src/ClickLightWin/Overlay/OverlayWindow.xaml.cs index f59d5bb..28a62de 100644 --- a/src/ClickLightWin/Overlay/OverlayWindow.xaml.cs +++ b/src/ClickLightWin/Overlay/OverlayWindow.xaml.cs @@ -24,6 +24,7 @@ public partial class OverlayWindow : Window private LaserRenderer? _laser; private AnnotationRenderer? _annotations; private ShortcutStackRenderer? _shortcuts; + private ModeIndicatorRenderer? _mode; private DrawStrokeRenderer? _drawStrokes; private bool _drawing; private nint _hwnd; @@ -152,6 +153,13 @@ public void ShowShortcut(IReadOnlyList keys, Settings settings, int scre _shortcuts.Show(keys, settings); } + /// Show a transient, color-coded mode-switch badge at the top-center. + public void ShowMode(string text, Color accent) + { + _mode ??= new ModeIndicatorRenderer(ModeHost); + _mode.Show(text, accent); + } + private void ShowShortcutNearPointer(IReadOnlyList keys, Settings settings, int screenX, int screenY) { var pill = ShortcutStackRenderer.BuildPill(keys, settings.ShortcutFontSize); diff --git a/src/ClickLightWin/Overlay/ZoomController.cs b/src/ClickLightWin/Overlay/ZoomController.cs new file mode 100644 index 0000000..754f83d --- /dev/null +++ b/src/ClickLightWin/Overlay/ZoomController.cs @@ -0,0 +1,68 @@ +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media.Imaging; +using ClickLightWin.Interop; +using DrawingPoint = System.Drawing.Point; +using Screen = System.Windows.Forms.Screen; + +namespace ClickLightWin.Overlay; + +/// +/// Toggles the ZoomIt-style full-screen zoom: on activate it snapshots the monitor under +/// the cursor and shows it in a that scales and pans around the +/// cursor. Esc, right-click, or the hotkey again closes it. +/// +public sealed class ZoomController : IDisposable +{ + private ZoomWindow? _window; + + public bool Active => _window is not null; + + public void Toggle() + { + if (Active) Close(); + else Open(); + } + + private void Open() + { + if (!NativeMethods.GetCursorPos(out var pt)) return; + var screen = Screen.FromPoint(new DrawingPoint(pt.X, pt.Y)); + var window = new ZoomWindow(screen, CaptureScreen(screen)); // capture before showing + window.ExitRequested += Close; + _window = window; + window.Show(); + window.Activate(); + } + + private void Close() + { + if (_window is null) return; + _window.ExitRequested -= Close; + _window.Close(); + _window = null; + } + + private static BitmapSource CaptureScreen(Screen screen) + { + var b = screen.Bounds; + using var bmp = new System.Drawing.Bitmap(b.Width, b.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (var g = System.Drawing.Graphics.FromImage(bmp)) + g.CopyFromScreen(b.X, b.Y, 0, 0, new System.Drawing.Size(b.Width, b.Height)); + + var hbitmap = bmp.GetHbitmap(); + try + { + var source = Imaging.CreateBitmapSourceFromHBitmap( + hbitmap, 0, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); + source.Freeze(); + return source; + } + finally + { + NativeMethods.DeleteObject(hbitmap); + } + } + + public void Dispose() => Close(); +} diff --git a/src/ClickLightWin/Overlay/ZoomWindow.xaml b/src/ClickLightWin/Overlay/ZoomWindow.xaml new file mode 100644 index 0000000..b6fe898 --- /dev/null +++ b/src/ClickLightWin/Overlay/ZoomWindow.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/src/ClickLightWin/Overlay/ZoomWindow.xaml.cs b/src/ClickLightWin/Overlay/ZoomWindow.xaml.cs new file mode 100644 index 0000000..b1372f6 --- /dev/null +++ b/src/ClickLightWin/Overlay/ZoomWindow.xaml.cs @@ -0,0 +1,89 @@ +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media.Imaging; +using ClickLightWin.Interop; +using KeyEventArgs = System.Windows.Input.KeyEventArgs; +using MouseEventArgs = System.Windows.Input.MouseEventArgs; +using Point = System.Windows.Point; +using Screen = System.Windows.Forms.Screen; + +namespace ClickLightWin.Overlay; + +/// +/// A ZoomIt-style full-screen zoom: a frozen snapshot of one monitor shown scaled and +/// panned around the cursor. Because it is a static image with a render transform, the +/// zoom/pan is GPU-cheap and stays smooth. Scroll changes zoom; the point under the +/// cursor stays fixed. Esc or right-click exits. +/// +public partial class ZoomWindow : Window +{ + private const double ZoomStep = 0.25; + private const double MinZoom = 1.0; + private const double MaxZoom = 6.0; + + private readonly Screen _screen; + private double _zoom = 2.0; + + public event Action? ExitRequested; + + public ZoomWindow(Screen screen, BitmapSource capture) + { + InitializeComponent(); + _screen = screen; + Frozen.Source = capture; + Loaded += (_, _) => + { + Activate(); + Focus(); + UpdateTransform(Mouse.GetPosition(this)); + }; + } + + protected override void OnMouseMove(MouseEventArgs e) => UpdateTransform(e.GetPosition(this)); + + protected override void OnMouseWheel(MouseWheelEventArgs e) + { + e.Handled = true; + var next = _zoom + (e.Delta > 0 ? ZoomStep : -ZoomStep); + if (next <= MinZoom) + { + ExitRequested?.Invoke(); // zoomed all the way out = leave zoom mode + return; + } + _zoom = Math.Min(next, MaxZoom); + UpdateTransform(e.GetPosition(this)); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Escape) ExitRequested?.Invoke(); + } + + protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) => ExitRequested?.Invoke(); + + private void UpdateTransform(Point cursor) + { + Scale.ScaleX = Scale.ScaleY = _zoom; + var w = ActualWidth; + var h = ActualHeight; + // Keep the point under the cursor fixed and magnify around it, clamped so the + // image always covers the window (no black edges). + Trans.X = Math.Clamp(cursor.X * (1 - _zoom), w * (1 - _zoom), 0); + Trans.Y = Math.Clamp(cursor.Y * (1 - _zoom), h * (1 - _zoom), 0); + } + + protected override void OnSourceInitialized(EventArgs e) + { + base.OnSourceInitialized(e); + var hwnd = new WindowInteropHelper(this).Handle; + + // Hide from alt-tab but stay activatable so it can take keyboard focus (Esc). + var ex = NativeMethods.GetWindowLongPtr(hwnd, NativeMethods.GWL_EXSTYLE); + NativeMethods.SetWindowLongPtr(hwnd, NativeMethods.GWL_EXSTYLE, (nint)((long)ex | NativeMethods.WS_EX_TOOLWINDOW)); + + var b = _screen.Bounds; + NativeMethods.SetWindowPos(hwnd, NativeMethods.HWND_TOPMOST, b.X, b.Y, b.Width, b.Height, + NativeMethods.SWP_SHOWWINDOW); + } +} diff --git a/src/ClickLightWin/Rendering/ModeIndicatorRenderer.cs b/src/ClickLightWin/Rendering/ModeIndicatorRenderer.cs new file mode 100644 index 0000000..cf36632 --- /dev/null +++ b/src/ClickLightWin/Rendering/ModeIndicatorRenderer.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Animation; +using Color = System.Windows.Media.Color; +using Panel = System.Windows.Controls.Panel; + +namespace ClickLightWin.Rendering; + +/// +/// A transient mode-switch toast (ClickLight / Laser Pointer / Off) shown when the +/// toggle hotkey cycles. It reuses the live shortcut display's pill layout, but sits +/// at the top-center with a color-accented border so it still reads as its own thing. +/// +public sealed class ModeIndicatorRenderer(Panel host) +{ + public void Show(string text, Color accent) + { + host.Children.Clear(); // only the latest switch is shown + + var pill = ShortcutStackRenderer.BuildPill([text], 16); + pill.BorderBrush = new SolidColorBrush(accent); + pill.BorderThickness = new Thickness(2); + host.Children.Add(pill); + + var fade = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(300)) + { + BeginTime = TimeSpan.FromMilliseconds(1100) + }; + fade.Completed += (_, _) => host.Children.Remove(pill); + pill.BeginAnimation(UIElement.OpacityProperty, fade); + } +} diff --git a/src/ClickLightWin/Settings.cs b/src/ClickLightWin/Settings.cs index 2073a79..5fcce14 100644 --- a/src/ClickLightWin/Settings.cs +++ b/src/ClickLightWin/Settings.cs @@ -37,6 +37,8 @@ public sealed class Settings : INotifyPropertyChanged private HotKeyBinding _toggleHotKey = HotKeyBinding.DefaultToggle; private HotKeyBinding _clearHotKey = HotKeyBinding.DefaultClear; private HotKeyBinding _drawModeHotKey = HotKeyBinding.DefaultDrawMode; + private HotKeyBinding _shortcutsHotKey = HotKeyBinding.DefaultShortcuts; + private HotKeyBinding _zoomHotKey = HotKeyBinding.DefaultZoom; private string _currentProfileName = ProfileStore.DefaultProfileName; private ShortcutPosition _shortcutPosition = ShortcutPosition.BottomCenter; private ShortcutSize _shortcutSize = ShortcutSize.Medium; @@ -66,6 +68,8 @@ public sealed class Settings : INotifyPropertyChanged public HotKeyBinding ToggleHotKey { get => _toggleHotKey; set => Set(ref _toggleHotKey, value); } public HotKeyBinding ClearHotKey { get => _clearHotKey; set => Set(ref _clearHotKey, value); } public HotKeyBinding DrawModeHotKey { get => _drawModeHotKey; set => Set(ref _drawModeHotKey, value); } + public HotKeyBinding ShortcutsHotKey { get => _shortcutsHotKey; set => Set(ref _shortcutsHotKey, value); } + public HotKeyBinding ZoomHotKey { get => _zoomHotKey; set => Set(ref _zoomHotKey, value); } /// Name of the profile currently selected in the settings window. public string CurrentProfileName { get => _currentProfileName; set => Set(ref _currentProfileName, value); } @@ -163,6 +167,8 @@ public void CopyFrom(Settings other) ToggleHotKey = other.ToggleHotKey; ClearHotKey = other.ClearHotKey; DrawModeHotKey = other.DrawModeHotKey; + ShortcutsHotKey = other.ShortcutsHotKey; + ZoomHotKey = other.ZoomHotKey; CurrentProfileName = other.CurrentProfileName; // Deep-copy the menu layout into this instance's own collection (kept, not diff --git a/src/ClickLightWin/Views/SettingsWindow.xaml b/src/ClickLightWin/Views/SettingsWindow.xaml index b4eae2f..177c3ee 100644 --- a/src/ClickLightWin/Views/SettingsWindow.xaml +++ b/src/ClickLightWin/Views/SettingsWindow.xaml @@ -652,16 +652,22 @@ - +