diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index 4ce0a1ad..65b9d325 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -387,6 +387,7 @@
Create new page
Open Preferences dialog
Switch active workspace
+ Switch active page
REPOSITORY
Commit staged changes
Commit and push staged changes
@@ -429,6 +430,8 @@
Open in Browser
ERROR
NOTICE
+ Switch Workspace
+ Switch Tab
Merge Branch
Into:
Merge Option:
@@ -635,7 +638,6 @@
Use relative time in histories
View Logs
Visit '{0}' in Browser
- Switch Workspace
WORKTREES
Add Worktree
Prune
diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml
index 1fabffda..a569cd97 100644
--- a/src/Resources/Locales/ru_RU.axaml
+++ b/src/Resources/Locales/ru_RU.axaml
@@ -433,6 +433,7 @@
Открыть в браузере
ОШИБКА
УВЕДОМЛЕНИЕ
+ Переключить рабочее место
Влить ветку
В:
Опции слияния:
@@ -639,7 +640,6 @@
Использовать относительное время в историях
Просмотр журналов
Посетить '{0}' в браузере
- Переключить рабочее место
РАБОЧИЕ КАТАЛОГИ
ДОБАВИТЬ РАБОЧИЙ КАТАЛОГ
ОБРЕЗАТЬ
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index 1fd1a610..1344dfed 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -391,6 +391,7 @@
新建页面
打开偏好设置面板
切换工作区
+ 切换显示页面
仓库页面快捷键
提交暂存区更改
提交暂存区更改并推送
@@ -433,6 +434,8 @@
在浏览器中访问
出错了
系统提示
+ 切换工作区
+ 切换页面
合并分支
目标分支 :
合并方式 :
@@ -639,7 +642,6 @@
在提交列表中使用相对时间
查看命令日志
访问远程仓库 '{0}'
- 切换工作区
工作树列表
新增工作树
清理
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index eca8214c..a95f7211 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -391,6 +391,7 @@
新增頁面
開啟偏好設定面板
切換工作區
+ 切換目前頁面
存放庫頁面快速鍵
提交暫存區變更
提交暫存區變更並推送
@@ -433,6 +434,8 @@
在瀏覽器中開啟連結
發生錯誤
系統提示
+ 切換工作區
+ 切換目前頁面
合併分支
目標分支:
合併方式:
@@ -639,7 +642,6 @@
在提交列表中使用相對時間
檢視 Git 指令記錄
檢視遠端存放庫 '{0}'
- 切換工作區
工作區列表
新增工作區
清理
diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs
index 9a54bb32..3b6a4dd8 100644
--- a/src/ViewModels/Launcher.cs
+++ b/src/ViewModels/Launcher.cs
@@ -23,12 +23,6 @@ namespace SourceGit.ViewModels
private set;
}
- public WorkspaceSwitcher WorkspaceSwitcher
- {
- get => _workspaceSwitcher;
- set => SetProperty(ref _workspaceSwitcher, value);
- }
-
public Workspace ActiveWorkspace
{
get => _activeWorkspace;
@@ -50,6 +44,12 @@ namespace SourceGit.ViewModels
}
}
+ public object Switcher
+ {
+ get => _switcher;
+ set => SetProperty(ref _switcher, value);
+ }
+
public Launcher(string startupRepo)
{
_ignoreIndexChange = true;
@@ -138,12 +138,17 @@ namespace SourceGit.ViewModels
public void OpenWorkspaceSwitcher()
{
- WorkspaceSwitcher = new WorkspaceSwitcher(this);
+ Switcher = new WorkspaceSwitcher(this);
}
- public void CancelWorkspaceSwitcher()
+ public void OpenTabSwitcher()
{
- WorkspaceSwitcher = null;
+ Switcher = new LauncherPageSwitcher(this);
+ }
+
+ public void CancelSwitcher()
+ {
+ Switcher = null;
}
public void SwitchWorkspace(Workspace to)
@@ -618,6 +623,6 @@ namespace SourceGit.ViewModels
private LauncherPage _activePage = null;
private bool _ignoreIndexChange = false;
private string _title = string.Empty;
- private WorkspaceSwitcher _workspaceSwitcher = null;
+ private object _switcher = null;
}
}
diff --git a/src/ViewModels/LauncherPageSwitcher.cs b/src/ViewModels/LauncherPageSwitcher.cs
new file mode 100644
index 00000000..b0dfaca3
--- /dev/null
+++ b/src/ViewModels/LauncherPageSwitcher.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace SourceGit.ViewModels
+{
+ public class LauncherPageSwitcher : ObservableObject
+ {
+ public List VisiblePages
+ {
+ get => _visiblePages;
+ private set => SetProperty(ref _visiblePages, value);
+ }
+
+ public string SearchFilter
+ {
+ get => _searchFilter;
+ set
+ {
+ if (SetProperty(ref _searchFilter, value))
+ UpdateVisiblePages();
+ }
+ }
+
+ public LauncherPage SelectedPage
+ {
+ get => _selectedPage;
+ set => SetProperty(ref _selectedPage, value);
+ }
+
+ public LauncherPageSwitcher(Launcher launcher)
+ {
+ _launcher = launcher;
+ UpdateVisiblePages();
+ }
+
+ public void ClearFilter()
+ {
+ SearchFilter = string.Empty;
+ }
+
+ public void Switch()
+ {
+ if (_selectedPage is { })
+ _launcher.ActivePage = _selectedPage;
+
+ _launcher.CancelSwitcher();
+ }
+
+ private void UpdateVisiblePages()
+ {
+ var visible = new List();
+ if (string.IsNullOrEmpty(_searchFilter))
+ {
+ visible.AddRange(_launcher.Pages);
+ }
+ else
+ {
+ foreach (var page in _launcher.Pages)
+ {
+ if (page.Node.Name.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase) ||
+ (page.Node.IsRepository && page.Node.Id.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase)))
+ {
+ visible.Add(page);
+ }
+ }
+ }
+
+ VisiblePages = visible;
+ }
+
+ private Launcher _launcher = null;
+ private List _visiblePages = [];
+ private string _searchFilter = string.Empty;
+ private LauncherPage _selectedPage = null;
+ }
+}
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index d24f6fbf..a3f63251 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -2632,7 +2632,7 @@ namespace SourceGit.ViewModels
if (node.Path.Equals(path, StringComparison.Ordinal))
return node;
- if (path!.StartsWith(node.Path, StringComparison.Ordinal))
+ if (path.StartsWith(node.Path, StringComparison.Ordinal))
{
var founded = FindBranchNode(node.Children, path);
if (founded != null)
diff --git a/src/ViewModels/WorkspaceSwitcher.cs b/src/ViewModels/WorkspaceSwitcher.cs
index 01d62744..41f47631 100644
--- a/src/ViewModels/WorkspaceSwitcher.cs
+++ b/src/ViewModels/WorkspaceSwitcher.cs
@@ -44,7 +44,7 @@ namespace SourceGit.ViewModels
if (_selectedWorkspace is { })
_launcher.SwitchWorkspace(_selectedWorkspace);
- _launcher.CancelWorkspaceSwitcher();
+ _launcher.CancelSwitcher();
}
private void UpdateVisibleWorkspaces()
diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml
index 5d9a6f9f..5275f264 100644
--- a/src/Views/Hotkeys.axaml
+++ b/src/Views/Hotkeys.axaml
@@ -45,8 +45,8 @@
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}"
Margin="0,0,0,8"/>
-
-
+
+
@@ -55,7 +55,7 @@
-
+
@@ -70,8 +70,11 @@
-
+
+
+
+
+ IsVisible="{Binding Switcher, Converter={x:Static ObjectConverters.IsNotNull}}">
-
+
+
+
+
+
diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs
index 02cc4f08..abcbaba9 100644
--- a/src/Views/Launcher.axaml.cs
+++ b/src/Views/Launcher.axaml.cs
@@ -133,8 +133,8 @@ namespace SourceGit.Views
return;
}
- // Ctrl+Shift+P opens preference dialog (macOS use hotkeys in system menu bar)
- if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: (KeyModifiers.Control | KeyModifiers.Shift), Key: Key.P })
+ // Ctrl+, opens preference dialog (macOS use hotkeys in system menu bar)
+ if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: KeyModifiers.Control, Key: Key.OemComma })
{
App.ShowWindow(new Preferences(), true);
e.Handled = true;
@@ -149,7 +149,7 @@ namespace SourceGit.Views
}
// Ctrl+Q quits the application (macOS use hotkeys in system menu bar)
- if (!OperatingSystem.IsMacOS() && e.KeyModifiers == KeyModifiers.Control && e.Key == Key.Q)
+ if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: KeyModifiers.Control, Key: Key.Q })
{
App.Quit(0);
return;
@@ -157,10 +157,18 @@ namespace SourceGit.Views
if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control))
{
- if (e.Key == Key.P)
+ if (e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.P)
{
vm.OpenWorkspaceSwitcher();
e.Handled = true;
+ return;
+ }
+
+ if (e.Key == Key.P)
+ {
+ vm.OpenTabSwitcher();
+ e.Handled = true;
+ return;
}
if (e.Key == Key.W)
@@ -257,7 +265,7 @@ namespace SourceGit.Views
else if (e.Key == Key.Escape)
{
vm.ActivePage.CancelPopup();
- vm.CancelWorkspaceSwitcher();
+ vm.CancelSwitcher();
e.Handled = true;
return;
}
diff --git a/src/Views/LauncherTabsSelector.axaml b/src/Views/LauncherPageSwitcher.axaml
similarity index 70%
rename from src/Views/LauncherTabsSelector.axaml
rename to src/Views/LauncherPageSwitcher.axaml
index 109a2ce7..09f42038 100644
--- a/src/Views/LauncherTabsSelector.axaml
+++ b/src/Views/LauncherPageSwitcher.axaml
@@ -3,19 +3,27 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
+ xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="SourceGit.Views.LauncherTabsSelector"
- x:Name="ThisControl">
-
-
+
+
+
+
+ VerticalContentAlignment="Center"
+ v:AutoFocusBehaviour.IsEnabled="True">
-
+ ItemsSource="{Binding VisiblePages, Mode=OneWay}"
+ SelectedItem="{Binding SelectedPage, Mode=TwoWay}">
@@ -72,30 +83,28 @@
-
+
-
-
diff --git a/src/Views/LauncherPageSwitcher.axaml.cs b/src/Views/LauncherPageSwitcher.axaml.cs
new file mode 100644
index 00000000..277eb549
--- /dev/null
+++ b/src/Views/LauncherPageSwitcher.axaml.cs
@@ -0,0 +1,49 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+
+namespace SourceGit.Views
+{
+ public partial class LauncherPageSwitcher : UserControl
+ {
+ public LauncherPageSwitcher()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+
+ if (e.Key == Key.Enter && DataContext is ViewModels.LauncherPageSwitcher switcher)
+ {
+ switcher.Switch();
+ e.Handled = true;
+ }
+ }
+
+ private void OnItemDoubleTapped(object sender, TappedEventArgs e)
+ {
+ if (DataContext is ViewModels.LauncherPageSwitcher switcher)
+ {
+ switcher.Switch();
+ e.Handled = true;
+ }
+ }
+
+ private void OnSearchBoxKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Down && PagesListBox.ItemCount > 0)
+ {
+ PagesListBox.Focus(NavigationMethod.Directional);
+
+ if (PagesListBox.SelectedIndex < 0)
+ PagesListBox.SelectedIndex = 0;
+ else if (PagesListBox.SelectedIndex < PagesListBox.ItemCount)
+ PagesListBox.SelectedIndex++;
+
+ e.Handled = true;
+ }
+ }
+ }
+}
+
diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml
index 0376e259..f770a5d9 100644
--- a/src/Views/LauncherTabBar.axaml
+++ b/src/Views/LauncherTabBar.axaml
@@ -40,7 +40,7 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/LauncherTabBar.axaml.cs b/src/Views/LauncherTabBar.axaml.cs
index 12bca91f..b75d93d6 100644
--- a/src/Views/LauncherTabBar.axaml.cs
+++ b/src/Views/LauncherTabBar.axaml.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia;
+using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -18,6 +19,20 @@ namespace SourceGit.Views
get => GetValue(IsScrollerVisibleProperty);
set => SetValue(IsScrollerVisibleProperty, value);
}
+
+ public static readonly StyledProperty SearchFilterProperty =
+ AvaloniaProperty.Register(nameof(SearchFilter));
+
+ public string SearchFilter
+ {
+ get => GetValue(SearchFilterProperty);
+ set => SetValue(SearchFilterProperty, value);
+ }
+
+ public AvaloniaList SelectablePages
+ {
+ get;
+ } = [];
public LauncherTabBar()
{
@@ -125,7 +140,15 @@ namespace SourceGit.Views
var stroke = new Pen(this.FindResource("Brush.Border0") as IBrush);
context.DrawGeometry(fill, stroke, geo);
}
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ if (change.Property == SearchFilterProperty)
+ UpdateSelectablePages();
+ }
+
private void ScrollTabs(object _, PointerWheelEventArgs e)
{
if (!e.KeyModifiers.HasFlag(KeyModifiers.Shift))
@@ -247,16 +270,92 @@ namespace SourceGit.Views
e.Handled = true;
}
-
- private void OnGotoSelectedPage(object sender, LauncherTabSelectedEventArgs e)
+
+ private void OnTabsDropdownOpened(object sender, EventArgs e)
{
- if (DataContext is ViewModels.Launcher vm)
- vm.ActivePage = e.Page;
+ UpdateSelectablePages();
+ }
+
+ private void OnTabsDropdownKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Escape)
+ {
+ PageSelector.Flyout?.Hide();
+ e.Handled = true;
+ }
+ else if (e.Key == Key.Enter)
+ {
+ if (TabsDropdownList.SelectedItem is ViewModels.LauncherPage page &&
+ DataContext is ViewModels.Launcher vm)
+ {
+ vm.ActivePage = page;
+ PageSelector.Flyout?.Hide();
+ e.Handled = true;
+ }
+ }
+ }
+
+ private void OnTabsDropdownSearchBoxKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Down && TabsDropdownList.ItemCount > 0)
+ {
+ TabsDropdownList.Focus(NavigationMethod.Directional);
- PageSelector.Flyout?.Hide();
- e.Handled = true;
+ if (TabsDropdownList.SelectedIndex < 0)
+ TabsDropdownList.SelectedIndex = 0;
+ else if (TabsDropdownList.SelectedIndex < TabsDropdownList.ItemCount)
+ TabsDropdownList.SelectedIndex++;
+
+ e.Handled = true;
+ }
}
+ private void OnTabsDropdownLostFocus(object sender, RoutedEventArgs e)
+ {
+ if (sender is Control { IsFocused: false, IsKeyboardFocusWithin: false })
+ PageSelector.Flyout?.Hide();
+ }
+
+ private void OnClearSearchFilter(object sender, RoutedEventArgs e)
+ {
+ SearchFilter = string.Empty;
+ }
+
+ private void OnTabsDropdownItemDoubleTapped(object sender, TappedEventArgs e)
+ {
+ if (sender is Control { DataContext: ViewModels.LauncherPage page } &&
+ DataContext is ViewModels.Launcher vm)
+ {
+ vm.ActivePage = page;
+ PageSelector.Flyout?.Hide();
+ e.Handled = true;
+ }
+ }
+
+ private void UpdateSelectablePages()
+ {
+ if (DataContext is not ViewModels.Launcher vm)
+ return;
+
+ SelectablePages.Clear();
+
+ var pages = vm.Pages;
+ var filter = SearchFilter?.Trim() ?? "";
+ if (string.IsNullOrEmpty(filter))
+ {
+ SelectablePages.AddRange(pages);
+ return;
+ }
+
+ foreach (var page in pages)
+ {
+ var node = page.Node;
+ if (node.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
+ (node.IsRepository && node.Id.Contains(filter, StringComparison.OrdinalIgnoreCase)))
+ SelectablePages.Add(page);
+ }
+ }
+
private bool _pressedTab = false;
private Point _pressedTabPosition = new Point();
private bool _startDragTab = false;
diff --git a/src/Views/LauncherTabsSelector.axaml.cs b/src/Views/LauncherTabsSelector.axaml.cs
deleted file mode 100644
index 61d7a966..00000000
--- a/src/Views/LauncherTabsSelector.axaml.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using System;
-
-using Avalonia;
-using Avalonia.Collections;
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-
-namespace SourceGit.Views
-{
- public class LauncherTabSelectedEventArgs : RoutedEventArgs
- {
- public ViewModels.LauncherPage Page { get; }
-
- public LauncherTabSelectedEventArgs(ViewModels.LauncherPage page)
- {
- RoutedEvent = LauncherTabsSelector.PageSelectedEvent;
- Page = page;
- }
- }
-
- public partial class LauncherTabsSelector : UserControl
- {
- public static readonly StyledProperty> PagesProperty =
- AvaloniaProperty.Register>(nameof(Pages));
-
- public AvaloniaList Pages
- {
- get => GetValue(PagesProperty);
- set => SetValue(PagesProperty, value);
- }
-
- public static readonly StyledProperty SearchFilterProperty =
- AvaloniaProperty.Register(nameof(SearchFilter));
-
- public string SearchFilter
- {
- get => GetValue(SearchFilterProperty);
- set => SetValue(SearchFilterProperty, value);
- }
-
- public static readonly RoutedEvent PageSelectedEvent =
- RoutedEvent.Register(nameof(PageSelected), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
-
- public event EventHandler PageSelected
- {
- add { AddHandler(PageSelectedEvent, value); }
- remove { RemoveHandler(PageSelectedEvent, value); }
- }
-
- public AvaloniaList VisiblePages
- {
- get;
- private set;
- }
-
- public LauncherTabsSelector()
- {
- VisiblePages = new AvaloniaList();
- InitializeComponent();
- }
-
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
- {
- base.OnPropertyChanged(change);
-
- if (change.Property == PagesProperty || change.Property == SearchFilterProperty)
- UpdateVisiblePages();
- }
-
- private void OnClearSearchFilter(object sender, RoutedEventArgs e)
- {
- SearchFilter = string.Empty;
- }
-
- private void OnPageSelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- if (sender is ListBox { SelectedItem: ViewModels.LauncherPage page })
- {
- _isProcessingSelection = true;
- RaiseEvent(new LauncherTabSelectedEventArgs(page));
- _isProcessingSelection = false;
- }
-
- e.Handled = true;
- }
-
- private void UpdateVisiblePages()
- {
- if (_isProcessingSelection)
- return;
-
- VisiblePages.Clear();
-
- if (Pages == null)
- return;
-
- var filter = SearchFilter?.Trim() ?? "";
- if (string.IsNullOrEmpty(filter))
- {
- foreach (var p in Pages)
- VisiblePages.Add(p);
-
- return;
- }
-
- foreach (var page in Pages)
- {
- if (!page.Node.IsRepository)
- continue;
-
- if (page.Node.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
- page.Node.Id.Contains(filter, StringComparison.OrdinalIgnoreCase))
- VisiblePages.Add(page);
- }
- }
-
- private bool _isProcessingSelection = false;
- }
-}
-
diff --git a/src/Views/WorkspaceSwitcher.axaml b/src/Views/WorkspaceSwitcher.axaml
index 49fed451..aa621b73 100644
--- a/src/Views/WorkspaceSwitcher.axaml
+++ b/src/Views/WorkspaceSwitcher.axaml
@@ -9,7 +9,7 @@
x:DataType="vm:WorkspaceSwitcher">
@@ -82,7 +82,7 @@
-
+