mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -08:00
refactor: rewrite the welcome page since the original TreeView
has many limitations (#391)
This commit is contained in:
parent
af6d2cc725
commit
38e2e0f3f4
12 changed files with 227 additions and 243 deletions
|
@ -1397,72 +1397,6 @@
|
||||||
<Setter Property="IsVisible" Value="False"/>
|
<Setter Property="IsVisible" Value="False"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="TreeViewItem">
|
|
||||||
<Style.Resources>
|
|
||||||
<x:Double x:Key="TreeViewItemIndent">16</x:Double>
|
|
||||||
</Style.Resources>
|
|
||||||
|
|
||||||
<Setter Property="BorderThickness" Value="0"/>
|
|
||||||
<Setter Property="MinHeight" Value="24" />
|
|
||||||
<Setter Property="Template">
|
|
||||||
<ControlTemplate>
|
|
||||||
<StackPanel>
|
|
||||||
<Border Name="PART_LayoutRoot"
|
|
||||||
Background="Transparent"
|
|
||||||
BorderThickness="0"
|
|
||||||
CornerRadius="0"
|
|
||||||
MinHeight="{TemplateBinding MinHeight}"
|
|
||||||
TemplatedControl.IsTemplateFocusTarget="True">
|
|
||||||
<Grid>
|
|
||||||
<Border Name="PART_Background" CornerRadius="{TemplateBinding CornerRadius}" Background="Transparent"/>
|
|
||||||
|
|
||||||
<Grid Name="PART_Header" ColumnDefinitions="16,*" Margin="{TemplateBinding Level, Mode=OneWay, Converter={StaticResource TreeViewItemLeftMarginConverter}}">
|
|
||||||
<Panel Name="PART_ExpandCollapseChevronContainer">
|
|
||||||
<ToggleButton Name="PART_ExpandCollapseChevron"
|
|
||||||
Classes="tree_expander"
|
|
||||||
Focusable="False"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}"/>
|
|
||||||
</Panel>
|
|
||||||
<ContentPresenter Name="PART_HeaderPresenter"
|
|
||||||
Grid.Column="1"
|
|
||||||
Focusable="False"
|
|
||||||
Background="Transparent"
|
|
||||||
Content="{TemplateBinding Header}"
|
|
||||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
|
||||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
|
||||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
|
||||||
Margin="{TemplateBinding Padding}" />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
<ItemsPresenter Name="PART_ItemsPresenter"
|
|
||||||
IsVisible="{TemplateBinding IsExpanded}"
|
|
||||||
ItemsPanel="{TemplateBinding ItemsPanel}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter>
|
|
||||||
|
|
||||||
<Style Selector="^ /template/ Border#PART_LayoutRoot:pointerover">
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
</Style>
|
|
||||||
<Style Selector="^ /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="^:selected /template/ Border#PART_LayoutRoot">
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
</Style>
|
|
||||||
<Style Selector="^:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".4"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="^:selected /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
|
||||||
<Setter Property="Opacity" Value=".65"/>
|
|
||||||
</Style>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="NumericUpDown">
|
<Style Selector="NumericUpDown">
|
||||||
<Style Selector="^ /template/ ButtonSpinner#PART_Spinner">
|
<Style Selector="^ /template/ ButtonSpinner#PART_Spinner">
|
||||||
<Setter Property="MinHeight" Value="0"/>
|
<Setter Property="MinHeight" Value="0"/>
|
||||||
|
|
|
@ -141,6 +141,7 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Welcome.Instance.Refresh();
|
||||||
launcher.OpenRepositoryInTab(node, page);
|
launcher.OpenRepositoryInTab(node, page);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ namespace SourceGit.ViewModels
|
||||||
IsExpanded = false,
|
IsExpanded = false,
|
||||||
}, _parent);
|
}, _parent);
|
||||||
|
|
||||||
|
Welcome.Instance.Refresh();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace SourceGit.ViewModels
|
||||||
public override Task<bool> Sure()
|
public override Task<bool> Sure()
|
||||||
{
|
{
|
||||||
Preference.Instance.RemoveNode(_node);
|
Preference.Instance.RemoveNode(_node);
|
||||||
|
Welcome.Instance.Refresh();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,10 @@ namespace SourceGit.ViewModels
|
||||||
_node.Bookmark = _bookmark;
|
_node.Bookmark = _bookmark;
|
||||||
|
|
||||||
if (needSort)
|
if (needSort)
|
||||||
|
{
|
||||||
Preference.Instance.SortByRenamedNode(_node);
|
Preference.Instance.SortByRenamedNode(_node);
|
||||||
|
Welcome.Instance.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
var normalizedPath = _targetPath.Replace("\\", "/");
|
var normalizedPath = _targetPath.Replace("\\", "/");
|
||||||
Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, _parentNode, true);
|
Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, _parentNode, true);
|
||||||
|
Welcome.Instance.Refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -48,6 +48,7 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
var normalized = root.Replace("\\", "/");
|
var normalized = root.Replace("\\", "/");
|
||||||
var node = pref.FindOrAddNodeByRepositoryPath(normalized, null, false);
|
var node = pref.FindOrAddNodeByRepositoryPath(normalized, null, false);
|
||||||
|
Welcome.Instance.Refresh();
|
||||||
OpenRepositoryInTab(node, null);
|
OpenRepositoryInTab(node, null);
|
||||||
}
|
}
|
||||||
else if (pref.RestoreTabs)
|
else if (pref.RestoreTabs)
|
||||||
|
|
|
@ -49,6 +49,13 @@ namespace SourceGit.ViewModels
|
||||||
set => SetProperty(ref _isVisible, value);
|
set => SetProperty(ref _isVisible, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public int Depth
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = 0;
|
||||||
|
|
||||||
public AvaloniaList<RepositoryNode> SubNodes
|
public AvaloniaList<RepositoryNode> SubNodes
|
||||||
{
|
{
|
||||||
get => _subNodes;
|
get => _subNodes;
|
||||||
|
|
|
@ -12,10 +12,11 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
public static Welcome Instance => _instance;
|
public static Welcome Instance => _instance;
|
||||||
|
|
||||||
public AvaloniaList<RepositoryNode> RepositoryNodes
|
public AvaloniaList<RepositoryNode> Rows
|
||||||
{
|
{
|
||||||
get => Preference.Instance.RepositoryNodes;
|
get;
|
||||||
}
|
private set;
|
||||||
|
} = [];
|
||||||
|
|
||||||
public string SearchFilter
|
public string SearchFilter
|
||||||
{
|
{
|
||||||
|
@ -27,6 +28,60 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Welcome()
|
||||||
|
{
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_searchFilter))
|
||||||
|
{
|
||||||
|
foreach (var node in Preference.Instance.RepositoryNodes)
|
||||||
|
ResetVisibility(node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var node in Preference.Instance.RepositoryNodes)
|
||||||
|
SetVisibilityBySearch(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = new List<RepositoryNode>();
|
||||||
|
MakeTreeRows(rows, Preference.Instance.RepositoryNodes);
|
||||||
|
Rows.Clear();
|
||||||
|
Rows.AddRange(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleNodeIsExpanded(RepositoryNode node)
|
||||||
|
{
|
||||||
|
node.IsExpanded = !node.IsExpanded;
|
||||||
|
|
||||||
|
var depth = node.Depth;
|
||||||
|
var idx = Rows.IndexOf(node);
|
||||||
|
if (idx == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (node.IsExpanded)
|
||||||
|
{
|
||||||
|
var subrows = new List<RepositoryNode>();
|
||||||
|
MakeTreeRows(subrows, node.SubNodes, depth + 1);
|
||||||
|
Rows.InsertRange(idx + 1, subrows);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var removeCount = 0;
|
||||||
|
for (int i = idx + 1; i < Rows.Count; i++)
|
||||||
|
{
|
||||||
|
var row = Rows[i];
|
||||||
|
if (row.Depth <= depth)
|
||||||
|
break;
|
||||||
|
|
||||||
|
removeCount++;
|
||||||
|
}
|
||||||
|
Rows.RemoveRange(idx + 1, removeCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void InitRepository(string path, RepositoryNode parent)
|
public void InitRepository(string path, RepositoryNode parent)
|
||||||
{
|
{
|
||||||
if (!Preference.Instance.IsGitConfigured())
|
if (!Preference.Instance.IsGitConfigured())
|
||||||
|
@ -36,10 +91,8 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PopupHost.CanCreatePopup())
|
if (PopupHost.CanCreatePopup())
|
||||||
{
|
|
||||||
PopupHost.ShowPopup(new Init(path, parent));
|
PopupHost.ShowPopup(new Init(path, parent));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void Clone()
|
public void Clone()
|
||||||
{
|
{
|
||||||
|
@ -75,30 +128,7 @@ namespace SourceGit.ViewModels
|
||||||
public void MoveNode(RepositoryNode from, RepositoryNode to)
|
public void MoveNode(RepositoryNode from, RepositoryNode to)
|
||||||
{
|
{
|
||||||
Preference.Instance.MoveNode(from, to);
|
Preference.Instance.MoveNode(from, to);
|
||||||
}
|
Refresh();
|
||||||
|
|
||||||
public RepositoryNode GetPrevVisible(RepositoryNode node)
|
|
||||||
{
|
|
||||||
var visibleRows = new List<RepositoryNode>();
|
|
||||||
CollectVisibleRows(visibleRows, RepositoryNodes);
|
|
||||||
|
|
||||||
var idx = visibleRows.IndexOf(node);
|
|
||||||
if (idx <= 1)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return visibleRows[idx - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public RepositoryNode GetNextVisible(RepositoryNode node)
|
|
||||||
{
|
|
||||||
var visibleRows = new List<RepositoryNode>();
|
|
||||||
CollectVisibleRows(visibleRows, RepositoryNodes);
|
|
||||||
|
|
||||||
var idx = visibleRows.IndexOf(node);
|
|
||||||
if (idx < 0 || idx >= visibleRows.Count - 1)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return visibleRows[idx + 1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContextMenu CreateContextMenu(RepositoryNode node)
|
public ContextMenu CreateContextMenu(RepositoryNode node)
|
||||||
|
@ -178,20 +208,6 @@ namespace SourceGit.ViewModels
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Refresh()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(_searchFilter))
|
|
||||||
{
|
|
||||||
foreach (var node in RepositoryNodes)
|
|
||||||
ResetVisibility(node);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var node in RepositoryNodes)
|
|
||||||
SetVisibilityBySearch(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetVisibility(RepositoryNode node)
|
private void ResetVisibility(RepositoryNode node)
|
||||||
{
|
{
|
||||||
node.IsVisible = true;
|
node.IsVisible = true;
|
||||||
|
@ -226,6 +242,23 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MakeTreeRows(List<RepositoryNode> rows, AvaloniaList<RepositoryNode> nodes, int depth = 0)
|
||||||
|
{
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
if (!node.IsVisible)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
node.Depth = depth;
|
||||||
|
rows.Add(node);
|
||||||
|
|
||||||
|
if (node.IsRepository || !node.IsExpanded)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
MakeTreeRows(rows, node.SubNodes, depth+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OpenAllInNode(Launcher launcher, RepositoryNode node)
|
private void OpenAllInNode(Launcher launcher, RepositoryNode node)
|
||||||
{
|
{
|
||||||
foreach (var subNode in node.SubNodes)
|
foreach (var subNode in node.SubNodes)
|
||||||
|
@ -237,20 +270,6 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CollectVisibleRows(List<RepositoryNode> visible, AvaloniaList<RepositoryNode> collection)
|
|
||||||
{
|
|
||||||
foreach (var node in collection)
|
|
||||||
{
|
|
||||||
if (node.IsVisible)
|
|
||||||
{
|
|
||||||
visible.Add(node);
|
|
||||||
|
|
||||||
if (!node.IsRepository)
|
|
||||||
CollectVisibleRows(visible, node.SubNodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Welcome _instance = new Welcome();
|
private static Welcome _instance = new Welcome();
|
||||||
private string _searchFilter = string.Empty;
|
private string _searchFilter = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
<Grid RowDefinitions="*,36">
|
<Grid RowDefinitions="*,36">
|
||||||
<Grid Grid.Row="0" Margin="0,8" ColumnDefinitions="*,600,*">
|
<Grid Grid.Row="0" Margin="0,8" ColumnDefinitions="*,600,*">
|
||||||
<Grid Grid.Column="1" RowDefinitions="Auto,*">
|
<Grid Grid.Column="1" RowDefinitions="Auto,*">
|
||||||
|
<!-- Search Box -->
|
||||||
<TextBox Grid.Row="0"
|
<TextBox Grid.Row="0"
|
||||||
|
x:Name="SearchBox"
|
||||||
Height="32"
|
Height="32"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
CornerRadius="16"
|
CornerRadius="16"
|
||||||
|
@ -19,7 +21,6 @@
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Background="{DynamicResource Brush.Contents}"
|
Background="{DynamicResource Brush.Contents}"
|
||||||
Watermark="{DynamicResource Text.Welcome.Search}"
|
Watermark="{DynamicResource Text.Welcome.Search}"
|
||||||
KeyDown="OnSearchBoxKeyDown"
|
|
||||||
VerticalContentAlignment="Center"
|
VerticalContentAlignment="Center"
|
||||||
Text="{Binding SearchFilter, Mode=TwoWay}"
|
Text="{Binding SearchFilter, Mode=TwoWay}"
|
||||||
v:AutoFocusBehaviour.IsEnabled="True">
|
v:AutoFocusBehaviour.IsEnabled="True">
|
||||||
|
@ -34,30 +35,32 @@
|
||||||
</TextBox.InnerRightContent>
|
</TextBox.InnerRightContent>
|
||||||
</TextBox>
|
</TextBox>
|
||||||
|
|
||||||
<TreeView Grid.Row="1"
|
<!-- Repository Tree -->
|
||||||
x:Name="ReposTree"
|
<ListBox Grid.Row="1"
|
||||||
|
x:Name="TreeContainer"
|
||||||
Margin="0,8,8,0"
|
Margin="0,8,8,0"
|
||||||
ItemsSource="{Binding RepositoryNodes}"
|
Focusable="True"
|
||||||
|
Background="Transparent"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
|
ItemsSource="{Binding Rows}"
|
||||||
|
SelectionMode="Single"
|
||||||
Loaded="SetupTreeViewDragAndDrop"
|
Loaded="SetupTreeViewDragAndDrop"
|
||||||
LostFocus="OnTreeViewLostFocus"
|
LostFocus="OnTreeViewLostFocus"
|
||||||
SelectionChanged="OnTreeViewSelectionChanged"
|
|
||||||
KeyDown="OnTreeViewKeyDown">
|
KeyDown="OnTreeViewKeyDown">
|
||||||
<TreeView.ContextMenu>
|
<ListBox.Styles>
|
||||||
<ContextMenu>
|
<Style Selector="ListBox">
|
||||||
<MenuItem Header="{DynamicResource Text.Welcome.AddRootFolder}" Command="{Binding AddRootNode}">
|
<Setter Property="FocusAdorner">
|
||||||
<MenuItem.Icon>
|
<FocusAdornerTemplate>
|
||||||
<Path Width="12" Height="12" Data="{DynamicResource Icons.Folder.Add}"/>
|
<Border Background="Transparent" BorderThickness="0"/>
|
||||||
</MenuItem.Icon>
|
</FocusAdornerTemplate>
|
||||||
</MenuItem>
|
</Setter>
|
||||||
</ContextMenu>
|
</Style>
|
||||||
</TreeView.ContextMenu>
|
|
||||||
|
|
||||||
<TreeView.Styles>
|
<Style Selector="ListBoxItem" x:DataType="vm:RepositoryNode">
|
||||||
<Style Selector="TreeViewItem" x:DataType="vm:RepositoryNode">
|
<Setter Property="Margin" Value="0"/>
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
<Setter Property="Padding" Value="0"/>
|
||||||
<Setter Property="IsVisible" Value="{Binding IsVisible}"/>
|
<Setter Property="Height" Value="30"/>
|
||||||
<Setter Property="CornerRadius" Value="4"/>
|
<Setter Property="CornerRadius" Value="4"/>
|
||||||
<Setter Property="FocusAdorner">
|
<Setter Property="FocusAdorner">
|
||||||
<FocusAdornerTemplate>
|
<FocusAdornerTemplate>
|
||||||
|
@ -65,13 +68,29 @@
|
||||||
</FocusAdornerTemplate>
|
</FocusAdornerTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
</TreeView.Styles>
|
</ListBox.Styles>
|
||||||
|
|
||||||
<TreeView.ItemTemplate>
|
<ListBox.ItemsPanel>
|
||||||
<TreeDataTemplate ItemsSource="{Binding SubNodes}">
|
<ItemsPanelTemplate>
|
||||||
<Grid Height="30"
|
<VirtualizingStackPanel Orientation="Vertical"/>
|
||||||
ColumnDefinitions="18,Auto,*"
|
</ItemsPanelTemplate>
|
||||||
Background="Transparent"
|
</ListBox.ItemsPanel>
|
||||||
|
|
||||||
|
<ListBox.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="{DynamicResource Text.Welcome.AddRootFolder}" Command="{Binding AddRootNode}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Path Width="12" Height="12" Data="{DynamicResource Icons.Folder.Add}"/>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
</ContextMenu>
|
||||||
|
</ListBox.ContextMenu>
|
||||||
|
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="vm:RepositoryNode">
|
||||||
|
<Grid Background="Transparent"
|
||||||
|
ColumnDefinitions="16,18,Auto,*"
|
||||||
|
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||||
Loaded="SetupTreeNodeDragAndDrop"
|
Loaded="SetupTreeNodeDragAndDrop"
|
||||||
ContextRequested="OnTreeNodeContextRequested"
|
ContextRequested="OnTreeNodeContextRequested"
|
||||||
PointerPressed="OnPointerPressedTreeNode"
|
PointerPressed="OnPointerPressedTreeNode"
|
||||||
|
@ -79,14 +98,21 @@
|
||||||
PointerReleased="OnPointerReleasedOnTreeNode"
|
PointerReleased="OnPointerReleasedOnTreeNode"
|
||||||
DoubleTapped="OnDoubleTappedTreeNode"
|
DoubleTapped="OnDoubleTappedTreeNode"
|
||||||
ClipToBounds="True">
|
ClipToBounds="True">
|
||||||
<Path Grid.Column="0"
|
<v:RepositoryTreeNodeToggleButton Grid.Column="0"
|
||||||
|
Classes="tree_expander"
|
||||||
|
Focusable="False"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
IsChecked="{Binding IsExpanded, Mode=OneWay}"
|
||||||
|
IsVisible="{Binding !IsRepository}"/>
|
||||||
|
|
||||||
|
<Path Grid.Column="1"
|
||||||
Width="14" Height="14"
|
Width="14" Height="14"
|
||||||
Fill="{Binding Bookmark, Converter={x:Static c:BookmarkConverters.ToBrush}}"
|
Fill="{Binding Bookmark, Converter={x:Static c:BookmarkConverters.ToBrush}}"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Data="{StaticResource Icons.Bookmark}"
|
Data="{StaticResource Icons.Bookmark}"
|
||||||
IsVisible="{Binding IsRepository}"/>
|
IsVisible="{Binding IsRepository}"/>
|
||||||
|
|
||||||
<ToggleButton Grid.Column="0"
|
<ToggleButton Grid.Column="1"
|
||||||
Classes="folder"
|
Classes="folder"
|
||||||
Focusable="False"
|
Focusable="False"
|
||||||
Width="14" Height="14"
|
Width="14" Height="14"
|
||||||
|
@ -95,8 +121,11 @@
|
||||||
IsChecked="{Binding IsExpanded}"
|
IsChecked="{Binding IsExpanded}"
|
||||||
IsVisible="{Binding !IsRepository}"/>
|
IsVisible="{Binding !IsRepository}"/>
|
||||||
|
|
||||||
<TextBlock Grid.Column="1" Classes="primary" VerticalAlignment="Center" Text="{Binding Name}"/>
|
|
||||||
<TextBlock Grid.Column="2"
|
<TextBlock Grid.Column="2"
|
||||||
|
Classes="primary"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Name}"/>
|
||||||
|
<TextBlock Grid.Column="3"
|
||||||
Classes="primary"
|
Classes="primary"
|
||||||
Margin="8,0"
|
Margin="8,0"
|
||||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||||
|
@ -104,12 +133,13 @@
|
||||||
Text="{Binding Id}"
|
Text="{Binding Id}"
|
||||||
IsVisible="{Binding IsRepository}"/>
|
IsVisible="{Binding IsRepository}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TreeDataTemplate>
|
</DataTemplate>
|
||||||
</TreeView.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</TreeView>
|
</ListBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Tips -->
|
||||||
<TextBlock Grid.Row="1"
|
<TextBlock Grid.Row="1"
|
||||||
Classes="italic"
|
Classes="italic"
|
||||||
Margin="0,0,8,0"
|
Margin="0,0,8,0"
|
||||||
|
|
|
@ -1,13 +1,31 @@
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
namespace SourceGit.Views
|
namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
|
public class RepositoryTreeNodeToggleButton : ToggleButton
|
||||||
|
{
|
||||||
|
protected override Type StyleKeyOverride => typeof(ToggleButton);
|
||||||
|
|
||||||
|
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
|
||||||
|
DataContext is ViewModels.RepositoryNode { IsRepository: false } node)
|
||||||
|
{
|
||||||
|
ViewModels.Welcome.Instance.ToggleNodeIsExpanded(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public partial class Welcome : UserControl
|
public partial class Welcome : UserControl
|
||||||
{
|
{
|
||||||
public Welcome()
|
public Welcome()
|
||||||
|
@ -15,9 +33,30 @@ namespace SourceGit.Views
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyDown(KeyEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnKeyDown(e);
|
||||||
|
|
||||||
|
if (!e.Handled)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Down && ViewModels.Welcome.Instance.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
TreeContainer.SelectedIndex = 0;
|
||||||
|
TreeContainer.Focus(NavigationMethod.Directional);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
else if (e.Key == Key.F &&
|
||||||
|
((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Meta)) ||
|
||||||
|
(!OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Control))))
|
||||||
|
{
|
||||||
|
SearchBox.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SetupTreeViewDragAndDrop(object sender, RoutedEventArgs _)
|
private void SetupTreeViewDragAndDrop(object sender, RoutedEventArgs _)
|
||||||
{
|
{
|
||||||
if (sender is TreeView view)
|
if (sender is ListBox view)
|
||||||
{
|
{
|
||||||
DragDrop.SetAllowDrop(view, true);
|
DragDrop.SetAllowDrop(view, true);
|
||||||
view.AddHandler(DragDrop.DragOverEvent, DragOverTreeView);
|
view.AddHandler(DragDrop.DragOverEvent, DragOverTreeView);
|
||||||
|
@ -35,65 +74,23 @@ namespace SourceGit.Views
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSearchBoxKeyDown(object sender, KeyEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Key == Key.Down || e.Key == Key.FnDownArrow)
|
|
||||||
{
|
|
||||||
var containers = ReposTree.GetRealizedContainers();
|
|
||||||
if (containers == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var c in containers)
|
|
||||||
{
|
|
||||||
if (c is TreeViewItem { IsVisible: true } item)
|
|
||||||
{
|
|
||||||
ReposTree.SelectedItem = item.DataContext;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTreeViewKeyDown(object sender, KeyEventArgs e)
|
private void OnTreeViewKeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (ReposTree.SelectedItem is ViewModels.RepositoryNode node)
|
if (TreeContainer.SelectedItem is ViewModels.RepositoryNode node && e.Key == Key.Enter)
|
||||||
{
|
{
|
||||||
if (e.Key == Key.Space && node.IsRepository)
|
if (node.IsRepository)
|
||||||
{
|
{
|
||||||
var parent = this.FindAncestorOfType<Launcher>();
|
var parent = this.FindAncestorOfType<Launcher>();
|
||||||
if (parent?.DataContext is ViewModels.Launcher launcher)
|
if (parent is { DataContext: ViewModels.Launcher launcher })
|
||||||
launcher.OpenRepositoryInTab(node, null);
|
launcher.OpenRepositoryInTab(node, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ViewModels.Welcome.Instance.ToggleNodeIsExpanded(node);
|
||||||
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
else if (e.Key == Key.Down)
|
|
||||||
{
|
|
||||||
var next = ViewModels.Welcome.Instance.GetNextVisible(node);
|
|
||||||
if (next != null)
|
|
||||||
ReposTree.SelectedItem = next;
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
else if (e.Key == Key.Up)
|
|
||||||
{
|
|
||||||
var prev = ViewModels.Welcome.Instance.GetPrevVisible(node);
|
|
||||||
if (prev != null)
|
|
||||||
ReposTree.SelectedItem = prev;
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTreeViewSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (ReposTree.SelectedItem is ViewModels.RepositoryNode node)
|
|
||||||
{
|
|
||||||
var item = FindTreeViewItemByNode(node, ReposTree);
|
|
||||||
item?.Focus(NavigationMethod.Directional);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTreeNodeContextRequested(object sender, ContextRequestedEventArgs e)
|
private void OnTreeNodeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||||
|
@ -256,17 +253,22 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
private void OnDoubleTappedTreeNode(object sender, TappedEventArgs e)
|
private void OnDoubleTappedTreeNode(object sender, TappedEventArgs e)
|
||||||
{
|
{
|
||||||
var grid = sender as Grid;
|
if (sender is Grid { DataContext: ViewModels.RepositoryNode node })
|
||||||
var to = grid?.DataContext as ViewModels.RepositoryNode;
|
{
|
||||||
if (to is not { IsRepository: true })
|
if (node.IsRepository)
|
||||||
return;
|
{
|
||||||
|
|
||||||
var parent = this.FindAncestorOfType<Launcher>();
|
var parent = this.FindAncestorOfType<Launcher>();
|
||||||
if (parent?.DataContext is ViewModels.Launcher launcher)
|
if (parent is { DataContext: ViewModels.Launcher launcher })
|
||||||
launcher.OpenRepositoryInTab(to, null);
|
launcher.OpenRepositoryInTab(node, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ViewModels.Welcome.Instance.ToggleNodeIsExpanded(node);
|
||||||
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OpenOrInitRepository(string path, ViewModels.RepositoryNode parent = null)
|
private void OpenOrInitRepository(string path, ViewModels.RepositoryNode parent = null)
|
||||||
{
|
{
|
||||||
|
@ -287,30 +289,12 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
var normalizedPath = root.Replace("\\", "/");
|
var normalizedPath = root.Replace("\\", "/");
|
||||||
var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, true);
|
var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, true);
|
||||||
|
ViewModels.Welcome.Instance.Refresh();
|
||||||
|
|
||||||
var launcher = this.FindAncestorOfType<Launcher>()?.DataContext as ViewModels.Launcher;
|
var launcher = this.FindAncestorOfType<Launcher>()?.DataContext as ViewModels.Launcher;
|
||||||
launcher?.OpenRepositoryInTab(node, launcher.ActivePage);
|
launcher?.OpenRepositoryInTab(node, launcher.ActivePage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TreeViewItem FindTreeViewItemByNode(ViewModels.RepositoryNode node, ItemsControl container)
|
|
||||||
{
|
|
||||||
var items = container.GetRealizedContainers();
|
|
||||||
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
if (item is TreeViewItem { DataContext: ViewModels.RepositoryNode test } treeViewItem)
|
|
||||||
{
|
|
||||||
if (test == node)
|
|
||||||
return treeViewItem;
|
|
||||||
|
|
||||||
var child = FindTreeViewItemByNode(node, treeViewItem);
|
|
||||||
if (child != null)
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _pressedTreeNode = false;
|
private bool _pressedTreeNode = false;
|
||||||
private Point _pressedTreeNodePosition = new Point();
|
private Point _pressedTreeNodePosition = new Point();
|
||||||
private bool _startDragTreeNode = false;
|
private bool _startDragTreeNode = false;
|
||||||
|
|
|
@ -56,6 +56,8 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
var normalizedPath = root.Replace("\\", "/");
|
var normalizedPath = root.Replace("\\", "/");
|
||||||
var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, false);
|
var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, false);
|
||||||
|
ViewModels.Welcome.Instance.Refresh();
|
||||||
|
|
||||||
var launcher = this.FindAncestorOfType<Launcher>()?.DataContext as ViewModels.Launcher;
|
var launcher = this.FindAncestorOfType<Launcher>()?.DataContext as ViewModels.Launcher;
|
||||||
launcher?.OpenRepositoryInTab(node, launcher.ActivePage);
|
launcher?.OpenRepositoryInTab(node, launcher.ActivePage);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue