Files
sourcegit/src/Views/SubmodulesView.axaml.cs
2026-04-15 12:14:55 +08:00

339 lines
13 KiB
C#

using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace SourceGit.Views
{
public class SubmoduleTreeNodeToggleButton : ToggleButton
{
protected override Type StyleKeyOverride => typeof(ToggleButton);
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
DataContext is ViewModels.SubmoduleTreeNode { IsFolder: true } node)
{
var view = this.FindAncestorOfType<SubmodulesView>();
view?.ToggleNodeIsExpanded(node);
}
e.Handled = true;
}
}
public class SubmoduleTreeNodeIcon : UserControl
{
public static readonly StyledProperty<bool> IsExpandedProperty =
AvaloniaProperty.Register<SubmoduleTreeNodeIcon, bool>(nameof(IsExpanded));
public bool IsExpanded
{
get => GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsExpandedProperty)
UpdateContent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
UpdateContent();
}
private void UpdateContent()
{
if (DataContext is not ViewModels.SubmoduleTreeNode node)
{
Content = null;
return;
}
if (node.Module != null)
CreateContent(new Thickness(0, 0, 0, 0), "Icons.Submodule");
else if (node.IsExpanded)
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder.Open");
else
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder");
}
private void CreateContent(Thickness margin, string iconKey)
{
if (this.FindResource(iconKey) is not StreamGeometry geo)
return;
Content = new Avalonia.Controls.Shapes.Path()
{
Width = 12,
Height = 12,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
Margin = margin,
Data = geo,
};
}
}
public partial class SubmodulesView : UserControl
{
public static readonly RoutedEvent<RoutedEventArgs> RowsChangedEvent =
RoutedEvent.Register<TagsView, RoutedEventArgs>(nameof(RowsChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> RowsChanged
{
add { AddHandler(RowsChangedEvent, value); }
remove { RemoveHandler(RowsChangedEvent, value); }
}
public static readonly RoutedEvent<RoutedEventArgs> SearchRequestedEvent =
RoutedEvent.Register<BranchTree, RoutedEventArgs>(nameof(SearchRequested), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> SearchRequested
{
add { AddHandler(SearchRequestedEvent, value); }
remove { RemoveHandler(SearchRequestedEvent, value); }
}
public int Rows
{
get;
private set;
}
public SubmodulesView()
{
InitializeComponent();
}
public void ToggleNodeIsExpanded(ViewModels.SubmoduleTreeNode node)
{
if (Content is ViewModels.SubmoduleCollectionAsTree tree)
{
tree.ToggleExpand(node);
Rows = tree.Rows.Count;
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ContentProperty)
{
if (Content is ViewModels.SubmoduleCollectionAsTree tree)
Rows = tree.Rows.Count;
else if (Content is ViewModels.SubmoduleCollectionAsList list)
Rows = list.Submodules.Count;
else
Rows = 0;
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
else if (change.Property == IsVisibleProperty)
{
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
}
private void OnItemDoubleTapped(object sender, TappedEventArgs e)
{
if (sender is Control control && DataContext is ViewModels.Repository repo)
{
if (control.DataContext is ViewModels.SubmoduleTreeNode node)
{
if (node.IsFolder)
ToggleNodeIsExpanded(node);
else if (node.Module.Status != Models.SubmoduleStatus.NotInited)
repo.OpenSubmodule(node.Module.Path);
}
else if (control.DataContext is Models.Submodule m && m.Status != Models.SubmoduleStatus.NotInited)
{
repo.OpenSubmodule(m.Path);
}
}
e.Handled = true;
}
private void OnItemContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is Control control && DataContext is ViewModels.Repository repo)
{
var submodule = control.DataContext switch
{
ViewModels.SubmoduleTreeNode node => node.Module,
Models.Submodule m => m,
_ => null,
};
if (submodule != null)
{
var open = new MenuItem();
open.Header = App.Text("Submodule.Open");
open.Icon = this.CreateMenuIcon("Icons.Folder.Open");
open.IsEnabled = submodule.Status != Models.SubmoduleStatus.NotInited;
open.Click += (_, ev) =>
{
repo.OpenSubmodule(submodule.Path);
ev.Handled = true;
};
var update = new MenuItem();
update.Header = App.Text("Submodule.Update");
update.Icon = this.CreateMenuIcon("Icons.Loading");
update.Click += (_, ev) =>
{
if (repo.CanCreatePopup())
repo.ShowPopup(new ViewModels.UpdateSubmodules(repo, submodule));
ev.Handled = true;
};
var move = new MenuItem();
move.Header = App.Text("Submodule.Move");
move.Icon = this.CreateMenuIcon("Icons.MoveTo");
move.Click += (_, ev) =>
{
if (repo.CanCreatePopup())
repo.ShowPopup(new ViewModels.MoveSubmodule(repo, submodule));
ev.Handled = true;
};
var setURL = new MenuItem();
setURL.Header = App.Text("Submodule.SetURL");
setURL.Icon = this.CreateMenuIcon("Icons.Edit");
setURL.Click += (_, ev) =>
{
if (repo.CanCreatePopup())
repo.ShowPopup(new ViewModels.ChangeSubmoduleUrl(repo, submodule));
ev.Handled = true;
};
var setBranch = new MenuItem();
setBranch.Header = App.Text("Submodule.SetBranch");
setBranch.Icon = this.CreateMenuIcon("Icons.Track");
setBranch.Click += (_, ev) =>
{
if (repo.CanCreatePopup())
repo.ShowPopup(new ViewModels.SetSubmoduleBranch(repo, submodule));
ev.Handled = true;
};
var deinit = new MenuItem();
deinit.Header = App.Text("Submodule.Deinit");
deinit.Icon = this.CreateMenuIcon("Icons.Undo");
deinit.IsEnabled = submodule.Status != Models.SubmoduleStatus.NotInited;
deinit.Click += (_, ev) =>
{
if (repo.CanCreatePopup())
repo.ShowPopup(new ViewModels.DeinitSubmodule(repo, submodule.Path));
ev.Handled = true;
};
var rm = new MenuItem();
rm.Header = App.Text("Submodule.Remove");
rm.Icon = this.CreateMenuIcon("Icons.Clear");
rm.Click += (_, ev) =>
{
if (repo.CanCreatePopup())
repo.ShowPopup(new ViewModels.DeleteSubmodule(repo, submodule.Path));
ev.Handled = true;
};
var histories = new MenuItem();
histories.Header = App.Text("Submodule.Histories");
histories.Icon = this.CreateMenuIcon("Icons.Histories");
histories.Click += (_, ev) =>
{
this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path));
ev.Handled = true;
};
var copySHA = new MenuItem();
copySHA.Header = App.Text("CommitDetail.Info.SHA");
copySHA.Icon = this.CreateMenuIcon("Icons.Hash");
copySHA.Click += async (_, ev) =>
{
await this.CopyTextAsync(submodule.SHA);
ev.Handled = true;
};
var copyBranch = new MenuItem();
copyBranch.Header = App.Text("Submodule.CopyBranch");
copyBranch.Icon = this.CreateMenuIcon("Icons.Branch");
copyBranch.Click += async (_, ev) =>
{
await this.CopyTextAsync(submodule.Branch);
ev.Handled = true;
};
var copyRelativePath = new MenuItem();
copyRelativePath.Header = App.Text("Submodule.CopyPath");
copyRelativePath.Icon = this.CreateMenuIcon("Icons.Folder");
copyRelativePath.Click += async (_, ev) =>
{
await this.CopyTextAsync(submodule.Path);
ev.Handled = true;
};
var copyURL = new MenuItem();
copyURL.Header = App.Text("Submodule.URL");
copyURL.Icon = this.CreateMenuIcon("Icons.Link");
copyURL.Click += async (_, ev) =>
{
await this.CopyTextAsync(submodule.URL);
ev.Handled = true;
};
var copy = new MenuItem();
copy.Header = App.Text("Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.Items.Add(copySHA);
copy.Items.Add(copyBranch);
copy.Items.Add(copyRelativePath);
copy.Items.Add(copyURL);
var menu = new ContextMenu();
menu.Items.Add(open);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(update);
menu.Items.Add(setURL);
menu.Items.Add(setBranch);
menu.Items.Add(move);
menu.Items.Add(deinit);
menu.Items.Add(rm);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(histories);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copy);
menu.Open(control);
}
}
e.Handled = true;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F && e.KeyModifiers == (OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control))
{
RaiseEvent(new RoutedEventArgs(SearchRequestedEvent));
e.Handled = true;
return;
}
}
}
}