BASE Temp 1.2.2
This commit is contained in:
1 parent
195360dc47
commit
63be149caf
9 files changed
+430
-10
No files matched your search
@@ -8,6 +8,7 @@ public sealed class AppSettings
|
|||||||
{
|
{
|
||||||
public string AppDisplayName { get; set; } = "FoxBrowser";
|
public string AppDisplayName { get; set; } = "FoxBrowser";
|
||||||
public string? AppIconPath { get; set; }
|
public string? AppIconPath { get; set; }
|
||||||
|
public Dictionary<string, string> Shortcuts { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AppSettingsStore
|
public static class AppSettingsStore
|
||||||
|
|||||||
@@ -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<string, KeyGesture> Defaults =
|
||||||
|
new Dictionary<string, KeyGesture>
|
||||||
|
{
|
||||||
|
[CloseTab] = new(Key.W, ModifierKeys.Control),
|
||||||
|
[NewTab] = new(Key.N, ModifierKeys.Control),
|
||||||
|
[ToggleTopPanel] = new(Key.F, ModifierKeys.Control | ModifierKeys.Shift),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Lazy<ShortcutManager> LazyInstance = new(() => new ShortcutManager());
|
||||||
|
private readonly Dictionary<string, KeyGesture> _shortcuts = new();
|
||||||
|
|
||||||
|
private ShortcutManager()
|
||||||
|
{
|
||||||
|
ResetToDefaults(save: false);
|
||||||
|
Load(AppSettingsStore.Load());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShortcutManager Instance => LazyInstance.Value;
|
||||||
|
public IReadOnlyDictionary<string, KeyGesture> 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<string>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-2
@@ -27,6 +27,7 @@ public sealed class TabManager : IAsyncDisposable
|
|||||||
private bool _navigationInProgress;
|
private bool _navigationInProgress;
|
||||||
private bool _switchInProgress;
|
private bool _switchInProgress;
|
||||||
private bool _restoringSession;
|
private bool _restoringSession;
|
||||||
|
private readonly List<BrowserTab> _sessionOrder = new();
|
||||||
private readonly string _blockedElementsPath = Path.Combine(AppContext.BaseDirectory, "blocked-elements.json");
|
private readonly string _blockedElementsPath = Path.Combine(AppContext.BaseDirectory, "blocked-elements.json");
|
||||||
private readonly Dictionary<string, List<string>> _blockedElements;
|
private readonly Dictionary<string, List<string>> _blockedElements;
|
||||||
|
|
||||||
@@ -81,13 +82,16 @@ public sealed class TabManager : IAsyncDisposable
|
|||||||
_restoringSession = false;
|
_restoringSession = false;
|
||||||
SessionReady = true;
|
SessionReady = true;
|
||||||
}
|
}
|
||||||
|
_sessionOrder.AddRange(Tabs);
|
||||||
await SaveSessionAsync();
|
await SaveSessionAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveSessionAsync()
|
public async Task SaveSessionAsync()
|
||||||
{
|
{
|
||||||
var activeIndex = ActiveTab is not null ? Tabs.IndexOf(ActiveTab) : 0;
|
var orderedTabs = _sessionOrder.Where(Tabs.Contains).Concat(Tabs.Where(tab => !_sessionOrder.Contains(tab)));
|
||||||
await SessionStore.SaveAsync(Tabs, activeIndex);
|
var snapshotOrder = orderedTabs.ToList();
|
||||||
|
var activeIndex = ActiveTab is not null ? snapshotOrder.IndexOf(ActiveTab) : 0;
|
||||||
|
await SessionStore.SaveAsync(snapshotOrder, activeIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RestoreSessionOrDefaultAsync()
|
private async Task RestoreSessionOrDefaultAsync()
|
||||||
@@ -125,6 +129,7 @@ public sealed class TabManager : IAsyncDisposable
|
|||||||
{
|
{
|
||||||
var tab = new BrowserTab(NormalizeAddress(address));
|
var tab = new BrowserTab(NormalizeAddress(address));
|
||||||
Tabs.Add(tab);
|
Tabs.Add(tab);
|
||||||
|
if (SessionReady) _sessionOrder.Add(tab);
|
||||||
await SwitchToTabAsync(tab);
|
await SwitchToTabAsync(tab);
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
@@ -318,6 +323,15 @@ public sealed class TabManager : IAsyncDisposable
|
|||||||
await SaveSessionAsync();
|
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()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
if (ActiveTab is not null) await CaptureActiveStateAsync();
|
if (ActiveTab is not null) await CaptureActiveStateAsync();
|
||||||
|
|||||||
+10
-2
@@ -96,8 +96,9 @@
|
|||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
|
<StackPanel x:Name="TopPanel" DockPanel.Dock="Top">
|
||||||
<!-- Tab strip -->
|
<!-- Tab strip -->
|
||||||
<Border DockPanel.Dock="Top" Background="{StaticResource HeaderBg}" Padding="12,10,12,8" BorderBrush="{StaticResource BorderDark}" BorderThickness="0,0,0,1">
|
<Border Background="{StaticResource HeaderBg}" Padding="12,10,12,8" BorderBrush="{StaticResource BorderDark}" BorderThickness="0,0,0,1">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="2,0,18,0">
|
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="2,0,18,0">
|
||||||
<Border Width="32" Height="32" CornerRadius="9" Background="{StaticResource AurBlueSoft}">
|
<Border Width="32" Height="32" CornerRadius="9" Background="{StaticResource AurBlueSoft}">
|
||||||
@@ -109,6 +110,11 @@
|
|||||||
ItemsSource="{Binding Tabs}"
|
ItemsSource="{Binding Tabs}"
|
||||||
ItemTemplate="{StaticResource TabTemplate}"
|
ItemTemplate="{StaticResource TabTemplate}"
|
||||||
SelectionChanged="TabStrip_SelectionChanged"
|
SelectionChanged="TabStrip_SelectionChanged"
|
||||||
|
AllowDrop="True"
|
||||||
|
PreviewMouseLeftButtonDown="TabStrip_PreviewMouseLeftButtonDown"
|
||||||
|
PreviewMouseMove="TabStrip_PreviewMouseMove"
|
||||||
|
DragOver="TabStrip_DragOver"
|
||||||
|
Drop="TabStrip_Drop"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
@@ -121,7 +127,7 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Browser controls -->
|
<!-- Browser controls -->
|
||||||
<Border DockPanel.Dock="Top" Background="{StaticResource HeaderBgElevated}" Padding="12,9" BorderBrush="{StaticResource BorderDark}" BorderThickness="0,0,0,1">
|
<Border Background="{StaticResource HeaderBgElevated}" Padding="12,9" BorderBrush="{StaticResource BorderDark}" BorderThickness="0,0,0,1">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
||||||
<Button Content="←" ToolTip="Назад" Click="Back_Click" Style="{StaticResource IconButton}" />
|
<Button Content="←" ToolTip="Назад" Click="Back_Click" Style="{StaticResource IconButton}" />
|
||||||
@@ -187,6 +193,7 @@
|
|||||||
</Popup>
|
</Popup>
|
||||||
<Button Content="♫" ToolTip="Режим лише аудіо" Click="AudioOnly_Click" Height="32" Width="36" Padding="0" Margin="0,0,4,0" />
|
<Button Content="♫" ToolTip="Режим лише аудіо" Click="AudioOnly_Click" Height="32" Width="36" Padding="0" Margin="0,0,4,0" />
|
||||||
<Button Content="Розширення" ToolTip="Керування unpacked extensions" Click="Extensions_Click" Height="32" Padding="11,0" />
|
<Button Content="Розширення" ToolTip="Керування unpacked extensions" Click="Extensions_Click" Height="32" Padding="11,0" />
|
||||||
|
<Button Content="⌨" ToolTip="Клавіатурні скорочення" Click="Shortcuts_Click" Height="32" Width="36" Padding="0" Margin="4,0,0,0" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Grid VerticalAlignment="Center">
|
<Grid VerticalAlignment="Center">
|
||||||
@@ -199,6 +206,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Web content -->
|
<!-- Web content -->
|
||||||
<Grid x:Name="WebViewHost" Background="#0B0E12">
|
<Grid x:Name="WebViewHost" Background="#0B0E12">
|
||||||
|
|||||||
+96
-1
@@ -11,6 +11,11 @@ using Microsoft.Web.WebView2.Core;
|
|||||||
using FoxBrowser.Core;
|
using FoxBrowser.Core;
|
||||||
using FoxBrowser.Native;
|
using FoxBrowser.Native;
|
||||||
using FoxBrowser.UI;
|
using FoxBrowser.UI;
|
||||||
|
using WpfDragDropEffects = System.Windows.DragDropEffects;
|
||||||
|
using WpfDragEventArgs = System.Windows.DragEventArgs;
|
||||||
|
using WpfKeyEventArgs = System.Windows.Input.KeyEventArgs;
|
||||||
|
using WpfMouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||||
|
using WpfPoint = System.Windows.Point;
|
||||||
|
|
||||||
namespace FoxBrowser;
|
namespace FoxBrowser;
|
||||||
|
|
||||||
@@ -24,6 +29,9 @@ public partial class MainWindow : Window
|
|||||||
private AudioSessionLabeler? _audioLabeler;
|
private AudioSessionLabeler? _audioLabeler;
|
||||||
private bool _updatingDarkModeMenu;
|
private bool _updatingDarkModeMenu;
|
||||||
private AppSettings _appSettings;
|
private AppSettings _appSettings;
|
||||||
|
private WpfPoint _tabDragStartPoint;
|
||||||
|
private BrowserTab? _tabDragItem;
|
||||||
|
private bool _topPanelVisible = true;
|
||||||
|
|
||||||
private const int WmNcRButtonDown = 0x00A4;
|
private const int WmNcRButtonDown = 0x00A4;
|
||||||
private const int WmNcRButtonUp = 0x00A5;
|
private const int WmNcRButtonUp = 0x00A5;
|
||||||
@@ -34,6 +42,7 @@ public partial class MainWindow : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_appSettings = AppSettingsStore.Load();
|
_appSettings = AppSettingsStore.Load();
|
||||||
ApplyAppIdentity(_appSettings);
|
ApplyAppIdentity(_appSettings);
|
||||||
|
PreviewKeyDown += MainWindow_PreviewKeyDown;
|
||||||
SourceInitialized += MainWindow_SourceInitialized;
|
SourceInitialized += MainWindow_SourceInitialized;
|
||||||
Loaded += MainWindow_Loaded;
|
Loaded += MainWindow_Loaded;
|
||||||
}
|
}
|
||||||
@@ -75,6 +84,16 @@ public partial class MainWindow : Window
|
|||||||
popup.IsOpen = false;
|
popup.IsOpen = false;
|
||||||
OpenAppSettings();
|
OpenAppSettings();
|
||||||
};
|
};
|
||||||
|
var shortcutsItem = new System.Windows.Controls.Button
|
||||||
|
{
|
||||||
|
Content = "Клавіатурні скорочення…",
|
||||||
|
Style = (Style)FindResource("FoxMenuButton")
|
||||||
|
};
|
||||||
|
shortcutsItem.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
popup.IsOpen = false;
|
||||||
|
Shortcuts_Click(this, new RoutedEventArgs());
|
||||||
|
};
|
||||||
popup.Child = new Border
|
popup.Child = new Border
|
||||||
{
|
{
|
||||||
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(23, 27, 34)),
|
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(23, 27, 34)),
|
||||||
@@ -82,7 +101,10 @@ public partial class MainWindow : Window
|
|||||||
BorderThickness = new Thickness(1),
|
BorderThickness = new Thickness(1),
|
||||||
CornerRadius = new CornerRadius(8),
|
CornerRadius = new CornerRadius(8),
|
||||||
Padding = new Thickness(8),
|
Padding = new Thickness(8),
|
||||||
Child = item
|
Child = new StackPanel
|
||||||
|
{
|
||||||
|
Children = { item, shortcutsItem }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
popup.IsOpen = true;
|
popup.IsOpen = true;
|
||||||
}
|
}
|
||||||
@@ -302,6 +324,79 @@ public partial class MainWindow : Window
|
|||||||
window.ShowDialog();
|
window.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Shortcuts_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var window = new ShortcutSettingsWindow { Owner = this };
|
||||||
|
window.ShowDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void MainWindow_PreviewKeyDown(object sender, WpfKeyEventArgs e)
|
||||||
|
{
|
||||||
|
var key = e.Key == Key.System ? e.SystemKey : e.Key;
|
||||||
|
var action = ShortcutManager.Instance.FindAction(key, Keyboard.Modifiers);
|
||||||
|
if (action is null) return;
|
||||||
|
e.Handled = true;
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case ShortcutManager.CloseTab when _tabManager?.ActiveTab is not null:
|
||||||
|
await _tabManager.CloseTabAsync(_tabManager.ActiveTab);
|
||||||
|
RefreshAddressBar();
|
||||||
|
break;
|
||||||
|
case ShortcutManager.NewTab when _tabManager is not null:
|
||||||
|
await _tabManager.AddTabAsync();
|
||||||
|
RefreshAddressBar();
|
||||||
|
break;
|
||||||
|
case ShortcutManager.ToggleTopPanel:
|
||||||
|
_topPanelVisible = !_topPanelVisible;
|
||||||
|
TopPanel.Visibility = _topPanelVisible ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TabStrip_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_tabDragStartPoint = e.GetPosition(TabStrip);
|
||||||
|
_tabDragItem = FindTabItem(e.OriginalSource as DependencyObject)?.DataContext as BrowserTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TabStrip_PreviewMouseMove(object sender, WpfMouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton != MouseButtonState.Pressed || _tabDragItem is null) return;
|
||||||
|
var position = e.GetPosition(TabStrip);
|
||||||
|
if (Math.Abs(position.X - _tabDragStartPoint.X) < SystemParameters.MinimumHorizontalDragDistance &&
|
||||||
|
Math.Abs(position.Y - _tabDragStartPoint.Y) < SystemParameters.MinimumVerticalDragDistance) return;
|
||||||
|
var item = _tabDragItem;
|
||||||
|
_tabDragItem = null;
|
||||||
|
System.Windows.DragDrop.DoDragDrop(TabStrip, item, WpfDragDropEffects.Move);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TabStrip_DragOver(object sender, WpfDragEventArgs e)
|
||||||
|
{
|
||||||
|
e.Effects = e.Data.GetDataPresent(typeof(BrowserTab)) ? WpfDragDropEffects.Move : WpfDragDropEffects.None;
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TabStrip_Drop(object sender, WpfDragEventArgs e)
|
||||||
|
{
|
||||||
|
if (_tabManager is null || !e.Data.GetDataPresent(typeof(BrowserTab))) return;
|
||||||
|
var source = e.Data.GetData(typeof(BrowserTab)) as BrowserTab;
|
||||||
|
var target = FindTabItem(e.OriginalSource as DependencyObject)?.DataContext as BrowserTab;
|
||||||
|
if (source is null || target is null || ReferenceEquals(source, target)) return;
|
||||||
|
var targetIndex = _tabManager.Tabs.IndexOf(target);
|
||||||
|
if (targetIndex >= 0) _tabManager.MoveTab(source, targetIndex);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TabItem? FindTabItem(DependencyObject? element)
|
||||||
|
{
|
||||||
|
while (element is not null)
|
||||||
|
{
|
||||||
|
if (element is TabItem tabItem) return tabItem;
|
||||||
|
element = System.Windows.Media.VisualTreeHelper.GetParent(element);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void PinnedExtension_Click(object sender, RoutedEventArgs e)
|
private void PinnedExtension_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_tabManager?.ExtensionManager is null || sender is not System.Windows.Controls.Button button || button.DataContext is not PinnedExtensionItem item)
|
if (_tabManager?.ExtensionManager is null || sender is not System.Windows.Controls.Button button || button.DataContext is not PinnedExtensionItem item)
|
||||||
|
|||||||
@@ -47,7 +47,12 @@ public partial class AppSettingsWindow : Window
|
|||||||
if (name.Length == 0) name = "FoxBrowser";
|
if (name.Length == 0) name = "FoxBrowser";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var settings = new AppSettings { AppDisplayName = name, AppIconPath = _iconPath };
|
var settings = new AppSettings
|
||||||
|
{
|
||||||
|
AppDisplayName = name,
|
||||||
|
AppIconPath = _iconPath,
|
||||||
|
Shortcuts = ShortcutManager.Instance.Shortcuts.ToDictionary(pair => pair.Key, pair => ShortcutManager.Format(pair.Value))
|
||||||
|
};
|
||||||
AppSettingsStore.Save(settings);
|
AppSettingsStore.Save(settings);
|
||||||
_apply(settings);
|
_apply(settings);
|
||||||
DialogResult = true;
|
DialogResult = true;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public partial class ExtensionPopupWindow : Window
|
|||||||
private readonly CoreWebView2Environment _environment;
|
private readonly CoreWebView2Environment _environment;
|
||||||
private readonly string _extensionId;
|
private readonly string _extensionId;
|
||||||
private readonly string _popupPage;
|
private readonly string _popupPage;
|
||||||
|
private WebView2? _view;
|
||||||
|
|
||||||
public ExtensionPopupWindow(
|
public ExtensionPopupWindow(
|
||||||
CoreWebView2Environment environment,
|
CoreWebView2Environment environment,
|
||||||
@@ -47,14 +48,21 @@ public partial class ExtensionPopupWindow : Window
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var view = new WebView2();
|
_view = new WebView2();
|
||||||
PopupHost.Children.Add(view);
|
PopupHost.Children.Add(_view);
|
||||||
await view.EnsureCoreWebView2Async(_environment);
|
await _view.EnsureCoreWebView2Async(_environment);
|
||||||
view.CoreWebView2.Navigate($"chrome-extension://{_extensionId}/{_popupPage}");
|
_view.CoreWebView2.Navigate($"chrome-extension://{_extensionId}/{_popupPage}");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnClosed(EventArgs e)
|
||||||
|
{
|
||||||
|
_view?.Dispose();
|
||||||
|
_view = null;
|
||||||
|
base.OnClosed(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<Window x:Class="FoxBrowser.UI.ShortcutSettingsWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="Клавіатурні скорочення" Width="580" Height="330"
|
||||||
|
MinWidth="520" Background="#15171A" Foreground="#F2F4F7"
|
||||||
|
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
|
||||||
|
<Grid Margin="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock Text="Клавіатурні скорочення" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,8" />
|
||||||
|
<TextBlock Grid.Row="0" Text="Натисніть «Змінити», потім потрібну комбінацію Ctrl/Shift/Alt + клавіша." Foreground="#AEB4BE" Margin="0,30,0,12" />
|
||||||
|
<StackPanel x:Name="ShortcutList" Grid.Row="1" />
|
||||||
|
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,0,0">
|
||||||
|
<Button Content="Скинути до стандартних" Click="Reset_Click" Padding="10,6" Margin="0,0,8,0" />
|
||||||
|
<Button Content="Закрити" Click="Close_Click" Padding="14,6" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using FoxBrowser.Core;
|
||||||
|
using WpfButton = System.Windows.Controls.Button;
|
||||||
|
using WpfColor = System.Windows.Media.Color;
|
||||||
|
using WpfKeyEventArgs = System.Windows.Input.KeyEventArgs;
|
||||||
|
using WpfTextBox = System.Windows.Controls.TextBox;
|
||||||
|
|
||||||
|
namespace FoxBrowser.UI;
|
||||||
|
|
||||||
|
public partial class ShortcutSettingsWindow : Window
|
||||||
|
{
|
||||||
|
private readonly ShortcutManager _shortcuts = ShortcutManager.Instance;
|
||||||
|
private readonly Dictionary<string, WpfTextBox> _boxes = new();
|
||||||
|
private string? _capturingAction;
|
||||||
|
private KeyGesture? _pendingGesture;
|
||||||
|
|
||||||
|
public ShortcutSettingsWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_shortcuts.Changed += Shortcuts_Changed;
|
||||||
|
BuildRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildRows()
|
||||||
|
{
|
||||||
|
ShortcutList.Children.Clear();
|
||||||
|
_boxes.Clear();
|
||||||
|
foreach (var action in new[] { ShortcutManager.CloseTab, ShortcutManager.NewTab, ShortcutManager.ToggleTopPanel })
|
||||||
|
{
|
||||||
|
var box = new WpfTextBox
|
||||||
|
{
|
||||||
|
Text = ShortcutManager.Format(_shortcuts.Get(action)),
|
||||||
|
IsReadOnly = true,
|
||||||
|
IsTabStop = false,
|
||||||
|
Width = 150,
|
||||||
|
Padding = new Thickness(7, 4, 7, 4),
|
||||||
|
Background = new SolidColorBrush(WpfColor.FromRgb(32, 35, 40)),
|
||||||
|
Foreground = new SolidColorBrush(WpfColor.FromRgb(242, 244, 247)),
|
||||||
|
BorderBrush = new SolidColorBrush(WpfColor.FromRgb(69, 76, 87)),
|
||||||
|
HorizontalContentAlignment = System.Windows.HorizontalAlignment.Center,
|
||||||
|
Tag = action,
|
||||||
|
};
|
||||||
|
var change = new WpfButton { Content = "Змінити", Padding = new Thickness(10, 5, 10, 5), Tag = action };
|
||||||
|
change.Click += Change_Click;
|
||||||
|
var row = new Grid { Margin = new Thickness(0, 0, 0, 8) };
|
||||||
|
row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||||
|
row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||||
|
row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||||
|
row.Children.Add(new TextBlock { Text = ShortcutManager.GetActionLabel(action), VerticalAlignment = VerticalAlignment.Center });
|
||||||
|
Grid.SetColumn(box, 1);
|
||||||
|
Grid.SetColumn(change, 2);
|
||||||
|
change.Margin = new Thickness(8, 0, 0, 0);
|
||||||
|
row.Children.Add(box);
|
||||||
|
row.Children.Add(change);
|
||||||
|
ShortcutList.Children.Add(row);
|
||||||
|
_boxes[action] = box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Change_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not WpfButton button || button.Tag is not string action || !_boxes.TryGetValue(action, out var box)) return;
|
||||||
|
_capturingAction = action;
|
||||||
|
_pendingGesture = null;
|
||||||
|
box.Text = "Натисніть комбінацію…";
|
||||||
|
box.Focus();
|
||||||
|
box.PreviewKeyDown -= CaptureBox_PreviewKeyDown;
|
||||||
|
box.PreviewKeyDown += CaptureBox_PreviewKeyDown;
|
||||||
|
box.PreviewKeyUp -= CaptureBox_PreviewKeyUp;
|
||||||
|
box.PreviewKeyUp += CaptureBox_PreviewKeyUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureBox_PreviewKeyDown(object sender, WpfKeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (_capturingAction is null || sender is not WpfTextBox box) return;
|
||||||
|
var key = e.Key == Key.System ? e.SystemKey : e.Key;
|
||||||
|
if (key == Key.Escape)
|
||||||
|
{
|
||||||
|
CancelCapture(box);
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var modifiers = Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt);
|
||||||
|
if (!ShortcutManager.TryCreateGesture(key, modifiers, out var gesture)) return;
|
||||||
|
_pendingGesture = gesture;
|
||||||
|
box.Text = ShortcutManager.Format(gesture);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureBox_PreviewKeyUp(object sender, WpfKeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (_capturingAction is null || _pendingGesture is null || sender is not WpfTextBox box) return;
|
||||||
|
var action = _capturingAction;
|
||||||
|
if (!_shortcuts.TrySet(action, _pendingGesture, out var conflictAction))
|
||||||
|
{
|
||||||
|
System.Windows.MessageBox.Show(this, $"Комбінація вже призначена для «{ShortcutManager.GetActionLabel(conflictAction!)}». Оберіть іншу.", "Конфлікт шорткатів", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
box.Text = ShortcutManager.Format(_shortcuts.Get(action));
|
||||||
|
}
|
||||||
|
StopCapture(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelCapture(WpfTextBox box)
|
||||||
|
{
|
||||||
|
box.Text = ShortcutManager.Format(_shortcuts.Get(_capturingAction!));
|
||||||
|
StopCapture(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopCapture(WpfTextBox box)
|
||||||
|
{
|
||||||
|
box.PreviewKeyDown -= CaptureBox_PreviewKeyDown;
|
||||||
|
box.PreviewKeyUp -= CaptureBox_PreviewKeyUp;
|
||||||
|
_capturingAction = null;
|
||||||
|
_pendingGesture = null;
|
||||||
|
Keyboard.ClearFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Shortcuts_Changed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(BuildRows);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BuildRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reset_Click(object sender, RoutedEventArgs e) => _shortcuts.ResetToDefaults();
|
||||||
|
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||||
|
|
||||||
|
protected override void OnClosed(EventArgs e)
|
||||||
|
{
|
||||||
|
_shortcuts.Changed -= Shortcuts_Changed;
|
||||||
|
base.OnClosed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user