optimize: new way to update corner radius of branch tree node to improve performance (#137)

This commit is contained in:
leo 2024-05-25 11:15:07 +08:00
parent 476f9265e1
commit 8e3a8f4c06
5 changed files with 128 additions and 208 deletions

View file

@ -1,99 +0,0 @@
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data.Converters;
namespace SourceGit.Converters
{
public static class BranchTreeNodeConverters
{
public static readonly CornerRadius DEFAULT = new CornerRadius(4);
public static readonly FuncMultiValueConverter<object, CornerRadius> ToCornerRadius =
new FuncMultiValueConverter<object, CornerRadius>(v =>
{
if (v == null)
return DEFAULT;
var array = new List<object>();
array.AddRange(v);
if (array.Count != 2)
return DEFAULT;
var item = array[1] as TreeViewItem;
if (item == null || !item.IsSelected)
return DEFAULT;
var prev = GetPrevTreeViewItem(item);
var next = GetNextTreeViewItem(item, true);
double top = 4, bottom = 4;
if (prev != null && prev.IsSelected)
top = 0;
if (next != null && next.IsSelected)
bottom = 0;
return new CornerRadius(top, bottom);
});
private static TreeViewItem GetPrevTreeViewItem(TreeViewItem item)
{
if (item.Parent is TreeView tree)
{
var idx = tree.IndexFromContainer(item);
if (idx == 0)
return null;
var prev = tree.ContainerFromIndex(idx - 1) as TreeViewItem;
if (prev != null && prev.IsExpanded && prev.ItemCount > 0)
return prev.ContainerFromIndex(prev.ItemCount - 1) as TreeViewItem;
return prev;
}
else if (item.Parent is TreeViewItem parentItem)
{
var idx = parentItem.IndexFromContainer(item);
if (idx == 0)
return parentItem;
var prev = parentItem.ContainerFromIndex(idx - 1) as TreeViewItem;
if (prev != null && prev.IsExpanded && prev.ItemCount > 0)
return prev.ContainerFromIndex(prev.ItemCount - 1) as TreeViewItem;
return prev;
}
else
{
return null;
}
}
private static TreeViewItem GetNextTreeViewItem(TreeViewItem item, bool intoSelf = false)
{
if (intoSelf && item.IsExpanded && item.ItemCount > 0)
return item.ContainerFromIndex(0) as TreeViewItem;
if (item.Parent is TreeView tree)
{
var idx = tree.IndexFromContainer(item);
if (idx == tree.ItemCount - 1)
return null;
return tree.ContainerFromIndex(idx + 1) as TreeViewItem;
}
else if (item.Parent is TreeViewItem parentItem)
{
var idx = parentItem.IndexFromContainer(item);
if (idx == parentItem.ItemCount - 1)
return GetNextTreeViewItem(parentItem);
return parentItem.ContainerFromIndex(idx + 1) as TreeViewItem;
}
else
{
return null;
}
}
}
}

View file

@ -1,8 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels namespace SourceGit.ViewModels
{ {
public enum BranchTreeNodeType public enum BranchTreeNodeType
@ -12,8 +16,10 @@ namespace SourceGit.ViewModels
Branch, Branch,
} }
public class BranchTreeNode public class BranchTreeNode : ObservableObject
{ {
public const double DEFAULT_CORNER = 4.0;
public string Name { get; set; } public string Name { get; set; }
public BranchTreeNodeType Type { get; set; } public BranchTreeNodeType Type { get; set; }
public object Backend { get; set; } public object Backend { get; set; }
@ -51,6 +57,43 @@ namespace SourceGit.ViewModels
get => IsBranch && (Backend as Models.Branch).IsCurrent; get => IsBranch && (Backend as Models.Branch).IsCurrent;
} }
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
public CornerRadius CornerRadius
{
get => _cornerRadius;
set => SetProperty(ref _cornerRadius, value);
}
public void UpdateCornerRadius(ref BranchTreeNode prev)
{
if (_isSelected && prev != null && prev.IsSelected)
{
var prevTop = prev.CornerRadius.TopLeft;
prev.CornerRadius = new CornerRadius(prevTop, 0);
CornerRadius = new CornerRadius(0, DEFAULT_CORNER);
}
else if (CornerRadius.TopLeft != DEFAULT_CORNER ||
CornerRadius.BottomLeft != DEFAULT_CORNER)
{
CornerRadius = new CornerRadius(DEFAULT_CORNER);
}
prev = this;
if (!IsBranch && IsExpanded)
{
foreach (var child in Children)
child.UpdateCornerRadius(ref prev);
}
}
private bool _isSelected = false;
private CornerRadius _cornerRadius = new CornerRadius(DEFAULT_CORNER);
public class Builder public class Builder
{ {
public List<BranchTreeNode> Locals => _locals; public List<BranchTreeNode> Locals => _locals;

View file

@ -14,75 +14,69 @@
Classes="bold" Classes="bold"
Text="{DynamicResource Text.DeleteMultiBranch}" /> Text="{DynamicResource Text.DeleteMultiBranch}" />
<Grid Margin="0,16,8,0" RowDefinitions="Auto,Auto" ColumnDefinitions="120,*"> <Border Margin="4,16,0,0"
<TextBlock Grid.Row="0" Grid.Column="0" Background="{DynamicResource Brush.Contents}"
HorizontalAlignment="Right" VerticalAlignment="Top" BorderThickness="1"
Text="{DynamicResource Text.DeleteMultiBranch.Targets}" /> BorderBrush="{DynamicResource Brush.Border1}"
<Border Grid.Row="0" Grid.Column="1" CornerRadius="4"
Background="{DynamicResource Brush.Contents}" Padding="4">
BorderThickness="1" <DataGrid MaxHeight="200"
BorderBrush="{DynamicResource Brush.Border1}" Background="Transparent"
CornerRadius="4" BorderThickness="0"
Padding="4"> ItemsSource="{Binding Targets}"
<DataGrid MaxHeight="200" SelectionMode="Single"
Background="Transparent" CanUserReorderColumns="False"
BorderThickness="0" CanUserResizeColumns="False"
ItemsSource="{Binding Targets}" CanUserSortColumns="False"
SelectionMode="Single" IsReadOnly="True"
CanUserReorderColumns="False" HeadersVisibility="None"
CanUserResizeColumns="False" Focusable="False"
CanUserSortColumns="False" RowHeight="26"
IsReadOnly="True" HorizontalScrollBarVisibility="Auto"
HeadersVisibility="None" VerticalScrollBarVisibility="Auto">
Focusable="False" <DataGrid.Styles>
RowHeight="26" <Style Selector="DataGridRow">
HorizontalScrollBarVisibility="Auto" <Setter Property="CornerRadius" Value="4" />
VerticalScrollBarVisibility="Auto"> </Style>
<DataGrid.Styles>
<Style Selector="DataGridRow">
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="DataGridRow /template/ Border#RowBorder"> <Style Selector="DataGridRow /template/ Border#RowBorder">
<Setter Property="ClipToBounds" Value="True" /> <Setter Property="ClipToBounds" Value="True" />
</Style> </Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle"> <Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" /> <Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
</Style> </Style>
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle"> <Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" /> <Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
</Style> </Style>
</DataGrid.Styles> </DataGrid.Styles>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTemplateColumn Header="ICON"> <DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Path Width="10" Height="10" Margin="4,0,8,0" Data="{StaticResource Icons.Branch}" /> <Path Width="10" Height="10" Margin="4,0,8,0" Data="{StaticResource Icons.Branch}" />
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="NAME"> <DataGridTemplateColumn Width="*" Header="NAME">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Converter={x:Static c:BranchConverters.ToName}}" ClipToBounds="True" <TextBlock Text="{Binding Converter={x:Static c:BranchConverters.ToName}}" ClipToBounds="True"
Classes="monospace" /> Classes="monospace" />
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Border> </Border>
<TextBlock Grid.Row="1" Grid.Column="1" <TextBlock Margin="4,8,0,0"
Margin="0,8,0,0" Text="{DynamicResource Text.DeleteMultiBranch.Tip}"
Text="{DynamicResource Text.DeleteMultiBranch.Tip}" TextWrapping="Wrap"
TextWrapping="Wrap" Foreground="{DynamicResource Brush.FG2}"
Foreground="{DynamicResource Brush.FG2}" FontStyle="Italic" />
FontStyle="Italic" />
</Grid>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View file

@ -208,14 +208,8 @@
<TreeView.Styles> <TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode"> <Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="CornerRadius"> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter.Value> <Setter Property="CornerRadius" Value="{Binding CornerRadius}"/>
<MultiBinding Converter="{x:Static c:BranchTreeNodeConverters.ToCornerRadius}">
<Binding Path="$parent[v:Repository].RefereshLocalBranchSelectionToken"/>
<Binding Path="$self"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style> </Style>
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot"> <Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot">
@ -278,14 +272,8 @@
<TreeView.Styles> <TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode"> <Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="CornerRadius"> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter.Value> <Setter Property="CornerRadius" Value="{Binding CornerRadius}"/>
<MultiBinding Converter="{x:Static c:BranchTreeNodeConverters.ToCornerRadius}">
<Binding Path="$parent[v:Repository].RefereshRemoteBranchSelectionToken"/>
<Binding Path="$self"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style> </Style>
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot"> <Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot">

View file

@ -58,24 +58,6 @@ namespace SourceGit.Views
public partial class Repository : UserControl public partial class Repository : UserControl
{ {
public static readonly StyledProperty<ulong> RefereshLocalBranchSelectionTokenProperty =
AvaloniaProperty.Register<Repository, ulong>(nameof(RefereshLocalBranchSelectionToken), 0);
public ulong RefereshLocalBranchSelectionToken
{
get => GetValue(RefereshLocalBranchSelectionTokenProperty);
set => SetValue(RefereshLocalBranchSelectionTokenProperty, value);
}
public static readonly StyledProperty<ulong> RefereshRemoteBranchSelectionTokenProperty =
AvaloniaProperty.Register<Repository, ulong>(nameof(RefereshRemoteBranchSelectionToken), 0);
public ulong RefereshRemoteBranchSelectionToken
{
get => GetValue(RefereshRemoteBranchSelectionTokenProperty);
set => SetValue(RefereshRemoteBranchSelectionTokenProperty, value);
}
public Repository() public Repository()
{ {
InitializeComponent(); InitializeComponent();
@ -93,18 +75,19 @@ namespace SourceGit.Views
private void OnLocalBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e) private void OnLocalBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
if (sender is TreeView tree && tree.SelectedItem != null) if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo)
{ {
remoteBranchTree.UnselectAll(); remoteBranchTree.UnselectAll();
tagsList.SelectedItem = null; tagsList.SelectedItem = null;
var next = RefereshLocalBranchSelectionToken + 1; ViewModels.BranchTreeNode prev = null;
SetCurrentValue(RefereshLocalBranchSelectionTokenProperty, next); foreach (var node in repo.LocalBranchTrees)
node.UpdateCornerRadius(ref prev);
if (tree.SelectedItems.Count == 1) if (tree.SelectedItems.Count == 1)
{ {
var node = tree.SelectedItem as ViewModels.BranchTreeNode; var node = tree.SelectedItem as ViewModels.BranchTreeNode;
if (node.IsBranch && DataContext is ViewModels.Repository repo) if (node.IsBranch)
repo.NavigateToCommit((node.Backend as Models.Branch).Head); repo.NavigateToCommit((node.Backend as Models.Branch).Head);
} }
} }
@ -112,18 +95,19 @@ namespace SourceGit.Views
private void OnRemoteBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e) private void OnRemoteBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
if (sender is TreeView tree && tree.SelectedItem != null) if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo)
{ {
localBranchTree.UnselectAll(); localBranchTree.UnselectAll();
tagsList.SelectedItem = null; tagsList.SelectedItem = null;
var next = RefereshRemoteBranchSelectionToken + 1; ViewModels.BranchTreeNode prev = null;
SetCurrentValue(RefereshRemoteBranchSelectionTokenProperty, next); foreach (var node in repo.RemoteBranchTrees)
node.UpdateCornerRadius(ref prev);
if (tree.SelectedItems.Count == 1) if (tree.SelectedItems.Count == 1)
{ {
var node = tree.SelectedItem as ViewModels.BranchTreeNode; var node = tree.SelectedItem as ViewModels.BranchTreeNode;
if (node.IsBranch && DataContext is ViewModels.Repository repo) if (node.IsBranch)
repo.NavigateToCommit((node.Backend as Models.Branch).Head); repo.NavigateToCommit((node.Backend as Models.Branch).Head);
} }
} }
@ -204,6 +188,11 @@ namespace SourceGit.Views
var repo = DataContext as ViewModels.Repository; var repo = DataContext as ViewModels.Repository;
var tree = sender as TreeView; var tree = sender as TreeView;
if (tree.SelectedItems.Count == 0)
{
e.Handled = true;
return;
}
var branches = new List<Models.Branch>(); var branches = new List<Models.Branch>();
foreach (var item in tree.SelectedItems) foreach (var item in tree.SelectedItems)
@ -243,6 +232,11 @@ namespace SourceGit.Views
var repo = DataContext as ViewModels.Repository; var repo = DataContext as ViewModels.Repository;
var tree = sender as TreeView; var tree = sender as TreeView;
if (tree.SelectedItems.Count == 0)
{
e.Handled = true;
return;
}
if (tree.SelectedItems.Count == 1) if (tree.SelectedItems.Count == 1)
{ {