mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -08:00
refactor: use custom BranchTree instead of TreeView to improve performance
This commit is contained in:
parent
1c524cf310
commit
3197b4bfe8
8 changed files with 703 additions and 643 deletions
|
@ -1,4 +1,5 @@
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
namespace SourceGit.Converters
|
namespace SourceGit.Converters
|
||||||
{
|
{
|
||||||
|
@ -24,5 +25,8 @@ namespace SourceGit.Converters
|
||||||
|
|
||||||
public static readonly FuncValueConverter<int, bool> IsSubjectLengthGood =
|
public static readonly FuncValueConverter<int, bool> IsSubjectLengthGood =
|
||||||
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preference.Instance.SubjectGuideLength);
|
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preference.Instance.SubjectGuideLength);
|
||||||
|
|
||||||
|
public static readonly FuncValueConverter<int, Thickness> ToTreeMargin =
|
||||||
|
new FuncValueConverter<int, Thickness>(v => new Thickness(v * 16, 0, 0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,22 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
namespace SourceGit.ViewModels
|
namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
public enum BranchTreeNodeType
|
|
||||||
{
|
|
||||||
DetachedHead,
|
|
||||||
Remote,
|
|
||||||
Folder,
|
|
||||||
Branch,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BranchTreeNode : ObservableObject
|
public class BranchTreeNode : ObservableObject
|
||||||
{
|
{
|
||||||
public const double DEFAULT_CORNER = 4.0;
|
public string Name { get; private set; } = string.Empty;
|
||||||
|
public object Backend { get; private set; } = null;
|
||||||
public string Name { get; set; }
|
public int Depth { get; set; } = 0;
|
||||||
public BranchTreeNodeType Type { get; set; }
|
public bool IsFiltered { get; set; } = false;
|
||||||
public object Backend { get; set; }
|
public List<BranchTreeNode> Children { get; private set; } = new List<BranchTreeNode>();
|
||||||
public bool IsFiltered { get; set; }
|
|
||||||
public List<BranchTreeNode> Children { get; set; } = new List<BranchTreeNode>();
|
|
||||||
|
|
||||||
public bool IsUpstreamTrackStatusVisible
|
|
||||||
{
|
|
||||||
get => IsBranch && !string.IsNullOrEmpty((Backend as Models.Branch).UpstreamTrackStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string UpstreamTrackStatus
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.Branch ? (Backend as Models.Branch).UpstreamTrackStatus : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsRemote
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.Remote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsFolder
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.Folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsBranch
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.Branch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsDetachedHead
|
|
||||||
{
|
|
||||||
get => Type == BranchTreeNodeType.DetachedHead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCurrent
|
|
||||||
{
|
|
||||||
get => IsBranch && (Backend as Models.Branch).IsCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSelected
|
|
||||||
{
|
|
||||||
get => _isSelected;
|
|
||||||
set => SetProperty(ref _isSelected, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsExpanded
|
public bool IsExpanded
|
||||||
{
|
{
|
||||||
|
@ -74,49 +24,39 @@ namespace SourceGit.ViewModels
|
||||||
set => SetProperty(ref _isExpanded, value);
|
set => SetProperty(ref _isExpanded, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Tooltip
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Backend is Models.Branch b)
|
|
||||||
return b.FriendlyName;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CornerRadius CornerRadius
|
public CornerRadius CornerRadius
|
||||||
{
|
{
|
||||||
get => _cornerRadius;
|
get => _cornerRadius;
|
||||||
set => SetProperty(ref _cornerRadius, value);
|
set => SetProperty(ref _cornerRadius, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCornerRadius(ref BranchTreeNode prev)
|
public bool IsBranch
|
||||||
{
|
{
|
||||||
if (_isSelected && prev != null && prev.IsSelected)
|
get => Backend is Models.Branch;
|
||||||
{
|
|
||||||
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;
|
public bool IsUpstreamTrackStatusVisible
|
||||||
|
|
||||||
if (!IsBranch && IsExpanded)
|
|
||||||
{
|
{
|
||||||
foreach (var child in Children)
|
get => Backend is Models.Branch { IsLocal: true } branch && !string.IsNullOrEmpty(branch.UpstreamTrackStatus);
|
||||||
child.UpdateCornerRadius(ref prev);
|
}
|
||||||
}
|
|
||||||
|
public string UpstreamTrackStatus
|
||||||
|
{
|
||||||
|
get => Backend is Models.Branch branch ? branch.UpstreamTrackStatus : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontWeight NameFontWeight
|
||||||
|
{
|
||||||
|
get => Backend is Models.Branch { IsCurrent: true } ? FontWeight.Bold : FontWeight.Regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Tooltip
|
||||||
|
{
|
||||||
|
get => Backend is Models.Branch b ? b.FriendlyName : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isSelected = false;
|
|
||||||
private bool _isExpanded = false;
|
private bool _isExpanded = false;
|
||||||
private CornerRadius _cornerRadius = new CornerRadius(DEFAULT_CORNER);
|
private CornerRadius _cornerRadius = new CornerRadius(4);
|
||||||
|
|
||||||
public class Builder
|
public class Builder
|
||||||
{
|
{
|
||||||
|
@ -133,7 +73,6 @@ namespace SourceGit.ViewModels
|
||||||
var node = new BranchTreeNode()
|
var node = new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = remote.Name,
|
Name = remote.Name,
|
||||||
Type = BranchTreeNodeType.Remote,
|
|
||||||
Backend = remote,
|
Backend = remote,
|
||||||
IsExpanded = bForceExpanded || _expanded.Contains(path),
|
IsExpanded = bForceExpanded || _expanded.Contains(path),
|
||||||
};
|
};
|
||||||
|
@ -176,9 +115,13 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
|
if (node.Backend is Models.Branch)
|
||||||
|
continue;
|
||||||
|
|
||||||
var path = prefix + "/" + node.Name;
|
var path = prefix + "/" + node.Name;
|
||||||
if (node.Type != BranchTreeNodeType.Branch && node.IsExpanded)
|
if (node.IsExpanded)
|
||||||
_expanded.Add(path);
|
_expanded.Add(path);
|
||||||
|
|
||||||
CollectExpandedNodes(node.Children, path);
|
CollectExpandedNodes(node.Children, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +134,6 @@ namespace SourceGit.ViewModels
|
||||||
roots.Add(new BranchTreeNode()
|
roots.Add(new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = branch.Name,
|
Name = branch.Name,
|
||||||
Type = BranchTreeNodeType.Branch,
|
|
||||||
Backend = branch,
|
Backend = branch,
|
||||||
IsExpanded = false,
|
IsExpanded = false,
|
||||||
IsFiltered = isFiltered,
|
IsFiltered = isFiltered,
|
||||||
|
@ -215,7 +157,6 @@ namespace SourceGit.ViewModels
|
||||||
lastFolder = new BranchTreeNode()
|
lastFolder = new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = BranchTreeNodeType.Folder,
|
|
||||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
||||||
};
|
};
|
||||||
roots.Add(lastFolder);
|
roots.Add(lastFolder);
|
||||||
|
@ -226,7 +167,6 @@ namespace SourceGit.ViewModels
|
||||||
var cur = new BranchTreeNode()
|
var cur = new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = BranchTreeNodeType.Folder,
|
|
||||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
||||||
};
|
};
|
||||||
lastFolder.Children.Add(cur);
|
lastFolder.Children.Add(cur);
|
||||||
|
@ -238,10 +178,9 @@ namespace SourceGit.ViewModels
|
||||||
sepIdx = branch.Name.IndexOf('/', start);
|
sepIdx = branch.Name.IndexOf('/', start);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFolder.Children.Add(new BranchTreeNode()
|
lastFolder?.Children.Add(new BranchTreeNode()
|
||||||
{
|
{
|
||||||
Name = Path.GetFileName(branch.Name),
|
Name = Path.GetFileName(branch.Name),
|
||||||
Type = branch.IsHead ? BranchTreeNodeType.DetachedHead : BranchTreeNodeType.Branch,
|
|
||||||
Backend = branch,
|
Backend = branch,
|
||||||
IsExpanded = false,
|
IsExpanded = false,
|
||||||
IsFiltered = isFiltered,
|
IsFiltered = isFiltered,
|
||||||
|
@ -252,16 +191,13 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
nodes.Sort((l, r) =>
|
nodes.Sort((l, r) =>
|
||||||
{
|
{
|
||||||
if (l.Type == BranchTreeNodeType.DetachedHead)
|
if (l.Backend is Models.Branch { IsHead: true })
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
if (l.Type == r.Type)
|
|
||||||
{
|
|
||||||
return l.Name.CompareTo(r.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)l.Type - (int)r.Type;
|
if (l.Backend is Models.Branch)
|
||||||
|
return r.Backend is Models.Branch ? string.Compare(l.Name, r.Name, StringComparison.Ordinal) : 1;
|
||||||
|
|
||||||
|
return r.Backend is Models.Branch ? -1 : string.Compare(l.Name, r.Name, StringComparison.Ordinal);
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
|
|
|
@ -1905,7 +1905,7 @@ namespace SourceGit.ViewModels
|
||||||
visibles.Add(b);
|
visibles.Add(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Run(visibles, remotes, visibles.Count <= 20);
|
builder.Run(visibles, remotes, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|
110
src/Views/BranchTree.axaml
Normal file
110
src/Views/BranchTree.axaml
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
<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:c="using:SourceGit.Converters"
|
||||||
|
xmlns:v="using:SourceGit.Views"
|
||||||
|
xmlns:vm="using:SourceGit.ViewModels"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="SourceGit.Views.BranchTree"
|
||||||
|
x:Name="ThisControl">
|
||||||
|
<DataGrid x:Name="BranchesPresenter"
|
||||||
|
ItemsSource="{Binding #ThisControl.Rows}"
|
||||||
|
Height="{Binding #ThisControl.Height}"
|
||||||
|
Background="Transparent"
|
||||||
|
RowHeight="24"
|
||||||
|
CanUserReorderColumns="False"
|
||||||
|
CanUserResizeColumns="False"
|
||||||
|
CanUserSortColumns="False"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
HeadersVisibility="None"
|
||||||
|
SelectionChanged="OnNodesSelectionChanged"
|
||||||
|
ContextRequested="OnTreeContextRequested">
|
||||||
|
<DataGrid.Styles>
|
||||||
|
<Style Selector="DataGridRow" x:DataType="vm:BranchTreeNode">
|
||||||
|
<Setter Property="CornerRadius" Value="{Binding CornerRadius}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||||
|
<Setter Property="ClipToBounds" Value="True" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Grid.repository_leftpanel DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||||
|
<Setter Property="Opacity" Value=".5"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid.repository_leftpanel DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||||
|
<Setter Property="Opacity" Value="1"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||||
|
<Setter Property="Opacity" Value=".65"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||||
|
<Setter Property="Opacity" Value=".8"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Styles>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Width="*">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:BranchTreeNode">
|
||||||
|
<Grid Height="24"
|
||||||
|
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||||
|
ColumnDefinitions="16,20,*,Auto,Auto"
|
||||||
|
Background="Transparent"
|
||||||
|
DoubleTapped="OnDoubleTappedBranchNode"
|
||||||
|
ToolTip.Tip="{Binding Tooltip}">
|
||||||
|
|
||||||
|
<!-- Tree Expander -->
|
||||||
|
<ToggleButton Grid.Column="0"
|
||||||
|
Classes="tree_expander"
|
||||||
|
Focusable="False"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
IsChecked="{Binding IsExpanded}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
IsVisible="{Binding !IsBranch}"/>
|
||||||
|
|
||||||
|
<!-- Icon -->
|
||||||
|
<v:BranchTreeNodeIcon Grid.Column="1"
|
||||||
|
Node="{Binding}"
|
||||||
|
IsExpanded="{Binding IsExpanded}"/>
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
Text="{Binding Name}"
|
||||||
|
Classes="monospace"
|
||||||
|
FontWeight="{Binding NameFontWeight}"/>
|
||||||
|
|
||||||
|
<!-- Tracking status -->
|
||||||
|
<Border Grid.Column="3"
|
||||||
|
Margin="8,0"
|
||||||
|
Height="18"
|
||||||
|
CornerRadius="9"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{DynamicResource Brush.Badge}"
|
||||||
|
IsVisible="{Binding IsUpstreamTrackStatusVisible}">
|
||||||
|
<TextBlock Classes="monospace" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding UpstreamTrackStatus}" Foreground="{DynamicResource Brush.BadgeFG}"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Filter Toggle Button -->
|
||||||
|
<ToggleButton Grid.Column="4"
|
||||||
|
Classes="filter"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
Background="Transparent"
|
||||||
|
IsCheckedChanged="OnToggleFilter"
|
||||||
|
IsVisible="{Binding IsBranch}"
|
||||||
|
IsChecked="{Binding IsFiltered}"
|
||||||
|
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</UserControl>
|
||||||
|
|
324
src/Views/BranchTree.axaml.cs
Normal file
324
src/Views/BranchTree.axaml.cs
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Controls.Shapes;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
|
namespace SourceGit.Views
|
||||||
|
{
|
||||||
|
public class BranchTreeNodeIcon : UserControl
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<ViewModels.BranchTreeNode> NodeProperty =
|
||||||
|
AvaloniaProperty.Register<BranchTreeNodeIcon, ViewModels.BranchTreeNode>(nameof(Node), null);
|
||||||
|
|
||||||
|
public ViewModels.BranchTreeNode Node
|
||||||
|
{
|
||||||
|
get => GetValue(NodeProperty);
|
||||||
|
set => SetValue(NodeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsExpandedProperty =
|
||||||
|
AvaloniaProperty.Register<BranchTreeNodeIcon, bool>(nameof(IsExpanded), false);
|
||||||
|
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => GetValue(IsExpandedProperty);
|
||||||
|
set => SetValue(IsExpandedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BranchTreeNodeIcon()
|
||||||
|
{
|
||||||
|
NodeProperty.Changed.AddClassHandler<BranchTreeNodeIcon>((icon, e) => icon.UpdateContent());
|
||||||
|
IsExpandedProperty.Changed.AddClassHandler<BranchTreeNodeIcon>((icon, e) => icon.UpdateContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateContent()
|
||||||
|
{
|
||||||
|
var node = Node;
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
Content = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Backend is Models.Remote)
|
||||||
|
{
|
||||||
|
CreateContent(12, new Thickness(0,2,0,0), "Icons.Remote");
|
||||||
|
}
|
||||||
|
else if (node.Backend is Models.Branch branch)
|
||||||
|
{
|
||||||
|
if (branch.IsCurrent)
|
||||||
|
CreateContent(12, new Thickness(0,2,0,0), "Icons.Check");
|
||||||
|
else
|
||||||
|
CreateContent(12, new Thickness(2,0,0,0), "Icons.Branch");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (node.IsExpanded)
|
||||||
|
CreateContent(10, new Thickness(0,2,0,0), "Icons.Folder.Open");
|
||||||
|
else
|
||||||
|
CreateContent(10, new Thickness(0,2,0,0), "Icons.Folder.Fill");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateContent(double size, Thickness margin, string iconKey)
|
||||||
|
{
|
||||||
|
var geo = this.FindResource(iconKey) as StreamGeometry;
|
||||||
|
if (geo == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Content = new Path()
|
||||||
|
{
|
||||||
|
Width = size,
|
||||||
|
Height = size,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Margin = margin,
|
||||||
|
Data = geo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class BranchTree : UserControl
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<List<ViewModels.BranchTreeNode>> NodesProperty =
|
||||||
|
AvaloniaProperty.Register<BranchTree, List<ViewModels.BranchTreeNode>>(nameof(Nodes), null);
|
||||||
|
|
||||||
|
public List<ViewModels.BranchTreeNode> Nodes
|
||||||
|
{
|
||||||
|
get => GetValue(NodesProperty);
|
||||||
|
set => SetValue(NodesProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<ViewModels.BranchTreeNode> Rows
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
} = new AvaloniaList<ViewModels.BranchTreeNode>();
|
||||||
|
|
||||||
|
public static readonly RoutedEvent<RoutedEventArgs> SelectionChangedEvent =
|
||||||
|
RoutedEvent.Register<BranchTree, RoutedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||||
|
|
||||||
|
public event EventHandler<RoutedEventArgs> SelectionChanged
|
||||||
|
{
|
||||||
|
add { AddHandler(SelectionChangedEvent, value); }
|
||||||
|
remove { RemoveHandler(SelectionChangedEvent, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public BranchTree()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnselectAll()
|
||||||
|
{
|
||||||
|
BranchesPresenter.SelectedItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
|
||||||
|
if (change.Property == NodesProperty)
|
||||||
|
{
|
||||||
|
Rows.Clear();
|
||||||
|
|
||||||
|
if (Nodes is { Count: > 0 })
|
||||||
|
{
|
||||||
|
var rows = new List<ViewModels.BranchTreeNode>();
|
||||||
|
MakeRows(rows, Nodes, 0);
|
||||||
|
Rows.AddRange(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
var repo = this.FindAncestorOfType<Repository>();
|
||||||
|
repo?.UpdateLeftSidebarLayout();
|
||||||
|
}
|
||||||
|
else if (change.Property == IsVisibleProperty)
|
||||||
|
{
|
||||||
|
var repo = this.FindAncestorOfType<Repository>();
|
||||||
|
repo?.UpdateLeftSidebarLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNodesSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var selected = BranchesPresenter.SelectedItems;
|
||||||
|
if (selected == null || selected.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var set = new HashSet<ViewModels.BranchTreeNode>();
|
||||||
|
foreach (var item in selected)
|
||||||
|
{
|
||||||
|
if (item is ViewModels.BranchTreeNode node)
|
||||||
|
set.Add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
var prev = null as ViewModels.BranchTreeNode;
|
||||||
|
var isPrevSelected = false;
|
||||||
|
foreach (var row in Rows)
|
||||||
|
{
|
||||||
|
var isSelected = set.Contains(row);
|
||||||
|
if (isSelected)
|
||||||
|
{
|
||||||
|
if (isPrevSelected)
|
||||||
|
{
|
||||||
|
var prevTop = prev.CornerRadius.TopLeft;
|
||||||
|
prev.CornerRadius = new CornerRadius(prevTop, 0);
|
||||||
|
row.CornerRadius = new CornerRadius(0, 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row.CornerRadius = new CornerRadius(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isPrevSelected = isSelected;
|
||||||
|
prev = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTreeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
var repo = DataContext as ViewModels.Repository;
|
||||||
|
if (repo?.Settings == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var selected = BranchesPresenter.SelectedItems;
|
||||||
|
if (selected == null || selected.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Remote remote })
|
||||||
|
{
|
||||||
|
var menu = repo.CreateContextMenuForRemote(remote);
|
||||||
|
this.OpenContextMenu(menu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var branches = new List<Models.Branch>();
|
||||||
|
foreach (var item in selected)
|
||||||
|
{
|
||||||
|
if (item is ViewModels.BranchTreeNode node)
|
||||||
|
CollectBranchesInNode(branches, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branches.Count == 1)
|
||||||
|
{
|
||||||
|
var branch = branches[0];
|
||||||
|
var menu = branch.IsLocal ?
|
||||||
|
repo.CreateContextMenuForLocalBranch(branch) :
|
||||||
|
repo.CreateContextMenuForRemoteBranch(branch);
|
||||||
|
this.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
else if (branches.Find(x => x.IsCurrent) == null)
|
||||||
|
{
|
||||||
|
var menu = new ContextMenu();
|
||||||
|
var deleteMulti = new MenuItem();
|
||||||
|
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
|
||||||
|
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
|
||||||
|
deleteMulti.Click += (_, ev) =>
|
||||||
|
{
|
||||||
|
repo.DeleteMultipleBranches(branches, branches[0].IsLocal);
|
||||||
|
ev.Handled = true;
|
||||||
|
};
|
||||||
|
menu.Items.Add(deleteMulti);
|
||||||
|
this.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoubleTappedBranchNode(object sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Grid { DataContext: ViewModels.BranchTreeNode node })
|
||||||
|
{
|
||||||
|
if (node.Backend is Models.Branch branch)
|
||||||
|
{
|
||||||
|
if (branch.IsCurrent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (DataContext is ViewModels.Repository { Settings: not null } repo)
|
||||||
|
repo.CheckoutBranch(branch);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node.IsExpanded = !node.IsExpanded;
|
||||||
|
|
||||||
|
var rows = Rows;
|
||||||
|
var depth = node.Depth;
|
||||||
|
var idx = rows.IndexOf(node);
|
||||||
|
if (idx == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (node.IsExpanded)
|
||||||
|
{
|
||||||
|
var subtree = new List<ViewModels.BranchTreeNode>();
|
||||||
|
MakeRows(subtree, node.Children, depth + 1);
|
||||||
|
rows.InsertRange(idx + 1, subtree);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
var repo = this.FindAncestorOfType<Repository>();
|
||||||
|
repo?.UpdateLeftSidebarLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToggleFilter(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
if (toggle.DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
|
||||||
|
repo.UpdateFilter(branch.FullName, toggle.IsChecked == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MakeRows(List<ViewModels.BranchTreeNode> rows, List<ViewModels.BranchTreeNode> nodes, int depth)
|
||||||
|
{
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
node.Depth = depth;
|
||||||
|
rows.Add(node);
|
||||||
|
|
||||||
|
if (!node.IsExpanded || node.Backend is Models.Branch)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
MakeRows(rows, node.Children, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CollectBranchesInNode(List<Models.Branch> outs, ViewModels.BranchTreeNode node)
|
||||||
|
{
|
||||||
|
if (node.Backend is Models.Branch branch && !outs.Contains(branch))
|
||||||
|
{
|
||||||
|
outs.Add(branch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var sub in node.Children)
|
||||||
|
CollectBranchesInNode(outs, sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:m="using:SourceGit.Models"
|
|
||||||
xmlns:vm="using:SourceGit.ViewModels"
|
xmlns:vm="using:SourceGit.ViewModels"
|
||||||
xmlns:v="using:SourceGit.Views"
|
|
||||||
xmlns:c="using:SourceGit.Converters"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="SourceGit.Views.DeleteBranch"
|
x:Class="SourceGit.Views.DeleteBranch"
|
||||||
x:DataType="vm:DeleteBranch">
|
x:DataType="vm:DeleteBranch">
|
||||||
|
@ -18,7 +15,7 @@
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Text="{DynamicResource Text.DeleteBranch.Branch}"/>
|
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Text="{DynamicResource Text.DeleteBranch.Branch}"/>
|
||||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
|
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
|
||||||
<Path Width="14" Height="14" Margin="8,0" Data="{StaticResource Icons.Branch}"/>
|
<Path Width="14" Height="14" Margin="8,0" Data="{StaticResource Icons.Branch}"/>
|
||||||
<TextBlock Text="{Binding Target.FriendlyName}}"/>
|
<TextBlock Text="{Binding Target.FriendlyName}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Border Grid.Row="1" Grid.Column="1" Height="32" IsVisible="{Binding !Target.IsLocal}">
|
<Border Grid.Row="1" Grid.Column="1" Height="32" IsVisible="{Binding !Target.IsLocal}">
|
||||||
|
|
|
@ -236,77 +236,12 @@
|
||||||
<ToggleButton Grid.Row="0" Classes="group_expander" IsChecked="{Binding IsLocalBranchGroupExpanded, Mode=TwoWay}">
|
<ToggleButton Grid.Row="0" Classes="group_expander" IsChecked="{Binding IsLocalBranchGroupExpanded, Mode=TwoWay}">
|
||||||
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.LocalBranches}"/>
|
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.LocalBranches}"/>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<TreeView Grid.Row="1"
|
<v:BranchTree Grid.Row="1"
|
||||||
x:Name="localBranchTree"
|
x:Name="localBranchTree"
|
||||||
Margin="8,0,4,0"
|
Margin="8,0,4,0"
|
||||||
SelectionMode="Multiple"
|
Nodes="{Binding LocalBranchTrees}"
|
||||||
ItemsSource="{Binding LocalBranchTrees}"
|
|
||||||
IsVisible="{Binding IsLocalBranchGroupExpanded}"
|
IsVisible="{Binding IsLocalBranchGroupExpanded}"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
SelectionChanged="OnLocalBranchTreeSelectionChanged"/>
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
|
||||||
ContextRequested="OnLocalBranchContextMenuRequested"
|
|
||||||
SelectionChanged="OnLocalBranchTreeSelectionChanged"
|
|
||||||
PropertyChanged="OnLeftSidebarTreeViewPropertyChanged">
|
|
||||||
<TreeView.Styles>
|
|
||||||
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
|
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
|
||||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
|
|
||||||
<Setter Property="CornerRadius" Value="{Binding CornerRadius}"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Grid.repository_leftpanel TreeViewItem /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
<Setter Property="Opacity" Value="1"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".8"/>
|
|
||||||
</Style>
|
|
||||||
</TreeView.Styles>
|
|
||||||
<TreeView.ItemTemplate>
|
|
||||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
|
|
||||||
<Grid Height="24" ColumnDefinitions="20,*,Auto,Auto" Background="Transparent" DoubleTapped="OnDoubleTappedBranchNode" ToolTip.Tip="{Binding Tooltip}">
|
|
||||||
<Path Grid.Column="0" Classes="folder_icon" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,1,0,0" IsVisible="{Binding IsFolder}"/>
|
|
||||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Check}" IsVisible="{Binding IsCurrent}" VerticalAlignment="Center"/>
|
|
||||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="2,0,0,0" Data="{StaticResource Icons.Branch}" VerticalAlignment="Center">
|
|
||||||
<Path.IsVisible>
|
|
||||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
|
||||||
<Binding Path="!IsFolder"/>
|
|
||||||
<Binding Path="!IsCurrent"/>
|
|
||||||
</MultiBinding>
|
|
||||||
</Path.IsVisible>
|
|
||||||
</Path>
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="1"
|
|
||||||
Text="{Binding Name}"
|
|
||||||
Classes="monospace"
|
|
||||||
FontWeight="{Binding IsCurrent, Converter={x:Static c:BoolConverters.BoldIfTrue}}"/>
|
|
||||||
|
|
||||||
<Border Grid.Column="2" Margin="8,0" Height="18" CornerRadius="9" VerticalAlignment="Center" Background="{DynamicResource Brush.Badge}" IsVisible="{Binding IsUpstreamTrackStatusVisible}">
|
|
||||||
<TextBlock Classes="monospace" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding UpstreamTrackStatus}" Foreground="{DynamicResource Brush.BadgeFG}"/>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<ToggleButton Grid.Column="3"
|
|
||||||
Classes="filter"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Background="Transparent"
|
|
||||||
IsVisible="{Binding IsBranch}"
|
|
||||||
Checked="OnToggleFilter"
|
|
||||||
Unchecked="OnToggleFilter"
|
|
||||||
IsChecked="{Binding IsFiltered}"
|
|
||||||
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
|
||||||
</Grid>
|
|
||||||
</TreeDataTemplate>
|
|
||||||
</TreeView.ItemTemplate>
|
|
||||||
</TreeView>
|
|
||||||
|
|
||||||
<!-- Remotes -->
|
<!-- Remotes -->
|
||||||
<ToggleButton Grid.Row="2" Classes="group_expander" IsChecked="{Binding IsRemoteGroupExpanded, Mode=TwoWay}">
|
<ToggleButton Grid.Row="2" Classes="group_expander" IsChecked="{Binding IsRemoteGroupExpanded, Mode=TwoWay}">
|
||||||
|
@ -317,64 +252,12 @@
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<TreeView Grid.Row="3"
|
<v:BranchTree Grid.Row="3"
|
||||||
x:Name="remoteBranchTree"
|
x:Name="remoteBranchTree"
|
||||||
Margin="8,0,4,0"
|
Margin="8,0,4,0"
|
||||||
SelectionMode="Multiple"
|
Nodes="{Binding RemoteBranchTrees}"
|
||||||
ItemsSource="{Binding RemoteBranchTrees}"
|
|
||||||
IsVisible="{Binding IsRemoteGroupExpanded}"
|
IsVisible="{Binding IsRemoteGroupExpanded}"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
SelectionChanged="OnRemoteBranchTreeSelectionChanged"/>
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
|
||||||
ContextRequested="OnRemoteBranchContextMenuRequested"
|
|
||||||
SelectionChanged="OnRemoteBranchTreeSelectionChanged"
|
|
||||||
PropertyChanged="OnLeftSidebarTreeViewPropertyChanged">
|
|
||||||
<TreeView.Styles>
|
|
||||||
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
|
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
|
||||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
|
|
||||||
<Setter Property="CornerRadius" Value="{Binding CornerRadius}"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Grid.repository_leftpanel TreeViewItem /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
<Setter Property="Opacity" Value="1"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".8"/>
|
|
||||||
</Style>
|
|
||||||
</TreeView.Styles>
|
|
||||||
|
|
||||||
<TreeView.ItemTemplate>
|
|
||||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
|
|
||||||
<Grid Height="24" ColumnDefinitions="20,*,Auto" Background="Transparent" DoubleTapped="OnDoubleTappedBranchNode" ToolTip.Tip="{Binding Tooltip}">
|
|
||||||
<Path Grid.Column="0" Classes="folder_icon" Width="10" Height="10" HorizontalAlignment="Left" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" VerticalAlignment="Center"/>
|
|
||||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Remote}" IsVisible="{Binding IsRemote}" VerticalAlignment="Center"/>
|
|
||||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="2,0,0,0" Data="{StaticResource Icons.Branch}" IsVisible="{Binding IsBranch}" VerticalAlignment="Center"/>
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Name}" Classes="monospace"/>
|
|
||||||
|
|
||||||
<ToggleButton Grid.Column="2"
|
|
||||||
Classes="filter"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Background="Transparent"
|
|
||||||
Checked="OnToggleFilter"
|
|
||||||
Unchecked="OnToggleFilter"
|
|
||||||
IsVisible="{Binding IsBranch}"
|
|
||||||
IsChecked="{Binding IsFiltered}"
|
|
||||||
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
|
||||||
</Grid>
|
|
||||||
</TreeDataTemplate>
|
|
||||||
</TreeView.ItemTemplate>
|
|
||||||
</TreeView>
|
|
||||||
|
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
<ToggleButton Grid.Row="4" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
|
<ToggleButton Grid.Row="4" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
|
||||||
|
@ -455,8 +338,8 @@
|
||||||
<ToggleButton Classes="filter"
|
<ToggleButton Classes="filter"
|
||||||
Margin="0,0,8,0"
|
Margin="0,0,8,0"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Checked="OnToggleFilter"
|
Checked="OnToggleTagFilter"
|
||||||
Unchecked="OnToggleFilter"
|
Unchecked="OnToggleTagFilter"
|
||||||
IsChecked="{Binding IsFiltered}"
|
IsChecked="{Binding IsFiltered}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.VisualTree;
|
|
||||||
using AvaloniaEdit.Utils;
|
|
||||||
|
|
||||||
namespace SourceGit.Views
|
namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
|
@ -18,393 +15,7 @@ namespace SourceGit.Views
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnLoaded(RoutedEventArgs e)
|
public void UpdateLeftSidebarLayout()
|
||||||
{
|
|
||||||
base.OnLoaded(e);
|
|
||||||
|
|
||||||
if (DataContext is ViewModels.Repository repo && !repo.IsSearching)
|
|
||||||
{
|
|
||||||
UpdateLeftSidebarLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenWithExternalTools(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is Button button && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var menu = repo.CreateContextMenuForExternalTools();
|
|
||||||
button.OpenContextMenu(menu);
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenGitFlowMenu(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var menu = repo.CreateContextMenuForGitFlow();
|
|
||||||
(sender as Control)?.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenGitLFSMenu(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var menu = repo.CreateContextMenuForGitLFS();
|
|
||||||
(sender as Control)?.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OpenStatistics(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var dialog = new Statistics() { DataContext = new ViewModels.Statistics(repo.FullPath) };
|
|
||||||
await dialog.ShowDialog(TopLevel.GetTopLevel(this) as Window);
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
var grid = sender as Grid;
|
|
||||||
if (e.Property == IsVisibleProperty && grid.IsVisible)
|
|
||||||
txtSearchCommitsBox.Focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSearchKeyDown(object sender, KeyEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Key == Key.Enter)
|
|
||||||
{
|
|
||||||
if (DataContext is ViewModels.Repository repo)
|
|
||||||
repo.StartSearchCommits();
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSearchResultDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
|
|
||||||
{
|
|
||||||
if (DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var commit = datagrid.SelectedItem as Models.Commit;
|
|
||||||
repo.NavigateToCommit(commit.SHA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLocalBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
remoteBranchTree.UnselectAll();
|
|
||||||
tagsList.SelectedItem = null;
|
|
||||||
|
|
||||||
ViewModels.BranchTreeNode prev = null;
|
|
||||||
foreach (var node in repo.LocalBranchTrees)
|
|
||||||
node.UpdateCornerRadius(ref prev);
|
|
||||||
|
|
||||||
if (tree.SelectedItems.Count == 1)
|
|
||||||
{
|
|
||||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
|
||||||
if (node.IsBranch)
|
|
||||||
repo.NavigateToCommit((node.Backend as Models.Branch).Head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRemoteBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
localBranchTree.UnselectAll();
|
|
||||||
tagsList.SelectedItem = null;
|
|
||||||
|
|
||||||
ViewModels.BranchTreeNode prev = null;
|
|
||||||
foreach (var node in repo.RemoteBranchTrees)
|
|
||||||
node.UpdateCornerRadius(ref prev);
|
|
||||||
|
|
||||||
if (tree.SelectedItems.Count == 1)
|
|
||||||
{
|
|
||||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
|
||||||
if (node.IsBranch)
|
|
||||||
repo.NavigateToCommit((node.Backend as Models.Branch).Head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLocalBranchContextMenuRequested(object sender, ContextRequestedEventArgs e)
|
|
||||||
{
|
|
||||||
remoteBranchTree.UnselectAll();
|
|
||||||
tagsList.SelectedItem = null;
|
|
||||||
|
|
||||||
var repo = DataContext as ViewModels.Repository;
|
|
||||||
var tree = sender as TreeView;
|
|
||||||
if (tree.SelectedItems.Count == 0)
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var branches = new List<Models.Branch>();
|
|
||||||
foreach (var item in tree.SelectedItems)
|
|
||||||
CollectBranchesFromNode(branches, item as ViewModels.BranchTreeNode);
|
|
||||||
|
|
||||||
if (branches.Count == 1)
|
|
||||||
{
|
|
||||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
var menu = repo.CreateContextMenuForLocalBranch(branches[0]);
|
|
||||||
item.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (branches.Count > 1 && branches.Find(x => x.IsCurrent) == null)
|
|
||||||
{
|
|
||||||
var menu = new ContextMenu();
|
|
||||||
var deleteMulti = new MenuItem();
|
|
||||||
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
|
|
||||||
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
|
|
||||||
deleteMulti.Click += (_, ev) =>
|
|
||||||
{
|
|
||||||
repo.DeleteMultipleBranches(branches, true);
|
|
||||||
ev.Handled = true;
|
|
||||||
};
|
|
||||||
menu.Items.Add(deleteMulti);
|
|
||||||
tree.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRemoteBranchContextMenuRequested(object sender, ContextRequestedEventArgs e)
|
|
||||||
{
|
|
||||||
localBranchTree.UnselectAll();
|
|
||||||
tagsList.SelectedItem = null;
|
|
||||||
|
|
||||||
var repo = DataContext as ViewModels.Repository;
|
|
||||||
var tree = sender as TreeView;
|
|
||||||
if (tree.SelectedItems.Count == 0)
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tree.SelectedItems.Count == 1)
|
|
||||||
{
|
|
||||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
|
||||||
if (node != null && node.IsRemote)
|
|
||||||
{
|
|
||||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
|
||||||
if (item != null && item.DataContext == node)
|
|
||||||
{
|
|
||||||
var menu = repo.CreateContextMenuForRemote(node.Backend as Models.Remote);
|
|
||||||
item.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var branches = new List<Models.Branch>();
|
|
||||||
foreach (var item in tree.SelectedItems)
|
|
||||||
CollectBranchesFromNode(branches, item as ViewModels.BranchTreeNode);
|
|
||||||
|
|
||||||
if (branches.Count == 1)
|
|
||||||
{
|
|
||||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
var menu = repo.CreateContextMenuForRemoteBranch(branches[0]);
|
|
||||||
item.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (branches.Count > 1)
|
|
||||||
{
|
|
||||||
var menu = new ContextMenu();
|
|
||||||
var deleteMulti = new MenuItem();
|
|
||||||
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
|
|
||||||
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
|
|
||||||
deleteMulti.Click += (_, ev) =>
|
|
||||||
{
|
|
||||||
repo.DeleteMultipleBranches(branches, false);
|
|
||||||
ev.Handled = true;
|
|
||||||
};
|
|
||||||
menu.Items.Add(deleteMulti);
|
|
||||||
tree.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoubleTappedBranchNode(object sender, TappedEventArgs e)
|
|
||||||
{
|
|
||||||
if (!ViewModels.PopupHost.CanCreatePopup())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (sender is Grid grid && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var node = grid.DataContext as ViewModels.BranchTreeNode;
|
|
||||||
if (node == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (node.IsBranch)
|
|
||||||
{
|
|
||||||
var branch = node.Backend as Models.Branch;
|
|
||||||
if (branch.IsCurrent)
|
|
||||||
return;
|
|
||||||
|
|
||||||
repo.CheckoutBranch(branch);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node.IsExpanded = !node.IsExpanded;
|
|
||||||
UpdateLeftSidebarLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
|
|
||||||
{
|
|
||||||
localBranchTree.UnselectAll();
|
|
||||||
remoteBranchTree.UnselectAll();
|
|
||||||
|
|
||||||
var tag = datagrid.SelectedItem as Models.Tag;
|
|
||||||
if (DataContext is ViewModels.Repository repo)
|
|
||||||
repo.NavigateToCommit(tag.SHA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTagContextRequested(object sender, ContextRequestedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var tag = datagrid.SelectedItem as Models.Tag;
|
|
||||||
var menu = repo.CreateContextMenuForTag(tag);
|
|
||||||
datagrid.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnToggleFilter(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is ToggleButton toggle)
|
|
||||||
{
|
|
||||||
var filter = string.Empty;
|
|
||||||
if (toggle.DataContext is ViewModels.BranchTreeNode node)
|
|
||||||
{
|
|
||||||
if (node.IsBranch)
|
|
||||||
filter = (node.Backend as Models.Branch).FullName;
|
|
||||||
}
|
|
||||||
else if (toggle.DataContext is Models.Tag tag)
|
|
||||||
{
|
|
||||||
filter = tag.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(filter) && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
repo.UpdateFilter(filter, toggle.IsChecked == true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSubmoduleContextRequested(object sender, ContextRequestedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var submodule = datagrid.SelectedItem as string;
|
|
||||||
var menu = repo.CreateContextMenuForSubmodule(submodule);
|
|
||||||
datagrid.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var submodule = datagrid.SelectedItem as string;
|
|
||||||
(DataContext as ViewModels.Repository).OpenSubmodule(submodule);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var worktree = datagrid.SelectedItem as Models.Worktree;
|
|
||||||
var menu = repo.CreateContextMenuForWorktree(worktree);
|
|
||||||
datagrid.OpenContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoubleTappedWorktree(object sender, TappedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
|
||||||
{
|
|
||||||
var worktree = datagrid.SelectedItem as Models.Worktree;
|
|
||||||
(DataContext as ViewModels.Repository).OpenWorktree(worktree);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CollectBranchesFromNode(List<Models.Branch> outs, ViewModels.BranchTreeNode node)
|
|
||||||
{
|
|
||||||
if (node == null || node.IsRemote)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (node.IsFolder)
|
|
||||||
{
|
|
||||||
foreach (var child in node.Children)
|
|
||||||
CollectBranchesFromNode(outs, child);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var b = node.Backend as Models.Branch;
|
|
||||||
if (b != null && !outs.Contains(b))
|
|
||||||
outs.Add(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLeftSidebarTreeViewPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Property == TreeView.ItemsSourceProperty || e.Property == TreeView.IsVisibleProperty)
|
|
||||||
{
|
|
||||||
UpdateLeftSidebarLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLeftSidebarDataGridPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty)
|
|
||||||
{
|
|
||||||
UpdateLeftSidebarLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateLeftSidebarLayout()
|
|
||||||
{
|
{
|
||||||
var vm = DataContext as ViewModels.Repository;
|
var vm = DataContext as ViewModels.Repository;
|
||||||
if (vm == null || vm.Settings == null)
|
if (vm == null || vm.Settings == null)
|
||||||
|
@ -414,8 +25,8 @@ namespace SourceGit.Views
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var leftHeight = leftSidebarGroups.Bounds.Height - 28.0 * 5;
|
var leftHeight = leftSidebarGroups.Bounds.Height - 28.0 * 5;
|
||||||
var localBranchRows = vm.IsLocalBranchGroupExpanded ? GetTreeRowsCount(vm.LocalBranchTrees) : 0;
|
var localBranchRows = vm.IsLocalBranchGroupExpanded ? localBranchTree.Rows.Count : 0;
|
||||||
var remoteBranchRows = vm.IsRemoteGroupExpanded ? GetTreeRowsCount(vm.RemoteBranchTrees) : 0;
|
var remoteBranchRows = vm.IsRemoteGroupExpanded ? remoteBranchTree.Rows.Count : 0;
|
||||||
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;
|
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;
|
||||||
var desiredTag = vm.IsTagGroupExpanded ? tagsList.RowHeight * vm.VisibleTags.Count : 0;
|
var desiredTag = vm.IsTagGroupExpanded ? tagsList.RowHeight * vm.VisibleTags.Count : 0;
|
||||||
var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? submoduleList.RowHeight * vm.Submodules.Count : 0;
|
var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? submoduleList.RowHeight * vm.Submodules.Count : 0;
|
||||||
|
@ -524,17 +135,212 @@ namespace SourceGit.Views
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetTreeRowsCount(List<ViewModels.BranchTreeNode> nodes)
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
int count = nodes.Count;
|
base.OnLoaded(e);
|
||||||
|
|
||||||
foreach (var node in nodes)
|
if (DataContext is ViewModels.Repository { IsSearching: false })
|
||||||
{
|
UpdateLeftSidebarLayout();
|
||||||
if (!node.IsBranch && node.IsExpanded)
|
|
||||||
count += GetTreeRowsCount(node.Children);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
private void OpenWithExternalTools(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var menu = repo.CreateContextMenuForExternalTools();
|
||||||
|
button.OpenContextMenu(menu);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenGitFlowMenu(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var menu = repo.CreateContextMenuForGitFlow();
|
||||||
|
(sender as Control)?.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenGitLFSMenu(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var menu = repo.CreateContextMenuForGitLFS();
|
||||||
|
(sender as Control)?.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OpenStatistics(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner)
|
||||||
|
{
|
||||||
|
var dialog = new Statistics() { DataContext = new ViewModels.Statistics(repo.FullPath) };
|
||||||
|
await dialog.ShowDialog(owner);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Property == IsVisibleProperty && sender is Grid { IsVisible: true} grid)
|
||||||
|
txtSearchCommitsBox.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearchKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Enter)
|
||||||
|
{
|
||||||
|
if (DataContext is ViewModels.Repository repo)
|
||||||
|
repo.StartSearchCommits();
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearchResultDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var commit = grid.SelectedItem as Models.Commit;
|
||||||
|
repo.NavigateToCommit(commit.SHA);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLocalBranchTreeSelectionChanged(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is BranchTree tree && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var selected = tree.BranchesPresenter.SelectedItems;
|
||||||
|
if (selected == null || selected.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
remoteBranchTree.UnselectAll();
|
||||||
|
tagsList.SelectedItem = null;
|
||||||
|
|
||||||
|
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
|
||||||
|
repo.NavigateToCommit(branch.Head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRemoteBranchTreeSelectionChanged(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is BranchTree tree && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var selected = tree.BranchesPresenter.SelectedItems;
|
||||||
|
if (selected == null || selected.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
localBranchTree.UnselectAll();
|
||||||
|
tagsList.SelectedItem = null;
|
||||||
|
|
||||||
|
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
|
||||||
|
repo.NavigateToCommit(branch.Head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is DataGrid { SelectedItem: not null } grid)
|
||||||
|
{
|
||||||
|
localBranchTree.UnselectAll();
|
||||||
|
remoteBranchTree.UnselectAll();
|
||||||
|
|
||||||
|
var tag = grid.SelectedItem as Models.Tag;
|
||||||
|
if (DataContext is ViewModels.Repository repo)
|
||||||
|
repo.NavigateToCommit(tag.SHA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTagContextRequested(object sender, ContextRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var tag = datagrid.SelectedItem as Models.Tag;
|
||||||
|
var menu = repo.CreateContextMenuForTag(tag);
|
||||||
|
datagrid.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToggleTagFilter(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is ToggleButton toggle)
|
||||||
|
{
|
||||||
|
var filter = string.Empty;
|
||||||
|
if (toggle.DataContext is Models.Tag tag)
|
||||||
|
{
|
||||||
|
filter = tag.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(filter) && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
repo.UpdateFilter(filter, toggle.IsChecked == true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSubmoduleContextRequested(object sender, ContextRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var submodule = datagrid.SelectedItem as string;
|
||||||
|
var menu = repo.CreateContextMenuForSubmodule(submodule);
|
||||||
|
datagrid.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var submodule = grid.SelectedItem as string;
|
||||||
|
repo.OpenSubmodule(submodule);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var worktree = grid.SelectedItem as Models.Worktree;
|
||||||
|
var menu = repo.CreateContextMenuForWorktree(worktree);
|
||||||
|
grid.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoubleTappedWorktree(object sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var worktree = grid.SelectedItem as Models.Worktree;
|
||||||
|
repo.OpenWorktree(worktree);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLeftSidebarDataGridPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty)
|
||||||
|
{
|
||||||
|
UpdateLeftSidebarLayout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue