refactor: use TreeDataGrid instead of TreeView/DataGrid to improve performance (#148)

This commit is contained in:
leo 2024-05-28 21:19:53 +08:00
parent 3160f1d142
commit b192a1c423
24 changed files with 1333 additions and 1330 deletions

View file

@ -0,0 +1,46 @@
<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:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.ChangeCollectionView"
x:Name="me">
<TreeDataGrid x:Name="tree"
AutoDragDropRows="False"
ShowColumnHeaders="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
ScrollViewer.BringIntoViewOnFocusChange="True">
<TreeDataGrid.Resources>
<DataTemplate x:Key="TreeModeTemplate" DataType="vm:FileTreeNode">
<Grid HorizontalAlignment="Stretch" Height="24" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
<v:ChangeStatusIcon Grid.Column="0" Width="14" Height="14" IsWorkingCopyChange="{Binding #me.IsWorkingCopyChange}" Change="{Binding Backend}" IsVisible="{Binding !IsFolder}"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" Margin="6,0,0,0"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ListModeTemplate" DataType="m:Change">
<StackPanel Orientation="Horizontal">
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="{Binding #me.IsWorkingCopyChange}" Change="{Binding}" Margin="4,0,0,0"/>
<TextBlock Classes="monospace" Text="{Binding Path}" Margin="4,0"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="GridModeFileTemplate" DataType="m:Change">
<StackPanel Orientation="Horizontal">
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="{Binding #me.IsWorkingCopyChange}" Change="{Binding}" Margin="4,0,0,0"/>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureFileName}}" Margin="4,0"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="GridModeDirTemplate" DataType="m:Change">
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureDirectoryName}}" Foreground="{DynamicResource Brush.FG2}"/>
</DataTemplate>
</TreeDataGrid.Resources>
</TreeDataGrid>
</UserControl>

View file

@ -0,0 +1,271 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Interactivity;
namespace SourceGit.Views
{
public partial class ChangeCollectionView : UserControl
{
public static readonly StyledProperty<bool> IsWorkingCopyChangeProperty =
AvaloniaProperty.Register<ChangeCollectionView, bool>(nameof(IsWorkingCopy), false);
public bool IsWorkingCopy
{
get => GetValue(IsWorkingCopyChangeProperty);
set => SetValue(IsWorkingCopyChangeProperty, value);
}
public static readonly StyledProperty<bool> SingleSelectProperty =
AvaloniaProperty.Register<ChangeCollectionView, bool>(nameof(SingleSelect), true);
public bool SingleSelect
{
get => GetValue(SingleSelectProperty);
set => SetValue(SingleSelectProperty, value);
}
public static readonly StyledProperty<Models.ChangeViewMode> ViewModeProperty =
AvaloniaProperty.Register<ChangeCollectionView, Models.ChangeViewMode>(nameof(ViewMode), Models.ChangeViewMode.Tree);
public Models.ChangeViewMode ViewMode
{
get => GetValue(ViewModeProperty);
set => SetValue(ViewModeProperty, value);
}
public static readonly StyledProperty<List<Models.Change>> ChangesProperty =
AvaloniaProperty.Register<ChangeCollectionView, List<Models.Change>>(nameof(Changes), null);
public List<Models.Change> Changes
{
get => GetValue(ChangesProperty);
set => SetValue(ChangesProperty, value);
}
public static readonly StyledProperty<List<Models.Change>> SelectedChangesProperty =
AvaloniaProperty.Register<ChangeCollectionView, List<Models.Change>>(nameof(SelectedChanges), null);
public List<Models.Change> SelectedChanges
{
get => GetValue(SelectedChangesProperty);
set => SetValue(SelectedChangesProperty, value);
}
public static readonly RoutedEvent<RoutedEventArgs> ChangeDoubleTappedEvent =
RoutedEvent.Register<ChangeCollectionView, RoutedEventArgs>(nameof(ChangeDoubleTapped), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> ChangeDoubleTapped
{
add { AddHandler(ChangeDoubleTappedEvent, value); }
remove { RemoveHandler(ChangeDoubleTappedEvent, value); }
}
static ChangeCollectionView()
{
ViewModeProperty.Changed.AddClassHandler<ChangeCollectionView>((c, e) => c.UpdateSource());
ChangesProperty.Changed.AddClassHandler<ChangeCollectionView>((c, e) => c.UpdateSource());
SelectedChangesProperty.Changed.AddClassHandler<ChangeCollectionView>((c, e) => c.UpdateSelected());
}
public ChangeCollectionView()
{
InitializeComponent();
}
private void UpdateSource()
{
if (tree.Source is IDisposable disposable)
{
disposable.Dispose();
tree.Source = null;
}
var changes = Changes;
if (changes == null)
return;
var viewMode = ViewMode;
if (viewMode == Models.ChangeViewMode.Tree)
{
var filetree = ViewModels.FileTreeNode.Build(changes, true);
var source = new HierarchicalTreeDataGridSource<ViewModels.FileTreeNode>(filetree)
{
Columns =
{
new HierarchicalExpanderColumn<ViewModels.FileTreeNode>(
new TemplateColumn<ViewModels.FileTreeNode>(null, "TreeModeTemplate", null, GridLength.Auto),
x => x.Children,
x => x.Children.Count > 0,
x => x.IsExpanded),
new TextColumn<ViewModels.FileTreeNode, string>(
null,
x => string.Empty,
GridLength.Star)
}
};
var selection = new Models.TreeDataGridSelectionModel<ViewModels.FileTreeNode>(source, x => x.Children);
selection.RowDoubleTapped += (_, e) => RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent));
source.Selection = selection;
source.RowSelection.SingleSelect = SingleSelect;
source.RowSelection.SelectionChanged += (s, _) =>
{
if (!_isSelecting && s is Models.TreeDataGridSelectionModel<ViewModels.FileTreeNode> model)
{
var selection = new List<Models.Change>();
foreach (var c in model.SelectedItems)
CollectChangesInNode(selection, c);
_isSelecting = true;
SetCurrentValue(SelectedChangesProperty, selection);
_isSelecting = false;
}
};
tree.Source = source;
}
else if (viewMode == Models.ChangeViewMode.List)
{
var source = new FlatTreeDataGridSource<Models.Change>(changes)
{
Columns = { new TemplateColumn<Models.Change>(null, "ListModeTemplate", null, GridLength.Auto) }
};
var selection = new Models.TreeDataGridSelectionModel<Models.Change>(source, null);
selection.RowDoubleTapped += (_, e) => RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent));
source.Selection = selection;
source.RowSelection.SingleSelect = SingleSelect;
source.RowSelection.SelectionChanged += (s, _) =>
{
if (!_isSelecting && s is Models.TreeDataGridSelectionModel<Models.Change> model)
{
var selection = new List<Models.Change>();
foreach (var c in model.SelectedItems)
selection.Add(c);
_isSelecting = true;
SetCurrentValue(SelectedChangesProperty, selection);
_isSelecting = false;
}
};
tree.Source = source;
}
else
{
var source = new FlatTreeDataGridSource<Models.Change>(changes)
{
Columns =
{
new TemplateColumn<Models.Change>(null, "GridModeFileTemplate", null, GridLength.Auto),
new TemplateColumn<Models.Change>(null, "GridModeDirTemplate", null, GridLength.Auto)
},
};
var selection = new Models.TreeDataGridSelectionModel<Models.Change>(source, null);
selection.RowDoubleTapped += (_, e) => RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent));
source.Selection = selection;
source.RowSelection.SingleSelect = SingleSelect;
source.RowSelection.SelectionChanged += (s, _) =>
{
if (!_isSelecting && s is Models.TreeDataGridSelectionModel<Models.Change> model)
{
var selection = new List<Models.Change>();
foreach (var c in model.SelectedItems)
selection.Add(c);
_isSelecting = true;
SetCurrentValue(SelectedChangesProperty, selection);
_isSelecting = false;
}
};
tree.Source = source;
}
}
private void UpdateSelected()
{
if (_isSelecting || tree.Source == null)
return;
_isSelecting = true;
var selected = SelectedChanges;
if (tree.Source.Selection is Models.TreeDataGridSelectionModel<Models.Change> changeSelection)
{
if (selected == null || selected.Count == 0)
changeSelection.Clear();
else
changeSelection.Select(selected);
}
else if (tree.Source.Selection is Models.TreeDataGridSelectionModel<ViewModels.FileTreeNode> treeSelection)
{
if (selected == null || selected.Count == 0)
{
treeSelection.Clear();
_isSelecting = false;
return;
}
var set = new HashSet<object>();
foreach (var c in selected)
set.Add(c);
var nodes = new List<ViewModels.FileTreeNode>();
foreach (var node in tree.Source.Items)
CollectSelectedNodeByChange(nodes, node as ViewModels.FileTreeNode, set);
if (nodes.Count == 0)
{
treeSelection.Clear();
}
else
{
treeSelection.Select(nodes);
}
}
_isSelecting = false;
}
private void CollectChangesInNode(List<Models.Change> outs, ViewModels.FileTreeNode node)
{
if (node.IsFolder)
{
foreach (var child in node.Children)
CollectChangesInNode(outs, child);
}
else
{
var change = node.Backend as Models.Change;
if (change != null && !outs.Contains(change))
outs.Add(change);
}
}
private void CollectSelectedNodeByChange(List<ViewModels.FileTreeNode> outs, ViewModels.FileTreeNode node, HashSet<object> selected)
{
if (node == null)
return;
if (node.IsFolder)
{
foreach (var child in node.Children)
CollectSelectedNodeByChange(outs, child, selected);
}
else if (node.Backend != null && selected.Contains(node.Backend))
{
outs.Add(node);
}
}
private bool _isSelecting = false;
}
}

View file

@ -14,17 +14,17 @@
<MenuFlyout Placement="BottomEdgeAlignedLeft">
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.List}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.List}">
<MenuItem.Icon>
<Path Width="12" Height="12" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.List}"/>
<Path Width="12" Height="12" Data="{StaticResource Icons.List}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.Grid}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.Grid}">
<MenuItem.Icon>
<Path Width="12" Height="12" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Grid}"/>
<Path Width="12" Height="12" Data="{StaticResource Icons.Grid}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.Tree}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.Tree}">
<MenuItem.Icon>
<Path Width="12" Height="12" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Tree}"/>
<Path Width="12" Height="12" Data="{StaticResource Icons.Tree}"/>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>

View file

@ -17,7 +17,7 @@
<Grid Grid.Column="0" RowDefinitions="26,*">
<!-- Search & Display Mode -->
<Grid Grid.Row="0" ColumnDefinitions="*,24">
<Grid Grid.Row="0" ColumnDefinitions="*,18">
<TextBox Grid.Column="0"
Height="26"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
@ -39,116 +39,18 @@
</TextBox>
<v:ChangeViewModeSwitcher Grid.Column="1"
Width="18" Height="18"
Width="14" Height="14"
HorizontalAlignment="Right"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}"/>
</Grid>
<!-- Changes -->
<Border Grid.Row="1" Margin="0,4,0,0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}">
<Grid>
<DataGrid Background="Transparent"
ItemsSource="{Binding VisibleChanges}"
SelectedItem="{Binding SelectedChange, Mode=TwoWay}"
SelectionMode="Single"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
SelectionChanged="OnDataGridSelectionChanged"
ContextRequested="OnDataGridContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsList}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path}" Margin="4,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid Background="Transparent"
ItemsSource="{Binding VisibleChanges}"
SelectedItem="{Binding SelectedChange, Mode=TwoWay}"
SelectionMode="Single"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
SelectionChanged="OnDataGridSelectionChanged"
ContextRequested="OnDataGridContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsGrid}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FILE_NAME">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureFileName}}" Margin="4,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FOLDER_PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureDirectoryName}}" Margin="4,0,0,0" Foreground="{DynamicResource Brush.FG2}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TreeView ItemsSource="{Binding ChangeTree}"
SelectedItem="{Binding SelectedChangeNode, Mode=TwoWay}"
AutoScrollToSelectedItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ContextRequested="OnTreeViewContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsTree}}">
<TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="vm:FileTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:FileTreeNode}">
<Grid Height="24" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
<v:ChangeStatusIcon Grid.Column="0" Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding Backend}" IsVisible="{Binding !IsFolder}"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" Margin="6,0,0,0"/>
</Grid>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<v:ChangeCollectionView IsWorkingCopy="False"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode}"
Changes="{Binding VisibleChanges}"
SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}"
ContextRequested="OnChangeContextRequested"/>
</Border>
</Grid>

View file

@ -1,3 +1,4 @@
using Avalonia;
using Avalonia.Controls;
namespace SourceGit.Views
@ -9,38 +10,16 @@ namespace SourceGit.Views
InitializeComponent();
}
private void OnDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGrid datagrid && datagrid.IsVisible && datagrid.SelectedItem != null)
if (DataContext is ViewModels.CommitDetail vm)
{
datagrid.ScrollIntoView(datagrid.SelectedItem, null);
}
e.Handled = true;
}
private void OnDataGridContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
{
var detail = DataContext as ViewModels.CommitDetail;
var menu = detail.CreateChangeContextMenu(datagrid.SelectedItem as Models.Change);
datagrid.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnTreeViewContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is TreeView view && view.SelectedItem != null)
{
var detail = DataContext as ViewModels.CommitDetail;
var node = view.SelectedItem as ViewModels.FileTreeNode;
if (node != null && !node.IsFolder)
var selected = (sender as ChangeCollectionView)?.SelectedChanges;
if (selected != null && selected.Count == 1)
{
var menu = detail.CreateChangeContextMenu(node.Backend as Models.Change);
view.OpenContextMenu(menu);
}
var menu = vm.CreateChangeContextMenu(selected[0]);
(sender as Control)?.OpenContextMenu(menu);
}
}
e.Handled = true;

View file

@ -22,40 +22,20 @@
<v:CommitBaseInfo Grid.Row="0" Content="{Binding Commit}"/>
<!-- Change List -->
<DataGrid Grid.Row="1"
Background="Transparent"
ItemsSource="{Binding Changes}"
SelectionMode="Single"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
Margin="80,0,8,0"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
ContextRequested="OnChangeListContextRequested"
DoubleTapped="OnChangeListDoubleTapped">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path}" Margin="8,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<v:ChangeCollectionView Grid.Row="1"
Margin="72,0,8,0"
IsWorkingCopy="False"
ViewMode="List"
Changes="{Binding Changes}"
SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}"
ContextRequested="OnChangeListContextRequested"
ChangeDoubleTapped="OnChangeDoubleTapped">
<v:ChangeCollectionView.Styles>
<Style Selector="TreeDataGrid">
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
</Style>
</v:ChangeCollectionView.Styles>
</v:ChangeCollectionView>
</Grid>
</TabItem>

View file

@ -1,5 +1,5 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace SourceGit.Views
{
@ -10,30 +10,30 @@ namespace SourceGit.Views
InitializeComponent();
}
private void OnChangeListDoubleTapped(object sender, TappedEventArgs e)
private void OnChangeListContextRequested(object sender, ContextRequestedEventArgs e)
{
if (DataContext is ViewModels.CommitDetail detail)
if (DataContext is ViewModels.CommitDetail vm && sender is ChangeCollectionView view)
{
var datagrid = sender as DataGrid;
detail.ActivePageIndex = 1;
detail.SelectedChange = datagrid.SelectedItem as Models.Change;
var selected = view.SelectedChanges;
if (selected != null && selected.Count == 1)
{
var menu = vm.CreateChangeContextMenu(selected[0]);
view.OpenContextMenu(menu);
}
}
e.Handled = true;
}
private void OnChangeListContextRequested(object sender, ContextRequestedEventArgs e)
private void OnChangeDoubleTapped(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.CommitDetail detail)
if (DataContext is ViewModels.CommitDetail vm && sender is ChangeCollectionView view)
{
var datagrid = sender as DataGrid;
if (datagrid.SelectedItem == null)
var selected = view.SelectedChanges;
if (selected != null && selected.Count == 1)
{
e.Handled = true;
return;
vm.ActivePageIndex = 1;
vm.SelectedChanges = new() { selected[0] };
}
var menu = detail.CreateChangeContextMenu(datagrid.SelectedItem as Models.Change);
datagrid.OpenContextMenu(menu);
}
e.Handled = true;
}

View file

@ -72,7 +72,7 @@
<Grid Grid.Column="0" RowDefinitions="26,*">
<!-- Search & Display Mode -->
<Grid Grid.Row="0" ColumnDefinitions="*,24">
<Grid Grid.Row="0" ColumnDefinitions="*,18">
<TextBox Grid.Column="0"
Height="26"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
@ -94,116 +94,18 @@
</TextBox>
<v:ChangeViewModeSwitcher Grid.Column="1"
Width="18" Height="18"
Width="14" Height="14"
HorizontalAlignment="Right"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}"/>
</Grid>
<!-- Changes -->
<Border Grid.Row="1" Margin="0,4,0,0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}">
<Grid>
<DataGrid Background="Transparent"
ItemsSource="{Binding VisibleChanges}"
SelectedItem="{Binding SelectedChange, Mode=TwoWay}"
SelectionMode="Single"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
SelectionChanged="OnDataGridSelectionChanged"
ContextRequested="OnDataGridContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsList}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path}" Margin="4,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid Background="Transparent"
ItemsSource="{Binding VisibleChanges}"
SelectedItem="{Binding SelectedChange, Mode=TwoWay}"
SelectionMode="Single"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
SelectionChanged="OnDataGridSelectionChanged"
ContextRequested="OnDataGridContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsGrid}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FILE_NAME">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureFileName}}" Margin="4,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FOLDER_PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureDirectoryName}}" Margin="4,0,0,0" Foreground="{DynamicResource Brush.FG2}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TreeView ItemsSource="{Binding ChangeTree}"
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"
AutoScrollToSelectedItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ContextRequested="OnTreeViewContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsTree}}">
<TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="vm:FileTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:FileTreeNode}">
<Grid Height="24" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
<v:ChangeStatusIcon Grid.Column="0" Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding Backend}" IsVisible="{Binding !IsFolder}"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" Margin="6,0,0,0"/>
</Grid>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<v:ChangeCollectionView IsWorkingCopy="False"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode}"
Changes="{Binding VisibleChanges}"
SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}"
ContextRequested="OnChangeContextRequested"/>
</Border>
</Grid>

View file

@ -10,38 +10,12 @@ namespace SourceGit.Views
InitializeComponent();
}
private void OnDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGrid datagrid && datagrid.IsVisible)
if (DataContext is ViewModels.RevisionCompare vm && sender is ChangeCollectionView view)
{
datagrid.ScrollIntoView(datagrid.SelectedItem, null);
}
e.Handled = true;
}
private void OnDataGridContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
{
var compare = DataContext as ViewModels.RevisionCompare;
var menu = compare.CreateChangeContextMenu(datagrid.SelectedItem as Models.Change);
datagrid.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnTreeViewContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is TreeView view && view.SelectedItem != null)
{
var compare = DataContext as ViewModels.RevisionCompare;
var node = view.SelectedItem as ViewModels.FileTreeNode;
if (node != null && !node.IsFolder)
{
var menu = compare.CreateChangeContextMenu(node.Backend as Models.Change);
view.OpenContextMenu(menu);
}
var menu = vm.CreateChangeContextMenu();
view.OpenContextMenu(menu);
}
e.Handled = true;
@ -49,11 +23,8 @@ namespace SourceGit.Views
private void OnPressedSHA(object sender, PointerPressedEventArgs e)
{
if (sender is TextBlock block)
{
var compare = DataContext as ViewModels.RevisionCompare;
compare.NavigateTo(block.Text);
}
if (DataContext is ViewModels.RevisionCompare vm && sender is TextBlock block)
vm.NavigateTo(block.Text);
e.Handled = true;
}

View file

@ -41,29 +41,22 @@
<!-- File Tree -->
<Border Grid.Row="1" Margin="0,4,0,0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}">
<TreeView Grid.Row="5"
ItemsSource="{Binding RevisionFilesTree}"
SelectedItem="{Binding SelectedRevisionFileNode, Mode=TwoWay}"
AutoScrollToSelectedItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ContextRequested="OnTreeViewContextRequested">
<TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="vm:FileTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:FileTreeNode}">
<Grid Height="24" ColumnDefinitions="Auto,*">
<TreeDataGrid AutoDragDropRows="False"
ShowColumnHeaders="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
Source="{Binding RevisionFiles}"
ContextRequested="OnFileContextRequested">
<TreeDataGrid.Resources>
<DataTemplate x:Key="FileTreeNodeExpanderTemplate" DataType="vm:FileTreeNode">
<Grid HorizontalAlignment="Stretch" Height="24" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
<Path Grid.Column="0" Width="14" Height="14" IsVisible="{Binding !IsFolder}" Data="{StaticResource Icons.File}" VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" Margin="6,0,0,0"/>
</Grid>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DataTemplate>
</TreeDataGrid.Resources>
</TreeDataGrid>
</Border>
</Grid>

View file

@ -213,14 +213,16 @@ namespace SourceGit.Views
InitializeComponent();
}
private void OnTreeViewContextRequested(object sender, ContextRequestedEventArgs e)
private void OnFileContextRequested(object sender, ContextRequestedEventArgs e)
{
var detail = DataContext as ViewModels.CommitDetail;
var node = detail.SelectedRevisionFileNode;
if (!node.IsFolder)
if (DataContext is ViewModels.CommitDetail vm && sender is TreeDataGrid tree)
{
var menu = detail.CreateRevisionFileContextMenu(node.Backend as Models.Object);
(sender as Control)?.OpenContextMenu(menu);
var selected = tree.RowSelection.SelectedItem as ViewModels.FileTreeNode;
if (selected != null && !selected.IsFolder && selected.Backend is Models.Object obj)
{
var menu = vm.CreateRevisionFileContextMenu(obj);
tree.OpenContextMenu(menu);
}
}
e.Handled = true;

View file

@ -21,7 +21,7 @@
<!-- Unstaged Toolbar -->
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto,Auto">
<v:ChangeViewModeSwitcher Grid.Column="0" Width="14" Height="14" Margin="8,0,0,0" ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=UnstagedChangeViewMode, Mode=TwoWay}"/>
<v:ChangeViewModeSwitcher Grid.Column="0" Width="12" Height="12" Margin="8,0,0,0" ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=UnstagedChangeViewMode, Mode=TwoWay}"/>
<TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Unstaged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" FontWeight="Bold" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Unstaged, Converter={x:Static c:ListConverters.ToCount}}"/>
<Path Grid.Column="3" Classes="rotating" Width="14" Height="14" Data="{StaticResource Icons.Loading}" Margin="8,0,0,0" IsVisible="{Binding IsStaging}"/>
@ -31,7 +31,7 @@
Width="26" Height="14"
Padding="0"
ToolTip.Tip="{DynamicResource Text.WorkingCopy.Unstaged.ViewAssumeUnchaged}"
Click="ViewAssumeUnchanged">
Command="{Binding OpenAssumeUnchanged}">
<Path Width="14" Height="14" Data="{StaticResource Icons.File.Ignore}"/>
</Button>
<ToggleButton Grid.Column="6"
@ -43,7 +43,7 @@
Classes="icon_button"
Width="26" Height="14"
Padding="0"
Click="StageSelected">
Command="{Binding StageSelected}">
<ToolTip.Tip>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Text.WorkingCopy.Unstaged.Stage}" VerticalAlignment="Center"/>
@ -56,130 +56,33 @@
Classes="icon_button"
Width="26" Height="14"
Padding="0"
ToolTip.Tip="{DynamicResource Text.WorkingCopy.Unstaged.StageAll}" Click="StageAll">
ToolTip.Tip="{DynamicResource Text.WorkingCopy.Unstaged.StageAll}"
Command="{Binding StageAll}">
<Path Width="14" Height="14" Data="{StaticResource Icons.DoubleDown}"/>
</Button>
</Grid>
</Border>
<!-- Unstaged Changes -->
<Grid Grid.Row="1" Background="{DynamicResource Brush.Contents}">
<DataGrid x:Name="unstagedList"
Background="Transparent"
ItemsSource="{Binding Unstaged}"
SelectedItem="{Binding SelectedUnstagedChange, Mode=TwoWay}"
SelectionMode="Extended"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
KeyDown="OnUnstagedListKeyDown"
ContextRequested="OnUnstagedListContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UnstagedChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsList}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="True" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path}" Margin="4,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid x:Name="unstagedGrid"
Background="Transparent"
ItemsSource="{Binding Unstaged}"
SelectedItem="{Binding SelectedUnstagedChange, Mode=TwoWay}"
SelectionMode="Extended"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
KeyDown="OnUnstagedListKeyDown"
ContextRequested="OnUnstagedListContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UnstagedChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsGrid}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="True" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FILE_NAME">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureFileName}}" Margin="4,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FOLDER_PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureDirectoryName}}" Margin="4,0,0,0" Foreground="{DynamicResource Brush.FG2}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TreeView x:Name="unstagedTree"
ItemsSource="{Binding UnstagedTree}"
SelectedItem="{Binding SelectedUnstagedTreeNode, Mode=TwoWay}"
SelectionMode="Multiple"
AutoScrollToSelectedItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
KeyDown="OnUnstagedTreeViewKeyDown"
ContextRequested="OnUnstagedTreeViewContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UnstagedChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsTree}}">
<TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="vm:FileTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:FileTreeNode}">
<Grid Height="24" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
<v:ChangeStatusIcon Grid.Column="0" Width="14" Height="14" IsWorkingCopyChange="True" Change="{Binding Backend}" IsVisible="{Binding !IsFolder}"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" Margin="6,0,0,0"/>
</Grid>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<v:ChangeCollectionView Grid.Row="1"
IsWorkingCopy="True"
SingleSelect="False"
Background="{DynamicResource Brush.Contents}"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=UnstagedChangeViewMode}"
Changes="{Binding Unstaged}"
SelectedChanges="{Binding SelectedUnstaged, Mode=TwoWay}"
ContextRequested="OnUnstagedContextRequested"
ChangeDoubleTapped="OnUnstagedChangeDoubleTapped"
KeyDown="OnUnstagedKeyDown"/>
<!-- Staged Toolbar -->
<Border Grid.Row="2" BorderThickness="0,1" BorderBrush="{DynamicResource Brush.Border0}">
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto">
<v:ChangeViewModeSwitcher Grid.Column="0" Width="14" Height="14" Margin="8,0,0,0" ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Mode=TwoWay}"/>
<v:ChangeViewModeSwitcher Grid.Column="0" Width="12" Height="12" Margin="8,0,0,0" ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Mode=TwoWay}"/>
<TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Staged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" FontWeight="Bold" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Staged, Converter={x:Static c:ListConverters.ToCount}}"/>
<Path Grid.Column="3" Classes="rotating" Width="14" Height="14" Data="{StaticResource Icons.Loading}" Margin="8,0,0,0" IsVisible="{Binding IsUnstaging}"/>
<Button Grid.Column="5" Classes="icon_button" Width="26" Height="14" Padding="0" Click="UnstageSelected">
<Button Grid.Column="5" Classes="icon_button" Width="26" Height="14" Padding="0" Command="{Binding UnstageSelected}">
<ToolTip.Tip>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Text.WorkingCopy.Staged.Unstage}" VerticalAlignment="Center"/>
@ -188,121 +91,23 @@
</ToolTip.Tip>
<Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Up}"/>
</Button>
<Button Grid.Column="6" Classes="icon_button" Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Staged.UnstageAll}" Click="UnstageAll">
<Button Grid.Column="6" Classes="icon_button" Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Staged.UnstageAll}" Command="{Binding UnstageAll}">
<Path Width="14" Height="14" Data="{StaticResource Icons.DoubleUp}"/>
</Button>
</Grid>
</Border>
<!-- Staged Changes -->
<Grid Grid.Row="3" Background="{DynamicResource Brush.Contents}">
<DataGrid x:Name="stagedList"
Background="Transparent"
ItemsSource="{Binding Staged}"
SelectedItem="{Binding SelectedStagedChange, Mode=TwoWay}"
SelectionMode="Extended"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
KeyDown="OnStagedListKeyDown"
ContextRequested="OnStagedListContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsList}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path}" Margin="4,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid x:Name="stagedGrid"
Background="Transparent"
ItemsSource="{Binding Staged}"
SelectedItem="{Binding SelectedStagedChange, Mode=TwoWay}"
SelectionMode="Extended"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
KeyDown="OnStagedListKeyDown"
ContextRequested="OnStagedListContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsGrid}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FILE_NAME">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureFileName}}" Margin="4,0,0,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FOLDER_PATH">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Classes="monospace" Text="{Binding Path, Converter={x:Static c:PathConverters.PureDirectoryName}}" Margin="4,0,0,0" Foreground="{DynamicResource Brush.FG2}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TreeView x:Name="stagedTree"
ItemsSource="{Binding StagedTree}"
SelectedItem="{Binding SelectedStagedTreeNode, Mode=TwoWay}"
SelectionMode="Multiple"
AutoScrollToSelectedItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
KeyDown="OnStagedTreeViewKeyDown"
ContextRequested="OnStagedTreeViewContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsTree}}">
<TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="vm:FileTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:FileTreeNode}">
<Grid Height="24" ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
<v:ChangeStatusIcon Grid.Column="0" Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding Backend}" IsVisible="{Binding !IsFolder}"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" Margin="6,0,0,0"/>
</Grid>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<v:ChangeCollectionView Grid.Row="3"
IsWorkingCopy="False"
SingleSelect="False"
Background="{DynamicResource Brush.Contents}"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode}"
Changes="{Binding Staged}"
SelectedChanges="{Binding SelectedStaged, Mode=TwoWay}"
ContextRequested="OnStagedContextRequested"
ChangeDoubleTapped="OnStagedChangeDoubleTapped"
KeyDown="OnStagedKeyDown"/>
</Grid>
<GridSplitter Grid.Column="1"
@ -384,8 +189,7 @@
Margin="12,0,0,0"
HorizontalAlignment="Left"
IsChecked="{Binding UseAmend, Mode=TwoWay}"
Content="{DynamicResource Text.WorkingCopy.Amend}"
Checked="StartAmend"/>
Content="{DynamicResource Text.WorkingCopy.Amend}"/>
<Path Grid.Column="3"
Classes="rotating"
@ -399,7 +203,7 @@
Height="28"
Margin="8,0,0,0"
Padding="8,0"
Click="Commit"/>
Command="{Binding Commit}"/>
<Button Grid.Column="5"
Classes="flat"
@ -407,7 +211,7 @@
Height="28"
Margin="8,0,0,0"
Padding="8,0"
Click="CommitWithPush">
Command="{Binding CommitWithPush}">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="$parent[v:Repository].DataContext.(vm:Repository).CanCommitWithPush"/>

View file

@ -1,9 +1,6 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace SourceGit.Views
{
@ -14,313 +11,6 @@ namespace SourceGit.Views
InitializeComponent();
}
private void ViewAssumeUnchanged(object sender, RoutedEventArgs e)
{
var repoPage = this.FindAncestorOfType<Repository>();
if (repoPage != null)
{
var repo = (repoPage.DataContext as ViewModels.Repository).FullPath;
var window = new AssumeUnchangedManager();
window.DataContext = new ViewModels.AssumeUnchangedManager(repo);
window.ShowDialog((Window)TopLevel.GetTopLevel(this));
}
e.Handled = true;
}
private void StageSelected(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModels.WorkingCopy;
if (vm == null)
return;
List<Models.Change> selected = new List<Models.Change>();
switch (ViewModels.Preference.Instance.UnstagedChangeViewMode)
{
case Models.ChangeViewMode.List:
foreach (var item in unstagedList.SelectedItems)
{
if (item is Models.Change change)
selected.Add(change);
}
break;
case Models.ChangeViewMode.Grid:
foreach (var item in unstagedGrid.SelectedItems)
{
if (item is Models.Change change)
selected.Add(change);
}
break;
default:
foreach (var item in unstagedTree.SelectedItems)
{
if (item is ViewModels.FileTreeNode node)
CollectChangesFromNode(selected, node);
}
break;
}
vm.StageChanges(selected);
e.Handled = true;
}
private void StageAll(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModels.WorkingCopy;
if (vm == null)
return;
vm.StageChanges(vm.Unstaged);
e.Handled = true;
}
private void UnstageSelected(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModels.WorkingCopy;
if (vm == null)
return;
List<Models.Change> selected = new List<Models.Change>();
switch (ViewModels.Preference.Instance.StagedChangeViewMode)
{
case Models.ChangeViewMode.List:
foreach (var item in stagedList.SelectedItems)
{
if (item is Models.Change change)
selected.Add(change);
}
break;
case Models.ChangeViewMode.Grid:
foreach (var item in stagedGrid.SelectedItems)
{
if (item is Models.Change change)
selected.Add(change);
}
break;
default:
foreach (var item in stagedTree.SelectedItems)
{
if (item is ViewModels.FileTreeNode node)
CollectChangesFromNode(selected, node);
}
break;
}
vm.UnstageChanges(selected);
e.Handled = true;
}
private void UnstageAll(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModels.WorkingCopy;
if (vm == null)
return;
vm.UnstageChanges(vm.Staged);
e.Handled = true;
}
private void OnUnstagedListKeyDown(object sender, KeyEventArgs e)
{
var datagrid = sender as DataGrid;
if (datagrid.SelectedItems.Count > 0 && e.Key == Key.Space && DataContext is ViewModels.WorkingCopy vm)
{
List<Models.Change> selected = new List<Models.Change>();
foreach (var item in datagrid.SelectedItems)
{
if (item is Models.Change change)
selected.Add(change);
}
vm.StageChanges(selected);
}
e.Handled = true;
}
private void OnUnstagedTreeViewKeyDown(object sender, KeyEventArgs e)
{
var tree = sender as TreeView;
if (tree.SelectedItems.Count > 0 && e.Key == Key.Space && DataContext is ViewModels.WorkingCopy vm)
{
List<Models.Change> selected = new List<Models.Change>();
foreach (var item in tree.SelectedItems)
{
if (item is ViewModels.FileTreeNode node)
CollectChangesFromNode(selected, node);
}
vm.StageChanges(selected);
}
e.Handled = true;
}
private void OnStagedListKeyDown(object sender, KeyEventArgs e)
{
var datagrid = sender as DataGrid;
if (datagrid.SelectedItems.Count > 0 && e.Key == Key.Space && DataContext is ViewModels.WorkingCopy vm)
{
List<Models.Change> selected = new List<Models.Change>();
foreach (var item in datagrid.SelectedItems)
{
if (item is Models.Change change)
selected.Add(change);
}
vm.UnstageChanges(selected);
}
e.Handled = true;
}
private void OnStagedTreeViewKeyDown(object sender, KeyEventArgs e)
{
var tree = sender as TreeView;
if (tree.SelectedItems.Count > 0 && e.Key == Key.Space && DataContext is ViewModels.WorkingCopy vm)
{
List<Models.Change> selected = new List<Models.Change>();
foreach (var item in tree.SelectedItems)
{
if (item is ViewModels.FileTreeNode node)
CollectChangesFromNode(selected, node);
}
vm.UnstageChanges(selected);
}
e.Handled = true;
}
private void OnUnstagedListContextRequested(object sender, ContextRequestedEventArgs e)
{
var datagrid = sender as DataGrid;
if (datagrid.SelectedItems.Count > 0 && DataContext is ViewModels.WorkingCopy vm)
{
List<Models.Change> selected = new List<Models.Change>();
foreach (var item in datagrid.SelectedItems)
{
if (item is Models.Change change)
selected.Add(change);
}
var menu = vm.CreateContextMenuForUnstagedChanges(selected);
datagrid.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnUnstagedTreeViewContextRequested(object sender, ContextRequestedEventArgs e)
{
var tree = sender as TreeView;
if (tree.SelectedItems.Count > 0 && DataContext is ViewModels.WorkingCopy vm)
{
List<Models.Change> selected = new List<Models.Change>();
foreach (var item in tree.SelectedItems)
{
if (item is ViewModels.FileTreeNode node)
CollectChangesFromNode(selected, node);
}
var menu = vm.CreateContextMenuForUnstagedChanges(selected);
tree.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnStagedListContextRequested(object sender, ContextRequestedEventArgs e)
{
var datagrid = sender as DataGrid;
if (datagrid.SelectedItems.Count > 0 && DataContext is ViewModels.WorkingCopy vm)
{
List<Models.Change> selected = new List<Models.Change>();
foreach (var item in datagrid.SelectedItems)
{
if (item is Models.Change change)
selected.Add(change);
}
var menu = vm.CreateContextMenuForStagedChanges(selected);
datagrid.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnStagedTreeViewContextRequested(object sender, ContextRequestedEventArgs e)
{
var tree = sender as TreeView;
if (tree.SelectedItems.Count > 0 && DataContext is ViewModels.WorkingCopy vm)
{
List<Models.Change> selected = new List<Models.Change>();
foreach (var item in tree.SelectedItems)
{
if (item is ViewModels.FileTreeNode node)
CollectChangesFromNode(selected, node);
}
var menu = vm.CreateContextMenuForStagedChanges(selected);
tree.OpenContextMenu(menu);
}
e.Handled = true;
}
private void StartAmend(object sender, RoutedEventArgs e)
{
var repoPage = this.FindAncestorOfType<Repository>();
if (repoPage != null)
{
var repo = (repoPage.DataContext as ViewModels.Repository).FullPath;
var commits = new Commands.QueryCommits(repo, "-n 1", false).Result();
if (commits.Count == 0)
{
App.RaiseException(repo, "No commits to amend!!!");
var chkBox = sender as CheckBox;
chkBox.IsChecked = false;
e.Handled = true;
return;
}
var vm = DataContext as ViewModels.WorkingCopy;
vm.CommitMessage = commits[0].FullMessage;
}
e.Handled = true;
}
private void Commit(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModels.WorkingCopy;
vm.DoCommit(false);
e.Handled = true;
}
private void CommitWithPush(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModels.WorkingCopy;
vm.DoCommit(true);
e.Handled = true;
}
private void CollectChangesFromNode(List<Models.Change> outs, ViewModels.FileTreeNode node)
{
if (node.IsFolder)
{
foreach (var child in node.Children)
CollectChangesFromNode(outs, child);
}
else
{
var change = node.Backend as Models.Change;
if (change != null && !outs.Contains(change))
outs.Add(change);
}
}
private void OnOpenCommitMessagePicker(object sender, RoutedEventArgs e)
{
if (sender is Button button && DataContext is ViewModels.WorkingCopy vm)
@ -331,5 +21,61 @@ namespace SourceGit.Views
e.Handled = true;
}
}
private void OnUnstagedContextRequested(object sender, ContextRequestedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm)
{
var menu = vm.CreateContextMenuForUnstagedChanges();
(sender as Control)?.OpenContextMenu(menu);
e.Handled = true;
}
}
private void OnStagedContextRequested(object sender, ContextRequestedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm)
{
var menu = vm.CreateContextMenuForStagedChanges();
(sender as Control)?.OpenContextMenu(menu);
e.Handled = true;
}
}
private void OnUnstagedChangeDoubleTapped(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm)
{
vm.StageSelected();
e.Handled = true;
}
}
private void OnStagedChangeDoubleTapped(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm)
{
vm.UnstageSelected();
e.Handled = true;
}
}
private void OnUnstagedKeyDown(object sender, KeyEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm && e.Key == Key.Space)
{
vm.StageSelected();
e.Handled = true;
}
}
private void OnStagedKeyDown(object sender, KeyEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm && e.Key == Key.Space)
{
vm.UnstageSelected();
e.Handled = true;
}
}
}
}