diff --git a/src/Models/RepositoryUIStates.cs b/src/Models/RepositoryUIStates.cs index 0b35f8d618..659fd8bef4 100644 --- a/src/Models/RepositoryUIStates.cs +++ b/src/Models/RepositoryUIStates.cs @@ -7,6 +7,18 @@ namespace SourceGit.Models { + public enum RepositoryWorkspaceOrientation + { + SideBySide, + Stacked, + } + + public enum RepositoryNavigationPlacement + { + Sidebar, + Top, + } + public class RepositoryUIStates { public HistoryShowFlags HistoryShowFlags @@ -45,6 +57,36 @@ public double AuthorColumnWidth set; } = 120; + public int SplitPrimaryViewIndex + { + get; + set; + } = -1; + + public int SecondaryViewIndex + { + get; + set; + } = -1; + + public RepositoryWorkspaceOrientation WorkspaceOrientation + { + get; + set; + } = RepositoryWorkspaceOrientation.SideBySide; + + public double WorkspaceSplitRatio + { + get; + set; + } = 0.5; + + public RepositoryNavigationPlacement NavigationPlacement + { + get; + set; + } = RepositoryNavigationPlacement.Sidebar; + public bool EnableTopoOrderInHistory { get; diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 96399d52ef..82c2b4dd5a 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -798,6 +798,7 @@ Run `git gc` command for this repository. Clear all Clear + Close Secondary View Configure this repository CONTINUE Custom Actions @@ -822,10 +823,15 @@ LOCAL BRANCHES More options... Navigate to HEAD + Repository navigation + VIEW NAVIGATION + Sidebar + Top Create Branch CLEAR NOTIFICATIONS Open as Folder Open in {0} + Open in Secondary Pane Open in External Tools REMOTES Add Remote @@ -844,7 +850,10 @@ Show Submodules as Tree Show Tags as Tree SKIP + Split Side by Side + Split Stacked Statistics + Swap Views SUBMODULES Add Submodule Update Submodule diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 450bdb41c6..45745a593d 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -74,15 +74,89 @@ public int SelectedViewIndex get => _selectedViewIndex; set { + if (!IsValidWorkspaceView(value)) + return; + + if (IsSplitViewEnabled && value == SecondaryViewIndex) + { + SwapWorkspaceViews(); + return; + } + if (SetProperty(ref _selectedViewIndex, value)) { - OnPropertyChanged(nameof(IsHistoriesVisible)); - OnPropertyChanged(nameof(IsWorkingCopyVisible)); - OnPropertyChanged(nameof(IsStashesVisible)); + if (IsSplitViewEnabled) + _uiStates.SplitPrimaryViewIndex = value; + NotifyWorkspaceVisibilityChanged(); + } + } + } + + public int SecondaryViewIndex + { + get => _uiStates.SecondaryViewIndex; + private set + { + if (_uiStates.SecondaryViewIndex != value) + { + _uiStates.SecondaryViewIndex = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsSplitViewEnabled)); + NotifyWorkspaceVisibilityChanged(); + } + } + } + + public bool IsSplitViewEnabled + { + get => SecondaryViewIndex >= 0; + } + + public Models.RepositoryWorkspaceOrientation WorkspaceOrientation + { + get => _uiStates.WorkspaceOrientation; + private set + { + if (_uiStates.WorkspaceOrientation != value) + { + _uiStates.WorkspaceOrientation = value; + OnPropertyChanged(); + } + } + } + + public double WorkspaceSplitRatio + { + get => double.IsFinite(_uiStates.WorkspaceSplitRatio) ? Math.Clamp(_uiStates.WorkspaceSplitRatio, 0.2, 0.8) : 0.5; + set + { + var ratio = double.IsFinite(value) ? Math.Clamp(value, 0.2, 0.8) : 0.5; + if (Math.Abs(_uiStates.WorkspaceSplitRatio - ratio) > 0.001) + { + _uiStates.WorkspaceSplitRatio = ratio; + OnPropertyChanged(); + } + } + } + + public Models.RepositoryNavigationPlacement NavigationPlacement + { + get => _uiStates.NavigationPlacement; + private set + { + if (_uiStates.NavigationPlacement != value) + { + _uiStates.NavigationPlacement = value; + OnPropertyChanged(); } } } + public void SetNavigationPlacement(Models.RepositoryNavigationPlacement placement) + { + NavigationPlacement = placement; + } + public Histories Histories { get => _histories; @@ -100,17 +174,70 @@ public StashesPage StashesPage public bool IsHistoriesVisible { - get => SelectedViewIndex == 0; + get => SelectedViewIndex == 0 || SecondaryViewIndex == 0; } public bool IsWorkingCopyVisible { - get => SelectedViewIndex == 1; + get => SelectedViewIndex == 1 || SecondaryViewIndex == 1; } public bool IsStashesVisible { - get => SelectedViewIndex == 2; + get => SelectedViewIndex == 2 || SecondaryViewIndex == 2; + } + + public void OpenViewInSecondary(int viewIndex, Models.RepositoryWorkspaceOrientation orientation) + { + if (!IsValidWorkspaceView(viewIndex) || viewIndex == SelectedViewIndex) + return; + + if (!IsSplitViewEnabled) + _uiStates.SplitPrimaryViewIndex = SelectedViewIndex; + + WorkspaceOrientation = orientation; + SecondaryViewIndex = viewIndex; + } + + public void SetWorkspaceOrientation(Models.RepositoryWorkspaceOrientation orientation) + { + if (IsSplitViewEnabled) + WorkspaceOrientation = orientation; + } + + public void CloseSecondaryView() + { + if (!IsSplitViewEnabled) + return; + + SecondaryViewIndex = -1; + _uiStates.SplitPrimaryViewIndex = -1; + } + + public void SwapWorkspaceViews() + { + if (!IsSplitViewEnabled) + return; + + var primary = SelectedViewIndex; + _selectedViewIndex = SecondaryViewIndex; + _uiStates.SecondaryViewIndex = primary; + _uiStates.SplitPrimaryViewIndex = _selectedViewIndex; + OnPropertyChanged(nameof(SelectedViewIndex)); + OnPropertyChanged(nameof(SecondaryViewIndex)); + NotifyWorkspaceVisibilityChanged(); + } + + private bool IsValidWorkspaceView(int viewIndex) + { + return viewIndex == 0 || (!IsBare && viewIndex is 1 or 2); + } + + private void NotifyWorkspaceVisibilityChanged() + { + OnPropertyChanged(nameof(IsHistoriesVisible)); + OnPropertyChanged(nameof(IsWorkingCopyVisible)); + OnPropertyChanged(nameof(IsStashesVisible)); } public bool EnableTopoOrderInHistory @@ -495,12 +622,41 @@ public void Open() _workingCopy = new WorkingCopy(this) { CommitMessage = _uiStates.LastCommitMessage }; _stashesPage = new StashesPage(this); _searchCommitContext = new SearchCommitContext(this); - _selectedViewIndex = Preferences.Instance.ShowLocalChangesByDefault ? 1 : 0; + RestoreWorkspaceState(); _lastFetchTime = DateTime.Now; _autoFetchTimer = new Timer(AutoFetchByTimer, null, 5000, 5000); RefreshAll(); } + private void RestoreWorkspaceState() + { + var defaultView = Preferences.Instance.ShowLocalChangesByDefault && !IsBare ? 1 : 0; + var primary = _uiStates.SplitPrimaryViewIndex; + var secondary = _uiStates.SecondaryViewIndex; + if (!IsValidWorkspaceView(primary) || + !IsValidWorkspaceView(secondary) || + primary == secondary) + { + _selectedViewIndex = defaultView; + _uiStates.SplitPrimaryViewIndex = -1; + _uiStates.SecondaryViewIndex = -1; + } + else + { + _selectedViewIndex = primary; + } + + if (!Enum.IsDefined(_uiStates.WorkspaceOrientation)) + _uiStates.WorkspaceOrientation = Models.RepositoryWorkspaceOrientation.SideBySide; + + if (!Enum.IsDefined(_uiStates.NavigationPlacement)) + _uiStates.NavigationPlacement = Models.RepositoryNavigationPlacement.Sidebar; + + _uiStates.WorkspaceSplitRatio = double.IsFinite(_uiStates.WorkspaceSplitRatio) + ? Math.Clamp(_uiStates.WorkspaceSplitRatio, 0.2, 0.8) + : 0.5; + } + public void Close() { var commitMessage = _workingCopy.CommitMessage; diff --git a/src/Views/DiffView.axaml.cs b/src/Views/DiffView.axaml.cs index d6d4b6c23d..6a9256b36f 100644 --- a/src/Views/DiffView.axaml.cs +++ b/src/Views/DiffView.axaml.cs @@ -43,7 +43,11 @@ protected override void OnLoaded(RoutedEventArgs e) if (DataContext is ViewModels.DiffContext vm) vm.CheckSettings(); - ToggleHotkeyBindings(IsEffectivelyVisible); + var repository = this.FindAncestorOfType(); + if (repository != null) + repository.UpdateWorkspaceHotkeys(); + else + ToggleHotkeyBindings(IsEffectivelyVisible); } private void OnGotoFirstChange(object _, RoutedEventArgs e) diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index 61f349b876..224c0c6ed0 100644 --- a/src/Views/Histories.axaml +++ b/src/Views/Histories.axaml @@ -33,6 +33,7 @@ SelectedCommits="{Binding SelectedCommits, Mode=TwoWay}" ColumnHeaderHeight="24" RowHeight="26" + SizeChanged="OnCommitListSizeChanged" LayoutUpdated="OnCommitListLayoutUpdated" ContextRequested="OnCommitListContextRequested" DoubleTapped="OnCommitListDoubleTapped"> @@ -99,7 +100,7 @@ - + @@ -146,9 +147,9 @@ - + - + @@ -174,9 +175,9 @@ - + - + @@ -191,9 +192,9 @@ - + - + @@ -211,9 +212,9 @@ - + - + @@ -231,7 +232,7 @@ - + diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs index 160e66ca6e..807dff37a3 100644 --- a/src/Views/Histories.axaml.cs +++ b/src/Views/Histories.axaml.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -36,19 +38,26 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang { base.OnPropertyChanged(change); - if (change.Property == UseHorizontalProperty && IsLoaded) + if ((change.Property == UseHorizontalProperty || change.Property == BoundsProperty) && IsLoaded) RefreshLayout(); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - RefreshLayout(); + RefreshLayout(true); } - private void RefreshLayout() + private void RefreshLayout(bool force = false) { - if (UseHorizontal) + var useHorizontal = UseHorizontal && Bounds.Width >= 720; + if (!force && _hasAppliedLayout && useHorizontal == _lastUseHorizontal) + return; + + _hasAppliedLayout = true; + _lastUseHorizontal = useHorizontal; + + if (useHorizontal) { var rowSpan = RowDefinitions.Count; for (int i = 0; i < Children.Count; i++) @@ -81,6 +90,8 @@ private void RefreshLayout() } private bool _useHorizontal = false; + private bool _hasAppliedLayout; + private bool _lastUseHorizontal; } public class HistoriesCommitList : DataGrid @@ -295,8 +306,19 @@ private void DecrNoSelectionChangeCount() private List _selectedCommits = []; } + public class HistoriesDataGridColumn : DataGridTemplateColumn + { + public string Name + { + get; + set; + } = string.Empty; + } + public partial class Histories : UserControl { + private const double GraphAndSubjectMinWidth = 320; + public static readonly DirectProperty CurrentBranchProperty = AvaloniaProperty.RegisterDirect( nameof(CurrentBranch), @@ -372,6 +394,101 @@ public bool IsDetailsPanelExpanded public Histories() { InitializeComponent(); + GraphAndSubjectColumn = FindColumn(nameof(GraphAndSubjectColumn)); + AuthorColumn = FindColumn(nameof(AuthorColumn)); + SHAColumn = FindColumn(nameof(SHAColumn)); + AuthorTimeColumn = FindColumn(nameof(AuthorTimeColumn)); + CommitTimeColumn = FindColumn(nameof(CommitTimeColumn)); + GraphAndSubjectColumn.MinWidth = GraphAndSubjectMinWidth; + } + + private HistoriesDataGridColumn FindColumn(string name) + { + return CommitListContainer.Columns.OfType().Single(x => x.Name == name); + } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + + if (DataContext is ViewModels.Histories vm) + SubscribeToViewModel(vm); + + ApplyResponsiveCommitColumns(); + } + + protected override void OnUnloaded(RoutedEventArgs e) + { + UnsubscribeFromViewModel(); + base.OnUnloaded(e); + } + + private void OnCommitListSizeChanged(object _, SizeChangedEventArgs e) + { + if (e.WidthChanged) + ApplyResponsiveCommitColumns(); + } + + private void ApplyResponsiveCommitColumns() + { + if (DataContext is not ViewModels.Histories vm || CommitListContainer.Bounds.Width <= 0) + return; + + var authorWidth = Math.Max(AuthorColumn.MinWidth, vm.AuthorColumnWidth); + AuthorColumn.Width = new DataGridLength(authorWidth, DataGridLengthUnitType.Pixel); + + var remaining = Math.Max(0, CommitListContainer.Bounds.Width - GraphAndSubjectMinWidth); + bool AdmitColumn(DataGridColumn column, bool requested, double width) + { + var visible = requested && remaining >= width; + if (visible) + remaining -= width; + + column.SetCurrentValue(DataGridColumn.IsVisibleProperty, visible); + return visible; + } + + AdmitColumn(AuthorColumn, vm.IsAuthorColumnVisible, authorWidth); + + if (vm.IsCommitTimeColumnVisible) + AdmitColumn(CommitTimeColumn, true, CommitTimeColumn.MinWidth); + else + AdmitColumn(AuthorTimeColumn, vm.IsAuthorTimeColumnVisible, AuthorTimeColumn.MinWidth); + + AdmitColumn(SHAColumn, vm.IsSHAColumnVisible, SHAColumn.MinWidth); + + if (vm.IsCommitTimeColumnVisible) + AdmitColumn(AuthorTimeColumn, vm.IsAuthorTimeColumnVisible, AuthorTimeColumn.MinWidth); + else + CommitTimeColumn.SetCurrentValue(DataGridColumn.IsVisibleProperty, false); + } + + private void SubscribeToViewModel(ViewModels.Histories vm) + { + if (_subscribedViewModel == vm) + return; + + UnsubscribeFromViewModel(); + _subscribedViewModel = vm; + _subscribedViewModel.PropertyChanged += OnHistoriesViewModelPropertyChanged; + } + + private void UnsubscribeFromViewModel() + { + if (_subscribedViewModel == null) + return; + + _subscribedViewModel.PropertyChanged -= OnHistoriesViewModelPropertyChanged; + _subscribedViewModel = null; + } + + private void OnHistoriesViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(ViewModels.Histories.IsAuthorColumnVisible) or + nameof(ViewModels.Histories.IsSHAColumnVisible) or + nameof(ViewModels.Histories.IsAuthorTimeColumnVisible) or + nameof(ViewModels.Histories.IsCommitTimeColumnVisible)) + ApplyResponsiveCommitColumns(); } public async Task GotoParent() @@ -465,7 +582,17 @@ protected override void OnDataContextChanged(EventArgs e) base.OnDataContextChanged(e); if (DataContext is ViewModels.Histories vm) - CommitListContainer.Columns[1].Width = new(vm.AuthorColumnWidth, DataGridLengthUnitType.Pixel); + { + if (IsLoaded) + SubscribeToViewModel(vm); + AuthorColumn.Width = new(vm.AuthorColumnWidth, DataGridLengthUnitType.Pixel); + } + else + { + UnsubscribeFromViewModel(); + } + + ApplyResponsiveCommitColumns(); } private void OnCommitListHeaderPointerMoved(object sender, PointerEventArgs e) @@ -473,22 +600,26 @@ private void OnCommitListHeaderPointerMoved(object sender, PointerEventArgs e) if (sender is not Border border) return; - if (DataContext is not ViewModels.Histories { IsAuthorColumnVisible: true } vm) + if (DataContext is not ViewModels.Histories vm) + return; + + if (!AuthorColumn.IsVisible) return; var pos = e.GetPosition(border); if (_resizingAuthorColumn) { - var posX = CommitListContainer.Columns[0].ActualWidth; - var maxW = posX + CommitListContainer.Columns[1].ActualWidth - 100; + var posX = GraphAndSubjectColumn.ActualWidth; + var otherColumnsWidth = GetVisibleMetadataWidthExceptAuthor(); + var maxW = Math.Max(AuthorColumn.MinWidth, CommitListContainer.Bounds.Width - GraphAndSubjectMinWidth - otherColumnsWidth); var delta = posX - pos.X; - var w = Math.Max(Math.Min(vm.AuthorColumnWidth + delta, maxW), 80); - CommitListContainer.Columns[1].Width = new(w, DataGridLengthUnitType.Pixel); + var w = Math.Clamp(vm.AuthorColumnWidth + delta, AuthorColumn.MinWidth, maxW); + AuthorColumn.Width = new(w, DataGridLengthUnitType.Pixel); vm.AuthorColumnWidth = w; } else { - var dis = CommitListContainer.Columns[0].ActualWidth - 4 - pos.X; + var dis = GraphAndSubjectColumn.ActualWidth - 4 - pos.X; if (dis < 4 && dis > -4) { if (border.Cursor != _resizingCursor) @@ -506,8 +637,11 @@ private void OnCommitListHeaderPointerPressed(object sender, PointerPressedEvent if (sender is not Border border) return; + if (!AuthorColumn.IsVisible) + return; + var pos = e.GetPosition(border); - var dis = CommitListContainer.Columns[0].ActualWidth - 4 - pos.X; + var dis = GraphAndSubjectColumn.ActualWidth - 4 - pos.X; if (dis > 4 || dis < -4) return; @@ -523,6 +657,18 @@ private void OnCommitListHeaderPointerReleased(object sender, PointerReleasedEve _resizingAuthorColumn = false; } + private double GetVisibleMetadataWidthExceptAuthor() + { + var width = 0.0; + if (SHAColumn.IsVisible) + width += SHAColumn.ActualWidth; + if (AuthorTimeColumn.IsVisible) + width += AuthorTimeColumn.ActualWidth; + if (CommitTimeColumn.IsVisible) + width += CommitTimeColumn.ActualWidth; + return width; + } + private void OnCommitListHeaderContextRequested(object sender, ContextRequestedEventArgs e) { if (DataContext is not ViewModels.Histories vm) @@ -614,7 +760,7 @@ private void OnCommitListLayoutUpdated(object _1, EventArgs _2) IsScrollToTopVisible = startY >= rowHeight; - var clipWidth = dataGrid.Columns[0].ActualWidth - 4; + var clipWidth = GraphAndSubjectColumn.ActualWidth - 4; var lastLayout = CommitGraph.Layout; if (lastLayout == null || Math.Abs(lastLayout.StartY - startY) > 0.01 || @@ -1754,5 +1900,11 @@ private async Task InteractiveRebaseWithPrefillActionAsync(ViewModels.Repository private bool _isDetailsPanelExpanded = true; private bool _resizingAuthorColumn = false; private Cursor _resizingCursor = new(StandardCursorType.SizeWestEast); + private ViewModels.Histories _subscribedViewModel = null; + private HistoriesDataGridColumn GraphAndSubjectColumn { get; } + private HistoriesDataGridColumn AuthorColumn { get; } + private HistoriesDataGridColumn SHAColumn { get; } + private HistoriesDataGridColumn AuthorTimeColumn { get; } + private HistoriesDataGridColumn CommitTimeColumn { get; } } } diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml index 00aa09a228..f9293354c2 100644 --- a/src/Views/Launcher.axaml +++ b/src/Views/Launcher.axaml @@ -11,7 +11,7 @@ x:Name="ThisControl" Icon="/App.ico" Title="{Binding Title}" - MinWidth="1024" MinHeight="600"> + MinWidth="480" MinHeight="360"> diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 8c9557e53d..ba2cc0cd2b 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -9,167 +9,110 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.Repository" x:DataType="vm:Repository"> - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -707,7 +650,7 @@ - - - + + + + + + @@ -821,7 +775,7 @@ Click="OnAbortInProgress"/> - - + @@ -966,7 +920,7 @@ VerticalAlignment="Center" Filter="{Binding Mode=OneWay}" IsFilterValid="{Binding IsValid, Mode=OneWay}"/> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index 0d03acb89b..21d1af473d 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -18,6 +18,72 @@ public RepositoryToolbar() InitializeComponent(); } + private void OnToolbarSizeChanged(object _, SizeChangedEventArgs e) + { + if (!e.WidthChanged) + return; + + var compact = e.NewSize.Width < 760; + FullToolbar.IsVisible = !compact; + CompactToolbar.IsVisible = compact; + } + + private void OpenCompactOperations(object sender, RoutedEventArgs ev) + { + if (sender is not Button button || DataContext is not ViewModels.Repository repo) + return; + + var logs = new MenuItem { Header = App.Text("Repository.ViewLogs"), Icon = this.CreateMenuIcon("Icons.Logs") }; + logs.Click += (_, e) => OpenGitLogs(button, e); + var statistics = new MenuItem { Header = App.Text("Repository.Statistics"), Icon = this.CreateMenuIcon("Icons.Statistics") }; + statistics.Click += (_, e) => OpenStatistics(button, e); + var configure = new MenuItem { Header = App.Text("Repository.Configure"), Icon = this.CreateMenuIcon("Icons.Settings") }; + configure.Click += (_, e) => OpenConfigure(button, e); + + var stash = new MenuItem { Header = App.Text("Stash"), Icon = this.CreateMenuIcon("Icons.Stashes.Add") }; + stash.Click += async (_, e) => + { + await repo.StashAllAsync(false); + e.Handled = true; + }; + var applyPatch = new MenuItem { Header = App.Text("Apply.Title"), Icon = this.CreateMenuIcon("Icons.ApplyPatch") }; + applyPatch.Click += (_, e) => + { + repo.ApplyPatch(); + e.Handled = true; + }; + + var gitFlow = new MenuItem { Header = App.Text("GitFlow"), Icon = this.CreateMenuIcon("Icons.GitFlow") }; + gitFlow.Click += (_, e) => OpenGitFlowMenu(button, e); + var gitLfs = new MenuItem { Header = App.Text("GitLFS"), Icon = this.CreateMenuIcon("Icons.LFS") }; + gitLfs.Click += (_, e) => OpenGitLFSMenu(button, e); + var bisect = new MenuItem { Header = App.Text("Bisect"), Icon = this.CreateMenuIcon("Icons.Bisect") }; + bisect.Click += (_, e) => StartBisect(button, e); + var customActions = new MenuItem { Header = App.Text("Repository.CustomActions"), Icon = this.CreateMenuIcon("Icons.Action") }; + customActions.Click += (_, e) => OpenCustomActionMenu(button, e); + var cleanup = new MenuItem { Header = App.Text("Repository.Clean"), Icon = this.CreateMenuIcon("Icons.Clean") }; + cleanup.Click += (_, e) => Cleanup(button, e); + + var menu = new ContextMenu { Placement = PlacementMode.BottomEdgeAlignedLeft }; + menu.Items.Add(logs); + menu.Items.Add(statistics); + menu.Items.Add(configure); + menu.Items.Add(new MenuItem { Header = "-" }); + if (!repo.IsBare) + { + menu.Items.Add(stash); + menu.Items.Add(applyPatch); + menu.Items.Add(new MenuItem { Header = "-" }); + menu.Items.Add(gitFlow); + menu.Items.Add(gitLfs); + menu.Items.Add(bisect); + } + menu.Items.Add(customActions); + menu.Items.Add(cleanup); + menu.Open(button); + ev.Handled = true; + } + private void OpenWithExternalTools(object sender, RoutedEventArgs ev) { if (sender is Button button && DataContext is ViewModels.Repository repo) diff --git a/src/Views/RepositoryViewSwitcher.axaml b/src/Views/RepositoryViewSwitcher.axaml new file mode 100644 index 0000000000..0de085db12 --- /dev/null +++ b/src/Views/RepositoryViewSwitcher.axaml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/RepositoryViewSwitcher.axaml.cs b/src/Views/RepositoryViewSwitcher.axaml.cs new file mode 100644 index 0000000000..61946f2da6 --- /dev/null +++ b/src/Views/RepositoryViewSwitcher.axaml.cs @@ -0,0 +1,67 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.VisualTree; + +namespace SourceGit.Views +{ + public partial class RepositoryViewSwitcher : UserControl + { + public static readonly StyledProperty OrientationProperty = + AvaloniaProperty.Register(nameof(Orientation), Orientation.Vertical); + + public Orientation Orientation + { + get => GetValue(OrientationProperty); + set => SetValue(OrientationProperty, value); + } + + public static readonly StyledProperty ShowInlineActionsProperty = + AvaloniaProperty.Register(nameof(ShowInlineActions), true); + + public bool ShowInlineActions + { + get => GetValue(ShowInlineActionsProperty); + set => SetValue(ShowInlineActionsProperty, value); + } + + public static readonly StyledProperty ItemMinWidthProperty = + AvaloniaProperty.Register(nameof(ItemMinWidth)); + + public double ItemMinWidth + { + get => GetValue(ItemMinWidthProperty); + set => SetValue(ItemMinWidthProperty, value); + } + + public RepositoryViewSwitcher() + { + InitializeComponent(); + ViewSelector.AddHandler(PointerPressedEvent, OnViewSelectorPointerPressed, RoutingStrategies.Tunnel); + } + + private void OnViewSelectorPointerPressed(object sender, PointerPressedEventArgs e) + { + if (e.GetCurrentPoint(ViewSelector).Properties.PointerUpdateKind == PointerUpdateKind.RightButtonPressed) + e.Handled = true; + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + this.FindAncestorOfType()?.CloseCompactSidebar(); + e.Handled = true; + } + + private void OnRepositoryViewContextRequested(object sender, ContextRequestedEventArgs e) + { + this.FindAncestorOfType()?.OpenRepositoryViewContextMenu(sender, e, !ShowInlineActions); + } + + private void OnOpenAdvancedHistoriesOption(object sender, RoutedEventArgs e) + { + this.FindAncestorOfType()?.OpenAdvancedHistoriesOption(sender, e); + } + } +} diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index 0ea6897f14..5f0bcb9ead 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -9,15 +9,20 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.StashesPage" x:DataType="vm:StashesPage"> - + - + - + + + + + + - + @@ -138,7 +143,7 @@ - - + diff --git a/src/Views/StashesPage.axaml.cs b/src/Views/StashesPage.axaml.cs index 678337c457..f11e1e4901 100644 --- a/src/Views/StashesPage.axaml.cs +++ b/src/Views/StashesPage.axaml.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text; +using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Platform.Storage; @@ -10,6 +11,10 @@ namespace SourceGit.Views { public partial class StashesPage : UserControl { + private const double SingleColumnThreshold = 720; + private bool _isSingleColumn; + private GridLength _expandedLeftWidth = new(300, GridUnitType.Pixel); + public StashesPage() { InitializeComponent(); @@ -22,12 +27,75 @@ private void OnMainLayoutSizeChanged(object sender, SizeChangedEventArgs e) var layout = ViewModels.Preferences.Instance.Layout; var width = grid.Bounds.Width; - var leftWidth = Math.Max(width - 304, 300); + if (width <= 0) + return; + + var useSingleColumn = width < SingleColumnThreshold; + if (useSingleColumn != _isSingleColumn) + SetSingleColumnLayout(useSingleColumn); + + if (useSingleColumn) + return; + + var leftWidth = Math.Max(220, width - 264); if (layout.StashesLeftWidth.Value - leftWidth > 1.0) layout.StashesLeftWidth = new GridLength(leftWidth, GridUnitType.Pixel); } + private void SetSingleColumnLayout(bool enabled) + { + var columns = MainLayout.ColumnDefinitions; + var rows = MainLayout.RowDefinitions; + var layout = ViewModels.Preferences.Instance.Layout; + + _isSingleColumn = enabled; + if (enabled) + { + if (layout.StashesLeftWidth.IsAbsolute && layout.StashesLeftWidth.Value >= 220) + _expandedLeftWidth = layout.StashesLeftWidth; + + columns[0].MinWidth = 0; + columns[0].SetCurrentValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Star)); + columns[1].Width = new GridLength(0); + columns[2].MinWidth = 0; + columns[2].Width = new GridLength(0); + rows[0].Height = new GridLength(2, GridUnitType.Star); + rows[1].Height = new GridLength(4, GridUnitType.Pixel); + rows[2].Height = new GridLength(3, GridUnitType.Star); + + Grid.SetColumn(StashListPanel, 0); + Grid.SetRow(StashListPanel, 0); + Grid.SetColumn(LayoutSplitter, 0); + Grid.SetRow(LayoutSplitter, 1); + Grid.SetColumn(DetailsPanel, 0); + Grid.SetRow(DetailsPanel, 2); + LayoutSplitter.BorderThickness = new Thickness(0, 1, 0, 0); + DetailsPanel.Margin = new Thickness(4, 0, 4, 4); + } + else + { + columns[0].MinWidth = 220; + columns[0].SetCurrentValue(ColumnDefinition.WidthProperty, _expandedLeftWidth); + columns[1].Width = new GridLength(4, GridUnitType.Pixel); + columns[2].MinWidth = 260; + columns[2].Width = new GridLength(1, GridUnitType.Star); + rows[0].Height = new GridLength(1, GridUnitType.Star); + rows[1].Height = new GridLength(0); + rows[2].Height = new GridLength(0); + + Grid.SetColumn(StashListPanel, 0); + Grid.SetRow(StashListPanel, 0); + Grid.SetColumn(LayoutSplitter, 1); + Grid.SetRow(LayoutSplitter, 0); + Grid.SetColumn(DetailsPanel, 2); + Grid.SetRow(DetailsPanel, 0); + LayoutSplitter.BorderThickness = new Thickness(1, 0, 0, 0); + DetailsPanel.Margin = new Thickness(0, 4, 4, 4); + layout.StashesLeftWidth = _expandedLeftWidth; + } + } + private async void OnStashListKeyDown(object sender, KeyEventArgs e) { if (DataContext is ViewModels.StashesPage { SelectedStash: { } stash } vm) diff --git a/src/Views/Welcome.axaml b/src/Views/Welcome.axaml index 18bf85870c..340b4b261d 100644 --- a/src/Views/Welcome.axaml +++ b/src/Views/Welcome.axaml @@ -12,7 +12,7 @@ - + diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 12d1afa8a9..3486f6b167 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -8,15 +8,20 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600" x:Class="SourceGit.Views.WorkingCopy" x:DataType="vm:WorkingCopy"> - + - + - + + + + + + - + @@ -194,7 +199,7 @@ - - + - + - - + + @@ -247,7 +252,7 @@ - + - + diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index 72e6fbb4b2..7428413075 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text; +using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -13,6 +14,10 @@ namespace SourceGit.Views { public partial class WorkingCopy : UserControl { + private const double SingleColumnThreshold = 720; + private bool _isSingleColumn; + private GridLength _expandedLeftWidth = new(300, GridUnitType.Pixel); + public WorkingCopy() { InitializeComponent(); @@ -25,12 +30,75 @@ private void OnMainLayoutSizeChanged(object sender, SizeChangedEventArgs e) var layout = ViewModels.Preferences.Instance.Layout; var width = grid.Bounds.Width; - var leftWidth = Math.Max(width - 304, 300); + if (width <= 0) + return; + + var useSingleColumn = width < SingleColumnThreshold; + if (useSingleColumn != _isSingleColumn) + SetSingleColumnLayout(useSingleColumn); + + if (useSingleColumn) + return; + + var leftWidth = Math.Max(220, width - 264); if (layout.WorkingCopyLeftWidth.Value - leftWidth > 1.0) layout.WorkingCopyLeftWidth = new GridLength(leftWidth, GridUnitType.Pixel); } + private void SetSingleColumnLayout(bool enabled) + { + var columns = MainLayout.ColumnDefinitions; + var rows = MainLayout.RowDefinitions; + var layout = ViewModels.Preferences.Instance.Layout; + + _isSingleColumn = enabled; + if (enabled) + { + if (layout.WorkingCopyLeftWidth.IsAbsolute && layout.WorkingCopyLeftWidth.Value >= 220) + _expandedLeftWidth = layout.WorkingCopyLeftWidth; + + columns[0].MinWidth = 0; + columns[0].SetCurrentValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Star)); + columns[1].Width = new GridLength(0); + columns[2].MinWidth = 0; + columns[2].Width = new GridLength(0); + rows[0].Height = new GridLength(2, GridUnitType.Star); + rows[1].Height = new GridLength(4, GridUnitType.Pixel); + rows[2].Height = new GridLength(3, GridUnitType.Star); + + Grid.SetColumn(ChangesPanel, 0); + Grid.SetRow(ChangesPanel, 0); + Grid.SetColumn(LayoutSplitter, 0); + Grid.SetRow(LayoutSplitter, 1); + Grid.SetColumn(DetailsPanel, 0); + Grid.SetRow(DetailsPanel, 2); + LayoutSplitter.BorderThickness = new Thickness(0, 1, 0, 0); + DetailsPanel.Margin = new Thickness(4, 0, 4, 4); + } + else + { + columns[0].MinWidth = 220; + columns[0].SetCurrentValue(ColumnDefinition.WidthProperty, _expandedLeftWidth); + columns[1].Width = new GridLength(4, GridUnitType.Pixel); + columns[2].MinWidth = 260; + columns[2].Width = new GridLength(1, GridUnitType.Star); + rows[0].Height = new GridLength(1, GridUnitType.Star); + rows[1].Height = new GridLength(0); + rows[2].Height = new GridLength(0); + + Grid.SetColumn(ChangesPanel, 0); + Grid.SetRow(ChangesPanel, 0); + Grid.SetColumn(LayoutSplitter, 1); + Grid.SetRow(LayoutSplitter, 0); + Grid.SetColumn(DetailsPanel, 2); + Grid.SetRow(DetailsPanel, 0); + LayoutSplitter.BorderThickness = new Thickness(1, 0, 0, 0); + DetailsPanel.Margin = new Thickness(0, 4, 4, 4); + layout.WorkingCopyLeftWidth = _expandedLeftWidth; + } + } + private async void OnOpenAssumeUnchanged(object sender, RoutedEventArgs e) { var repoView = this.FindAncestorOfType();