From d4215a56ecfe37b6a827fe355c4cc396e0190c49 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Jun 2020 23:18:15 +0200 Subject: [PATCH] Restyle `ScrollViewer` in menus. Make it appear as an up and down button at the top and bottom of the menu when needed. This follows WPF's menu scrolling behavior. --- .../MenuScrollingVisibilityConverter.cs | 64 ++++++++ src/Avalonia.Controls/ScrollViewer.cs | 32 ++++ src/Avalonia.Themes.Default/ContextMenu.xaml | 2 +- src/Avalonia.Themes.Default/MenuItem.xaml | 2 +- src/Avalonia.Themes.Default/ScrollViewer.xaml | 150 ++++++++++++------ 5 files changed, 202 insertions(+), 48 deletions(-) create mode 100644 src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs new file mode 100644 index 0000000000..e6420fe342 --- /dev/null +++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Avalonia.Controls.Primitives; +using Avalonia.Data.Converters; +using Avalonia.Utilities; + +namespace Avalonia.Controls.Converters +{ + public class MenuScrollingVisibilityConverter : IMultiValueConverter + { + public static readonly MenuScrollingVisibilityConverter Instance = new MenuScrollingVisibilityConverter(); + + public object Convert(IList values, Type targetType, object parameter, CultureInfo culture) + { + if (parameter == null || + values == null || + values.Count != 4 || + !(values[0] is ScrollBarVisibility visiblity) || + !(values[1] is double offset) || + !(values[2] is double extent) || + !(values[3] is double viewport)) + { + return AvaloniaProperty.UnsetValue; + } + + if (visiblity == ScrollBarVisibility.Auto) + { + if (extent == viewport) + { + return false; + } + + double target; + + if (parameter is double d) + { + target = d; + } + else if (parameter is string s) + { + target = double.Parse(s, NumberFormatInfo.InvariantInfo); + } + else + { + return AvaloniaProperty.UnsetValue; + } + + // Calculate the percent so that we can see if we are near the edge of the range + double percent = MathUtilities.Clamp(offset * 100.0 / (extent - viewport), 0, 100); + + if (MathUtilities.AreClose(percent, target)) + { + // We are at the end of the range, so no need for this button to be shown + return false; + } + + return true; + } + + return false; + } + } +} diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index c3f0dc0056..813d6967c3 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -387,6 +387,38 @@ namespace Avalonia.Controls /// IControl IScrollAnchorProvider.CurrentAnchor => null; // TODO: Implement + /// + /// Scrolls the content up one line. + /// + public void LineUp() + { + Offset -= new Vector(0, _smallChange.Height); + } + + /// + /// Scrolls the content down one line. + /// + public void LineDown() + { + Offset += new Vector(0, _smallChange.Height); + } + + /// + /// Scrolls the content left one line. + /// + public void LineLeft() + { + Offset -= new Vector(_smallChange.Width, 0); + } + + /// + /// Scrolls the content right one line. + /// + public void LineRight() + { + Offset += new Vector(_smallChange.Width, 0); + } + /// /// Scrolls to the top-left corner of the content. /// diff --git a/src/Avalonia.Themes.Default/ContextMenu.xaml b/src/Avalonia.Themes.Default/ContextMenu.xaml index 75f8f7c23d..9b84253c8a 100644 --- a/src/Avalonia.Themes.Default/ContextMenu.xaml +++ b/src/Avalonia.Themes.Default/ContextMenu.xaml @@ -10,7 +10,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> - + - + - - - - - - - + + + + + + + + + + + + + + + +