Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/App.JsonCodeGen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public override void Write(Utf8JsonWriter writer, DataGridLength value, JsonSeri
[JsonSerializable(typeof(Models.ThemeOverrides))]
[JsonSerializable(typeof(Models.Version))]
[JsonSerializable(typeof(Models.RepositorySettings))]
[JsonSerializable(typeof(List<int>))]
[JsonSerializable(typeof(List<Models.ConventionalCommitType>))]
[JsonSerializable(typeof(List<Models.LFSLock>))]
[JsonSerializable(typeof(List<Models.VisualStudioInstance>))]
Expand Down
1 change: 1 addition & 0 deletions src/Resources/Icons.axaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StreamGeometry x:Key="Icons.GripVertical">M384 192a64 64 0 110 128 64 64 0 010-128zm256 0a64 64 0 110 128 64 64 0 010-128zM384 448a64 64 0 110 128 64 64 0 010-128zm256 0a64 64 0 110 128 64 64 0 010-128zM384 704a64 64 0 110 128 64 64 0 010-128zm256 0a64 64 0 110 128 64 64 0 010-128z</StreamGeometry>
<StreamGeometry x:Key="Icons.Action">M41 512c0-128 46-241 138-333C271 87 384 41 512 41s241 46 333 138c92 92 138 205 138 333s-46 241-138 333c-92 92-205 138-333 138s-241-46-333-138C87 753 41 640 41 512zm87 0c0 108 36 195 113 271s164 113 271 113c108 0 195-36 271-113s113-164 113-271-36-195-113-271c-77-77-164-113-271-113-108 0-195 36-271 113C164 317 128 404 128 512zm256 148V292l195 113L768 512l-195 113-195 113v-77zm148-113-61 36V440l61 36 61 36-61 36z</StreamGeometry>
<StreamGeometry x:Key="Icons.AIAssist">M304 464a128 128 0 01128-128c71 0 128 57 128 128v224a32 32 0 01-64 0V592h-128v95a32 32 0 01-64 0v-224zm64 1v64h128v-64a64 64 0 00-64-64c-35 0-64 29-64 64zM688 337c18 0 32 14 32 32v319a32 32 0 01-32 32c-18 0-32-14-32-32v-319a32 32 0 0132-32zM84 911l60-143A446 446 0 0164 512C64 265 265 64 512 64s448 201 448 448-201 448-448 448c-54 0-105-9-153-27l-242 22a32 32 0 01-32-44zm133-150-53 126 203-18 13 5c41 15 85 23 131 23 212 0 384-172 384-384S724 128 512 128 128 300 128 512c0 82 26 157 69 220l20 29z</StreamGeometry>
<StreamGeometry x:Key="Icons.Archive">M366 146l293 0 0-73-293 0 0 73zm658 366 0 274q0 38-27 65t-65 27l-841 0q-38 0-65-27t-27-65l0-274 384 0 0 91q0 15 11 26t26 11l183 0q15 0 26-11t11-26l0-91 384 0zm-439 0 0 73-146 0 0-73 146 0zm439-274 0 219-1024 0 0-219q0-38 27-65t65-27l201 0 0-91q0-23 16-39t39-16l329 0q23 0 39 16t16 39l0 91 201 0q38 0 65 27t27 65z</StreamGeometry>
Expand Down
11 changes: 10 additions & 1 deletion src/ViewModels/LayoutInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Avalonia.Controls;
using System.Collections.Generic;

using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;

namespace SourceGit.ViewModels
Expand Down Expand Up @@ -76,6 +78,13 @@ public DataGridLength AuthorColumnWidth
private GridLength _stashesLeftWidth = new GridLength(300, GridUnitType.Pixel);
private GridLength _commitDetailChangesLeftWidth = new GridLength(256, GridUnitType.Pixel);
private GridLength _commitDetailFilesLeftWidth = new GridLength(256, GridUnitType.Pixel);
public List<int> SidebarViewOrder
{
get => _sidebarViewOrder;
set => SetProperty(ref _sidebarViewOrder, value);
}

private DataGridLength _authorColumnWidth = new DataGridLength(120, DataGridLengthUnitType.Pixel, 120, 120);
private List<int> _sidebarViewOrder = new List<int> { 0, 1, 2 };
}
}
71 changes: 50 additions & 21 deletions src/Views/Repository.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<!-- Page Switcher for Right Panel -->
<Border Grid.Row="0" Margin="8,0,4,0" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}" CornerRadius="6">
<Border CornerRadius="6" ClipToBounds="True">
<ListBox Background="Transparent" SelectedIndex="{Binding SelectedViewIndex, Mode=TwoWay}" SelectionMode="AlwaysSelected">
<ListBox x:Name="ViewSwitcher" Background="Transparent" SelectionMode="AlwaysSelected" SelectionChanged="OnViewSwitcherSelectionChanged">
<ListBox.Styles>
<Style Selector="Path.icon">
<Setter Property="Width" Value="12"/>
Expand Down Expand Up @@ -96,28 +96,37 @@
</ItemsPanelTemplate>
</ListBox.ItemsPanel>

<ListBoxItem>
<Grid Classes="view_mode" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<Path Grid.Column="0" Classes="icon" Data="{StaticResource Icons.Histories}"/>
<TextBlock Grid.Column="1" Classes="header" Text="{DynamicResource Text.Histories}"/>
<ListBoxItem Tag="0"
DragDrop.AllowDrop="True"
DragDrop.Drop="OnViewSwitcherItemDrop">
<Grid Classes="view_mode" ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
<Border Grid.Column="0"
Width="10" Margin="3,0,0,0" Background="Transparent" Cursor="SizeAll"
PointerPressed="OnViewSwitcherItemPointerPressed"
PointerMoved="OnViewSwitcherItemPointerMoved"
PointerReleased="OnViewSwitcherItemPointerReleased">
<Path Width="8" Height="12" Stretch="Uniform" Data="{StaticResource Icons.GripVertical}" Fill="{DynamicResource Brush.FG2}" Opacity="0.5"/>
</Border>
<Path Grid.Column="1" Classes="icon" Margin="3,0,6,0" Data="{StaticResource Icons.Histories}"/>
<TextBlock Grid.Column="2" Classes="header" Text="{DynamicResource Text.Histories}"/>

<ToggleButton Grid.Column="2"
<ToggleButton Grid.Column="3"
Classes="line_path"
Width="26" Height="26"
Background="Transparent"
IsChecked="{Binding OnlyHighlightCurrentBranchInHistories, Mode=TwoWay}"
ToolTip.Tip="{DynamicResource Text.Repository.OnlyHighlightCurrentBranchInGraph}">
<Path Width="12" Height="12" Data="{StaticResource Icons.LightOn}"/>
</ToggleButton>
<ToggleButton Grid.Column="3"
<ToggleButton Grid.Column="4"
Classes="line_path"
Width="26" Height="26"
Background="Transparent"
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=DisplayTimeAsPeriodInHistories, Mode=TwoWay}"
ToolTip.Tip="{DynamicResource Text.Repository.UseRelativeTimeInGraph}">
<Path Width="12" Height="12" Data="{StaticResource Icons.Stopwatch}"/>
</ToggleButton>
<Button Grid.Column="4"
<Button Grid.Column="5"
Classes="icon_button"
Width="26" Height="26"
Click="OnOpenAdvancedHistoriesOption"
Expand All @@ -127,11 +136,21 @@
</Grid>
</ListBoxItem>

<ListBoxItem IsVisible="{Binding !IsBare}">
<Grid Classes="view_mode" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<Path Grid.Column="0" Classes="icon" Data="{StaticResource Icons.Changes}"/>
<TextBlock Grid.Column="1" Classes="header" Text="{DynamicResource Text.WorkingCopy}"/>
<Border Grid.Column="2"
<ListBoxItem Tag="1"
IsVisible="{Binding !IsBare}"
DragDrop.AllowDrop="True"
DragDrop.Drop="OnViewSwitcherItemDrop">
<Grid Classes="view_mode" ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
<Border Grid.Column="0"
Width="10" Margin="3,0,0,0" Background="Transparent" Cursor="SizeAll"
PointerPressed="OnViewSwitcherItemPointerPressed"
PointerMoved="OnViewSwitcherItemPointerMoved"
PointerReleased="OnViewSwitcherItemPointerReleased">
<Path Width="8" Height="12" Stretch="Uniform" Data="{StaticResource Icons.GripVertical}" Fill="{DynamicResource Brush.FG2}" Opacity="0.5"/>
</Border>
<Path Grid.Column="1" Classes="icon" Margin="3,0,6,0" Data="{StaticResource Icons.Changes}"/>
<TextBlock Grid.Column="2" Classes="header" Text="{DynamicResource Text.WorkingCopy}"/>
<Border Grid.Column="3"
Height="18"
Margin="6,0" Padding="9,0"
CornerRadius="9"
Expand All @@ -143,13 +162,13 @@
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="10"/>
</Border>
<Path Grid.Column="3"
<Path Grid.Column="4"
Width="12" Height="12"
Margin="0,0,6,0"
Data="{StaticResource Icons.Info}"
Fill="DarkOrange"
IsVisible="{Binding InProgressContext, Converter={x:Static ObjectConverters.IsNotNull}}"/>
<Button Grid.Column="4"
<Button Grid.Column="5"
Classes="icon_button"
Width="26" Height="26"
Command="{Binding DiscardAllChanges}"
Expand All @@ -159,11 +178,21 @@
</Grid>
</ListBoxItem>

<ListBoxItem IsVisible="{Binding !IsBare}">
<Grid Classes="view_mode" ColumnDefinitions="Auto,*,Auto,Auto">
<Path Grid.Column="0" Classes="icon" Data="{StaticResource Icons.Stashes}"/>
<TextBlock Grid.Column="1" Classes="header" Text="{DynamicResource Text.Stashes}"/>
<Border Grid.Column="2"
<ListBoxItem Tag="2"
IsVisible="{Binding !IsBare}"
DragDrop.AllowDrop="True"
DragDrop.Drop="OnViewSwitcherItemDrop">
<Grid Classes="view_mode" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
<Border Grid.Column="0"
Width="10" Margin="3,0,0,0" Background="Transparent" Cursor="SizeAll"
PointerPressed="OnViewSwitcherItemPointerPressed"
PointerMoved="OnViewSwitcherItemPointerMoved"
PointerReleased="OnViewSwitcherItemPointerReleased">
<Path Width="8" Height="12" Stretch="Uniform" Data="{StaticResource Icons.GripVertical}" Fill="{DynamicResource Brush.FG2}" Opacity="0.5"/>
</Border>
<Path Grid.Column="1" Classes="icon" Margin="3,0,6,0" Data="{StaticResource Icons.Stashes}"/>
<TextBlock Grid.Column="2" Classes="header" Text="{DynamicResource Text.Stashes}"/>
<Border Grid.Column="3"
Height="18"
Margin="6,0" Padding="9,0"
CornerRadius="9"
Expand All @@ -175,7 +204,7 @@
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="10"/>
</Border>
<Button Grid.Column="3"
<Button Grid.Column="4"
Classes="icon_button"
Width="26" Height="26"
Command="{Binding ClearStashes}"
Expand Down
189 changes: 189 additions & 0 deletions src/Views/Repository.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;

using Avalonia;
using Avalonia.Controls;
Expand All @@ -17,9 +19,196 @@ public Repository()
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
ApplySidebarViewOrder();
SyncViewSwitcherSelection();
UpdateLeftSidebarLayout();

if (DataContext is ViewModels.Repository repo)
repo.PropertyChanged += OnRepositoryPropertyChanged;
}

protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);

if (DataContext is ViewModels.Repository repo)
repo.PropertyChanged -= OnRepositoryPropertyChanged;
}

private void OnRepositoryPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ViewModels.Repository.SelectedViewIndex))
SyncViewSwitcherSelection();
}

#region View Switcher Drag-Drop Reorder

private static readonly DataFormat<string> _dndViewSwitcherFormat =
DataFormat.CreateStringApplicationFormat("sourcegit-dnd-view-switcher");

private bool _pressedViewItem = false;
private bool _startDragViewItem = false;
private Point _pressedViewItemPosition;

private void ApplySidebarViewOrder()
{
var layout = ViewModels.Preferences.Instance.Layout;
var order = layout.SidebarViewOrder;
if (order == null || order.Count != 3)
return;

// Collect items by their Tag
var itemsByTag = new Dictionary<int, ListBoxItem>();
foreach (var obj in ViewSwitcher.Items)
{
if (obj is ListBoxItem item && item.Tag is string tagStr && int.TryParse(tagStr, out var tag))
itemsByTag[tag] = item;
}

if (itemsByTag.Count != 3)
return;

// Reorder
ViewSwitcher.Items.Clear();
foreach (var tag in order)
{
if (itemsByTag.TryGetValue(tag, out var item))
ViewSwitcher.Items.Add(item);
}
}

private void SyncViewSwitcherSelection()
{
if (DataContext is not ViewModels.Repository repo)
return;

// Find the ListBoxItem whose Tag matches the current SelectedViewIndex
foreach (var obj in ViewSwitcher.Items)
{
if (obj is ListBoxItem item && item.Tag is string tagStr &&
int.TryParse(tagStr, out var tag) && tag == repo.SelectedViewIndex)
{
_suppressSelectionChanged = true;
ViewSwitcher.SelectedItem = item;
_suppressSelectionChanged = false;
break;
}
}
}

private bool _suppressSelectionChanged = false;

private void OnViewSwitcherSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_suppressSelectionChanged)
return;

if (DataContext is ViewModels.Repository repo &&
ViewSwitcher.SelectedItem is ListBoxItem item &&
item.Tag is string tagStr &&
int.TryParse(tagStr, out var viewIndex))
{
repo.SelectedViewIndex = viewIndex;
}
}

private static ListBoxItem FindParentListBoxItem(Control control)
{
var parent = control.Parent;
while (parent != null)
{
if (parent is ListBoxItem item)
return item;
parent = (parent as Control)?.Parent;
}
return null;
}

private void OnViewSwitcherItemPointerPressed(object sender, PointerPressedEventArgs e)
{
if (sender is Border border)
{
_pressedViewItem = true;
_startDragViewItem = false;
_pressedViewItemPosition = e.GetPosition(border);
e.Handled = true;
}
}

private void OnViewSwitcherItemPointerReleased(object sender, PointerReleasedEventArgs e)
{
_pressedViewItem = false;
_startDragViewItem = false;
}

private async void OnViewSwitcherItemPointerMoved(object sender, PointerEventArgs e)
{
if (!_pressedViewItem || _startDragViewItem || sender is not Border border)
return;

var delta = e.GetPosition(border) - _pressedViewItemPosition;
var sizeSquared = delta.X * delta.X + delta.Y * delta.Y;
if (sizeSquared < 64)
return;

_startDragViewItem = true;

var listBoxItem = FindParentListBoxItem(border);
if (listBoxItem == null)
return;

var tag = listBoxItem.Tag as string ?? "";
var data = new DataTransfer();
data.Add(DataTransferItem.Create(_dndViewSwitcherFormat, tag));
await DragDrop.DoDragDropAsync(e, data, DragDropEffects.Move);
}

private void OnViewSwitcherItemDrop(object sender, DragEventArgs e)
{
if (e.DataTransfer.TryGetValue(_dndViewSwitcherFormat) is not { Length: > 0 } sourceTag)
return;

if (sender is not ListBoxItem targetItem || targetItem.Tag is not string targetTag)
return;

if (sourceTag == targetTag)
return;

// Find source and target indices in current Items list
var items = ViewSwitcher.Items.Cast<ListBoxItem>().ToList();
var sourceIdx = items.FindIndex(i => (i.Tag as string) == sourceTag);
var targetIdx = items.FindIndex(i => (i.Tag as string) == targetTag);

if (sourceIdx < 0 || targetIdx < 0)
return;

// Swap
var sourceItem = items[sourceIdx];
items.RemoveAt(sourceIdx);
items.Insert(targetIdx, sourceItem);

// Rebuild ListBox
_suppressSelectionChanged = true;
var selectedItem = ViewSwitcher.SelectedItem;
ViewSwitcher.Items.Clear();
foreach (var item in items)
ViewSwitcher.Items.Add(item);
ViewSwitcher.SelectedItem = selectedItem;
_suppressSelectionChanged = false;

// Persist order
var newOrder = items.Select(i => int.Parse(i.Tag as string ?? "0")).ToList();
var layout = ViewModels.Preferences.Instance.Layout;
layout.SidebarViewOrder = newOrder;
ViewModels.Preferences.Instance.Save();

_pressedViewItem = false;
_startDragViewItem = false;
e.Handled = true;
}

#endregion

private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == IsVisibleProperty && sender is Grid { IsVisible: true })
Expand Down