diff --git a/TRANSLATION.md b/TRANSLATION.md index d810b5b2..1a9d00d1 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-98.48%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-98.23%25-yellow)
Missing keys in de_DE.axaml @@ -14,9 +14,11 @@ This document shows the translation status of each locale file in the repository - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace +- Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.TextEditor.OpenExternalMergeTool +- Text.Launcher.Workspaces +- Text.Launcher.Pages - Text.Repository.ShowSubmodulesAsTree -- Text.Repository.WorkspaceSwitcher - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -26,17 +28,19 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-99.75%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-99.49%25-yellow)
Missing keys in es_ES.axaml - Text.Hotkeys.Global.SwitchWorkspace -- Text.Repository.WorkspaceSwitcher +- Text.Hotkeys.Global.SwitchTab +- Text.Launcher.Workspaces +- Text.Launcher.Pages
-### ![fr__FR](https://img.shields.io/badge/fr__FR-94.29%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-94.05%25-yellow)
Missing keys in fr_FR.axaml @@ -61,7 +65,10 @@ This document shows the translation status of each locale file in the repository - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace +- Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.TextEditor.OpenExternalMergeTool +- Text.Launcher.Workspaces +- Text.Launcher.Pages - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate @@ -70,7 +77,6 @@ This document shows the translation status of each locale file in the repository - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs - Text.Repository.Visit -- Text.Repository.WorkspaceSwitcher - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -89,17 +95,19 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-99.75%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-99.49%25-yellow)
Missing keys in it_IT.axaml - Text.Hotkeys.Global.SwitchWorkspace -- Text.Repository.WorkspaceSwitcher +- Text.Hotkeys.Global.SwitchTab +- Text.Launcher.Workspaces +- Text.Launcher.Pages
-### ![ja__JP](https://img.shields.io/badge/ja__JP-94.04%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-93.80%25-yellow)
Missing keys in ja_JP.axaml @@ -124,7 +132,10 @@ This document shows the translation status of each locale file in the repository - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace +- Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.TextEditor.OpenExternalMergeTool +- Text.Launcher.Workspaces +- Text.Launcher.Pages - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate @@ -135,7 +146,6 @@ This document shows the translation status of each locale file in the repository - Text.Repository.Tags.OrderByNameDes - Text.Repository.ViewLogs - Text.Repository.Visit -- Text.Repository.WorkspaceSwitcher - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -154,7 +164,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-85.79%25-yellow) +### ![pt__BR](https://img.shields.io/badge/pt__BR-85.57%25-yellow)
Missing keys in pt_BR.axaml @@ -208,11 +218,14 @@ This document shows the translation status of each locale file in the repository - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.Clone - Text.Hotkeys.Global.SwitchWorkspace +- Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.TextEditor.OpenExternalMergeTool - Text.InProgress.CherryPick.Head - Text.InProgress.Merge.Operating - Text.InProgress.Rebase.StoppedAt - Text.InProgress.Revert.Head +- Text.Launcher.Workspaces +- Text.Launcher.Pages - Text.Merge.Source - Text.MergeMultiple - Text.MergeMultiple.CommitChanges @@ -245,7 +258,6 @@ This document shows the translation status of each locale file in the repository - Text.Repository.UseRelativeTimeInHistories - Text.Repository.ViewLogs - Text.Repository.Visit -- Text.Repository.WorkspaceSwitcher - Text.SetUpstream - Text.SetUpstream.Local - Text.SetUpstream.Unset @@ -274,9 +286,17 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.75%25-yellow) -### ![ta__IN](https://img.shields.io/badge/ta__IN-94.29%25-yellow) +
+Missing keys in ru_RU.axaml + +- Text.Hotkeys.Global.SwitchTab +- Text.Launcher.Pages + +
+ +### ![ta__IN](https://img.shields.io/badge/ta__IN-94.05%25-yellow)
Missing keys in ta_IN.axaml @@ -301,7 +321,10 @@ This document shows the translation status of each locale file in the repository - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace +- Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.TextEditor.OpenExternalMergeTool +- Text.Launcher.Workspaces +- Text.Launcher.Pages - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate @@ -310,7 +333,6 @@ This document shows the translation status of each locale file in the repository - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs - Text.Repository.Visit -- Text.Repository.WorkspaceSwitcher - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -329,7 +351,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-95.43%25-yellow) +### ![uk__UA](https://img.shields.io/badge/uk__UA-95.19%25-yellow)
Missing keys in uk_UA.axaml @@ -350,7 +372,10 @@ This document shows the translation status of each locale file in the repository - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace +- Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.TextEditor.OpenExternalMergeTool +- Text.Launcher.Workspaces +- Text.Launcher.Pages - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate @@ -359,7 +384,6 @@ This document shows the translation status of each locale file in the repository - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs - Text.Repository.Visit -- Text.Repository.WorkspaceSwitcher - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 4ce0a1ad..bb259272 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 + Workspaces + Pages 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..77a7ba4f 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..18569a14 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..ded99a14 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/Histories.cs b/src/ViewModels/Histories.cs index 555954d9..f21d2636 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.IO; @@ -387,7 +387,7 @@ namespace SourceGit.ViewModels { var builder = new StringBuilder(); foreach (var c in selected) - builder.AppendLine($"{c.SHA.Substring(0, 10)} - {c.Subject}"); + builder.AppendLine($"{c.SHA.AsSpan(0, 10)} - {c.Subject}"); App.CopyText(builder.ToString()); e.Handled = true; @@ -780,7 +780,7 @@ namespace SourceGit.ViewModels copyInfo.Icon = App.CreateMenuIcon("Icons.Info"); copyInfo.Click += (_, e) => { - App.CopyText($"{commit.SHA.Substring(0, 10)} - {commit.Subject}"); + App.CopyText($"{commit.SHA.AsSpan(0, 10)} - {commit.Subject}"); e.Handled = true; }; diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 9a54bb32..4c0714df 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 IDisposable Switcher + { + get => _switcher; + private set => SetProperty(ref _switcher, value); + } + public Launcher(string startupRepo) { _ignoreIndexChange = true; @@ -138,17 +138,23 @@ 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?.Dispose(); + Switcher = null; } public void SwitchWorkspace(Workspace to) { - if (to.IsActive) + if (to == null || to.IsActive) return; foreach (var one in Pages) @@ -618,6 +624,6 @@ namespace SourceGit.ViewModels private LauncherPage _activePage = null; private bool _ignoreIndexChange = false; private string _title = string.Empty; - private WorkspaceSwitcher _workspaceSwitcher = null; + private IDisposable _switcher = null; } } diff --git a/src/ViewModels/LauncherPageSwitcher.cs b/src/ViewModels/LauncherPageSwitcher.cs new file mode 100644 index 00000000..5f53021d --- /dev/null +++ b/src/ViewModels/LauncherPageSwitcher.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class LauncherPageSwitcher : ObservableObject, IDisposable + { + 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() + { + _launcher.ActivePage = _selectedPage ?? _launcher.ActivePage; + _launcher.CancelSwitcher(); + } + + public void Dispose() + { + _visiblePages.Clear(); + _selectedPage = null; + _searchFilter = string.Empty; + } + + 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; + SelectedPage = visible.Count > 0 ? visible[0] : null; + } + + 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..7a2da9be 100644 --- a/src/ViewModels/WorkspaceSwitcher.cs +++ b/src/ViewModels/WorkspaceSwitcher.cs @@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public class WorkspaceSwitcher : ObservableObject + public class WorkspaceSwitcher : ObservableObject, IDisposable { public List VisibleWorkspaces { @@ -41,10 +41,15 @@ namespace SourceGit.ViewModels public void Switch() { - if (_selectedWorkspace is { }) - _launcher.SwitchWorkspace(_selectedWorkspace); + _launcher.SwitchWorkspace(_selectedWorkspace); + _launcher.CancelSwitcher(); + } - _launcher.CancelWorkspaceSwitcher(); + public void Dispose() + { + _visibleWorkspaces.Clear(); + _selectedWorkspace = null; + _searchFilter = string.Empty; } 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}}" + PointerPressed="OnCancelSwitcher"> - + + + + + diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 02cc4f08..be4cdf5b 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; } @@ -314,6 +322,13 @@ namespace SourceGit.Views e.Handled = true; } + private void OnCancelSwitcher(object sender, PointerPressedEventArgs e) + { + if (e.Source == sender) + (DataContext as ViewModels.Launcher)?.CancelSwitcher(); + e.Handled = true; + } + private KeyModifiers _unhandledModifiers = KeyModifiers.None; private WindowState _lastWindowState = WindowState.Normal; } 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..4d785b55 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..1effb93c --- /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..a56da2b0 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..270902bc 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; @@ -19,6 +20,20 @@ namespace SourceGit.Views 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() { InitializeComponent(); @@ -126,6 +141,14 @@ namespace SourceGit.Views 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)) @@ -248,13 +271,95 @@ 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(); + } - PageSelector.Flyout?.Hide(); - e.Handled = true; + private void OnTabsDropdownClosed(object sender, EventArgs e) + { + SelectablePages.Clear(); + SearchFilter = string.Empty; + } + + 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); + + 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; 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..4a324f3e 100644 --- a/src/Views/WorkspaceSwitcher.axaml +++ b/src/Views/WorkspaceSwitcher.axaml @@ -7,15 +7,15 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.WorkspaceSwitcher" x:DataType="vm:WorkspaceSwitcher"> - + + HorizontalAlignment="Center"/> - +