Pārlūkot izejas kodu

Merge branch 'master' into feature/button-ispressed

Steven Kirk 8 gadi atpakaļ
vecāks
revīzija
51454b2fc8

+ 15 - 37
src/Avalonia.Controls/Button.cs

@@ -2,13 +2,10 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Linq;
 using System.Windows.Input;
 using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Interactivity;
-using Avalonia.Rendering;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
 {
@@ -68,7 +65,7 @@ namespace Avalonia.Controls
         /// Defines the <see cref="Click"/> event.
         /// </summary>
         public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
-            RoutedEvent.Register<Button, RoutedEventArgs>("Click", RoutingStrategies.Bubble);
+            RoutedEvent.Register<Button, RoutedEventArgs>(nameof(Click), RoutingStrategies.Bubble);
 
         private ICommand _command;
 
@@ -81,7 +78,6 @@ namespace Avalonia.Controls
         static Button()
         {
             FocusableProperty.OverrideDefaultValue(typeof(Button), true);
-            ClickEvent.AddClassHandler<Button>(x => x.OnClick);
             CommandProperty.Changed.Subscribe(CommandChanged);
             IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
             PseudoClass(IsPressedProperty, ":pressed");
@@ -155,9 +151,7 @@ namespace Avalonia.Controls
 
             if (IsDefault)
             {
-                var inputElement = e.Root as IInputElement;
-
-                if (inputElement != null)
+                if (e.Root is IInputElement inputElement)
                 {
                     ListenForDefault(inputElement);
                 }
@@ -169,14 +163,14 @@ namespace Avalonia.Controls
         {
             if (e.Key == Key.Enter)
             {
-                RaiseClickEvent();
+                OnClick();
                 e.Handled = true;
             }
             else if (e.Key == Key.Space)
             {
                 if (ClickMode == ClickMode.Press)
                 {
-                    RaiseClickEvent();
+                    OnClick();
                 }
                 IsPressed = true;
                 e.Handled = true;
@@ -192,7 +186,7 @@ namespace Avalonia.Controls
             {
                 if (ClickMode == ClickMode.Release)
                 {
-                    RaiseClickEvent();
+                    OnClick();
                 }
                 IsPressed = false;
                 e.Handled = true;
@@ -206,9 +200,7 @@ namespace Avalonia.Controls
 
             if (IsDefault)
             {
-                var inputElement = e.Root as IInputElement;
-
-                if (inputElement != null)
+                if (e.Root is IInputElement inputElement)
                 {
                     StopListeningForDefault(inputElement);
                 }
@@ -218,9 +210,11 @@ namespace Avalonia.Controls
         /// <summary>
         /// Invokes the <see cref="Click"/> event.
         /// </summary>
-        /// <param name="e">The event args.</param>
-        protected virtual void OnClick(RoutedEventArgs e)
+        protected virtual void OnClick()
         {
+            var e = new RoutedEventArgs(ClickEvent);
+            RaiseEvent(e);
+
             if (Command != null)
             {
                 Command.Execute(CommandParameter);
@@ -241,7 +235,7 @@ namespace Avalonia.Controls
 
                 if (ClickMode == ClickMode.Press)
                 {
-                    RaiseClickEvent();
+                    OnClick();
                 }
             }
         }
@@ -259,7 +253,7 @@ namespace Avalonia.Controls
 
                 if (ClickMode == ClickMode.Release && new Rect(Bounds.Size).Contains(e.GetPosition(this)))
                 {
-                    RaiseClickEvent();
+                    OnClick();
                 }
             }
         }
@@ -282,9 +276,7 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         private static void CommandChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            var button = e.Sender as Button;
-
-            if (button != null)
+            if (e.Sender is Button button)
             {
                 var oldCommand = e.OldValue as ICommand;
                 var newCommand = e.NewValue as ICommand;
@@ -311,9 +303,8 @@ namespace Avalonia.Controls
         {
             var button = e.Sender as Button;
             var isDefault = (bool)e.NewValue;
-            var inputRoot = button?.VisualRoot as IInputElement;
 
-            if (inputRoot != null)
+            if (button?.VisualRoot is IInputElement inputRoot)
             {
                 if (isDefault)
                 {
@@ -356,19 +347,6 @@ namespace Avalonia.Controls
             root.RemoveHandler(KeyDownEvent, RootKeyDown);
         }
 
-        /// <summary>
-        /// Raises the <see cref="Click"/> event.
-        /// </summary>
-        private void RaiseClickEvent()
-        {
-            RoutedEventArgs click = new RoutedEventArgs
-            {
-                RoutedEvent = ClickEvent,
-            };
-
-            RaiseEvent(click);
-        }
-
         /// <summary>
         /// Called when a key is pressed on the input root and the button <see cref="IsDefault"/>.
         /// </summary>
@@ -378,7 +356,7 @@ namespace Avalonia.Controls
         {
             if (e.Key == Key.Enter && IsVisible && IsEnabled)
             {
-                RaiseClickEvent();
+                OnClick();
             }
         }
     }

+ 24 - 0
src/Avalonia.Controls/Primitives/RangeBase.cs

@@ -38,6 +38,18 @@ namespace Avalonia.Controls.Primitives
                 o => o.Value,
                 (o, v) => o.Value = v);
 
+        /// <summary>
+        /// Defines the <see cref="SmallChange"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> SmallChangeProperty =
+            AvaloniaProperty.Register<RangeBase, double>(nameof(SmallChange), 0.1);
+
+        /// <summary>
+        /// Defines the <see cref="LargeChange"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> LargeChangeProperty =
+            AvaloniaProperty.Register<RangeBase, double>(nameof(LargeChange), 1);
+
         private double _minimum;
         private double _maximum = 100.0;
         private double _value;
@@ -103,6 +115,18 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        public double SmallChange
+        {
+            get => GetValue(SmallChangeProperty);
+            set => SetValue(SmallChangeProperty, value);
+        }
+
+        public double LargeChange
+        {
+            get => GetValue(LargeChangeProperty);
+            set => SetValue(LargeChangeProperty, value);
+        }
+
         /// <summary>
         /// Throws an exception if the double valus is NaN or Inf.
         /// </summary>

+ 99 - 4
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -5,6 +5,7 @@ using System;
 using System.Reactive;
 using System.Reactive.Linq;
 using Avalonia.Data;
+using Avalonia.Interactivity;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -31,13 +32,18 @@ namespace Avalonia.Controls.Primitives
         public static readonly StyledProperty<Orientation> OrientationProperty =
             AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation));
 
+        private Button _lineUpButton;
+        private Button _lineDownButton;
+        private Button _pageUpButton;
+        private Button _pageDownButton;
+
         /// <summary>
         /// Initializes static members of the <see cref="ScrollBar"/> class. 
         /// </summary>
         static ScrollBar()
         {
-            PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
-            PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
+            PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
+            PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
         }
 
         /// <summary>
@@ -97,12 +103,101 @@ namespace Avalonia.Controls.Primitives
                     return false;
 
                 case ScrollBarVisibility.Auto:
-                    var viewportSize = ViewportSize;
-                    return double.IsNaN(viewportSize) || viewportSize < Maximum - Minimum;
+                    return double.IsNaN(ViewportSize) || Maximum > 0;
 
                 default:
                     throw new InvalidOperationException("Invalid value for ScrollBar.Visibility.");
             }
         }
+
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            base.OnTemplateApplied(e);
+
+            if (_lineUpButton != null)
+            {
+                _lineUpButton.Click -= LineUpClick;
+            }
+
+            if (_lineDownButton != null)
+            {
+                _lineDownButton.Click -= LineDownClick;
+            }
+
+            if (_pageUpButton != null)
+            {
+                _pageUpButton.Click -= PageUpClick;
+            }
+
+            if (_pageDownButton != null)
+            {
+                _pageDownButton.Click -= PageDownClick;
+            }
+
+            _lineUpButton = e.NameScope.Find<Button>("PART_LineUpButton");
+            _lineDownButton = e.NameScope.Find<Button>("PART_LineDownButton");
+            _pageUpButton = e.NameScope.Find<Button>("PART_PageUpButton");
+            _pageDownButton = e.NameScope.Find<Button>("PART_PageDownButton");
+
+            if (_lineUpButton != null)
+            {
+                _lineUpButton.Click += LineUpClick;
+            }
+
+            if (_lineDownButton != null)
+            {
+                _lineDownButton.Click += LineDownClick;
+            }
+
+            if (_pageUpButton != null)
+            {
+                _pageUpButton.Click += PageUpClick;
+            }
+
+            if (_pageDownButton != null)
+            {
+                _pageDownButton.Click += PageDownClick;
+            }
+        }
+
+        private void LineUpClick(object sender, RoutedEventArgs e)
+        {
+            SmallDecrement();
+        }
+
+        private void LineDownClick(object sender, RoutedEventArgs e)
+        {
+            SmallIncrement();
+        }
+
+        private void PageUpClick(object sender, RoutedEventArgs e)
+        {
+            LargeDecrement();
+        }
+
+        private void PageDownClick(object sender, RoutedEventArgs e)
+        {
+            LargeIncrement();
+        }
+
+        private void SmallDecrement()
+        {
+            Value = Math.Max(Value - SmallChange * ViewportSize, Minimum);
+        }
+
+        private void SmallIncrement()
+        {
+            Value = Math.Min(Value + SmallChange * ViewportSize, Maximum);
+        }
+
+        private void LargeDecrement()
+        {
+            Value = Math.Max(Value - LargeChange * ViewportSize, Minimum);
+        }
+
+        private void LargeIncrement()
+        {
+            Value = Math.Min(Value + LargeChange * ViewportSize, Maximum);
+        }
     }
 }

+ 2 - 2
src/Avalonia.Controls/Primitives/ToggleButton.cs

@@ -29,10 +29,10 @@ namespace Avalonia.Controls.Primitives
             set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
         }
 
-        protected override void OnClick(RoutedEventArgs e)
+        protected override void OnClick()
         {
             Toggle();
-            base.OnClick(e);
+            base.OnClick();
         }
 
         protected virtual void Toggle()

+ 80 - 22
src/Avalonia.Controls/Primitives/Track.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Controls.Primitives
     public class Track : Control
     {
         public static readonly DirectProperty<Track, double> MinimumProperty =
-            RangeBase.MinimumProperty.AddOwner<Track>(o => o.Minimum, (o,v) => o.Minimum = v);
+            RangeBase.MinimumProperty.AddOwner<Track>(o => o.Minimum, (o, v) => o.Minimum = v);
 
         public static readonly DirectProperty<Track, double> MaximumProperty =
             RangeBase.MaximumProperty.AddOwner<Track>(o => o.Maximum, (o, v) => o.Maximum = v);
@@ -25,7 +25,13 @@ namespace Avalonia.Controls.Primitives
             ScrollBar.OrientationProperty.AddOwner<Track>();
 
         public static readonly StyledProperty<Thumb> ThumbProperty =
-            AvaloniaProperty.Register<Track, Thumb>("Thumb");
+            AvaloniaProperty.Register<Track, Thumb>(nameof(Thumb));
+
+        public static readonly StyledProperty<Button> IncreaseButtonProperty =
+            AvaloniaProperty.Register<Track, Button>(nameof(IncreaseButton));
+
+        public static readonly StyledProperty<Button> DecreaseButtonProperty =
+            AvaloniaProperty.Register<Track, Button>(nameof(DecreaseButton));
 
         private double _minimum;
         private double _maximum = 100.0;
@@ -34,6 +40,8 @@ namespace Avalonia.Controls.Primitives
         static Track()
         {
             ThumbProperty.Changed.AddClassHandler<Track>(x => x.ThumbChanged);
+            IncreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged);
+            DecreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged);
             AffectsArrange(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
         }
 
@@ -74,6 +82,18 @@ namespace Avalonia.Controls.Primitives
             set { SetValue(ThumbProperty, value); }
         }
 
+        public Button IncreaseButton
+        {
+            get { return GetValue(IncreaseButtonProperty); }
+            set { SetValue(IncreaseButtonProperty, value); }
+        }
+
+        public Button DecreaseButton
+        {
+            get { return GetValue(DecreaseButtonProperty); }
+            set { SetValue(DecreaseButtonProperty, value); }
+        }
+
         protected override Size MeasureOverride(Size availableSize)
         {
             var thumb = Thumb;
@@ -98,34 +118,54 @@ namespace Avalonia.Controls.Primitives
         protected override Size ArrangeOverride(Size finalSize)
         {
             var thumb = Thumb;
+            var increaseButton = IncreaseButton;
+            var decreaseButton = DecreaseButton;
 
-            if (thumb != null)
+            var range = Maximum - Minimum;
+            var offset = Math.Min(Value - Minimum, range);
+            var viewportSize = ViewportSize;
+            var extent = range + viewportSize;
+
+            if (Orientation == Orientation.Horizontal)
             {
-                var range = Maximum - Minimum;
-                var thumbFraction = ViewportSize / range;
-                var valueFraction = (Value - Minimum) / range;
+                var thumbWidth = double.IsNaN(viewportSize) ? thumb?.DesiredSize.Width ?? 0 : finalSize.Width * viewportSize / extent;
+                var remaining = finalSize.Width - thumbWidth;
+                var firstWidth = range <= 0 ? 0 : remaining * offset / range;
 
-                if (double.IsNaN(valueFraction) || double.IsInfinity(valueFraction))
+                if (decreaseButton != null)
                 {
-                    valueFraction = 0;
-                    thumbFraction = 1;
+                    decreaseButton.Arrange(new Rect(0, 0, firstWidth, finalSize.Height));
                 }
-                else if (double.IsNaN(thumbFraction) || double.IsInfinity(thumbFraction))
+
+                if (thumb != null)
                 {
-                    thumbFraction = 0;
+                    thumb.Arrange(new Rect(firstWidth, 0, thumbWidth, finalSize.Height));
                 }
 
-                if (Orientation == Orientation.Horizontal)
+                if (increaseButton != null)
                 {
-                    var width = Math.Max(finalSize.Width * thumbFraction, thumb.MinWidth);
-                    var x = (finalSize.Width - width) * valueFraction;
-                    thumb.Arrange(new Rect(x, 0, width, finalSize.Height));
+                    increaseButton.Arrange(new Rect(firstWidth + thumbWidth, 0, remaining - firstWidth, finalSize.Height));
                 }
-                else
+            }
+            else
+            {
+                var thumbHeight = double.IsNaN(viewportSize) ? thumb?.DesiredSize.Height ?? 0 : finalSize.Height * viewportSize / extent;
+                var remaining = finalSize.Height - thumbHeight;
+                var firstHeight = range <= 0 ? 0 : remaining * offset / range;
+
+                if (decreaseButton != null)
                 {
-                    var height = Math.Max(finalSize.Height * thumbFraction, thumb.MinHeight);
-                    var y = (finalSize.Height - height) * valueFraction;
-                    thumb.Arrange(new Rect(0, y, finalSize.Width, height));
+                    decreaseButton.Arrange(new Rect(0, 0, finalSize.Width, firstHeight));
+                }
+
+                if (thumb != null)
+                {
+                    thumb.Arrange(new Rect(0, firstHeight, finalSize.Width, thumbHeight));
+                }
+
+                if (increaseButton != null)
+                {
+                    increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, remaining - firstHeight));
                 }
             }
 
@@ -140,10 +180,10 @@ namespace Avalonia.Controls.Primitives
             if (oldThumb != null)
             {
                 oldThumb.DragDelta -= ThumbDragged;
-            }
 
-            LogicalChildren.Clear();
-            VisualChildren.Clear();
+                LogicalChildren.Remove(oldThumb);
+                VisualChildren.Remove(oldThumb);
+            }
 
             if (newThumb != null)
             {
@@ -153,6 +193,24 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        private void ButtonChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var oldButton = (Button)e.OldValue;
+            var newButton = (Button)e.NewValue;
+
+            if (oldButton != null)
+            {
+                LogicalChildren.Remove(oldButton);
+                VisualChildren.Remove(oldButton);
+            }
+
+            if (newButton != null)
+            {
+                LogicalChildren.Add(newButton);
+                VisualChildren.Add(newButton);
+            }
+        }
+
         private void ThumbDragged(object sender, VectorEventArgs e)
         {
             double range = Maximum - Minimum;

+ 82 - 0
src/Avalonia.Controls/RepeatButton.cs

@@ -0,0 +1,82 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls
+{
+    public class RepeatButton : Button
+    {
+        /// <summary>
+        /// Defines the <see cref="Delay"/> property.
+        /// </summary>
+        public static readonly StyledProperty<int> DelayProperty =
+            AvaloniaProperty.Register<Button, int>(nameof(Delay), 100);
+
+        private DispatcherTimer _repeatTimer;
+
+        /// <summary>
+        /// Gets or sets the amount of time, in milliseconds, of repeating clicks.
+        /// </summary>
+        public int Delay
+        {
+            get { return GetValue(DelayProperty); }
+            set { SetValue(DelayProperty, value); }
+        }
+
+        private void StartTimer()
+        {
+            if (_repeatTimer == null)
+            {
+                _repeatTimer = new DispatcherTimer();
+                _repeatTimer.Tick += (o, e) => OnClick();
+            }
+
+            if (_repeatTimer.IsEnabled) return;
+
+            _repeatTimer.Interval = TimeSpan.FromMilliseconds(Delay);
+            _repeatTimer.Start();
+        }
+
+        private void StopTimer()
+        {
+            _repeatTimer?.Stop();
+        }
+
+        protected override void OnKeyDown(KeyEventArgs e)
+        {
+            base.OnKeyDown(e);
+
+            if (e.Key == Key.Space)
+            {
+                StartTimer();
+            }
+        }
+
+        protected override void OnKeyUp(KeyEventArgs e)
+        {
+            base.OnKeyUp(e);
+
+            StopTimer();
+        }
+
+        protected override void OnPointerPressed(PointerPressedEventArgs e)
+        {
+            base.OnPointerPressed(e);
+
+            if (e.MouseButton == MouseButton.Left)
+            {
+                StartTimer();
+            }
+        }
+
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
+        {
+            base.OnPointerReleased(e);
+
+            if (e.MouseButton == MouseButton.Left)
+            {
+                StopTimer();
+            }
+        }
+    }
+}

+ 2 - 2
src/Avalonia.Controls/ScrollViewer.cs

@@ -275,7 +275,7 @@ namespace Avalonia.Controls
         /// </summary>
         protected double HorizontalScrollBarViewportSize
         {
-            get { return Max((_viewport.Width / _extent.Width) * (_extent.Width - _viewport.Width), 0); }
+            get { return _viewport.Width; }
         }
 
         /// <summary>
@@ -308,7 +308,7 @@ namespace Avalonia.Controls
         /// </summary>
         protected double VerticalScrollBarViewportSize
         {
-            get { return Max((_viewport.Height / _extent.Height) * (_extent.Height - _viewport.Height), 0); }
+            get { return _viewport.Height; }
         }
 
         /// <summary>

+ 53 - 2
src/Avalonia.Controls/Slider.cs

@@ -33,6 +33,8 @@ namespace Avalonia.Controls
 
         // Slider required parts
         private Track _track;
+        private Button _decreaseButton;
+        private Button _increaseButton;
 
         /// <summary>
         /// Initializes static members of the <see cref="Slider"/> class. 
@@ -44,6 +46,8 @@ namespace Avalonia.Controls
             Thumb.DragStartedEvent.AddClassHandler<Slider>(x => x.OnThumbDragStarted, RoutingStrategies.Bubble);
             Thumb.DragDeltaEvent.AddClassHandler<Slider>(x => x.OnThumbDragDelta, RoutingStrategies.Bubble);
             Thumb.DragCompletedEvent.AddClassHandler<Slider>(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble);
+            SmallChangeProperty.OverrideDefaultValue<Slider>(1);
+            LargeChangeProperty.OverrideDefaultValue<Slider>(10);
         }
 
         /// <summary>
@@ -83,7 +87,54 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
-            _track = e.NameScope.Get<Track>("PART_Track");
+            if (_decreaseButton != null)
+            {
+                _decreaseButton.Click -= DecreaseClick;
+            }
+
+            if (_increaseButton != null)
+            {
+                _increaseButton.Click -= IncreaseClick;
+            }
+
+            _decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
+            _track = e.NameScope.Find<Track>("PART_Track");
+            _increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
+
+            if (_decreaseButton != null)
+            {
+                _decreaseButton.Click += DecreaseClick;
+            }
+
+            if (_increaseButton != null)
+            {
+                _increaseButton.Click += IncreaseClick;
+            }
+        }
+
+        private void DecreaseClick(object sender, RoutedEventArgs e)
+        {
+            ChangeValueBy(-LargeChange);
+        }
+
+        private void IncreaseClick(object sender, RoutedEventArgs e)
+        {
+            ChangeValueBy(LargeChange);
+        }
+
+        private void ChangeValueBy(double by)
+        {
+            if (IsSnapToTickEnabled)
+            {
+                by = by < 0 ? Math.Min(-TickFrequency, by) : Math.Max(TickFrequency, by);
+            }
+
+            var value = Value;
+            var next = SnapToTick(Math.Max(Math.Min(value + by, Maximum), Minimum));
+            if (next != value)
+            {
+                Value = next;
+            }
         }
 
         /// <summary>
@@ -101,7 +152,7 @@ namespace Avalonia.Controls
         protected virtual void OnThumbDragDelta(VectorEventArgs e)
         {
             Thumb thumb = e.Source as Thumb;
-            if (thumb != null && _track.Thumb == thumb)
+            if (thumb != null && _track?.Thumb == thumb)
             {
                 MoveToNextTick(_track.Value);
             }

+ 1 - 0
src/Avalonia.Themes.Default/DefaultTheme.xaml

@@ -20,6 +20,7 @@
   <StyleInclude Source="resm:Avalonia.Themes.Default.PopupRoot.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ProgressBar.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.RadioButton.xaml?assembly=Avalonia.Themes.Default"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Default.RepeatButton.xaml?assembly=Avalonia.Themes.Default" />
   <StyleInclude Source="resm:Avalonia.Themes.Default.Separator.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.Slider.xaml?assembly=Avalonia.Themes.Default"/>  
   <StyleInclude Source="resm:Avalonia.Themes.Default.ScrollBar.xaml?assembly=Avalonia.Themes.Default"/>

+ 44 - 0
src/Avalonia.Themes.Default/RepeatButton.xaml

@@ -0,0 +1,44 @@
+<Styles xmlns="https://github.com/avaloniaui">
+    <Style Selector="RepeatButton">
+        <Setter Property="Background"
+                Value="{StyleResource ThemeControlMidBrush}" />
+        <Setter Property="BorderBrush"
+                Value="{StyleResource ThemeBorderLightBrush}" />
+        <Setter Property="BorderThickness"
+                Value="{StyleResource ThemeBorderThickness}" />
+        <Setter Property="Foreground"
+                Value="{StyleResource ThemeForegroundBrush}" />
+        <Setter Property="HorizontalContentAlignment"
+                Value="Center" />
+        <Setter Property="VerticalContentAlignment"
+                Value="Center" />
+        <Setter Property="Padding"
+                Value="4" />
+        <Setter Property="Template">
+            <ControlTemplate>
+                <ContentPresenter Name="PART_ContentPresenter"
+                                  Background="{TemplateBinding Background}"
+                                  BorderBrush="{TemplateBinding BorderBrush}"
+                                  BorderThickness="{TemplateBinding BorderThickness}"
+                                  Content="{TemplateBinding Content}"
+                                  ContentTemplate="{TemplateBinding ContentTemplate}"
+                                  Padding="{TemplateBinding Padding}"
+                                  TextBlock.Foreground="{TemplateBinding Foreground}"
+                                  HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                                  VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
+            </ControlTemplate>
+        </Setter>
+    </Style>
+    <Style Selector="RepeatButton:pointerover /template/ ContentPresenter">
+        <Setter Property="BorderBrush"
+                Value="{StyleResource ThemeBorderMidBrush}" />
+    </Style>
+    <Style Selector="RepeatButton:pressed  /template/ ContentPresenter">
+        <Setter Property="Background"
+                Value="{StyleResource ThemeControlDarkBrush}" />
+    </Style>
+    <Style Selector="RepeatButton:disabled">
+        <Setter Property="Opacity"
+                Value="{StyleResource ThemeDisabledOpacity}" />
+    </Style>
+</Styles>

+ 125 - 33
src/Avalonia.Themes.Default/ScrollBar.xaml

@@ -1,35 +1,127 @@
 <Styles xmlns="https://github.com/avaloniaui">
-  <Style Selector="ScrollBar">
-    <Setter Property="Template">
-      <ControlTemplate>
-        <Border Background="{StyleResource ThemeControlMidBrush}">
-          <Track Minimum="{TemplateBinding Minimum}"
-                 Maximum="{TemplateBinding Maximum}"
-                 Value="{TemplateBinding Path=Value, Mode=TwoWay}"
-                 ViewportSize="{TemplateBinding ViewportSize}"
-                 Orientation="{TemplateBinding Orientation}">
-            <Thumb Name="thumb">
-              <Thumb.Template>
-                <ControlTemplate>
-                  <Border Background="{StyleResource ThemeControlDarkBrush}"/>
-                </ControlTemplate>
-              </Thumb.Template>
-            </Thumb>
-          </Track>
-        </Border>
-      </ControlTemplate>
-    </Setter>
-  </Style>
-  <Style Selector="ScrollBar:horizontal">
-    <Setter Property="Height" Value="10"/>
-  </Style>
-  <Style Selector="ScrollBar:horizontal /template/ Thumb#thumb">
-    <Setter Property="MinWidth" Value="10"/>
-  </Style>
-  <Style Selector="ScrollBar:vertical">
-    <Setter Property="Width" Value="10"/>
-  </Style>
-  <Style Selector="ScrollBar:vertical /template/ Thumb#thumb">
-    <Setter Property="MinHeight" Value="10"/>
-  </Style>
+    <Style Selector="ScrollBar">
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Border Background="{StyleResource ThemeControlMidBrush}">
+                    <Grid RowDefinitions="10,*,10">
+                        <RepeatButton Name="PART_LineUpButton"
+                                      Classes="repeat"
+                                      Grid.Row="0"
+                                      Grid.Column="0">
+                            <Path Data="M 0,4 C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4 z"
+                                  Stretch="Uniform"
+                                  Fill="Gray" />
+                        </RepeatButton>
+                        <Track Grid.Row="1"
+                               Grid.Column="1"
+                               Minimum="{TemplateBinding Minimum}"
+                               Maximum="{TemplateBinding Maximum}"
+                               Value="{TemplateBinding Path=Value, Mode=TwoWay}"
+                               ViewportSize="{TemplateBinding ViewportSize}"
+                               Orientation="{TemplateBinding Orientation}">
+                            <Track.DecreaseButton>
+                                <RepeatButton Name="PART_PageUpButton"
+                                              Classes="repeattrack" />
+                            </Track.DecreaseButton>
+                            <Track.IncreaseButton>
+                                <RepeatButton Name="PART_PageDownButton"
+                                              Classes="repeattrack" />
+                            </Track.IncreaseButton>
+                            <Thumb Name="thumb">
+                                <Thumb.Template>
+                                    <ControlTemplate>
+                                        <Border Background="{StyleResource ThemeControlDarkBrush}" />
+                                    </ControlTemplate>
+                                </Thumb.Template>
+                            </Thumb>
+                        </Track>
+                        <RepeatButton Name="PART_LineDownButton"
+                                      Classes="repeat"
+                                      Grid.Row="2"
+                                      Grid.Column="2">
+                            <Path Data="M 0,2.5 C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5 z"
+                                  Stretch="Uniform"
+                                  Fill="Gray" />
+                        </RepeatButton>
+                    </Grid>
+                </Border>
+            </ControlTemplate>
+        </Setter>
+    </Style>
+    <Style Selector="ScrollBar:horizontal">
+        <Setter Property="Height"
+                Value="10" />
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Border Background="{StyleResource ThemeControlMidBrush}">
+                    <Grid ColumnDefinitions="10,*,10">
+                        <RepeatButton Name="PART_LineUpButton"
+                                      Classes="repeat"
+                                      Grid.Row="0"
+                                      Grid.Column="0">
+                            <Path Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z"
+                                  Stretch="Uniform"
+                                  Fill="Gray" />
+                        </RepeatButton>
+                        <Track Grid.Row="1"
+                               Grid.Column="1"
+                               Minimum="{TemplateBinding Minimum}"
+                               Maximum="{TemplateBinding Maximum}"
+                               Value="{TemplateBinding Path=Value, Mode=TwoWay}"
+                               ViewportSize="{TemplateBinding ViewportSize}"
+                               Orientation="{TemplateBinding Orientation}">
+                            <Track.DecreaseButton>
+                                <RepeatButton Name="PART_PageUpButton"
+                                              Classes="repeattrack" />
+                            </Track.DecreaseButton>
+                            <Track.IncreaseButton>
+                                <RepeatButton Name="PART_PageDownButton"
+                                              Classes="repeattrack" />
+                            </Track.IncreaseButton>
+                            <Thumb Name="thumb">
+                                <Thumb.Template>
+                                    <ControlTemplate>
+                                        <Border Background="{StyleResource ThemeControlDarkBrush}" />
+                                    </ControlTemplate>
+                                </Thumb.Template>
+                            </Thumb>
+                        </Track>
+                        <RepeatButton Name="PART_LineDownButton"
+                                      Classes="repeat"
+                                      Grid.Row="2"
+                                      Grid.Column="2">
+                            <Path Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z"
+                                  Stretch="Uniform"
+                                  Fill="Gray" />
+                        </RepeatButton>
+                    </Grid>
+                </Border>
+            </ControlTemplate>
+        </Setter>
+    </Style>
+    <Style Selector="ScrollBar:horizontal /template/ Thumb#thumb">
+        <Setter Property="MinWidth"
+                Value="10" />
+    </Style>
+    <Style Selector="ScrollBar:vertical">
+        <Setter Property="Width"
+                Value="10" />
+    </Style>
+    <Style Selector="ScrollBar:vertical /template/ Thumb#thumb">
+        <Setter Property="MinHeight"
+                Value="10" />
+    </Style>
+    <Style Selector="ScrollBar /template/ RepeatButton.repeat">
+        <Setter Property="Padding"
+                Value="2" />
+        <Setter Property="BorderThickness"
+                Value="0" />
+    </Style>
+    <Style Selector="ScrollBar /template/ RepeatButton.repeattrack">
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Border Background="{TemplateBinding Background}" />
+            </ControlTemplate>
+        </Setter>
+    </Style>
 </Styles>

+ 2 - 0
src/Avalonia.Themes.Default/ScrollViewer.xaml

@@ -1,4 +1,6 @@
 <Style xmlns="https://github.com/avaloniaui" Selector="ScrollViewer">
+  <Setter Property="Background"
+          Value="Transparent" />
   <Setter Property="Template">
     <ControlTemplate>
       <Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">

+ 25 - 1
src/Avalonia.Themes.Default/Slider.xaml

@@ -12,6 +12,14 @@
           </Grid.RowDefinitions>
           <Border Name="TrackBackground" Grid.Row="1" Height="4" Margin="6,0" VerticalAlignment="Center"/>
           <Track Name="PART_Track" Grid.Row="1">
+            <Track.DecreaseButton>
+               <RepeatButton Name="PART_DecreaseButton"
+                             Classes="repeattrack" />
+            </Track.DecreaseButton>
+            <Track.IncreaseButton>
+               <RepeatButton Name="PART_IncreaseButton"
+                             Classes="repeattrack" />
+            </Track.IncreaseButton>
             <Thumb MinWidth="20" MinHeight="20">
               <Thumb.Template>
                 <ControlTemplate>
@@ -39,6 +47,14 @@
           </Grid.ColumnDefinitions>
           <Border Name="TrackBackground" Grid.Column="1" Width="4" Margin="0,6" HorizontalAlignment="Center"/>
           <Track Name="PART_Track" Grid.Column="1">
+            <Track.DecreaseButton>
+               <RepeatButton Name="PART_DecreaseButton"
+                             Classes="repeattrack" />
+            </Track.DecreaseButton>
+            <Track.IncreaseButton>
+               <RepeatButton Name="PART_IncreaseButton"
+                             Classes="repeattrack" />
+            </Track.IncreaseButton>
             <Thumb MinWidth="20" MinHeight="20">
               <Thumb.Template>
                 <ControlTemplate>
@@ -62,5 +78,13 @@
   <Style Selector="Slider /template/ Border#TrackBackground">
     <Setter Property="BorderThickness" Value="2"/>
     <Setter Property="BorderBrush" Value="{StyleResource ThemeBorderLightBrush}"/>
-  </Style>  
+  </Style>
+  <Style Selector="Slider /template/ RepeatButton.repeattrack">
+    <Setter Property="Background" Value="Transparent"/>
+    <Setter Property="Template">
+        <ControlTemplate>
+            <Border Background="{TemplateBinding Background}" />
+        </ControlTemplate>
+    </Setter>
+  </Style>
 </Styles>

+ 2 - 2
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -517,7 +517,7 @@ namespace Avalonia.Win32
                         WindowsMouseDevice.Instance,
                         timestamp,
                         _owner,
-                        ScreenToClient(DipFromLParam(lParam)),
+                        PointToClient(PointFromLParam(lParam)),
                         new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam));
                     break;
 
@@ -526,7 +526,7 @@ namespace Avalonia.Win32
                         WindowsMouseDevice.Instance,
                         timestamp,
                         _owner,
-                        ScreenToClient(DipFromLParam(lParam)),
+                        PointToClient(PointFromLParam(lParam)),
                         new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam));
                     break;
 

+ 2 - 3
tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs

@@ -65,9 +65,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
             var target = new ScrollBar();
 
             target.Visibility = ScrollBarVisibility.Auto;
-            target.Minimum = 0;
-            target.Maximum = 100;
-            target.ViewportSize = 100;
+            target.ViewportSize = 1;
+            target.Maximum = 0;
 
             Assert.False(target.IsVisible);
         }

+ 5 - 5
tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs

@@ -69,7 +69,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
             target.Measure(new Size(100, 100));
             target.Arrange(new Rect(0, 0, 100, 100));
 
-            Assert.Equal(new Rect(25, 0, 50, 12), thumb.Bounds);
+            Assert.Equal(new Rect(33, 0, 34, 12), thumb.Bounds);
         }
 
         [Fact]
@@ -85,7 +85,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 Thumb = thumb,
                 Orientation = Orientation.Vertical,
                 Minimum = 100,
-                Maximum = 300,
+                Maximum = 200,
                 Value = 150,
                 ViewportSize = 50,
                 Width = 12,
@@ -94,11 +94,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
             target.Measure(new Size(100, 100));
             target.Arrange(new Rect(0, 0, 100, 100));
 
-            Assert.Equal(new Rect(0, 18, 12, 25), thumb.Bounds);
+            Assert.Equal(new Rect(0, 33, 12, 34), thumb.Bounds);
         }
 
         [Fact]
-        public void Thumb_Should_Fill_Track_When_Minimum_Equals_Maximum()
+        public void Thumb_Should_Have_Zero_Width_When_Minimum_Equals_Maximum()
         {
             var thumb = new Thumb
             {
@@ -117,7 +117,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
             target.Measure(new Size(100, 100));
             target.Arrange(new Rect(0, 0, 100, 100));
 
-            Assert.Equal(new Rect(0, 0, 100, 12), thumb.Bounds);
+            Assert.Equal(new Rect(0, 0, 0, 12), thumb.Bounds);
         }
 
         [Fact]

+ 9 - 0
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@@ -177,6 +177,15 @@ namespace Avalonia.Layout.UnitTests
                     It.IsAny<IReadOnlyList<FormattedTextStyleSpan>>()))
                 .Returns(new FormattedTextMock("TEST"));
 
+            var streamGeometry = new Mock<IStreamGeometryImpl>();
+            streamGeometry.Setup(x =>
+                    x.Open())
+                .Returns(new Mock<IStreamGeometryContextImpl>().Object);
+
+            renderInterface.Setup(x =>
+                    x.CreateStreamGeometry())
+                .Returns(streamGeometry.Object);
+
             var windowImpl = new Mock<IWindowImpl>();
 
             Size clientSize = default(Size);