Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 5b1970d

Browse files
committed
Fix scrolling in PR details view.
Added a `ScollingVerticalStackPanel` which can be used to mark controls as "fixed" meaning they don't scroll horizontally. Mark all controls in the PR details as fixed except the changes tree. Fixes #1698
1 parent d308872 commit 5b1970d

4 files changed

Lines changed: 205 additions & 31 deletions

File tree

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using System;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using System.Windows.Controls.Primitives;
5+
using System.Windows.Media;
6+
7+
namespace GitHub.UI.Controls
8+
{
9+
/// <summary>
10+
/// A vertical stack panel which implements its own logical scrolling, allowing controls to be
11+
/// fixed horizontally in the scroll area.
12+
/// </summary>
13+
/// <remarks>
14+
/// This panel is needed by the PullRequestDetailsView because of #1698: there is no default
15+
/// panel in WPF which allows the horizontal scrollbar to always be present at the bottom while
16+
/// also making the PR description etc be fixed horizontally (non-scrollable) in the viewport.
17+
/// </remarks>
18+
public class ScollingVerticalStackPanel : Panel, IScrollInfo
19+
{
20+
const int lineSize = 16;
21+
const int mouseWheelSize = 48;
22+
23+
/// <summary>
24+
/// Attached property which when set to True on a child control, will cause it to be fixed
25+
/// horizontally within the scrollable viewport.
26+
/// </summary>
27+
public static readonly DependencyProperty IsFixedProperty =
28+
DependencyProperty.RegisterAttached(
29+
"IsFixed",
30+
typeof(bool),
31+
typeof(ScollingVerticalStackPanel),
32+
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure));
33+
34+
public bool CanHorizontallyScroll
35+
{
36+
get { return true; }
37+
set { }
38+
}
39+
40+
public bool CanVerticallyScroll
41+
{
42+
get { return true; }
43+
set { }
44+
}
45+
46+
public double ExtentHeight { get; private set; }
47+
public double ExtentWidth { get; private set; }
48+
public double HorizontalOffset { get; private set; }
49+
public double VerticalOffset { get; private set; }
50+
public double ViewportHeight { get; private set; }
51+
public double ViewportWidth { get; private set; }
52+
public ScrollViewer ScrollOwner { get; set; }
53+
54+
public static bool GetIsFixed(FrameworkElement control)
55+
{
56+
return (bool)control.GetValue(IsFixedProperty);
57+
}
58+
59+
public static void SetIsFixed(FrameworkElement control, bool value)
60+
{
61+
control.SetValue(IsFixedProperty, value);
62+
}
63+
64+
public void LineDown() => SetVerticalOffset(VerticalOffset + lineSize);
65+
public void LineLeft() => SetHorizontalOffset(HorizontalOffset - lineSize);
66+
public void LineRight() => SetHorizontalOffset(HorizontalOffset + lineSize);
67+
public void LineUp() => SetVerticalOffset(VerticalOffset - lineSize);
68+
public void MouseWheelDown() => SetVerticalOffset(VerticalOffset + mouseWheelSize);
69+
public void MouseWheelLeft() => SetHorizontalOffset(HorizontalOffset - mouseWheelSize);
70+
public void MouseWheelRight() => SetHorizontalOffset(HorizontalOffset + mouseWheelSize);
71+
public void MouseWheelUp() => SetVerticalOffset(VerticalOffset - mouseWheelSize);
72+
public void PageDown() => SetVerticalOffset(VerticalOffset + ViewportHeight);
73+
public void PageLeft() => SetHorizontalOffset(HorizontalOffset - ViewportWidth);
74+
public void PageRight() => SetHorizontalOffset(HorizontalOffset + ViewportWidth);
75+
public void PageUp() => SetVerticalOffset(VerticalOffset - ViewportHeight);
76+
77+
public Rect MakeVisible(Visual visual, Rect rectangle)
78+
{
79+
var transform = visual.TransformToVisual(this);
80+
var rect = transform.TransformBounds(rectangle);
81+
var offsetX = HorizontalOffset;
82+
var offsetY = VerticalOffset;
83+
84+
if (rect.Bottom > ViewportHeight)
85+
{
86+
var delta = rect.Bottom - ViewportHeight;
87+
offsetY += delta;
88+
rect.Y -= delta;
89+
}
90+
91+
if (rect.Y < 0)
92+
{
93+
offsetY += rect.Y;
94+
}
95+
96+
// We technially should be trying to also show the right-hand side of the rect here
97+
// using the same technique that we just used to show the bottom of the rect above,
98+
// but in the case of the PR details view, the left hand side of the item is much
99+
// more important than the right hand side and it actually feels better to not do
100+
// this. If this control is used elsewhere and this behavior is required, we could
101+
// put in a switch to enable it.
102+
103+
if (rect.X < 0)
104+
{
105+
offsetX += rect.X;
106+
}
107+
108+
SetHorizontalOffset(offsetX);
109+
SetVerticalOffset(offsetY);
110+
111+
return rect;
112+
}
113+
114+
public void SetHorizontalOffset(double offset)
115+
{
116+
var value = Math.Max(0, offset);
117+
118+
if (value != HorizontalOffset)
119+
{
120+
HorizontalOffset = value;
121+
InvalidateArrange();
122+
}
123+
}
124+
125+
public void SetVerticalOffset(double offset)
126+
{
127+
var value = Math.Max(0, offset);
128+
129+
if (value != VerticalOffset)
130+
{
131+
VerticalOffset = value;
132+
InvalidateArrange();
133+
}
134+
}
135+
136+
protected override Size MeasureOverride(Size availableSize)
137+
{
138+
var maxWidth = 0.0;
139+
var height = 0.0;
140+
141+
foreach (FrameworkElement child in Children)
142+
{
143+
var isFixed = GetIsFixed(child);
144+
var childConstraint = new Size(
145+
isFixed ? availableSize.Width : double.PositiveInfinity,
146+
double.PositiveInfinity);
147+
child.Measure(childConstraint);
148+
maxWidth = Math.Max(maxWidth, child.DesiredSize.Width);
149+
height += child.DesiredSize.Height;
150+
}
151+
152+
ExtentWidth = maxWidth;
153+
ExtentHeight = height;
154+
155+
return new Size(
156+
Math.Min(maxWidth, availableSize.Width),
157+
Math.Min(height, availableSize.Height));
158+
}
159+
160+
protected override Size ArrangeOverride(Size finalSize)
161+
{
162+
var y = -VerticalOffset;
163+
164+
foreach (FrameworkElement child in Children)
165+
{
166+
var isFixed = GetIsFixed(child);
167+
var x = isFixed ? 0 : -HorizontalOffset;
168+
child.Arrange(new Rect(x, y, child.DesiredSize.Width, child.DesiredSize.Height));
169+
y += child.DesiredSize.Height;
170+
}
171+
172+
ViewportWidth = finalSize.Width;
173+
ViewportHeight = finalSize.Height;
174+
ScrollOwner?.InvalidateScrollInfo();
175+
return finalSize;
176+
}
177+
}
178+
}

src/GitHub.UI/GitHub.UI.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
<DesignTime>True</DesignTime>
8686
<DependentUpon>OcticonPaths.resx</DependentUpon>
8787
</Compile>
88+
<Compile Include="Controls\ScollingVerticalStackPanel.cs" />
8889
<Compile Include="Controls\SectionControl.cs" />
8990
<Compile Include="Controls\Spinner.xaml.cs">
9091
<DependentUpon>Spinner.xaml</DependentUpon>

src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -162,26 +162,15 @@
162162

163163
<Rectangle DockPanel.Dock="Top" Style="{StaticResource Separator}" Height="2" Margin="-8,5,-8,0"/>
164164

165-
<ScrollViewer VerticalScrollBarVisibility="Auto" Margin="0,0,-8,0">
166-
<Grid Name="bodyGrid" Margin="0 5 0 0">
167-
<Grid.ColumnDefinitions>
168-
<ColumnDefinition Width="*"/>
169-
</Grid.ColumnDefinitions>
170-
171-
<Grid.RowDefinitions>
172-
<!-- Author and open time -->
173-
<RowDefinition Height="Auto"/>
174-
<!-- PR body -->
175-
<RowDefinition Height="Auto"/>
176-
<!-- View on github link -->
177-
<RowDefinition Height="Auto"/>
178-
<!-- Reviewers -->
179-
<RowDefinition Height="Auto"/>
180-
<!-- Files changed -->
181-
<RowDefinition Height="Auto"/>
182-
</Grid.RowDefinitions>
183-
184-
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0 -4 0 0">
165+
<ScrollViewer CanContentScroll="True"
166+
Margin="0,0,-8,0"
167+
HorizontalScrollBarVisibility="Auto"
168+
VerticalScrollBarVisibility="Auto">
169+
<ghfvs:ScollingVerticalStackPanel>
170+
171+
<StackPanel Orientation="Horizontal"
172+
Margin="0 -4 0 0"
173+
ghfvs:ScollingVerticalStackPanel.IsFixed="true">
185174
<ghfvs:GitHubActionLink Margin="0 6" Command="{Binding OpenOnGitHub}">
186175
View on GitHub
187176
</ghfvs:GitHubActionLink>
@@ -255,9 +244,9 @@
255244

256245
<!-- Author and open time -->
257246
<ghfvs:SectionControl Name="descriptionSection"
258-
HeaderText="Description"
259-
IsExpanded="True"
260-
Grid.Row="1">
247+
HeaderText="Description"
248+
IsExpanded="True"
249+
ghfvs:ScollingVerticalStackPanel.IsFixed="true">
261250
<StackPanel Orientation="Vertical">
262251
<!-- View conversation on GitHub -->
263252
<StackPanel Orientation="Horizontal" Margin="0 4 0 0">
@@ -282,7 +271,7 @@
282271
HeaderText="Reviewers"
283272
IsExpanded="True"
284273
Margin="0 8 0 0"
285-
Grid.Row="2">
274+
ghfvs:ScollingVerticalStackPanel.IsFixed="true">
286275
<ItemsControl ItemsSource="{Binding Reviews}" Margin="0 4 12 4">
287276
<ItemsControl.ItemTemplate>
288277
<DataTemplate>
@@ -294,13 +283,17 @@
294283

295284
<!-- Files changed -->
296285
<ghfvs:SectionControl Name="changesSection"
297-
Grid.Row="4"
298-
IsExpanded="True"
299-
HeaderText="{Binding Files.ChangedFilesCount, StringFormat={x:Static prop:Resources.ChangesCountFormat}}"
300-
Margin="0 8 10 0">
301-
<local:PullRequestFilesView DataContext="{Binding Files}"/>
302-
</ghfvs:SectionControl>
303-
</Grid>
286+
Grid.Row="4"
287+
IsExpanded="True"
288+
HeaderText="{Binding Files.ChangedFilesCount, StringFormat={x:Static prop:Resources.ChangesCountFormat}}"
289+
Margin="0 8 10 0"
290+
ghfvs:ScollingVerticalStackPanel.IsFixed="true"/>
291+
292+
<!-- Put the changes tree outside its expander, so it can scroll horizontally
293+
while the header remains fixed -->
294+
<local:PullRequestFilesView DataContext="{Binding Files}"
295+
Visibility="{Binding ElementName=changesSection, Path=IsExpanded, Converter={ghfvs:BooleanToVisibilityConverter}}"/>
296+
</ghfvs:ScollingVerticalStackPanel>
304297
</ScrollViewer>
305298
</DockPanel>
306299
</local:GenericPullRequestDetailView>

src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using GitHub.Exports;
88
using GitHub.UI.Helpers;
99
using GitHub.ViewModels.GitHubPane;
10+
using GitHub.VisualStudio.UI.Helpers;
1011

1112
namespace GitHub.VisualStudio.Views.GitHubPane
1213
{
@@ -17,6 +18,7 @@ public partial class PullRequestFilesView : UserControl
1718
public PullRequestFilesView()
1819
{
1920
InitializeComponent();
21+
PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll;
2022
}
2123

2224
protected override void OnMouseDown(MouseButtonEventArgs e)

0 commit comments

Comments
 (0)