From d1337b4d8f3bc95176f05875e0263d055ce883e1 Mon Sep 17 00:00:00 2001 From: Danya Ulianchenko Date: Sun, 11 Oct 2026 23:49:55 +0300 Subject: [PATCH] BASE Temp 1.2.2 --- Core/AppSettings.cs | 1 + Core/ShortcutManager.cs | 130 ++++++++++++++++++++++++++++ Core/TabManager.cs | 18 +++- MainWindow.xaml | 12 ++- MainWindow.xaml.cs | 97 ++++++++++++++++++++- UI/AppSettingsWindow.xaml.cs | 7 +- UI/ExtensionPopupWindow.xaml.cs | 16 +++- UI/ShortcutSettingsWindow.xaml | 21 +++++ UI/ShortcutSettingsWindow.xaml.cs | 138 ++++++++++++++++++++++++++++++ 9 files changed, 430 insertions(+), 10 deletions(-) create mode 100644 Core/ShortcutManager.cs create mode 100644 UI/ShortcutSettingsWindow.xaml create mode 100644 UI/ShortcutSettingsWindow.xaml.cs diff --git a/Core/AppSettings.cs b/Core/AppSettings.cs index 9b198e0..508cf81 100644 --- a/Core/AppSettings.cs +++ b/Core/AppSettings.cs @@ -8,6 +8,7 @@ public sealed class AppSettings { public string AppDisplayName { get; set; } = "FoxBrowser"; public string? AppIconPath { get; set; } + public Dictionary Shortcuts { get; set; } = new(); } public static class AppSettingsStore diff --git a/Core/ShortcutManager.cs b/Core/ShortcutManager.cs new file mode 100644 index 0000000..33320fb --- /dev/null +++ b/Core/ShortcutManager.cs @@ -0,0 +1,130 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows.Input; + +namespace FoxBrowser.Core; + +public sealed class ShortcutManager +{ + public const string CloseTab = "CloseTab"; + public const string NewTab = "NewTab"; + public const string ToggleTopPanel = "ToggleTopPanel"; + + private static readonly IReadOnlyDictionary Defaults = + new Dictionary + { + [CloseTab] = new(Key.W, ModifierKeys.Control), + [NewTab] = new(Key.N, ModifierKeys.Control), + [ToggleTopPanel] = new(Key.F, ModifierKeys.Control | ModifierKeys.Shift), + }; + + private static readonly Lazy LazyInstance = new(() => new ShortcutManager()); + private readonly Dictionary _shortcuts = new(); + + private ShortcutManager() + { + ResetToDefaults(save: false); + Load(AppSettingsStore.Load()); + } + + public static ShortcutManager Instance => LazyInstance.Value; + public IReadOnlyDictionary Shortcuts => _shortcuts; + public event EventHandler? Changed; + + public KeyGesture Get(string action) => _shortcuts[action]; + + public string? FindAction(Key key, ModifierKeys modifiers) + { + return _shortcuts.FirstOrDefault(pair => pair.Value.Key == key && pair.Value.Modifiers == modifiers).Key; + } + + public bool TrySet(string action, KeyGesture gesture, out string? conflictAction) + { + conflictAction = _shortcuts.FirstOrDefault(pair => pair.Key != action && GesturesEqual(pair.Value, gesture)).Key; + if (!string.IsNullOrEmpty(conflictAction)) return false; + _shortcuts[action] = gesture; + Save(); + Changed?.Invoke(this, EventArgs.Empty); + return true; + } + + public void ResetToDefaults() + { + ResetToDefaults(save: true); + } + + public static string GetActionLabel(string action) => action switch + { + CloseTab => "Закрити вкладку", + NewTab => "Нова вкладка", + ToggleTopPanel => "Показати/сховати верхню панель", + _ => action, + }; + + public static string Format(KeyGesture gesture) + { + var parts = new List(); + if (gesture.Modifiers.HasFlag(ModifierKeys.Control)) parts.Add("Ctrl"); + if (gesture.Modifiers.HasFlag(ModifierKeys.Shift)) parts.Add("Shift"); + if (gesture.Modifiers.HasFlag(ModifierKeys.Alt)) parts.Add("Alt"); + if (gesture.Modifiers.HasFlag(ModifierKeys.Windows)) parts.Add("Win"); + parts.Add(gesture.Key.ToString()); + return string.Join("+", parts); + } + + public static bool TryCreateGesture(Key key, ModifierKeys modifiers, out KeyGesture? gesture) + { + gesture = null; + if (key is Key.LeftCtrl or Key.RightCtrl or Key.LeftShift or Key.RightShift or Key.LeftAlt or Key.RightAlt or Key.LWin or Key.RWin) + return false; + if (key == Key.None || modifiers == ModifierKeys.None) + return false; + gesture = new KeyGesture(key, modifiers); + return true; + } + + private void Load(AppSettings settings) + { + if (settings.Shortcuts is null) return; + foreach (var pair in settings.Shortcuts) + { + if (!Defaults.ContainsKey(pair.Key)) continue; + if (TryParse(pair.Value, out var gesture)) _shortcuts[pair.Key] = gesture!; + } + } + + private void Save() + { + var settings = AppSettingsStore.Load(); + settings.Shortcuts = _shortcuts.ToDictionary(pair => pair.Key, pair => Format(pair.Value)); + AppSettingsStore.Save(settings); + } + + private void ResetToDefaults(bool save) + { + _shortcuts.Clear(); + foreach (var pair in Defaults) _shortcuts[pair.Key] = pair.Value; + if (save) Save(); + } + + private static bool GesturesEqual(KeyGesture left, KeyGesture right) => left.Key == right.Key && left.Modifiers == right.Modifiers; + + private static bool TryParse(string? value, out KeyGesture? gesture) + { + gesture = null; + if (string.IsNullOrWhiteSpace(value)) return false; + var parts = value.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (parts.Length < 2 || !Enum.TryParse(parts[^1], true, out Key key)) return false; + var modifiers = ModifierKeys.None; + foreach (var part in parts[..^1]) + { + if (part.Equals("Ctrl", StringComparison.OrdinalIgnoreCase) || part.Equals("Control", StringComparison.OrdinalIgnoreCase)) modifiers |= ModifierKeys.Control; + else if (part.Equals("Shift", StringComparison.OrdinalIgnoreCase)) modifiers |= ModifierKeys.Shift; + else if (part.Equals("Alt", StringComparison.OrdinalIgnoreCase)) modifiers |= ModifierKeys.Alt; + else if (part.Equals("Win", StringComparison.OrdinalIgnoreCase) || part.Equals("Windows", StringComparison.OrdinalIgnoreCase)) modifiers |= ModifierKeys.Windows; + else return false; + } + if (!TryCreateGesture(key, modifiers, out gesture)) return false; + return true; + } +} diff --git a/Core/TabManager.cs b/Core/TabManager.cs index ca4e7ea..d1bc319 100644 --- a/Core/TabManager.cs +++ b/Core/TabManager.cs @@ -27,6 +27,7 @@ public sealed class TabManager : IAsyncDisposable private bool _navigationInProgress; private bool _switchInProgress; private bool _restoringSession; + private readonly List _sessionOrder = new(); private readonly string _blockedElementsPath = Path.Combine(AppContext.BaseDirectory, "blocked-elements.json"); private readonly Dictionary> _blockedElements; @@ -81,13 +82,16 @@ public sealed class TabManager : IAsyncDisposable _restoringSession = false; SessionReady = true; } + _sessionOrder.AddRange(Tabs); await SaveSessionAsync(); } public async Task SaveSessionAsync() { - var activeIndex = ActiveTab is not null ? Tabs.IndexOf(ActiveTab) : 0; - await SessionStore.SaveAsync(Tabs, activeIndex); + var orderedTabs = _sessionOrder.Where(Tabs.Contains).Concat(Tabs.Where(tab => !_sessionOrder.Contains(tab))); + var snapshotOrder = orderedTabs.ToList(); + var activeIndex = ActiveTab is not null ? snapshotOrder.IndexOf(ActiveTab) : 0; + await SessionStore.SaveAsync(snapshotOrder, activeIndex); } private async Task RestoreSessionOrDefaultAsync() @@ -125,6 +129,7 @@ public sealed class TabManager : IAsyncDisposable { var tab = new BrowserTab(NormalizeAddress(address)); Tabs.Add(tab); + if (SessionReady) _sessionOrder.Add(tab); await SwitchToTabAsync(tab); return tab; } @@ -318,6 +323,15 @@ public sealed class TabManager : IAsyncDisposable await SaveSessionAsync(); } + public void MoveTab(BrowserTab tab, int targetIndex) + { + var sourceIndex = Tabs.IndexOf(tab); + if (sourceIndex < 0 || Tabs.Count < 2) return; + targetIndex = Math.Clamp(targetIndex, 0, Tabs.Count - 1); + if (sourceIndex == targetIndex) return; + Tabs.Move(sourceIndex, targetIndex); + } + public async ValueTask DisposeAsync() { if (ActiveTab is not null) await CaptureActiveStateAsync(); diff --git a/MainWindow.xaml b/MainWindow.xaml index 2683168..ac6629a 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -96,8 +96,9 @@ + - + @@ -109,6 +110,11 @@ ItemsSource="{Binding Tabs}" ItemTemplate="{StaticResource TabTemplate}" SelectionChanged="TabStrip_SelectionChanged" + AllowDrop="True" + PreviewMouseLeftButtonDown="TabStrip_PreviewMouseLeftButtonDown" + PreviewMouseMove="TabStrip_PreviewMouseMove" + DragOver="TabStrip_DragOver" + Drop="TabStrip_Drop" Background="Transparent" BorderThickness="0" Padding="0" @@ -121,7 +127,7 @@ - +