enhance: add a opened tabs selector popup (#958)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2025-02-10 20:06:44 +08:00
parent e757e63bf7
commit 0c8179b934
No known key found for this signature in database
5 changed files with 249 additions and 2 deletions

View file

@ -4,11 +4,12 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:c="using:SourceGit.Converters"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.LauncherTabBar"
x:DataType="vm:Launcher"
x:Name="ThisControl">
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
<Grid ColumnDefinitions="Auto,*,Auto">
<RepeatButton Grid.Column="0"
Classes="icon_button"
Width="18" Height="30"
@ -123,7 +124,16 @@
<Path Width="8" Height="14" Stretch="Fill" Data="{StaticResource Icons.TriangleRight}"/>
</RepeatButton>
<Button Classes="icon_button" Width="16" Height="16" Margin="8,0" Command="{Binding AddNewTab}">
<Button x:Name="PageSelector" Classes="icon_button" Width="16" Height="16" Margin="8,0">
<Button.Flyout>
<Flyout>
<v:LauncherTabsSelector Pages="{Binding Pages}" PageSelected="OnGotoSelectedPage"/>
</Flyout>
</Button.Flyout>
<Path Width="14" Height="14" Data="{StaticResource Icons.CircleDown}"/>
</Button>
<Button Classes="icon_button" Width="16" Height="16" Command="{Binding AddNewTab}">
<ToolTip.Tip>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Text.PageTabBar.New}" VerticalAlignment="Center"/>

View file

@ -248,6 +248,15 @@ namespace SourceGit.Views
e.Handled = true;
}
private void OnGotoSelectedPage(object sender, LauncherTabSelectedEventArgs e)
{
if (DataContext is ViewModels.Launcher vm)
vm.ActivePage = e.Page;
PageSelector.Flyout?.Hide();
e.Handled = true;
}
private bool _pressedTab = false;
private Point _pressedTabPosition = new Point();
private bool _startDragTab = false;

View file

@ -0,0 +1,107 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.LauncherTabsSelector"
x:Name="ThisControl">
<Grid RowDefinitions="28,Auto">
<TextBox Grid.Row="0"
Height="24"
Margin="4,0"
BorderThickness="1"
CornerRadius="12"
Text="{Binding #ThisControl.SearchFilter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Click="OnClearSearchFilter"
IsVisible="{Binding #ThisControl.SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<ListBox Grid.Row="1"
Width="200"
MaxHeight="400"
Margin="0,4,0,0"
Background="Transparent"
SelectionMode="Single"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding #ThisControl.VisiblePages}"
SelectionChanged="OnPageSelectionChanged">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="MinHeight" Value="26"/>
<Setter Property="CornerRadius" Value="4"/>
</Style>
<Style Selector="ListBox">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Grid/>
</FocusAdornerTemplate>
</Setter>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:LauncherPage">
<Grid ColumnDefinitions="Auto,*" VerticalAlignment="Center">
<Path Grid.Column="0"
Width="12" Height="12" Margin="12,0"
Fill="{Binding Node.Bookmark, Converter={x:Static c:IntConverters.ToBookmarkBrush}}"
Data="{StaticResource Icons.Bookmark}"
IsVisible="{Binding Node.IsRepository}"
IsHitTestVisible="False"/>
<Path Grid.Column="0"
Width="12" Height="12" Margin="12,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Repositories}"
IsVisible="{Binding !Node.IsRepository}"
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Classes="primary"
VerticalAlignment="Center"
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
Text="{Binding Node.Name}"
IsVisible="{Binding Node.IsRepository}"
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Classes="primary"
VerticalAlignment="Center"
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
Text="{DynamicResource Text.PageTabBar.Welcome.Title}"
IsVisible="{Binding !Node.IsRepository}"
IsHitTestVisible="False"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>

View file

@ -0,0 +1,120 @@
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<AvaloniaList<ViewModels.LauncherPage>> PagesProperty =
AvaloniaProperty.Register<LauncherTabsSelector, AvaloniaList<ViewModels.LauncherPage>>(nameof(Pages));
public AvaloniaList<ViewModels.LauncherPage> Pages
{
get => GetValue(PagesProperty);
set => SetValue(PagesProperty, value);
}
public static readonly StyledProperty<string> SearchFilterProperty =
AvaloniaProperty.Register<LauncherTabsSelector, string>(nameof(SearchFilter));
public string SearchFilter
{
get => GetValue(SearchFilterProperty);
set => SetValue(SearchFilterProperty, value);
}
public static readonly RoutedEvent<LauncherTabSelectedEventArgs> PageSelectedEvent =
RoutedEvent.Register<ChangeCollectionView, LauncherTabSelectedEventArgs>(nameof(PageSelected), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
public event EventHandler<LauncherTabSelectedEventArgs> PageSelected
{
add { AddHandler(PageSelectedEvent, value); }
remove { RemoveHandler(PageSelectedEvent, value); }
}
public AvaloniaList<ViewModels.LauncherPage> VisiblePages
{
get;
private set;
}
public LauncherTabsSelector()
{
VisiblePages = new AvaloniaList<ViewModels.LauncherPage>();
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;
}
}