Browse Source

Merge branch 'master' into refactor/standardassetloader-public

pull/11204/head
Max Katz 3 years ago
committed by GitHub
parent
commit
ebb678284b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  2. 2
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  3. 2
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  4. 15
      src/Avalonia.Controls/Primitives/Thumb.cs
  5. 28
      src/Avalonia.Controls/Primitives/Track.cs
  6. 2
      src/Avalonia.Controls/TreeViewItem.cs
  7. 13
      src/Avalonia.Native/AvaloniaNativeDragSource.cs
  8. 12
      src/Windows/Avalonia.Win32/Win32StorageProvider.cs
  9. 170
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  10. 10
      tests/Avalonia.UnitTests/MouseTestHelper.cs

2
src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Validates properties on that have <see cref="ValidationAttribute"/>s.
/// </summary>
internal class DataAnnotationsValidationPlugin : IDataValidationPlugin
public class DataAnnotationsValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]

2
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Validates properties that report errors by throwing exceptions.
/// </summary>
internal class ExceptionValidationPlugin : IDataValidationPlugin
public class ExceptionValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]

2
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>.
/// </summary>
internal class IndeiValidationPlugin : IDataValidationPlugin
public class IndeiValidationPlugin : IDataValidationPlugin
{
private static readonly WeakEvent<INotifyDataErrorInfo, DataErrorsChangedEventArgs>
ErrorsChangedWeakEvent = WeakEvent.Register<INotifyDataErrorInfo, DataErrorsChangedEventArgs>(

15
src/Avalonia.Controls/Primitives/Thumb.cs

@ -4,7 +4,6 @@ using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
@ -47,6 +46,12 @@ namespace Avalonia.Controls.Primitives
remove { RemoveHandler(DragCompletedEvent, value); }
}
internal void AdjustDrag(Vector v)
{
if (_lastPoint.HasValue)
_lastPoint = _lastPoint.Value + v;
}
protected override AutomationPeer OnCreateAutomationPeer() => new ThumbAutomationPeer(this);
protected virtual void OnDragStarted(VectorEventArgs e)
@ -85,22 +90,20 @@ namespace Avalonia.Controls.Primitives
{
if (_lastPoint.HasValue)
{
var point = e.GetPosition(null);
var ev = new VectorEventArgs
{
RoutedEvent = DragDeltaEvent,
Vector = point - _lastPoint.Value,
Vector = e.GetPosition(this) - _lastPoint.Value,
};
RaiseEvent(ev);
_lastPoint = point;
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
e.Handled = true;
_lastPoint = e.GetPosition(null);
_lastPoint = e.GetPosition(this);
var ev = new VectorEventArgs
{
@ -123,7 +126,7 @@ namespace Avalonia.Controls.Primitives
var ev = new VectorEventArgs
{
RoutedEvent = DragCompletedEvent,
Vector = (Vector)e.GetPosition(null),
Vector = (Vector)e.GetPosition(this),
};
RaiseEvent(ev);

28
src/Avalonia.Controls/Primitives/Track.cs

@ -45,6 +45,8 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> IgnoreThumbDragProperty =
AvaloniaProperty.Register<Track, bool>(nameof(IgnoreThumbDrag));
private Vector _lastDrag;
static Track()
{
ThumbProperty.Changed.AddClassHandler<Track>((x, e) => x.ThumbChanged(e));
@ -245,7 +247,10 @@ namespace Avalonia.Controls.Primitives
if (Thumb != null)
{
Thumb.Arrange(new Rect(offset, pieceSize));
var bounds = new Rect(offset, pieceSize);
var adjust = CalculateThumbAdjustment(Thumb, bounds);
Thumb.Arrange(bounds);
Thumb.AdjustDrag(adjust);
}
ThumbCenterOffset = offset.Y + (thumbLength * 0.5);
@ -277,12 +282,16 @@ namespace Avalonia.Controls.Primitives
if (Thumb != null)
{
Thumb.Arrange(new Rect(offset, pieceSize));
var bounds = new Rect(offset, pieceSize);
var adjust = CalculateThumbAdjustment(Thumb, bounds);
Thumb.Arrange(bounds);
Thumb.AdjustDrag(adjust);
}
ThumbCenterOffset = offset.X + (thumbLength * 0.5);
}
_lastDrag = default;
return arrangeSize;
}
@ -296,6 +305,12 @@ namespace Avalonia.Controls.Primitives
}
}
private Vector CalculateThumbAdjustment(Thumb thumb, Rect newThumbBounds)
{
var thumbDelta = newThumbBounds.Position - thumb.Bounds.Position;
return _lastDrag - thumbDelta;
}
private static void CoerceLength(ref double componentLength, double trackLength)
{
if (componentLength < 0)
@ -440,10 +455,17 @@ namespace Avalonia.Controls.Primitives
if (IgnoreThumbDrag)
return;
var value = Value;
var delta = ValueFromDistance(e.Vector.X, e.Vector.Y);
var factor = e.Vector / delta;
SetCurrentValue(ValueProperty, MathUtilities.Clamp(
Value + ValueFromDistance(e.Vector.X, e.Vector.Y),
value + delta,
Minimum,
Maximum));
// Record the part of the drag that actually had effect as the last drag delta.
_lastDrag = (Value - value) * factor;
}
private void ShowChildren(bool visible)

2
src/Avalonia.Controls/TreeViewItem.cs

@ -165,7 +165,7 @@ namespace Avalonia.Controls
{
Key.Left => ApplyToItemOrRecursivelyIfCtrl(FocusAwareCollapseItem, e.KeyModifiers),
Key.Right => ApplyToItemOrRecursivelyIfCtrl(ExpandItem, e.KeyModifiers),
Key.Enter or Key.Space => ApplyToItemOrRecursivelyIfCtrl(IsExpanded ? CollapseItem : ExpandItem, e.KeyModifiers),
Key.Enter => ApplyToItemOrRecursivelyIfCtrl(IsExpanded ? CollapseItem : ExpandItem, e.KeyModifiers),
// do not handle CTRL with numpad keys
Key.Subtract => FocusAwareCollapseItem,

13
src/Avalonia.Native/AvaloniaNativeDragSource.cs

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Native.Interop;
namespace Avalonia.Native
@ -18,16 +17,6 @@ namespace Avalonia.Native
_factory = factory;
}
private static TopLevel FindRoot(object? element)
{
while (element is Interactive interactive && element is not Visual)
element = interactive.GetInteractiveParent();
if (element == null)
return null;
var visual = (Visual)element;
return TopLevel.GetTopLevel(visual);
}
class DndCallback : NativeCallbackBase, IAvnDndResultCallback
{
private TaskCompletionSource<DragDropEffects> _tcs;
@ -46,7 +35,7 @@ namespace Avalonia.Native
public Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects)
{
// Sanity check
var tl = FindRoot(triggerEvent.Source);
var tl = TopLevel.GetTopLevel(triggerEvent.Source as Visual);
var view = tl?.PlatformImpl as WindowBaseImpl;
if (view == null)
throw new ArgumentException();

12
src/Windows/Avalonia.Win32/Win32StorageProvider.cs

@ -99,12 +99,14 @@ namespace Avalonia.Win32
}
frm.SetOptions(options);
if (defaultExtension is not null)
if (defaultExtension is null)
{
fixed (char* pExt = defaultExtension)
{
frm.SetDefaultExtension(pExt);
}
defaultExtension = String.Empty;
}
fixed (char* pExt = defaultExtension)
{
frm.SetDefaultExtension(pExt);
}
suggestedFileName ??= "";

170
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
@ -12,6 +15,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ScrollViewerTests
{
private readonly MouseTestHelper _mouse = new();
[Fact]
public void Content_Is_Created()
{
@ -249,6 +254,121 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Vector(20, 20), target.Offset);
}
[Fact]
public void Scroll_Does_Not_Jump_When_Viewport_Becomes_Smaller_While_Dragging_ScrollBar_Thumb()
{
var content = new TestContent
{
MeasureSize = new Size(1000, 10000),
};
var target = new ScrollViewer
{
Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
Content = content,
};
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Size(1000, 10000), target.Extent);
Assert.Equal(new Size(1000, 1000), target.Viewport);
// We're working in absolute coordinates (i.e. relative to the root) and clicking on
// the center of the vertical thumb.
var thumb = GetVerticalThumb(target);
var p = GetRootPoint(thumb, thumb.Bounds.Center);
// Press the mouse button in the center of the thumb.
_mouse.Down(thumb, position: p);
root.LayoutManager.ExecuteLayoutPass();
// Drag the thumb down 300 pixels.
_mouse.Move(thumb, p += new Vector(0, 300));
root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(new Vector(0, 3000), target.Offset);
Assert.Equal(300, thumb.Bounds.Top);
// Now the extent changes from 10,000 to 5000.
content.MeasureSize /= 2;
content.InvalidateMeasure();
root.LayoutManager.ExecuteLayoutPass();
// Due to the extent change, the thumb moves down but the value remains the same.
Assert.Equal(600, thumb.Bounds.Top);
Assert.Equal(new Vector(0, 3000), target.Offset);
// Drag the thumb down another 100 pixels.
_mouse.Move(thumb, p += new Vector(0, 100));
root.LayoutManager.ExecuteLayoutPass();
// The drag should not cause the offset/thumb to jump *up* to the current absolute
// mouse position, i.e. it should move down in the direction of the drag even if the
// absolute mouse position is now above the thumb.
Assert.Equal(700, thumb.Bounds.Top);
Assert.Equal(new Vector(0, 3500), target.Offset);
}
[Fact]
public void Thumb_Does_Not_Become_Detached_From_Mouse_Position_When_Scrolling_Past_The_Start()
{
var content = new TestContent();
var target = new ScrollViewer
{
Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
Content = content,
};
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Size(1000, 2000), target.Extent);
Assert.Equal(new Size(1000, 1000), target.Viewport);
// We're working in absolute coordinates (i.e. relative to the root) and clicking on
// the center of the vertical thumb.
var thumb = GetVerticalThumb(target);
var p = GetRootPoint(thumb, thumb.Bounds.Center);
// Press the mouse button in the center of the thumb.
_mouse.Down(thumb, position: p);
root.LayoutManager.ExecuteLayoutPass();
// Drag the thumb down 100 pixels.
_mouse.Move(thumb, p += new Vector(0, 100));
root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(new Vector(0, 200), target.Offset);
Assert.Equal(100, thumb.Bounds.Top);
// Drag the thumb up 200 pixels - 100 pixels past the top of the scrollbar.
_mouse.Move(thumb, p -= new Vector(0, 200));
root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(new Vector(0, 0), target.Offset);
Assert.Equal(0, thumb.Bounds.Top);
// Drag the thumb back down 200 pixels.
_mouse.Move(thumb, p += new Vector(0, 200));
root.LayoutManager.ExecuteLayoutPass();
// We should now be back in the state after we first scrolled down 100 pixels.
Assert.Equal(new Vector(0, 200), target.Offset);
Assert.Equal(100, thumb.Bounds.Top);
}
private Point GetRootPoint(Visual control, Point p)
{
if (control.GetVisualRoot() is Visual root &&
control.TransformToVisual(root) is Matrix m)
{
return p.Transform(m);
}
throw new InvalidOperationException("Could not get the point in root coordinates.");
}
private Control CreateTemplate(ScrollViewer control, INameScope scope)
{
return new Grid
@ -273,6 +393,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_HorizontalScrollBar",
Orientation = Orientation.Horizontal,
Template = new FuncControlTemplate<ScrollBar>(CreateScrollBarTemplate),
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
[Grid.RowProperty] = 1,
}.RegisterInNameScope(scope),
@ -280,6 +401,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_VerticalScrollBar",
Orientation = Orientation.Vertical,
Template = new FuncControlTemplate<ScrollBar>(CreateScrollBarTemplate),
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
[Grid.ColumnProperty] = 1,
}.RegisterInNameScope(scope),
@ -287,6 +409,44 @@ namespace Avalonia.Controls.UnitTests
};
}
private Control CreateScrollBarTemplate(ScrollBar scrollBar, INameScope scope)
{
return new Border
{
Child = new Track
{
Name = "track",
IsDirectionReversed = true,
[!Track.MinimumProperty] = scrollBar[!RangeBase.MinimumProperty],
[!Track.MaximumProperty] = scrollBar[!RangeBase.MaximumProperty],
[!!Track.ValueProperty] = scrollBar[!!RangeBase.ValueProperty],
[!Track.ViewportSizeProperty] = scrollBar[!ScrollBar.ViewportSizeProperty],
[!Track.OrientationProperty] = scrollBar[!ScrollBar.OrientationProperty],
Thumb = new Thumb
{
Template = new FuncControlTemplate<Thumb>(CreateThumbTemplate),
},
}.RegisterInNameScope(scope),
};
}
private static Control CreateThumbTemplate(Thumb control, INameScope scope)
{
return new Border
{
Background = Brushes.Gray,
};
}
private Thumb GetVerticalThumb(ScrollViewer target)
{
var scrollbar = Assert.IsType<ScrollBar>(
target.GetTemplateChildren().FirstOrDefault(x => x.Name == "PART_VerticalScrollBar"));
var track = Assert.IsType<Track>(
scrollbar.GetTemplateChildren().FirstOrDefault(x => x.Name == "track"));
return Assert.IsType<Thumb>(track.Thumb);
}
private static void InitializeScrollViewer(ScrollViewer target)
{
target.ApplyTemplate();
@ -295,5 +455,15 @@ namespace Avalonia.Controls.UnitTests
presenter.AttachToScrollViewer();
presenter.UpdateChild();
}
private class TestContent : Control
{
public Size MeasureSize { get; set; } = new Size(1000, 2000);
protected override Size MeasureOverride(Size availableSize)
{
return MeasureSize;
}
}
}
}

10
tests/Avalonia.UnitTests/MouseTestHelper.cs

@ -59,7 +59,7 @@ namespace Avalonia.UnitTests
{
_pressedButton = mouseButton;
_pointer.Capture((IInputElement)target);
source.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (Visual)source, position, Timestamp(), props,
source.RaiseEvent(new PointerPressedEventArgs(source, _pointer, GetRoot(target), position, Timestamp(), props,
modifiers, clickCount));
}
}
@ -68,7 +68,7 @@ namespace Avalonia.UnitTests
public void Move(Interactive target, Interactive source, in Point position, KeyModifiers modifiers = default)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position,
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, GetRoot(target), position,
Timestamp(), new PointerPointProperties((RawInputModifiers)_pressedButtons, PointerUpdateKind.Other), modifiers));
}
@ -88,7 +88,7 @@ namespace Avalonia.UnitTests
);
if (ButtonCount(props) == 0)
{
target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (Visual)target, position,
target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, GetRoot(target), position,
Timestamp(), props, modifiers, _pressedButton));
_pointer.Capture(null);
}
@ -131,5 +131,9 @@ namespace Avalonia.UnitTests
Timestamp(), new PointerPointProperties((RawInputModifiers)_pressedButtons, PointerUpdateKind.Other), KeyModifiers.None));
}
private Visual GetRoot(Interactive source)
{
return ((source as Visual)?.GetVisualRoot() as Visual) ?? (Visual)source;
}
}
}

Loading…
Cancel
Save