refactor: build tags view data in viewmodels instead of views

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2025-05-16 12:22:37 +08:00
parent f46bbd01cd
commit fd935259aa
No known key found for this signature in database
7 changed files with 151 additions and 172 deletions

View file

@ -178,9 +178,9 @@ namespace SourceGit.ViewModels
public bool ShowTagsAsTree public bool ShowTagsAsTree
{ {
get => _showTagsAsTree; get;
set => SetProperty(ref _showTagsAsTree, value); set;
} } = false;
public bool ShowTagsInGraph public bool ShowTagsInGraph
{ {
@ -677,7 +677,6 @@ namespace SourceGit.ViewModels
private double _lastCheckUpdateTime = 0; private double _lastCheckUpdateTime = 0;
private string _ignoreUpdateTag = string.Empty; private string _ignoreUpdateTag = string.Empty;
private bool _showTagsAsTree = false;
private bool _showTagsInGraph = true; private bool _showTagsInGraph = true;
private bool _useTwoColumnsLayoutInHistories = false; private bool _useTwoColumnsLayoutInHistories = false;
private bool _displayTimeAsPeriodInHistories = false; private bool _displayTimeAsPeriodInHistories = false;

View file

@ -198,7 +198,21 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _tags, value); private set => SetProperty(ref _tags, value);
} }
public List<Models.Tag> VisibleTags public bool ShowTagsAsTree
{
get => Preferences.Instance.ShowTagsAsTree;
set
{
if (value != Preferences.Instance.ShowTagsAsTree)
{
Preferences.Instance.ShowTagsAsTree = value;
VisibleTags = BuildVisibleTags();
OnPropertyChanged();
}
}
}
public object VisibleTags
{ {
get => _visibleTags; get => _visibleTags;
private set => SetProperty(ref _visibleTags, value); private set => SetProperty(ref _visibleTags, value);
@ -548,7 +562,7 @@ namespace SourceGit.ViewModels
_localBranchTrees.Clear(); _localBranchTrees.Clear();
_remoteBranchTrees.Clear(); _remoteBranchTrees.Clear();
_tags.Clear(); _tags.Clear();
_visibleTags.Clear(); _visibleTags = null;
_submodules.Clear(); _submodules.Clear();
_visibleSubmodules = null; _visibleSubmodules = null;
_searchedCommits.Clear(); _searchedCommits.Clear();
@ -2492,7 +2506,7 @@ namespace SourceGit.ViewModels
return builder; return builder;
} }
private List<Models.Tag> BuildVisibleTags() private object BuildVisibleTags()
{ {
switch (_settings.TagSortMode) switch (_settings.TagSortMode)
{ {
@ -2523,7 +2537,11 @@ namespace SourceGit.ViewModels
var historiesFilters = _settings.CollectHistoriesFilters(); var historiesFilters = _settings.CollectHistoriesFilters();
UpdateTagFilterMode(historiesFilters); UpdateTagFilterMode(historiesFilters);
return visible;
if (Preferences.Instance.ShowTagsAsTree)
return TagCollectionAsTree.Build(visible, _visibleTags as TagCollectionAsTree);
else
return new TagCollectionAsList() { Tags = visible };
} }
private object BuildVisibleSubmodules() private object BuildVisibleSubmodules()
@ -2775,7 +2793,7 @@ namespace SourceGit.ViewModels
private List<BranchTreeNode> _remoteBranchTrees = new List<BranchTreeNode>(); private List<BranchTreeNode> _remoteBranchTrees = new List<BranchTreeNode>();
private List<Models.Worktree> _worktrees = new List<Models.Worktree>(); private List<Models.Worktree> _worktrees = new List<Models.Worktree>();
private List<Models.Tag> _tags = new List<Models.Tag>(); private List<Models.Tag> _tags = new List<Models.Tag>();
private List<Models.Tag> _visibleTags = new List<Models.Tag>(); private object _visibleTags = null;
private List<Models.Submodule> _submodules = new List<Models.Submodule>(); private List<Models.Submodule> _submodules = new List<Models.Submodule>();
private object _visibleSubmodules = null; private object _visibleSubmodules = null;

View file

@ -150,18 +150,12 @@ namespace SourceGit.ViewModels
collection.Tree = SubmoduleTreeNode.Build(submodules, oldExpanded); collection.Tree = SubmoduleTreeNode.Build(submodules, oldExpanded);
var rows = new List<SubmoduleTreeNode>(); var rows = new List<SubmoduleTreeNode>();
collection.MakeTreeRows(rows, collection.Tree); MakeTreeRows(rows, collection.Tree);
collection.Rows.AddRange(rows); collection.Rows.AddRange(rows);
return collection; return collection;
} }
public void Clear()
{
Tree.Clear();
Rows.Clear();
}
public void ToggleExpand(SubmoduleTreeNode node) public void ToggleExpand(SubmoduleTreeNode node)
{ {
node.IsExpanded = !node.IsExpanded; node.IsExpanded = !node.IsExpanded;
@ -193,7 +187,7 @@ namespace SourceGit.ViewModels
} }
} }
private void MakeTreeRows(List<SubmoduleTreeNode> rows, List<SubmoduleTreeNode> nodes) private static void MakeTreeRows(List<SubmoduleTreeNode> rows, List<SubmoduleTreeNode> nodes)
{ {
foreach (var node in nodes) foreach (var node in nodes)
{ {

View file

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Collections; using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels namespace SourceGit.ViewModels
@ -61,7 +63,7 @@ namespace SourceGit.ViewModels
Counter = 1; Counter = 1;
} }
public static List<TagTreeNode> Build(IList<Models.Tag> tags, HashSet<string> expaneded) public static List<TagTreeNode> Build(List<Models.Tag> tags, HashSet<string> expaneded)
{ {
var nodes = new List<TagTreeNode>(); var nodes = new List<TagTreeNode>();
var folders = new Dictionary<string, TagTreeNode>(); var folders = new Dictionary<string, TagTreeNode>();
@ -131,7 +133,7 @@ namespace SourceGit.ViewModels
public class TagCollectionAsList public class TagCollectionAsList
{ {
public AvaloniaList<Models.Tag> Tags public List<Models.Tag> Tags
{ {
get; get;
set; set;
@ -151,5 +153,71 @@ namespace SourceGit.ViewModels
get; get;
set; set;
} = []; } = [];
public static TagCollectionAsTree Build(List<Models.Tag> tags, TagCollectionAsTree old)
{
var oldExpanded = new HashSet<string>();
if (old != null)
{
foreach (var row in old.Rows)
{
if (row.IsFolder && row.IsExpanded)
oldExpanded.Add(row.FullPath);
}
}
var collection = new TagCollectionAsTree();
collection.Tree = TagTreeNode.Build(tags, oldExpanded);
var rows = new List<TagTreeNode>();
MakeTreeRows(rows, collection.Tree);
collection.Rows.AddRange(rows);
return collection;
}
public void ToggleExpand(TagTreeNode node)
{
node.IsExpanded = !node.IsExpanded;
var rows = Rows;
var depth = node.Depth;
var idx = rows.IndexOf(node);
if (idx == -1)
return;
if (node.IsExpanded)
{
var subrows = new List<TagTreeNode>();
MakeTreeRows(subrows, node.Children);
rows.InsertRange(idx + 1, subrows);
}
else
{
var removeCount = 0;
for (int i = idx + 1; i < rows.Count; i++)
{
var row = rows[i];
if (row.Depth <= depth)
break;
removeCount++;
}
rows.RemoveRange(idx + 1, removeCount);
}
}
private static void MakeTreeRows(List<TagTreeNode> rows, List<TagTreeNode> nodes)
{
foreach (var node in nodes)
{
rows.Add(node);
if (!node.IsExpanded || !node.IsFolder)
continue;
MakeTreeRows(rows, node.Children);
}
}
} }
} }

View file

@ -271,7 +271,7 @@
<ToggleButton Grid.Column="2" <ToggleButton Grid.Column="2"
Classes="show_as_tree" Classes="show_as_tree"
Width="14" Width="14"
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowTagsAsTree, Mode=TwoWay}" IsChecked="{Binding ShowTagsAsTree, Mode=TwoWay}"
ToolTip.Tip="{DynamicResource Text.Repository.ShowTagsAsTree}"/> ToolTip.Tip="{DynamicResource Text.Repository.ShowTagsAsTree}"/>
<Button Grid.Column="3" <Button Grid.Column="3"
Classes="icon_button" Classes="icon_button"
@ -296,8 +296,7 @@
Height="0" Height="0"
Margin="8,0,4,0" Margin="8,0,4,0"
Background="Transparent" Background="Transparent"
ShowTagsAsTree="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowTagsAsTree, Mode=OneWay}" Content="{Binding VisibleTags}"
Tags="{Binding VisibleTags}"
Focusable="False" Focusable="False"
IsVisible="{Binding IsTagGroupExpanded, Mode=OneWay}" IsVisible="{Binding IsTagGroupExpanded, Mode=OneWay}"
SelectionChanged="OnTagsSelectionChanged" SelectionChanged="OnTagsSelectionChanged"

View file

@ -23,32 +23,33 @@
<ListBox Classes="repo_left_content_list" <ListBox Classes="repo_left_content_list"
ItemsSource="{Binding Rows}" ItemsSource="{Binding Rows}"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="OnRowSelectionChanged"> SelectionChanged="OnSelectionChanged">
<ListBox.DataTemplates>
<DataTemplate DataType="vm:TagTreeNodeToolTip">
<StackPanel Orientation="Vertical" Spacing="6">
<StackPanel Orientation="Horizontal">
<Path Width="10" Height="10" Data="{StaticResource Icons.Tag}"/>
<TextBlock FontWeight="Bold" Margin="4,0,0,0" Text="{Binding Name}"/>
<Border Background="Green" Margin="4,0,0,0" CornerRadius="4" IsVisible="{Binding IsAnnotated}">
<TextBlock Text="{DynamicResource Text.CreateTag.Type.Annotated}" Classes="primary" Margin="4,0" Foreground="White"/>
</Border>
</StackPanel>
<TextBlock Text="{Binding Message}" IsVisible="{Binding Message, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
</DataTemplate>
</ListBox.DataTemplates>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate DataType="vm:TagTreeNode"> <DataTemplate DataType="vm:TagTreeNode">
<Border Height="24" <Border Height="24"
Background="Transparent" Background="Transparent"
PointerPressed="OnRowPointerPressed" PointerPressed="OnItemPointerPressed"
DoubleTapped="OnDoubleTappedNode" DoubleTapped="OnItemDoubleTapped"
ContextRequested="OnRowContextRequested" ContextRequested="OnItemContextRequested"
ToolTip.Tip="{Binding ToolTip}" ToolTip.Tip="{Binding ToolTip}"
ToolTip.Placement="Right"> ToolTip.Placement="Right">
<Border.DataTemplates>
<DataTemplate DataType="vm:TagTreeNodeToolTip">
<StackPanel Orientation="Vertical" Spacing="6">
<StackPanel Orientation="Horizontal">
<Path Width="10" Height="10" Data="{StaticResource Icons.Tag}"/>
<TextBlock FontWeight="Bold" Margin="4,0,0,0" Text="{Binding Name}"/>
<Border Background="Green" Margin="4,0,0,0" CornerRadius="4" IsVisible="{Binding IsAnnotated}">
<TextBlock Text="{DynamicResource Text.CreateTag.Type.Annotated}" Classes="primary" Margin="4,0" Foreground="White"/>
</Border>
</StackPanel>
<TextBlock Text="{Binding Message}" IsVisible="{Binding Message, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
</DataTemplate>
</Border.DataTemplates>
<Grid ColumnDefinitions="16,Auto,*,Auto" <Grid ColumnDefinitions="16,Auto,*,Auto"
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}" Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
VerticalAlignment="Center"> VerticalAlignment="Center">
@ -59,9 +60,7 @@
IsChecked="{Binding IsExpanded, Mode=OneWay}" IsChecked="{Binding IsExpanded, Mode=OneWay}"
IsVisible="{Binding IsFolder}"/> IsVisible="{Binding IsFolder}"/>
<v:TagTreeNodeIcon Grid.Column="1" <v:TagTreeNodeIcon Grid.Column="1" IsExpanded="{Binding IsExpanded, Mode=OneWay}"/>
Node="{Binding .}"
IsExpanded="{Binding IsExpanded, Mode=OneWay}"/>
<TextBlock Grid.Column="2" <TextBlock Grid.Column="2"
Classes="primary" Classes="primary"
@ -89,13 +88,13 @@
Margin="8,0,0,0" Margin="8,0,0,0"
ItemsSource="{Binding Tags}" ItemsSource="{Binding Tags}"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="OnRowSelectionChanged"> SelectionChanged="OnSelectionChanged">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate DataType="m:Tag"> <DataTemplate DataType="m:Tag">
<Border Height="24" <Border Height="24"
Background="Transparent" Background="Transparent"
PointerPressed="OnRowPointerPressed" PointerPressed="OnItemPointerPressed"
ContextRequested="OnRowContextRequested" ContextRequested="OnItemContextRequested"
ToolTip.Placement="Right"> ToolTip.Placement="Right">
<ToolTip.Tip> <ToolTip.Tip>
<StackPanel Orientation="Vertical" Spacing="6"> <StackPanel Orientation="Vertical" Spacing="6">

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
@ -31,15 +30,6 @@ namespace SourceGit.Views
public class TagTreeNodeIcon : UserControl public class TagTreeNodeIcon : UserControl
{ {
public static readonly StyledProperty<ViewModels.TagTreeNode> NodeProperty =
AvaloniaProperty.Register<TagTreeNodeIcon, ViewModels.TagTreeNode>(nameof(Node));
public ViewModels.TagTreeNode Node
{
get => GetValue(NodeProperty);
set => SetValue(NodeProperty, value);
}
public static readonly StyledProperty<bool> IsExpandedProperty = public static readonly StyledProperty<bool> IsExpandedProperty =
AvaloniaProperty.Register<TagTreeNodeIcon, bool>(nameof(IsExpanded)); AvaloniaProperty.Register<TagTreeNodeIcon, bool>(nameof(IsExpanded));
@ -49,16 +39,23 @@ namespace SourceGit.Views
set => SetValue(IsExpandedProperty, value); set => SetValue(IsExpandedProperty, value);
} }
static TagTreeNodeIcon() protected override void OnDataContextChanged(EventArgs e)
{ {
NodeProperty.Changed.AddClassHandler<TagTreeNodeIcon>((icon, _) => icon.UpdateContent()); base.OnDataContextChanged(e);
IsExpandedProperty.Changed.AddClassHandler<TagTreeNodeIcon>((icon, _) => icon.UpdateContent()); UpdateContent();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsExpandedProperty)
UpdateContent();
} }
private void UpdateContent() private void UpdateContent()
{ {
var node = Node; if (DataContext is not ViewModels.TagTreeNode node)
if (node == null)
{ {
Content = null; Content = null;
return; return;
@ -92,24 +89,6 @@ namespace SourceGit.Views
public partial class TagsView : UserControl public partial class TagsView : UserControl
{ {
public static readonly StyledProperty<bool> ShowTagsAsTreeProperty =
AvaloniaProperty.Register<TagsView, bool>(nameof(ShowTagsAsTree));
public bool ShowTagsAsTree
{
get => GetValue(ShowTagsAsTreeProperty);
set => SetValue(ShowTagsAsTreeProperty, value);
}
public static readonly StyledProperty<List<Models.Tag>> TagsProperty =
AvaloniaProperty.Register<TagsView, List<Models.Tag>>(nameof(Tags));
public List<Models.Tag> Tags
{
get => GetValue(TagsProperty);
set => SetValue(TagsProperty, value);
}
public static readonly RoutedEvent<RoutedEventArgs> SelectionChangedEvent = public static readonly RoutedEvent<RoutedEventArgs> SelectionChangedEvent =
RoutedEvent.Register<TagsView, RoutedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutedEvent.Register<TagsView, RoutedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
@ -150,33 +129,7 @@ namespace SourceGit.Views
{ {
if (Content is ViewModels.TagCollectionAsTree tree) if (Content is ViewModels.TagCollectionAsTree tree)
{ {
node.IsExpanded = !node.IsExpanded; tree.ToggleExpand(node);
var depth = node.Depth;
var idx = tree.Rows.IndexOf(node);
if (idx == -1)
return;
if (node.IsExpanded)
{
var subrows = new List<ViewModels.TagTreeNode>();
MakeTreeRows(subrows, node.Children);
tree.Rows.InsertRange(idx + 1, subrows);
}
else
{
var removeCount = 0;
for (int i = idx + 1; i < tree.Rows.Count; i++)
{
var row = tree.Rows[i];
if (row.Depth <= depth)
break;
removeCount++;
}
tree.Rows.RemoveRange(idx + 1, removeCount);
}
Rows = tree.Rows.Count; Rows = tree.Rows.Count;
RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
} }
@ -186,9 +139,15 @@ namespace SourceGit.Views
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
if (change.Property == ShowTagsAsTreeProperty || change.Property == TagsProperty) if (change.Property == ContentProperty)
{ {
UpdateDataSource(); if (Content is ViewModels.TagCollectionAsTree tree)
Rows = tree.Rows.Count;
else if (Content is ViewModels.TagCollectionAsList list)
Rows = list.Tags.Count;
else
Rows = 0;
RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
} }
else if (change.Property == IsVisibleProperty) else if (change.Property == IsVisibleProperty)
@ -197,7 +156,7 @@ namespace SourceGit.Views
} }
} }
private void OnDoubleTappedNode(object sender, TappedEventArgs e) private void OnItemDoubleTapped(object sender, TappedEventArgs e)
{ {
if (sender is Control { DataContext: ViewModels.TagTreeNode { IsFolder: true } node }) if (sender is Control { DataContext: ViewModels.TagTreeNode { IsFolder: true } node })
ToggleNodeIsExpanded(node); ToggleNodeIsExpanded(node);
@ -205,7 +164,7 @@ namespace SourceGit.Views
e.Handled = true; e.Handled = true;
} }
private void OnRowPointerPressed(object sender, PointerPressedEventArgs e) private void OnItemPointerPressed(object sender, PointerPressedEventArgs e)
{ {
var p = e.GetCurrentPoint(this); var p = e.GetCurrentPoint(this);
if (!p.Properties.IsLeftButtonPressed) if (!p.Properties.IsLeftButtonPressed)
@ -220,7 +179,7 @@ namespace SourceGit.Views
repo.NavigateToCommit(nodeTag.SHA); repo.NavigateToCommit(nodeTag.SHA);
} }
private void OnRowContextRequested(object sender, ContextRequestedEventArgs e) private void OnItemContextRequested(object sender, ContextRequestedEventArgs e)
{ {
var control = sender as Control; var control = sender as Control;
if (control == null) if (control == null)
@ -243,7 +202,7 @@ namespace SourceGit.Views
e.Handled = true; e.Handled = true;
} }
private void OnRowSelectionChanged(object sender, SelectionChangedEventArgs _) private void OnSelectionChanged(object sender, SelectionChangedEventArgs _)
{ {
var selected = (sender as ListBox)?.SelectedItem; var selected = (sender as ListBox)?.SelectedItem;
var selectedTag = null as Models.Tag; var selectedTag = null as Models.Tag;
@ -255,63 +214,6 @@ namespace SourceGit.Views
if (selectedTag != null) if (selectedTag != null)
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent)); RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
} }
private void MakeTreeRows(List<ViewModels.TagTreeNode> rows, List<ViewModels.TagTreeNode> nodes)
{
foreach (var node in nodes)
{
rows.Add(node);
if (!node.IsExpanded || !node.IsFolder)
continue;
MakeTreeRows(rows, node.Children);
}
}
private void UpdateDataSource()
{
var tags = Tags;
if (tags == null || tags.Count == 0)
{
Rows = 0;
Content = null;
return;
}
if (ShowTagsAsTree)
{
var oldExpanded = new HashSet<string>();
if (Content is ViewModels.TagCollectionAsTree oldTree)
{
foreach (var row in oldTree.Rows)
{
if (row.IsFolder && row.IsExpanded)
oldExpanded.Add(row.FullPath);
}
}
var tree = new ViewModels.TagCollectionAsTree();
tree.Tree = ViewModels.TagTreeNode.Build(tags, oldExpanded);
var rows = new List<ViewModels.TagTreeNode>();
MakeTreeRows(rows, tree.Tree);
tree.Rows.AddRange(rows);
Content = tree;
Rows = rows.Count;
}
else
{
var list = new ViewModels.TagCollectionAsList();
list.Tags.AddRange(tags);
Content = list;
Rows = tags.Count;
}
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
} }
} }