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

Commit 718cc4b

Browse files
authored
Merge branch 'master' into fixes/1686-mark-pr-branch
2 parents 68cae75 + 8d59a27 commit 718cc4b

File tree

4 files changed

+233
-31
lines changed

4 files changed

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

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\ScrollingVerticalStackPanel.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:ScrollingVerticalStackPanel>
170+
171+
<StackPanel Orientation="Horizontal"
172+
Margin="0 -4 0 0"
173+
ghfvs:ScrollingVerticalStackPanel.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:ScrollingVerticalStackPanel.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:ScrollingVerticalStackPanel.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:ScrollingVerticalStackPanel.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:ScrollingVerticalStackPanel>
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)