Skip to content

Commit a869442

Browse files
committed
ux: improve Interactive Rebase window UX
Signed-off-by: leo <longshuang@msn.cn>
1 parent 79bc093 commit a869442

5 files changed

Lines changed: 194 additions & 29 deletions

File tree

src/Converters/InteractiveRebaseActionConverters.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,5 @@ public static class InteractiveRebaseActionConverters
2121

2222
public static readonly FuncValueConverter<Models.InteractiveRebaseAction, string> ToName =
2323
new FuncValueConverter<Models.InteractiveRebaseAction, string>(v => v.ToString());
24-
25-
public static readonly FuncValueConverter<Models.InteractiveRebaseAction, bool> CanEditMessage =
26-
new FuncValueConverter<Models.InteractiveRebaseAction, bool>(v => v == Models.InteractiveRebaseAction.Reword || v == Models.InteractiveRebaseAction.Squash);
2724
}
2825
}

src/ViewModels/InteractiveRebase.cs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,6 @@ public Models.Commit Commit
2525
get;
2626
}
2727

28-
public bool CanSquashOrFixup
29-
{
30-
get => _canSquashOrFixup;
31-
set
32-
{
33-
if (SetProperty(ref _canSquashOrFixup, value))
34-
{
35-
if (_action == Models.InteractiveRebaseAction.Squash || _action == Models.InteractiveRebaseAction.Fixup)
36-
Action = Models.InteractiveRebaseAction.Pick;
37-
}
38-
}
39-
}
40-
4128
public Models.InteractiveRebaseAction Action
4229
{
4330
get => _action;
@@ -64,6 +51,47 @@ public string FullMessage
6451
}
6552
}
6653

54+
public string OriginalFullMessage
55+
{
56+
get;
57+
set;
58+
}
59+
60+
public bool CanSquashOrFixup
61+
{
62+
get => _canSquashOrFixup;
63+
set
64+
{
65+
if (SetProperty(ref _canSquashOrFixup, value))
66+
{
67+
if (_action == Models.InteractiveRebaseAction.Squash || _action == Models.InteractiveRebaseAction.Fixup)
68+
{
69+
Action = Models.InteractiveRebaseAction.Pick;
70+
FullMessage = OriginalFullMessage;
71+
}
72+
}
73+
}
74+
}
75+
76+
public bool CanReword
77+
{
78+
get => _canReword;
79+
set
80+
{
81+
if (SetProperty(ref _canReword, value) && _action == Models.InteractiveRebaseAction.Reword)
82+
{
83+
Action = Models.InteractiveRebaseAction.Pick;
84+
FullMessage = OriginalFullMessage;
85+
}
86+
}
87+
}
88+
89+
public bool ShowEditMessageButton
90+
{
91+
get => _showEditMessageButton;
92+
set => SetProperty(ref _showEditMessageButton, value);
93+
}
94+
6795
public bool IsDropBeforeVisible
6896
{
6997
get => _isDropBeforeVisible;
@@ -81,13 +109,16 @@ public InteractiveRebaseItem(int order, Models.Commit c, string message, bool ca
81109
OriginalOrder = order;
82110
Commit = c;
83111
FullMessage = message;
112+
OriginalFullMessage = message;
84113
CanSquashOrFixup = canSquashOrFixup;
85114
}
86115

87116
private Models.InteractiveRebaseAction _action = Models.InteractiveRebaseAction.Pick;
88117
private string _subject;
89118
private string _fullMessage;
90119
private bool _canSquashOrFixup = true;
120+
private bool _canReword = true;
121+
private bool _showEditMessageButton = false;
91122
private bool _isDropBeforeVisible = false;
92123
private bool _isDropAfterVisible = false;
93124
}
@@ -307,6 +338,20 @@ private void UpdateItems()
307338
hasValidParent = item.Action != Models.InteractiveRebaseAction.Drop;
308339
}
309340
}
341+
342+
var hasPendingTarget = false;
343+
for (var i = 0; i < Items.Count; i++)
344+
{
345+
var item = Items[i];
346+
if (item.Action == Models.InteractiveRebaseAction.Pick || item.Action == Models.InteractiveRebaseAction.Edit)
347+
item.FullMessage = item.OriginalFullMessage;
348+
349+
item.CanReword = !hasPendingTarget;
350+
item.ShowEditMessageButton = item.CanReword && (item.Action == Models.InteractiveRebaseAction.Squash || item.Action == Models.InteractiveRebaseAction.Reword);
351+
352+
if (item.Action != Models.InteractiveRebaseAction.Drop)
353+
hasPendingTarget = item.Action == Models.InteractiveRebaseAction.Squash || item.Action == Models.InteractiveRebaseAction.Fixup;
354+
}
310355
}
311356

312357
private Repository _repo = null;

src/Views/CommitSubjectPresenter.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Collections.Specialized;
34
using System.Globalization;
45
using System.Text.RegularExpressions;
@@ -76,6 +77,15 @@ public IBrush LinkForeground
7677
set => SetValue(LinkForegroundProperty, value);
7778
}
7879

80+
public static readonly StyledProperty<bool> ShowStrikethroughProperty =
81+
AvaloniaProperty.Register<CommitSubjectPresenter, bool>(nameof(ShowStrikethrough), false);
82+
83+
public bool ShowStrikethrough
84+
{
85+
get => GetValue(ShowStrikethroughProperty);
86+
set => SetValue(ShowStrikethroughProperty, value);
87+
}
88+
7989
public static readonly StyledProperty<string> SubjectProperty =
8090
AvaloniaProperty.Register<CommitSubjectPresenter, string>(nameof(Subject));
8191

@@ -115,6 +125,7 @@ public override void Render(DrawingContext context)
115125
{
116126
var height = Bounds.Height;
117127
var width = Bounds.Width;
128+
var maxX = 0.0;
118129
foreach (var inline in _inlines)
119130
{
120131
if (inline.X > width)
@@ -126,12 +137,17 @@ public override void Render(DrawingContext context)
126137
var roundedRect = new RoundedRect(rect, new CornerRadius(4));
127138
context.DrawRectangle(InlineCodeBackground, null, roundedRect);
128139
context.DrawText(inline.Text, new Point(inline.X + 4, (height - inline.Text.Height) * 0.5));
140+
maxX = Math.Min(width, inline.X + inline.Text.WidthIncludingTrailingWhitespace + 8);
129141
}
130142
else
131143
{
132144
context.DrawText(inline.Text, new Point(inline.X, (height - inline.Text.Height) * 0.5));
145+
maxX = Math.Min(width, inline.X + inline.Text.WidthIncludingTrailingWhitespace);
133146
}
134147
}
148+
149+
if (ShowStrikethrough)
150+
context.DrawLine(new Pen(Foreground), new Point(0, height * 0.5), new Point(maxX, height * 0.5));
135151
}
136152
}
137153

@@ -164,7 +180,8 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
164180
_needRebuildInlines = true;
165181
InvalidateVisual();
166182
}
167-
else if (change.Property == InlineCodeBackgroundProperty)
183+
else if (change.Property == InlineCodeBackgroundProperty ||
184+
change.Property == ShowStrikethroughProperty)
168185
{
169186
InvalidateVisual();
170187
}

src/Views/InteractiveRebase.axaml

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898

9999
<v:InteractiveRebaseListBox.ItemTemplate>
100100
<DataTemplate DataType="vm:InteractiveRebaseItem">
101-
<Grid Height="26"
101+
<Grid Height="28"
102102
Margin="8,0"
103103
Background="Transparent"
104104
ClipToBounds="True"
@@ -110,6 +110,7 @@
110110
<Grid.ColumnDefinitions>
111111
<ColumnDefinition Width="Auto" SharedSizeGroup="CommitOrderColumn"/>
112112
<ColumnDefinition Width="110"/>
113+
<ColumnDefinition Width="24"/>
113114
<ColumnDefinition Width="*"/>
114115
<ColumnDefinition Width="32"/>
115116
<ColumnDefinition Width="108"/>
@@ -132,12 +133,18 @@
132133
</StackPanel>
133134
</Button>
134135

136+
<!-- Graph -->
137+
<v:InteractiveRebasePath Grid.Column="2"
138+
Fill="{DynamicResource Brush.FG1}"
139+
Action="{Binding Action, Mode=OneWay}"
140+
CanReword="{Binding CanReword, Mode=OneWay}"/>
141+
135142
<!-- Subject -->
136-
<Grid Grid.Column="2" ColumnDefinitions="Auto,*" ClipToBounds="True">
143+
<Grid Grid.Column="3" ColumnDefinitions="Auto,*" ClipToBounds="True">
137144
<Button Grid.Column="0"
138145
Classes="icon_button"
139146
Margin="0,0,6,0" Padding="0"
140-
IsVisible="{Binding Action, Converter={x:Static c:InteractiveRebaseActionConverters.CanEditMessage}}"
147+
IsVisible="{Binding ShowEditMessageButton, Mode=OneWay}"
141148
Click="OnOpenCommitMessageEditor">
142149
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Edit}"/>
143150
</Button>
@@ -151,39 +158,41 @@
151158
LinkForeground="{DynamicResource Brush.Link}"
152159
Subject="{Binding Subject}"
153160
IssueTrackers="{Binding $parent[v:InteractiveRebase].((vm:InteractiveRebase)DataContext).IssueTrackers}"
154-
FontWeight="Normal"/>
161+
FontWeight="Normal"
162+
Opacity="{Binding CanReword, Converter={x:Static c:BoolConverters.IsMergedToOpacity}}"
163+
ShowStrikethrough="{Binding Action, Mode=OneWay, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:InteractiveRebaseAction.Drop}}"/>
155164
</Grid>
156165

157166
<!-- Author Avatar -->
158-
<v:Avatar Grid.Column="3"
167+
<v:Avatar Grid.Column="4"
159168
Width="16" Height="16"
160169
Margin="8,0,0,0"
161170
VerticalAlignment="Center"
162171
User="{Binding Commit.Author}"/>
163172

164173
<!-- Author Name -->
165-
<Border Grid.Column="4" ClipToBounds="True">
174+
<Border Grid.Column="5" ClipToBounds="True">
166175
<TextBlock Margin="6,0,12,0" Text="{Binding Commit.Author.Name}"/>
167176
</Border>
168177

169178
<!-- Commit SHA -->
170-
<Border Grid.Column="5" ClipToBounds="True">
179+
<Border Grid.Column="6" ClipToBounds="True">
171180
<TextBlock Text="{Binding Commit.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"/>
172181
</Border>
173182

174183
<!-- Commit Time -->
175-
<Border Grid.Column="6">
184+
<Border Grid.Column="7">
176185
<TextBlock Margin="16,0,8,0" Text="{Binding Commit.CommitterTimeStr}"/>
177186
</Border>
178187

179188
<!-- Drop Indicator -->
180-
<Rectangle Grid.Column="0" Grid.ColumnSpan="7"
189+
<Rectangle Grid.Column="0" Grid.ColumnSpan="8"
181190
Height="2"
182191
VerticalAlignment="Top"
183192
Fill="{DynamicResource Brush.Accent}"
184193
IsVisible="{Binding IsDropBeforeVisible, Mode=OneWay}"
185194
IsHitTestVisible="False"/>
186-
<Rectangle Grid.Column="0" Grid.ColumnSpan="7"
195+
<Rectangle Grid.Column="0" Grid.ColumnSpan="8"
187196
Height="2"
188197
VerticalAlignment="Bottom"
189198
Fill="{DynamicResource Brush.Accent}"

src/Views/InteractiveRebase.axaml.cs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,101 @@ protected override void OnKeyDown(KeyEventArgs e)
8585
}
8686
}
8787

88+
public class InteractiveRebasePath : Control
89+
{
90+
public static readonly StyledProperty<IBrush> FillProperty =
91+
AvaloniaProperty.Register<InteractiveRebasePath, IBrush>(nameof(Fill), Brushes.Transparent);
92+
93+
public IBrush Fill
94+
{
95+
get => GetValue(FillProperty);
96+
set => SetValue(FillProperty, value);
97+
}
98+
99+
public static readonly StyledProperty<Models.InteractiveRebaseAction> ActionProperty =
100+
AvaloniaProperty.Register<InteractiveRebasePath, Models.InteractiveRebaseAction>(nameof(Action));
101+
102+
public Models.InteractiveRebaseAction Action
103+
{
104+
get => GetValue(ActionProperty);
105+
set => SetValue(ActionProperty, value);
106+
}
107+
108+
public static readonly StyledProperty<bool> CanRewordProperty =
109+
AvaloniaProperty.Register<InteractiveRebasePath, bool>(nameof(CanReword));
110+
111+
public bool CanReword
112+
{
113+
get => GetValue(CanRewordProperty);
114+
set => SetValue(CanRewordProperty, value);
115+
}
116+
117+
public override void Render(DrawingContext context)
118+
{
119+
base.Render(context);
120+
121+
var startW = 4;
122+
var endW = Bounds.Width - 4;
123+
var height = Bounds.Height;
124+
var halfH = height * 0.5;
125+
var action = Action;
126+
var fill = Fill;
127+
128+
if (CanReword)
129+
{
130+
if (action == Models.InteractiveRebaseAction.Squash || action == Models.InteractiveRebaseAction.Fixup)
131+
{
132+
var center = new Point(startW, halfH);
133+
context.DrawEllipse(fill, null, center, 4, 4);
134+
context.DrawLine(new Pen(fill, 2), center, new Point(startW, height));
135+
}
136+
}
137+
else
138+
{
139+
if (action == Models.InteractiveRebaseAction.Squash || action == Models.InteractiveRebaseAction.Fixup)
140+
{
141+
context.DrawEllipse(fill, null, new Point(startW, halfH), 4, 4);
142+
context.DrawLine(new Pen(fill, 2), new Point(startW, 0), new Point(startW, height));
143+
}
144+
else if (action == Models.InteractiveRebaseAction.Drop)
145+
{
146+
context.DrawLine(new Pen(fill, 2), new Point(startW, 0), new Point(startW, height));
147+
}
148+
else
149+
{
150+
var geoPath = new StreamGeometry();
151+
using (var ctx = geoPath.Open())
152+
{
153+
ctx.BeginFigure(new Point(startW, 0), false);
154+
ctx.QuadraticBezierTo(new Point(startW, halfH), new Point(endW, halfH));
155+
ctx.EndFigure(false);
156+
}
157+
context.DrawGeometry(null, new Pen(fill, 2), geoPath);
158+
159+
var geoArrow = new StreamGeometry();
160+
using (var ctx = geoPath.Open())
161+
{
162+
ctx.BeginFigure(new Point(endW, halfH), true);
163+
ctx.LineTo(new Point(endW - 4, halfH + 2));
164+
ctx.LineTo(new Point(endW - 4, halfH - 2));
165+
ctx.EndFigure(true);
166+
}
167+
context.DrawGeometry(fill, null, geoArrow);
168+
}
169+
}
170+
}
171+
172+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
173+
{
174+
base.OnPropertyChanged(change);
175+
176+
if (change.Property == FillProperty ||
177+
change.Property == ActionProperty ||
178+
change.Property == CanRewordProperty)
179+
InvalidateVisual();
180+
}
181+
}
182+
88183
public partial class InteractiveRebase : ChromelessWindow
89184
{
90185
public InteractiveRebase()
@@ -312,7 +407,9 @@ private void OnShowActionsDropdownMenu(object sender, RoutedEventArgs e)
312407

313408
CreateActionMenuItem(flyout, item, Models.InteractiveRebaseAction.Pick, Brushes.Green, "Use this commit", "P");
314409
CreateActionMenuItem(flyout, item, Models.InteractiveRebaseAction.Edit, Brushes.Orange, "Stop for amending", "E");
315-
CreateActionMenuItem(flyout, item, Models.InteractiveRebaseAction.Reword, Brushes.Orange, "Edit the commit message", "R");
410+
411+
if (item.CanReword)
412+
CreateActionMenuItem(flyout, item, Models.InteractiveRebaseAction.Reword, Brushes.Orange, "Edit the commit message", "R");
316413

317414
if (item.CanSquashOrFixup)
318415
{

0 commit comments

Comments
 (0)