Browse Source

Merge branch 'master' into fixes/macBuilds

pull/3211/head
Benedikt Stebner 7 years ago
committed by GitHub
parent
commit
89702d1aa5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  2. 2
      src/Avalonia.Animation/TransitionInstance.cs
  3. 23
      src/Avalonia.Base/EnumExtensions.cs
  4. 5
      src/Avalonia.Base/Utilities/MathUtilities.cs
  5. 12
      src/Avalonia.Base/ValueStore.cs
  6. 5
      src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
  7. 10
      src/Avalonia.Controls/ItemsControl.cs
  8. 7
      src/Avalonia.Controls/Presenters/IItemsPresenter.cs
  9. 15
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  10. 29
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  11. 6
      src/Avalonia.Controls/ToolTipService.cs
  12. 2
      src/Avalonia.Controls/WrapPanel.cs
  13. 13
      src/Avalonia.Input/MouseDevice.cs
  14. 2
      src/Avalonia.Input/Pointer.cs
  15. 9
      src/Avalonia.Input/PointerPoint.cs
  16. 21
      src/Avalonia.Input/TouchDevice.cs
  17. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  18. 5
      src/Avalonia.Native/WindowImplBase.cs
  19. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  20. 2
      src/Avalonia.X11/X11Platform.cs
  21. 10
      src/Avalonia.X11/X11Window.cs
  22. 12
      src/Avalonia.X11/XI2Manager.cs
  23. 8
      src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs
  24. 39
      src/Windows/Avalonia.Win32/WindowImpl.cs
  25. 28
      tests/Avalonia.Animation.UnitTests/TransitionsTests.cs
  26. 12
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs
  27. 53
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  28. 189
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  29. 20
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  30. 109
      tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
  31. 78
      tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs
  32. 39
      tests/Avalonia.Input.UnitTests/PointerTests.cs
  33. 2
      tests/Avalonia.UnitTests/MouseTestHelper.cs
  34. 11
      tests/Avalonia.UnitTests/TestRoot.cs
  35. 75
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

1
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@ -10,3 +10,4 @@ using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]
[assembly: InternalsVisibleTo("Avalonia.LeakTests")] [assembly: InternalsVisibleTo("Avalonia.LeakTests")]
[assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests")]

2
src/Avalonia.Animation/TransitionInstance.cs

@ -28,7 +28,7 @@ namespace Avalonia.Animation
private void TimerTick(TimeSpan t) private void TimerTick(TimeSpan t)
{ {
var interpVal = (double)t.Ticks / _duration.Ticks; var interpVal = _duration.Ticks == 0 ? 1d : (double)t.Ticks / _duration.Ticks;
// Clamp interpolation value. // Clamp interpolation value.
if (interpVal >= 1d | interpVal < 0d) if (interpVal >= 1d | interpVal < 0d)

23
src/Avalonia.Base/EnumExtensions.cs

@ -0,0 +1,23 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Runtime.CompilerServices;
namespace Avalonia
{
/// <summary>
/// Provides extension methods for enums.
/// </summary>
public static class EnumExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
{
var intValue = *(int*)&value;
var intFlag = *(int*)&flag;
return (intValue & intFlag) == intFlag;
}
}
}

5
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -159,6 +159,11 @@ namespace Avalonia.Utilities
/// <returns>The clamped value.</returns> /// <returns>The clamped value.</returns>
public static int Clamp(int val, int min, int max) public static int Clamp(int val, int min, int max)
{ {
if (min > max)
{
throw new ArgumentException($"{min} cannot be greater than {max}.");
}
if (val < min) if (val < min)
{ {
return min; return min;

12
src/Avalonia.Base/ValueStore.cs

@ -57,7 +57,8 @@ namespace Avalonia
{ {
if (priority == (int)BindingPriority.LocalValue) if (priority == (int)BindingPriority.LocalValue)
{ {
_propertyValues.SetValue(property, Validate(property, value)); Validate(property, ref value);
_propertyValues.SetValue(property, value);
Changed(property, priority, v, value); Changed(property, priority, v, value);
return; return;
} }
@ -78,7 +79,8 @@ namespace Avalonia
if (priority == (int)BindingPriority.LocalValue) if (priority == (int)BindingPriority.LocalValue)
{ {
_propertyValues.AddValue(property, Validate(property, value)); Validate(property, ref value);
_propertyValues.AddValue(property, value);
Changed(property, priority, AvaloniaProperty.UnsetValue, value); Changed(property, priority, AvaloniaProperty.UnsetValue, value);
return; return;
} }
@ -166,16 +168,14 @@ namespace Avalonia
validate2); validate2);
} }
private object Validate(AvaloniaProperty property, object value) private void Validate(AvaloniaProperty property, ref object value)
{ {
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType());
if (validate != null && value != AvaloniaProperty.UnsetValue) if (validate != null && value != AvaloniaProperty.UnsetValue)
{ {
return validate(_owner, value); value = validate(_owner, value);
} }
return value;
} }
private DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty property) private DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty property)

5
src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs

@ -48,11 +48,6 @@ namespace Avalonia.Controls.Generators
tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty]; tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty];
} }
if (tabItem.Content == null)
{
tabItem[~ContentControl.ContentProperty] = tabItem[~StyledElement.DataContextProperty];
}
return tabItem; return tabItem;
} }
} }

10
src/Avalonia.Controls/ItemsControl.cs

@ -359,6 +359,12 @@ namespace Avalonia.Controls
UpdateItemCount(); UpdateItemCount();
RemoveControlItemsFromLogicalChildren(oldValue); RemoveControlItemsFromLogicalChildren(oldValue);
AddControlItemsToLogicalChildren(newValue); AddControlItemsToLogicalChildren(newValue);
if (Presenter != null)
{
Presenter.Items = newValue;
}
SubscribeToItems(newValue); SubscribeToItems(newValue);
} }
@ -370,6 +376,8 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
UpdateItemCount();
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
@ -381,7 +389,7 @@ namespace Avalonia.Controls
break; break;
} }
UpdateItemCount(); Presenter?.ItemsChanged(e);
var collection = sender as ICollection; var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0); PseudoClasses.Set(":empty", collection == null || collection.Count == 0);

7
src/Avalonia.Controls/Presenters/IItemsPresenter.cs

@ -1,12 +1,19 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections;
using System.Collections.Specialized;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
{ {
public interface IItemsPresenter : IPresenter public interface IItemsPresenter : IPresenter
{ {
IEnumerable Items { get; set; }
IPanel Panel { get; } IPanel Panel { get; }
void ItemsChanged(NotifyCollectionChangedEventArgs e);
void ScrollIntoView(object item); void ScrollIntoView(object item);
} }
} }

15
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -63,7 +63,7 @@ namespace Avalonia.Controls.Presenters
_itemsSubscription?.Dispose(); _itemsSubscription?.Dispose();
_itemsSubscription = null; _itemsSubscription = null;
if (_createdPanel && value is INotifyCollectionChanged incc) if (!IsHosted && _createdPanel && value is INotifyCollectionChanged incc)
{ {
_itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged); _itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
} }
@ -130,6 +130,8 @@ namespace Avalonia.Controls.Presenters
private set; private set;
} }
protected bool IsHosted => TemplatedParent is IItemsPresenterHost;
/// <inheritdoc/> /// <inheritdoc/>
public override sealed void ApplyTemplate() public override sealed void ApplyTemplate()
{ {
@ -144,6 +146,15 @@ namespace Avalonia.Controls.Presenters
{ {
} }
/// <inheritdoc/>
void IItemsPresenter.ItemsChanged(NotifyCollectionChangedEventArgs e)
{
if (Panel != null)
{
ItemsChanged(e);
}
}
/// <summary> /// <summary>
/// Creates the <see cref="ItemContainerGenerator"/> for the control. /// Creates the <see cref="ItemContainerGenerator"/> for the control.
/// </summary> /// </summary>
@ -215,7 +226,7 @@ namespace Avalonia.Controls.Presenters
_createdPanel = true; _createdPanel = true;
if (_itemsSubscription == null && Items is INotifyCollectionChanged incc) if (!IsHosted && _itemsSubscription == null && Items is INotifyCollectionChanged incc)
{ {
_itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged); _itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
} }

29
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -302,13 +302,24 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/> /// <inheritdoc/>
protected override void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) protected override void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
base.ItemsCollectionChanged(sender, e);
if (_updateCount > 0) if (_updateCount > 0)
{ {
base.ItemsCollectionChanged(sender, e);
return; return;
} }
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
_selection.ItemsInserted(e.NewStartingIndex, e.NewItems.Count);
break;
case NotifyCollectionChangedAction.Remove:
_selection.ItemsRemoved(e.OldStartingIndex, e.OldItems.Count);
break;
}
base.ItemsCollectionChanged(sender, e);
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
@ -318,14 +329,12 @@ namespace Avalonia.Controls.Primitives
} }
else else
{ {
_selection.ItemsInserted(e.NewStartingIndex, e.NewItems.Count);
UpdateSelectedItem(_selection.First(), false); UpdateSelectedItem(_selection.First(), false);
} }
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
_selection.ItemsRemoved(e.OldStartingIndex, e.OldItems.Count);
UpdateSelectedItem(_selection.First(), false); UpdateSelectedItem(_selection.First(), false);
ResetSelectedItems(); ResetSelectedItems();
break; break;
@ -1088,9 +1097,15 @@ namespace Avalonia.Controls.Primitives
} }
else else
{ {
SelectedIndex = _updateSelectedIndex != int.MinValue ? if (_updateSelectedIndex != int.MinValue)
_updateSelectedIndex : {
AlwaysSelected ? 0 : -1; SelectedIndex = _updateSelectedIndex;
}
if (AlwaysSelected && SelectedIndex == -1)
{
SelectedIndex = 0;
}
} }
} }
} }

6
src/Avalonia.Controls/ToolTipService.cs

@ -1,6 +1,7 @@
using System; using System;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -79,7 +80,10 @@ namespace Avalonia.Controls
{ {
StopTimer(); StopTimer();
ToolTip.SetIsOpen(control, true); if ((control as IVisual).IsAttachedToVisualTree)
{
ToolTip.SetIsOpen(control, true);
}
} }
private void Close(Control control) private void Close(Control control)

2
src/Avalonia.Controls/WrapPanel.cs

@ -42,7 +42,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static WrapPanel() static WrapPanel()
{ {
AffectsMeasure<WrapPanel>(OrientationProperty); AffectsMeasure<WrapPanel>(OrientationProperty, ItemWidthProperty, ItemHeightProperty);
} }
/// <summary> /// <summary>

13
src/Avalonia.Input/MouseDevice.cs

@ -14,13 +14,14 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Represents a mouse device. /// Represents a mouse device.
/// </summary> /// </summary>
public class MouseDevice : IMouseDevice public class MouseDevice : IMouseDevice, IDisposable
{ {
private int _clickCount; private int _clickCount;
private Rect _lastClickRect; private Rect _lastClickRect;
private ulong _lastClickTime; private ulong _lastClickTime;
private readonly Pointer _pointer; private readonly Pointer _pointer;
private bool _disposed;
public MouseDevice(Pointer pointer = null) public MouseDevice(Pointer pointer = null)
{ {
@ -126,7 +127,9 @@ namespace Avalonia.Input
{ {
Contract.Requires<ArgumentNullException>(e != null); Contract.Requires<ArgumentNullException>(e != null);
var mouse = (IMouseDevice)e.Device; var mouse = (MouseDevice)e.Device;
if(mouse._disposed)
return;
Position = e.Root.PointToScreen(e.Position); Position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e); var props = CreateProperties(e);
@ -441,5 +444,11 @@ namespace Avalonia.Input
el = (IInputElement)el.VisualParent; el = (IInputElement)el.VisualParent;
} }
} }
public void Dispose()
{
_disposed = true;
_pointer?.Dispose();
}
} }
} }

2
src/Avalonia.Input/Pointer.cs

@ -37,7 +37,7 @@ namespace Avalonia.Input
{ {
if (Captured != null) if (Captured != null)
Captured.DetachedFromVisualTree -= OnCaptureDetached; Captured.DetachedFromVisualTree -= OnCaptureDetached;
var oldCapture = control; var oldCapture = Captured;
Captured = control; Captured = control;
PlatformCapture(control); PlatformCapture(control);
if (oldCapture != null) if (oldCapture != null)

9
src/Avalonia.Input/PointerPoint.cs

@ -1,3 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Input namespace Avalonia.Input
{ {
public sealed class PointerPoint public sealed class PointerPoint
@ -27,9 +30,9 @@ namespace Avalonia.Input
public PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind) public PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind)
{ {
PointerUpdateKind = kind; PointerUpdateKind = kind;
IsLeftButtonPressed = modifiers.HasFlag(RawInputModifiers.LeftMouseButton); IsLeftButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.LeftMouseButton);
IsMiddleButtonPressed = modifiers.HasFlag(RawInputModifiers.MiddleMouseButton); IsMiddleButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.MiddleMouseButton);
IsRightButtonPressed = modifiers.HasFlag(RawInputModifiers.RightMouseButton); IsRightButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.RightMouseButton);
// The underlying input source might be reporting the previous state, // The underlying input source might be reporting the previous state,
// so make sure that we reflect the current state // so make sure that we reflect the current state

21
src/Avalonia.Input/TouchDevice.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
@ -11,10 +12,11 @@ namespace Avalonia.Input
/// This class is supposed to be used on per-toplevel basis, don't use a shared one /// This class is supposed to be used on per-toplevel basis, don't use a shared one
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public class TouchDevice : IInputDevice public class TouchDevice : IInputDevice, IDisposable
{ {
Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>(); private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();
private bool _disposed;
KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) => KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) =>
(KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask); (KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask);
@ -28,6 +30,8 @@ namespace Avalonia.Input
public void ProcessRawEvent(RawInputEventArgs ev) public void ProcessRawEvent(RawInputEventArgs ev)
{ {
if(_disposed)
return;
var args = (RawTouchEventArgs)ev; var args = (RawTouchEventArgs)ev;
if (!_pointers.TryGetValue(args.TouchPointId, out var pointer)) if (!_pointers.TryGetValue(args.TouchPointId, out var pointer))
{ {
@ -82,6 +86,17 @@ namespace Avalonia.Input
} }
public void Dispose()
{
if(_disposed)
return;
var values = _pointers.Values.ToList();
_pointers.Clear();
_disposed = true;
foreach (var p in values)
p.Dispose();
}
} }
} }

1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -95,7 +95,6 @@ namespace Avalonia.Native
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) .Bind<IStandardCursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>() .Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice) .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IPlatformSettings>().ToConstant(this) .Bind<IPlatformSettings>().ToConstant(this)
.Bind<IWindowingPlatform>().ToConstant(this) .Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) .Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))

5
src/Avalonia.Native/WindowImplBase.cs

@ -24,7 +24,7 @@ namespace Avalonia.Native
private object _syncRoot = new object(); private object _syncRoot = new object();
private bool _deferredRendering = false; private bool _deferredRendering = false;
private bool _gpu = false; private bool _gpu = false;
private readonly IMouseDevice _mouse; private readonly MouseDevice _mouse;
private readonly IKeyboardDevice _keyboard; private readonly IKeyboardDevice _keyboard;
private readonly IStandardCursorFactory _cursorFactory; private readonly IStandardCursorFactory _cursorFactory;
private Size _savedLogicalSize; private Size _savedLogicalSize;
@ -38,7 +38,7 @@ namespace Avalonia.Native
_deferredRendering = opts.UseDeferredRendering; _deferredRendering = opts.UseDeferredRendering;
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>(); _keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mouse = AvaloniaLocator.Current.GetService<IMouseDevice>(); _mouse = new MouseDevice();
_cursorFactory = AvaloniaLocator.Current.GetService<IStandardCursorFactory>(); _cursorFactory = AvaloniaLocator.Current.GetService<IStandardCursorFactory>();
} }
@ -142,6 +142,7 @@ namespace Avalonia.Native
{ {
n?.Dispose(); n?.Dispose();
} }
_parent._mouse.Dispose();
} }
void IAvnWindowBaseEvents.Activated() => _parent.Activated?.Invoke(); void IAvnWindowBaseEvents.Activated() => _parent.Activated?.Invoke();

2
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -51,7 +51,7 @@ namespace Avalonia.Rendering.SceneGraph
UpdateSize(scene); UpdateSize(scene);
} }
if (visual.VisualRoot != null) if (visual.VisualRoot == scene.Root.Visual)
{ {
if (node?.Parent != null && if (node?.Parent != null &&
visual.VisualParent != null && visual.VisualParent != null &&

2
src/Avalonia.X11/X11Platform.cs

@ -19,9 +19,7 @@ namespace Avalonia.X11
class AvaloniaX11Platform : IWindowingPlatform class AvaloniaX11Platform : IWindowingPlatform
{ {
private Lazy<KeyboardDevice> _keyboardDevice = new Lazy<KeyboardDevice>(() => new KeyboardDevice()); private Lazy<KeyboardDevice> _keyboardDevice = new Lazy<KeyboardDevice>(() => new KeyboardDevice());
private Lazy<MouseDevice> _mouseDevice = new Lazy<MouseDevice>(() => new MouseDevice());
public KeyboardDevice KeyboardDevice => _keyboardDevice.Value; public KeyboardDevice KeyboardDevice => _keyboardDevice.Value;
public MouseDevice MouseDevice => _mouseDevice.Value;
public Dictionary<IntPtr, Action<XEvent>> Windows = new Dictionary<IntPtr, Action<XEvent>>(); public Dictionary<IntPtr, Action<XEvent>> Windows = new Dictionary<IntPtr, Action<XEvent>>();
public XI2Manager XI2; public XI2Manager XI2;
public X11Info Info { get; private set; } public X11Info Info { get; private set; }

10
src/Avalonia.X11/X11Window.cs

@ -32,7 +32,8 @@ namespace Avalonia.X11
private PixelPoint? _configurePoint; private PixelPoint? _configurePoint;
private bool _triggeredExpose; private bool _triggeredExpose;
private IInputRoot _inputRoot; private IInputRoot _inputRoot;
private readonly IMouseDevice _mouse; private readonly MouseDevice _mouse;
private readonly TouchDevice _touch;
private readonly IKeyboardDevice _keyboard; private readonly IKeyboardDevice _keyboard;
private PixelPoint? _position; private PixelPoint? _position;
private PixelSize _realSize; private PixelSize _realSize;
@ -57,7 +58,8 @@ namespace Avalonia.X11
_platform = platform; _platform = platform;
_popup = popupParent != null; _popup = popupParent != null;
_x11 = platform.Info; _x11 = platform.Info;
_mouse = platform.MouseDevice; _mouse = new MouseDevice();
_touch = new TouchDevice();
_keyboard = platform.KeyboardDevice; _keyboard = platform.KeyboardDevice;
var glfeature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>(); var glfeature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
@ -702,6 +704,8 @@ namespace Avalonia.X11
_platform.XI2?.OnWindowDestroyed(_handle); _platform.XI2?.OnWindowDestroyed(_handle);
_handle = IntPtr.Zero; _handle = IntPtr.Zero;
Closed?.Invoke(); Closed?.Invoke();
_mouse.Dispose();
_touch.Dispose();
} }
if (_useRenderWindow && _renderHandle != IntPtr.Zero) if (_useRenderWindow && _renderHandle != IntPtr.Zero)
@ -830,6 +834,8 @@ namespace Avalonia.X11
} }
public IMouseDevice MouseDevice => _mouse; public IMouseDevice MouseDevice => _mouse;
public TouchDevice TouchDevice => _touch;
public IPopupImpl CreatePopup() public IPopupImpl CreatePopup()
=> _platform.Options.OverlayPopups ? null : new X11Window(_platform, this); => _platform.Options.OverlayPopups ? null : new X11Window(_platform, this);

12
src/Avalonia.X11/XI2Manager.cs

@ -92,8 +92,6 @@ namespace Avalonia.X11
private PointerDeviceInfo _pointerDevice; private PointerDeviceInfo _pointerDevice;
private AvaloniaX11Platform _platform; private AvaloniaX11Platform _platform;
private readonly TouchDevice _touchDevice = new TouchDevice();
public bool Init(AvaloniaX11Platform platform) public bool Init(AvaloniaX11Platform platform)
{ {
@ -198,7 +196,7 @@ namespace Avalonia.X11
(ev.Type == XiEventType.XI_TouchUpdate ? (ev.Type == XiEventType.XI_TouchUpdate ?
RawPointerEventType.TouchUpdate : RawPointerEventType.TouchUpdate :
RawPointerEventType.TouchEnd); RawPointerEventType.TouchEnd);
client.ScheduleInput(new RawTouchEventArgs(_touchDevice, client.ScheduleInput(new RawTouchEventArgs(client.TouchDevice,
ev.Timestamp, client.InputRoot, type, ev.Position, ev.Modifiers, ev.Detail)); ev.Timestamp, client.InputRoot, type, ev.Position, ev.Modifiers, ev.Detail));
return; return;
} }
@ -232,10 +230,10 @@ namespace Avalonia.X11
} }
if (scrollDelta != default) if (scrollDelta != default)
client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp, client.ScheduleInput(new RawMouseWheelEventArgs(client.MouseDevice, ev.Timestamp,
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers)); client.InputRoot, ev.Position, scrollDelta, ev.Modifiers));
if (_pointerDevice.HasMotion(ev)) if (_pointerDevice.HasMotion(ev))
client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
RawPointerEventType.Move, ev.Position, ev.Modifiers)); RawPointerEventType.Move, ev.Position, ev.Modifiers));
} }
@ -248,7 +246,7 @@ namespace Avalonia.X11
: ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp) : ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp)
: (RawPointerEventType?)null; : (RawPointerEventType?)null;
if (type.HasValue) if (type.HasValue)
client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
type.Value, ev.Position, ev.Modifiers)); type.Value, ev.Position, ev.Modifiers));
} }
@ -310,5 +308,7 @@ namespace Avalonia.X11
{ {
IInputRoot InputRoot { get; } IInputRoot InputRoot { get; }
void ScheduleInput(RawInputEventArgs args); void ScheduleInput(RawInputEventArgs args);
IMouseDevice MouseDevice { get; }
TouchDevice TouchDevice { get; }
} }
} }

8
src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs

@ -11,19 +11,11 @@ namespace Avalonia.Win32.Input
{ {
class WindowsMouseDevice : MouseDevice class WindowsMouseDevice : MouseDevice
{ {
public static WindowsMouseDevice Instance { get; } = new WindowsMouseDevice();
public WindowsMouseDevice() : base(new WindowsMousePointer()) public WindowsMouseDevice() : base(new WindowsMousePointer())
{ {
} }
public WindowImpl CurrentWindow
{
get;
set;
}
class WindowsMousePointer : Pointer class WindowsMousePointer : Pointer
{ {
public WindowsMousePointer() : base(Pointer.GetNextFreeId(),PointerType.Mouse, true) public WindowsMousePointer() : base(Pointer.GetNextFreeId(),PointerType.Mouse, true)

39
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -30,6 +30,7 @@ namespace Avalonia.Win32
private IntPtr _hwnd; private IntPtr _hwnd;
private bool _multitouch; private bool _multitouch;
private TouchDevice _touchDevice = new TouchDevice(); private TouchDevice _touchDevice = new TouchDevice();
private MouseDevice _mouseDevice = new WindowsMouseDevice();
private IInputRoot _owner; private IInputRoot _owner;
private ManagedDeferredRendererLock _rendererLock = new ManagedDeferredRendererLock(); private ManagedDeferredRendererLock _rendererLock = new ManagedDeferredRendererLock();
private bool _trackingMouse; private bool _trackingMouse;
@ -205,7 +206,7 @@ namespace Avalonia.Win32
} }
} }
public IMouseDevice MouseDevice => WindowsMouseDevice.Instance; public IMouseDevice MouseDevice => _mouseDevice;
public WindowState WindowState public WindowState WindowState
{ {
@ -333,7 +334,7 @@ namespace Avalonia.Win32
public void BeginMoveDrag(PointerPressedEventArgs e) public void BeginMoveDrag(PointerPressedEventArgs e)
{ {
WindowsMouseDevice.Instance.Capture(null); _mouseDevice.Capture(null);
UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN,
new IntPtr((int)UnmanagedMethods.HitTestValues.HTCAPTION), IntPtr.Zero); new IntPtr((int)UnmanagedMethods.HitTestValues.HTCAPTION), IntPtr.Zero);
e.Pointer.Capture(null); e.Pointer.Capture(null);
@ -356,7 +357,7 @@ namespace Avalonia.Win32
#if USE_MANAGED_DRAG #if USE_MANAGED_DRAG
_managedDrag.BeginResizeDrag(edge, ScreenToClient(MouseDevice.Position)); _managedDrag.BeginResizeDrag(edge, ScreenToClient(MouseDevice.Position));
#else #else
WindowsMouseDevice.Instance.Capture(null); _mouseDevice.Capture(null);
UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN,
new IntPtr((int)EdgeDic[edge]), IntPtr.Zero); new IntPtr((int)EdgeDic[edge]), IntPtr.Zero);
#endif #endif
@ -437,9 +438,7 @@ namespace Avalonia.Win32
uint timestamp = unchecked((uint)UnmanagedMethods.GetMessageTime()); uint timestamp = unchecked((uint)UnmanagedMethods.GetMessageTime());
RawInputEventArgs e = null; RawInputEventArgs e = null;
WindowsMouseDevice.Instance.CurrentWindow = this;
switch ((UnmanagedMethods.WindowsMessage)msg) switch ((UnmanagedMethods.WindowsMessage)msg)
{ {
case UnmanagedMethods.WindowsMessage.WM_ACTIVATE: case UnmanagedMethods.WindowsMessage.WM_ACTIVATE:
@ -485,6 +484,8 @@ namespace Avalonia.Win32
_parent._disabledBy.Remove(this); _parent._disabledBy.Remove(this);
_parent.UpdateEnabled(); _parent.UpdateEnabled();
} }
_mouseDevice.Dispose();
_touchDevice?.Dispose();
//Free other resources //Free other resources
Dispose(); Dispose();
return IntPtr.Zero; return IntPtr.Zero;
@ -542,7 +543,7 @@ namespace Avalonia.Win32
if(ShouldIgnoreTouchEmulatedMessage()) if(ShouldIgnoreTouchEmulatedMessage())
break; break;
e = new RawPointerEventArgs( e = new RawPointerEventArgs(
WindowsMouseDevice.Instance, _mouseDevice,
timestamp, timestamp,
_owner, _owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN
@ -559,7 +560,7 @@ namespace Avalonia.Win32
if(ShouldIgnoreTouchEmulatedMessage()) if(ShouldIgnoreTouchEmulatedMessage())
break; break;
e = new RawPointerEventArgs( e = new RawPointerEventArgs(
WindowsMouseDevice.Instance, _mouseDevice,
timestamp, timestamp,
_owner, _owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONUP msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONUP
@ -587,7 +588,7 @@ namespace Avalonia.Win32
} }
e = new RawPointerEventArgs( e = new RawPointerEventArgs(
WindowsMouseDevice.Instance, _mouseDevice,
timestamp, timestamp,
_owner, _owner,
RawPointerEventType.Move, RawPointerEventType.Move,
@ -597,7 +598,7 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_MOUSEWHEEL: case UnmanagedMethods.WindowsMessage.WM_MOUSEWHEEL:
e = new RawMouseWheelEventArgs( e = new RawMouseWheelEventArgs(
WindowsMouseDevice.Instance, _mouseDevice,
timestamp, timestamp,
_owner, _owner,
PointToClient(PointFromLParam(lParam)), PointToClient(PointFromLParam(lParam)),
@ -606,7 +607,7 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_MOUSEHWHEEL: case UnmanagedMethods.WindowsMessage.WM_MOUSEHWHEEL:
e = new RawMouseWheelEventArgs( e = new RawMouseWheelEventArgs(
WindowsMouseDevice.Instance, _mouseDevice,
timestamp, timestamp,
_owner, _owner,
PointToClient(PointFromLParam(lParam)), PointToClient(PointFromLParam(lParam)),
@ -616,7 +617,7 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_MOUSELEAVE: case UnmanagedMethods.WindowsMessage.WM_MOUSELEAVE:
_trackingMouse = false; _trackingMouse = false;
e = new RawPointerEventArgs( e = new RawPointerEventArgs(
WindowsMouseDevice.Instance, _mouseDevice,
timestamp, timestamp,
_owner, _owner,
RawPointerEventType.LeaveWindow, RawPointerEventType.LeaveWindow,
@ -627,7 +628,7 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN:
e = new RawPointerEventArgs( e = new RawPointerEventArgs(
WindowsMouseDevice.Instance, _mouseDevice,
timestamp, timestamp,
_owner, _owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN msg == (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN
@ -649,9 +650,9 @@ namespace Avalonia.Win32
{ {
Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time,
_owner, _owner,
touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_UP) ? touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ?
RawPointerEventType.TouchEnd : RawPointerEventType.TouchEnd :
touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_DOWN) ? touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ?
RawPointerEventType.TouchBegin : RawPointerEventType.TouchBegin :
RawPointerEventType.TouchUpdate, RawPointerEventType.TouchUpdate,
PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)), PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)),
@ -771,11 +772,11 @@ namespace Avalonia.Win32
{ {
var keys = (UnmanagedMethods.ModifierKeys)ToInt32(wParam); var keys = (UnmanagedMethods.ModifierKeys)ToInt32(wParam);
var modifiers = WindowsKeyboardDevice.Instance.Modifiers; var modifiers = WindowsKeyboardDevice.Instance.Modifiers;
if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_LBUTTON)) if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_LBUTTON))
modifiers |= RawInputModifiers.LeftMouseButton; modifiers |= RawInputModifiers.LeftMouseButton;
if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_RBUTTON)) if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_RBUTTON))
modifiers |= RawInputModifiers.RightMouseButton; modifiers |= RawInputModifiers.RightMouseButton;
if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_MBUTTON)) if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_MBUTTON))
modifiers |= RawInputModifiers.MiddleMouseButton; modifiers |= RawInputModifiers.MiddleMouseButton;
return modifiers; return modifiers;
} }
@ -785,7 +786,7 @@ namespace Avalonia.Win32
// Ensure that the delegate doesn't get garbage collected by storing it as a field. // Ensure that the delegate doesn't get garbage collected by storing it as a field.
_wndProcDelegate = new UnmanagedMethods.WndProc(WndProc); _wndProcDelegate = new UnmanagedMethods.WndProc(WndProc);
_className = "Avalonia-" + Guid.NewGuid(); _className = $"Avalonia-{Guid.NewGuid().ToString()}";
UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX
{ {

28
tests/Avalonia.Animation.UnitTests/TransitionsTests.cs

@ -1,14 +1,7 @@
using System; using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Data;
using Xunit; using Xunit;
using Avalonia.Animation.Easings;
namespace Avalonia.Animation.UnitTests namespace Avalonia.Animation.UnitTests
{ {
@ -69,5 +62,26 @@ namespace Avalonia.Animation.UnitTests
Assert.Equal(0, border.Opacity); Assert.Equal(0, border.Opacity);
} }
} }
[Fact]
public void TransitionInstance_With_Zero_Duration_Is_Completed_On_First_Tick()
{
var clock = new MockGlobalClock();
using (UnitTestApplication.Start(new TestServices(globalClock: clock)))
{
int i = 0;
var inst = new TransitionInstance(clock, TimeSpan.Zero).Subscribe(nextValue =>
{
switch (i++)
{
case 0: Assert.Equal(0, nextValue); break;
case 1: Assert.Equal(1d, nextValue); break;
}
});
clock.Pulse(TimeSpan.FromMilliseconds(10));
}
}
} }
} }

12
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs

@ -78,6 +78,18 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(10, target.GetValue(Class1.AttachedProperty)); Assert.Equal(10, target.GetValue(Class1.AttachedProperty));
} }
[Fact]
public void PropertyChanged_Event_Uses_Coerced_Value()
{
var inst = new Class1();
inst.PropertyChanged += (sender, e) =>
{
Assert.Equal(10, e.NewValue);
};
inst.SetValue(Class1.QuxProperty, 15);
}
private class Class1 : AvaloniaObject private class Class1 : AvaloniaObject
{ {
public static readonly StyledProperty<int> QuxProperty = public static readonly StyledProperty<int> QuxProperty =

53
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -12,6 +12,7 @@ using Xunit;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Input; using Avalonia.Input;
using System.Collections.Generic;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
{ {
@ -104,6 +105,28 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { child }, target.GetLogicalChildren()); Assert.Equal(new[] { child }, target.GetLogicalChildren());
} }
[Fact]
public void Added_Container_Should_Have_LogicalParent_Set_To_ItemsControl()
{
var item = new Border();
var items = new ObservableCollection<Border>();
var target = new ItemsControl
{
Template = GetTemplate(),
Items = items,
};
var root = new TestRoot(true, target);
root.Measure(new Size(100, 100));
root.Arrange(new Rect(0, 0, 100, 100));
items.Add(item);
Assert.Equal(target, item.Parent);
}
[Fact] [Fact]
public void Control_Item_Should_Be_Removed_From_Logical_Children_Before_ApplyTemplate() public void Control_Item_Should_Be_Removed_From_Logical_Children_Before_ApplyTemplate()
{ {
@ -522,6 +545,36 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Presenter_Items_Should_Be_In_Sync()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = new object[]
{
new Button(),
new Button(),
},
};
var root = new TestRoot { Child = target };
var otherPanel = new StackPanel();
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.ItemContainerGenerator.Materialized += (s, e) =>
{
Assert.IsType<Canvas>(e.Containers[0].Item);
};
target.Items = new[]
{
new Canvas()
};
}
private class Item private class Item
{ {
public Item(string value) public Item(string value)

189
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
@ -14,6 +15,7 @@ using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
using Xunit; using Xunit;
@ -23,7 +25,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
public class SelectingItemsControlTests public class SelectingItemsControlTests
{ {
private MouseTestHelper _helper = new MouseTestHelper(); private MouseTestHelper _helper = new MouseTestHelper();
[Fact] [Fact]
public void SelectedIndex_Should_Initially_Be_Minus_1() public void SelectedIndex_Should_Initially_Be_Minus_1()
{ {
@ -168,6 +170,130 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal("B", listBox.SelectedItem); Assert.Equal("B", listBox.SelectedItem);
} }
[Fact]
public void Setting_SelectedIndex_Before_Initialize_Should_Retain()
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single,
Items = new[] { "foo", "bar", "baz" },
SelectedIndex = 1
};
listBox.BeginInit();
listBox.EndInit();
Assert.Equal(1, listBox.SelectedIndex);
Assert.Equal("bar", listBox.SelectedItem);
}
[Fact]
public void Setting_SelectedIndex_During_Initialize_Should_Take_Priority_Over_Previous_Value()
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single,
Items = new[] { "foo", "bar", "baz" },
SelectedIndex = 2
};
listBox.BeginInit();
listBox.SelectedIndex = 1;
listBox.EndInit();
Assert.Equal(1, listBox.SelectedIndex);
Assert.Equal("bar", listBox.SelectedItem);
}
[Fact]
public void Setting_SelectedItem_Before_Initialize_Should_Retain()
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single,
Items = new[] { "foo", "bar", "baz" },
SelectedItem = "bar"
};
listBox.BeginInit();
listBox.EndInit();
Assert.Equal(1, listBox.SelectedIndex);
Assert.Equal("bar", listBox.SelectedItem);
}
[Fact]
public void Setting_SelectedItems_Before_Initialize_Should_Retain()
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Multiple,
Items = new[] { "foo", "bar", "baz" },
};
var selected = new[] { "foo", "bar" };
foreach (var v in selected)
{
listBox.SelectedItems.Add(v);
}
listBox.BeginInit();
listBox.EndInit();
Assert.Equal(selected, listBox.SelectedItems);
}
[Fact]
public void Setting_SelectedItems_During_Initialize_Should_Take_Priority_Over_Previous_Value()
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Multiple,
Items = new[] { "foo", "bar", "baz" },
};
var selected = new[] { "foo", "bar" };
foreach (var v in new[] { "bar", "baz" })
{
listBox.SelectedItems.Add(v);
}
listBox.BeginInit();
listBox.SelectedItems = new AvaloniaList<object>(selected);
listBox.EndInit();
Assert.Equal(selected, listBox.SelectedItems);
}
[Fact]
public void Setting_SelectedIndex_Before_Initialize_With_AlwaysSelected_Should_Retain()
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected,
Items = new[] { "foo", "bar", "baz" },
SelectedIndex = 1
};
listBox.BeginInit();
listBox.EndInit();
Assert.Equal(1, listBox.SelectedIndex);
Assert.Equal("bar", listBox.SelectedItem);
}
[Fact] [Fact]
public void Setting_SelectedIndex_Before_ApplyTemplate_Should_Set_Item_IsSelected_True() public void Setting_SelectedIndex_Before_ApplyTemplate_Should_Set_Item_IsSelected_True()
{ {
@ -849,7 +975,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox var target = new ListBox
{ {
Template = Template(), Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz"}, Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
}; };
target.ApplyTemplate(); target.ApplyTemplate();
@ -980,6 +1106,45 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.True(raised); Assert.True(raised);
} }
[Fact]
public void AutoScrollToSelectedItem_On_Reset_Works()
{
// Issue #3148
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = new ResettingCollection(100);
var target = new ListBox
{
Items = items,
ItemTemplate = new FuncDataTemplate<string>((x, _) =>
new TextBlock
{
Text = x,
Width = 100,
Height = 10
}),
AutoScrollToSelectedItem = true,
VirtualizationMode = ItemVirtualizationMode.Simple,
};
var root = new TestRoot(true, target);
root.Measure(new Size(100, 100));
root.Arrange(new Rect(0, 0, 100, 100));
Assert.True(target.Presenter.Panel.Children.Count > 0);
Assert.True(target.Presenter.Panel.Children.Count < 100);
target.SelectedItem = "Item99";
// #3148 triggered here.
items.Reset(new[] { "Item99" });
Assert.Equal(0, target.SelectedIndex);
Assert.Equal(1, target.Presenter.Panel.Children.Count);
}
}
[Fact] [Fact]
public void Can_Set_Both_SelectedItem_And_SelectedItems_During_Initialization() public void Can_Set_Both_SelectedItem_And_SelectedItems_During_Initialization()
{ {
@ -1028,6 +1193,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Name = "itemsPresenter", Name = "itemsPresenter",
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
[~ItemsPresenter.VirtualizationModeProperty] = control[~ListBox.VirtualizationModeProperty],
}.RegisterInNameScope(scope)); }.RegisterInNameScope(scope));
} }
@ -1072,5 +1238,24 @@ namespace Avalonia.Controls.UnitTests.Primitives
return base.MoveSelection(direction, wrap); return base.MoveSelection(direction, wrap);
} }
} }
private class ResettingCollection : List<string>, INotifyCollectionChanged
{
public ResettingCollection(int itemCount)
{
AddRange(Enumerable.Range(0, itemCount).Select(x => $"Item{x}"));
}
public void Reset(IEnumerable<string> items)
{
Clear();
AddRange(items);
CollectionChanged?.Invoke(
this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
} }
} }

20
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
@ -287,6 +288,25 @@ namespace Avalonia.Controls.UnitTests
Assert.Single(target.GetLogicalChildren(), content); Assert.Single(target.GetLogicalChildren(), content);
} }
[Fact]
public void Should_Not_Propagate_DataContext_To_TabItem_Content()
{
var dataContext = "DataContext";
var tabItem = new TabItem();
var target = new TabControl
{
Template = TabControlTemplate(),
DataContext = dataContext,
Items = new AvaloniaList<object> { tabItem }
};
ApplyTemplate(target);
Assert.NotEqual(dataContext, tabItem.Content);
}
private IControlTemplate TabControlTemplate() private IControlTemplate TabControlTemplate()
{ {
return new FuncControlTemplate<TabControl>((parent, scope) => return new FuncControlTemplate<TabControl>((parent, scope) =>

109
tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

@ -0,0 +1,109 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Disposables;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class TolTipTests
{
private MouseTestHelper _mouseHelper = new MouseTestHelper();
[Fact]
public void Should_Not_Open_On_Detached_Control()
{
//issue #3188
var control = new Decorator()
{
[ToolTip.TipProperty] = "Tip",
[ToolTip.ShowDelayProperty] = 0
};
Assert.False((control as IVisual).IsAttachedToVisualTree);
//here in issue #3188 exception is raised
_mouseHelper.Enter(control);
Assert.False(ToolTip.GetIsOpen(control));
}
[Fact]
public void Should_Open_On_Pointer_Enter()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var target = new Decorator()
{
[ToolTip.TipProperty] = "Tip",
[ToolTip.ShowDelayProperty] = 0
};
window.Content = target;
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.True((target as IVisual).IsAttachedToVisualTree);
_mouseHelper.Enter(target);
Assert.True(ToolTip.GetIsOpen(target));
}
}
[Fact]
public void Should_Open_On_Pointer_Enter_With_Delay()
{
Action timercallback = null;
var delay = TimeSpan.Zero;
var pti = Mock.Of<IPlatformThreadingInterface>(x => x.CurrentThreadIsLoopThread == true);
Mock.Get(pti)
.Setup(v => v.StartTimer(It.IsAny<DispatcherPriority>(), It.IsAny<TimeSpan>(), It.IsAny<Action>()))
.Callback<DispatcherPriority, TimeSpan, Action>((priority, interval, tick) =>
{
delay = interval;
timercallback = tick;
})
.Returns(Disposable.Empty);
using (UnitTestApplication.Start(TestServices.StyledWindow.With(threadingInterface: pti)))
{
var window = new Window();
var target = new Decorator()
{
[ToolTip.TipProperty] = "Tip",
[ToolTip.ShowDelayProperty] = 1
};
window.Content = target;
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.True((target as IVisual).IsAttachedToVisualTree);
_mouseHelper.Enter(target);
Assert.Equal(TimeSpan.FromMilliseconds(1), delay);
Assert.NotNull(timercallback);
Assert.False(ToolTip.GetIsOpen(target));
timercallback();
Assert.True(ToolTip.GetIsOpen(target));
}
}
}
}

78
tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

@ -12,14 +12,14 @@ namespace Avalonia.Controls.UnitTests
public void Lays_Out_Horizontally_On_Separate_Lines() public void Lays_Out_Horizontally_On_Separate_Lines()
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Width = 100, Width = 100,
Children = Children =
{ {
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize)); target.Arrange(new Rect(target.DesiredSize));
@ -33,14 +33,14 @@ namespace Avalonia.Controls.UnitTests
public void Lays_Out_Horizontally_On_A_Single_Line() public void Lays_Out_Horizontally_On_A_Single_Line()
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Width = 200, Width = 200,
Children = Children =
{ {
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize)); target.Arrange(new Rect(target.DesiredSize));
@ -54,15 +54,15 @@ namespace Avalonia.Controls.UnitTests
public void Lays_Out_Vertically_Children_On_A_Single_Line() public void Lays_Out_Vertically_Children_On_A_Single_Line()
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Orientation = Orientation.Vertical, Orientation = Orientation.Vertical,
Height = 120, Height = 120,
Children = Children =
{ {
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize)); target.Arrange(new Rect(target.DesiredSize));
@ -76,15 +76,15 @@ namespace Avalonia.Controls.UnitTests
public void Lays_Out_Vertically_On_Separate_Lines() public void Lays_Out_Vertically_On_Separate_Lines()
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Orientation = Orientation.Vertical, Orientation = Orientation.Vertical,
Height = 60, Height = 60,
Children = Children =
{ {
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize)); target.Arrange(new Rect(target.DesiredSize));
@ -98,17 +98,17 @@ namespace Avalonia.Controls.UnitTests
public void Applies_ItemWidth_And_ItemHeight_Properties() public void Applies_ItemWidth_And_ItemHeight_Properties()
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Orientation = Orientation.Horizontal, Orientation = Orientation.Horizontal,
Width = 50, Width = 50,
ItemWidth = 20, ItemWidth = 20,
ItemHeight = 15, ItemHeight = 15,
Children = Children =
{ {
new Border(), new Border(),
new Border(), new Border(),
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize)); target.Arrange(new Rect(target.DesiredSize));
@ -117,5 +117,33 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 20, 15), target.Children[0].Bounds); Assert.Equal(new Rect(0, 0, 20, 15), target.Children[0].Bounds);
Assert.Equal(new Rect(20, 0, 20, 15), target.Children[1].Bounds); Assert.Equal(new Rect(20, 0, 20, 15), target.Children[1].Bounds);
} }
[Fact]
void ItemWidth_Trigger_InvalidateMeasure()
{
var target = new WrapPanel();
target.Measure(new Size(10, 10));
Assert.True(target.IsMeasureValid);
target.ItemWidth = 1;
Assert.False(target.IsMeasureValid);
}
[Fact]
void ItemHeight_Trigger_InvalidateMeasure()
{
var target = new WrapPanel();
target.Measure(new Size(10, 10));
Assert.True(target.IsMeasureValid);
target.ItemHeight = 1;
Assert.False(target.IsMeasureValid);
}
} }
} }

39
tests/Avalonia.Input.UnitTests/PointerTests.cs

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Input.UnitTests
{
public class PointerTests
{
[Fact]
public void On_Capture_Transfer_PointerCaptureLost_Should_Propagate_Up_To_The_Common_Parent()
{
Border initialParent, initialCapture, newParent, newCapture;
var el = new StackPanel
{
Children =
{
(initialParent = new Border { Child = initialCapture = new Border() }),
(newParent = new Border { Child = newCapture = new Border() })
}
};
var receivers = new List<object>();
var root = new TestRoot(el);
foreach (InputElement d in root.GetSelfAndVisualDescendants())
d.PointerCaptureLost += (s, e) => receivers.Add(s);
var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
pointer.Capture(initialCapture);
pointer.Capture(newCapture);
Assert.True(receivers.SequenceEqual(new[] { initialCapture, initialParent }));
receivers.Clear();
pointer.Capture(null);
Assert.True(receivers.SequenceEqual(new object[] { newCapture, newParent, el, root }));
}
}
}

2
tests/Avalonia.UnitTests/MouseTestHelper.cs

@ -84,9 +84,9 @@ namespace Avalonia.UnitTests
); );
if (ButtonCount(props) == 0) if (ButtonCount(props) == 0)
{ {
_pointer.Capture(null);
target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position,
Timestamp(), props, GetModifiers(modifiers), _pressedButton)); Timestamp(), props, GetModifiers(modifiers), _pressedButton));
_pointer.Capture(null);
} }
else else
Move(target, source, position); Move(target, source, position);

11
tests/Avalonia.UnitTests/TestRoot.cs

@ -24,8 +24,19 @@ namespace Avalonia.UnitTests
} }
public TestRoot(IControl child) public TestRoot(IControl child)
: this(false, child)
{
Child = child;
}
public TestRoot(bool useGlobalStyles, IControl child)
: this() : this()
{ {
if (useGlobalStyles)
{
StylingParent = UnitTestApplication.Current;
}
Child = child; Child = child;
} }

75
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -369,6 +369,81 @@ namespace Avalonia.Visuals.UnitTests.Rendering
} }
[Fact]
public void Should_Update_VisualNodes_When_Child_Moved_To_New_Parent_And_New_Root()
{
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
Decorator moveFrom;
Decorator moveTo;
Canvas moveMe;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
(moveFrom = new Decorator
{
Child = moveMe = new Canvas(),
})
}
}
};
var otherRoot = new TestRoot
{
Child = new StackPanel
{
Children =
{
(moveTo = new Decorator())
}
}
};
var sceneBuilder = new SceneBuilder();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder,
dispatcher: dispatcher);
var otherSceneBuilder = new SceneBuilder();
var otherTarget = new DeferredRenderer(
otherRoot,
loop.Object,
sceneBuilder: otherSceneBuilder,
dispatcher: dispatcher);
root.Renderer = target;
otherRoot.Renderer = otherTarget;
target.Start();
otherTarget.Start();
RunFrame(target);
RunFrame(otherTarget);
moveFrom.Child = null;
moveTo.Child = moveMe;
RunFrame(target);
RunFrame(otherTarget);
var scene = target.UnitTestScene();
var otherScene = otherTarget.UnitTestScene();
var moveFromNode = (VisualNode)scene.FindNode(moveFrom);
var moveToNode = (VisualNode)otherScene.FindNode(moveTo);
Assert.Empty(moveFromNode.Children);
Assert.Equal(1, moveToNode.Children.Count);
Assert.Same(moveMe, moveToNode.Children[0].Visual);
}
[Fact] [Fact]
public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity() public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity()
{ {

Loading…
Cancel
Save