소스 검색

Merge branch 'master' into fixes/1271-contenttemplate-before-content

danwalmsley 8 년 전
부모
커밋
5f1837b5b9
86개의 변경된 파일2398개의 추가작업 그리고 827개의 파일을 삭제
  1. 1 2
      .ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
  2. 1 1
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  3. 121 30
      src/Avalonia.Base/AvaloniaObject.cs
  4. 4 20
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  5. 3 2
      src/Avalonia.Base/Collections/AvaloniaList.cs
  6. 15 0
      src/Avalonia.Base/Collections/IAvaloniaList.cs
  7. 7 0
      src/Avalonia.Base/IAvaloniaObject.cs
  8. 32 14
      src/Avalonia.Base/PriorityBindingEntry.cs
  9. 42 21
      src/Avalonia.Base/PriorityValue.cs
  10. 0 16
      src/Avalonia.Base/Reactive/AnonymousSubject`1.cs
  11. 0 49
      src/Avalonia.Base/Reactive/AnonymousSubject`2.cs
  12. 156 0
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  13. 1 1
      src/Avalonia.Controls/Calendar/Calendar.cs
  14. 0 1
      src/Avalonia.Controls/Control.cs
  15. 32 1
      src/Avalonia.Controls/DropDown.cs
  16. 2 2
      src/Avalonia.Controls/IPanel.cs
  17. 11 0
      src/Avalonia.Controls/ItemsControl.cs
  18. 9 34
      src/Avalonia.Controls/Panel.cs
  19. 2 11
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  20. 3 1
      src/Avalonia.Controls/Primitives/RangeBase.cs
  21. 45 27
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  22. 10 2
      src/Avalonia.Controls/Primitives/Track.cs
  23. 0 3
      src/Avalonia.Controls/ProgressBar.cs
  24. 1 4
      src/Avalonia.Controls/TreeView.cs
  25. 5 3
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  26. 6 0
      src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
  27. 1 1
      src/Avalonia.Visuals/Media/PathMarkupParser.cs
  28. 30 20
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  29. 1 1
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  30. 11 6
      src/Avalonia.Visuals/Rendering/RendererBase.cs
  31. 4 2
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  32. 13 4
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  33. 28 2
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  34. 13 0
      src/Avalonia.Visuals/Visual.cs
  35. 36 73
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  36. 2 0
      src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
  37. 150 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  38. 1 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  39. 72 19
      src/Markup/Avalonia.Markup/ControlLocator.cs
  40. 1 9
      src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
  41. 0 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  42. 127 12
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  43. 120 9
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  44. 22 0
      tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
  45. 12 0
      tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
  46. 1 0
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  47. 43 0
      tests/Avalonia.Benchmarks/Base/Properties.cs
  48. 58 0
      tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
  49. 2 2
      tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
  50. 1 1
      tests/Avalonia.Controls.UnitTests/DropDownTests.cs
  51. 42 42
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  52. 1 1
      tests/Avalonia.Controls.UnitTests/GridTests.cs
  53. 70 1
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  54. 29 31
      tests/Avalonia.Controls.UnitTests/PanelTests.cs
  55. 108 0
      tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
  56. 4 4
      tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  57. 26 0
      tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs
  58. 1 1
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  59. 6 6
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  60. 1 1
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  61. 1 1
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  62. 26 26
      tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs
  63. 61 63
      tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs
  64. 83 85
      tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
  65. 4 4
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  66. 5 5
      tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs
  67. 1 0
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  68. 162 0
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
  69. 4 4
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
  70. 32 0
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
  71. 174 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs
  72. 1 1
      tests/Avalonia.RenderTests/GeometryClippingTests.cs
  73. 1 1
      tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
  74. 1 1
      tests/Avalonia.RenderTests/OpacityMaskTests.cs
  75. 1 1
      tests/Avalonia.RenderTests/SVGPathTests.cs
  76. 5 0
      tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
  77. 5 0
      tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
  78. 5 0
      tests/Avalonia.Styling.UnitTests/TestControlBase.cs
  79. 5 0
      tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs
  80. 15 3
      tests/Avalonia.UnitTests/TestRoot.cs
  81. 6 6
      tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs
  82. 206 67
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  83. 7 7
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
  84. 7 7
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
  85. 5 1
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  86. 29 46
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

+ 1 - 2
.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject

@@ -1,7 +1,6 @@
 <ProjectConfiguration>
   <Settings>
-    <DefaultTestTimeout>1000</DefaultTestTimeout>
-    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+    <DefaultTestTimeout>3000</DefaultTestTimeout>
     <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
   </Settings>
 </ProjectConfiguration>

+ 1 - 1
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@@ -56,7 +56,7 @@ namespace Avalonia.AndroidTestApplication
                 {
                     Margin = new Thickness(30),
                     Background = Brushes.Yellow,
-                    Children = new Avalonia.Controls.Controls
+                    Children =
                     {
                         new TextBlock
                         {

+ 121 - 30
src/Avalonia.Base/AvaloniaObject.cs

@@ -51,6 +51,21 @@ namespace Avalonia
         /// </summary>
         private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
 
+        private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
+
+        /// <summary>
+        /// Delayed setter helper for direct properties. Used to fix #855.
+        /// </summary>
+        private DeferredSetter<AvaloniaProperty, object> DirectPropertyDeferredSetter
+        {
+            get
+            {
+                return _directDeferredSetter ??
+                    (_directDeferredSetter = new DeferredSetter<AvaloniaProperty, object>());
+            }
+        }
+
+
         /// <summary>
         /// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
         /// </summary>
@@ -225,6 +240,19 @@ namespace Avalonia
             return (T)GetValue((AvaloniaProperty)property);
         }
 
+        /// <summary>
+        /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <returns>True if the property is animating, otherwise false.</returns>
+        public bool IsAnimating(AvaloniaProperty property)
+        {
+            Contract.Requires<ArgumentNullException>(property != null);
+            VerifyAccess();
+
+            return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false;
+        }
+
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
         /// </summary>
@@ -311,9 +339,6 @@ namespace Avalonia
 
             var description = GetDescription(source);
 
-            var scheduler = AvaloniaLocator.Current.GetService<IScheduler>() ?? ImmediateScheduler.Instance;
-            source = source.ObserveOn(scheduler); 
-
             if (property.IsDirect)
             {
                 if (property.IsReadOnly)
@@ -539,6 +564,45 @@ namespace Avalonia
             }
         }
 
+        /// <summary>
+        /// A callback type for encapsulating complex logic for setting direct properties.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="value">The value to which to set the property.</param>
+        /// <param name="field">The backing field for the property.</param>
+        /// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
+        protected delegate void SetAndRaiseCallback<T>(T value, ref T field, Action<Action> notifyWrapper);
+
+        /// <summary>
+        /// Sets the backing field for a direct avalonia property, raising the 
+        /// <see cref="PropertyChanged"/> event if the value has changed.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="field">The backing field.</param>
+        /// <param name="setterCallback">A callback called to actually set the value to the backing field.</param>
+        /// <param name="value">The value.</param>
+        /// <returns>
+        /// True if the value changed, otherwise false.
+        /// </returns>
+        protected bool SetAndRaise<T>(
+            AvaloniaProperty<T> property,
+            ref T field,
+            SetAndRaiseCallback<T> setterCallback,
+            T value)
+        {
+            Contract.Requires<ArgumentNullException>(setterCallback != null);
+            return DirectPropertyDeferredSetter.SetAndNotify(
+                property,
+                ref field,
+                (object val, ref T backing, Action<Action> notify) =>
+                {
+                    setterCallback((T)val, ref backing, notify);
+                    return true;
+                },
+                value);
+        }
+
         /// <summary>
         /// Sets the backing field for a direct avalonia property, raising the 
         /// <see cref="PropertyChanged"/> event if the value has changed.
@@ -553,17 +617,32 @@ namespace Avalonia
         protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
         {
             VerifyAccess();
-            if (!object.Equals(field, value))
-            {
-                var old = field;
-                field = value;
-                RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
-                return true;
-            }
-            else
-            {
-                return false;
-            }
+            return SetAndRaise(
+                property,
+                ref field,
+                (T val, ref T backing, Action<Action> notifyWrapper)
+                    => SetAndRaiseCore(property, ref backing, val, notifyWrapper),
+                value);
+        }
+
+        /// <summary>
+        /// Default assignment logic for SetAndRaise.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="field">The backing field.</param>
+        /// <param name="value">The value.</param>
+        /// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
+        /// <returns>
+        /// True if the value changed, otherwise false.
+        /// </returns>
+        private bool SetAndRaiseCore<T>(AvaloniaProperty property, ref T field, T value, Action<Action> notifyWrapper)
+        {
+            var old = field;
+            field = value;
+
+            notifyWrapper(() => RaisePropertyChanged(property, old, value, BindingPriority.LocalValue));
+            return true;
         }
 
         /// <summary>
@@ -661,29 +740,41 @@ namespace Avalonia
         /// <param name="value">The value.</param>
         private void SetDirectValue(AvaloniaProperty property, object value)
         {
-            var notification = value as BindingNotification;
-
-            if (notification != null)
+            void Set()
             {
-                notification.LogIfError(this, property);
-                value = notification.Value;
-            }
+                var notification = value as BindingNotification;
 
-            if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
-            {
-                var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
-                var accessor = (IDirectPropertyAccessor)GetRegistered(property);
-                var finalValue = value == AvaloniaProperty.UnsetValue ? 
-                    metadata.UnsetValue : value;
+                if (notification != null)
+                {
+                    notification.LogIfError(this, property);
+                    value = notification.Value;
+                }
 
-                LogPropertySet(property, value, BindingPriority.LocalValue);
+                if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
+                {
+                    var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
+                    var accessor = (IDirectPropertyAccessor)GetRegistered(property);
+                    var finalValue = value == AvaloniaProperty.UnsetValue ?
+                        metadata.UnsetValue : value;
 
-                accessor.SetValue(this, finalValue);
+                    LogPropertySet(property, value, BindingPriority.LocalValue);
+
+                    accessor.SetValue(this, finalValue);
+                }
+
+                if (notification != null)
+                {
+                    UpdateDataValidation(property, notification);
+                }
             }
 
-            if (notification != null)
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                Set();
+            }
+            else
             {
-                UpdateDataValidation(property, notification);
+                Dispatcher.UIThread.InvokeAsync(Set);
             }
         }
 

+ 4 - 20
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -138,17 +138,9 @@ namespace Avalonia
             AvaloniaProperty property,
             BindingPriority priority = BindingPriority.LocalValue)
         {
-            // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the 
-            // AnonymousSubject classes and use Subject.Create<T>.
-            var output = new Subject<object>();
-            var result = new AnonymousSubject<object>(
-                Observer.Create<object>(
-                    x => output.OnNext(x),
-                    e => output.OnError(e),
-                    () => output.OnCompleted()),
+            return Subject.Create<object>(
+                Observer.Create<object>(x => o.SetValue(property, x, priority)),
                 o.GetObservable(property));
-            o.Bind(property, output, priority);
-            return result;
         }
 
         /// <summary>
@@ -169,17 +161,9 @@ namespace Avalonia
             AvaloniaProperty<T> property,
             BindingPriority priority = BindingPriority.LocalValue)
         {
-            // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the 
-            // AnonymousSubject classes from this file and use Subject.Create<T>.
-            var output = new Subject<T>();
-            var result = new AnonymousSubject<T>(
-                Observer.Create<T>(
-                    x => output.OnNext(x),
-                    e => output.OnError(e),
-                    () => output.OnCompleted()),
+            return Subject.Create<T>(
+                Observer.Create<T>(x => o.SetValue(property, x, priority)),
                 o.GetObservable(property));
-            o.Bind(property, output, priority);
-            return result;
         }
 
         /// <summary>

+ 3 - 2
src/Avalonia.Base/Collections/AvaloniaList.cs

@@ -350,14 +350,15 @@ namespace Avalonia.Collections
         public void MoveRange(int oldIndex, int count, int newIndex)
         {
             var items = _inner.GetRange(oldIndex, count);
+            var modifiedNewIndex = newIndex;
             _inner.RemoveRange(oldIndex, count);
 
             if (newIndex > oldIndex)
             {
-                newIndex -= count;
+                modifiedNewIndex -= count;
             }
 
-            _inner.InsertRange(newIndex, items);
+            _inner.InsertRange(modifiedNewIndex, items);
 
             if (_collectionChanged != null)
             {

+ 15 - 0
src/Avalonia.Base/Collections/IAvaloniaList.cs

@@ -36,6 +36,21 @@ namespace Avalonia.Collections
         /// <param name="items">The items.</param>
         void InsertRange(int index, IEnumerable<T> items);
 
+        /// <summary>
+        /// Moves an item to a new index.
+        /// </summary>
+        /// <param name="oldIndex">The index of the item to move.</param>
+        /// <param name="newIndex">The index to move the item to.</param>
+        void Move(int oldIndex, int newIndex);
+
+        /// <summary>
+        /// Moves multiple items to a new index.
+        /// </summary>
+        /// <param name="oldIndex">The first index of the items to move.</param>
+        /// <param name="count">The number of items to move.</param>
+        /// <param name="newIndex">The index to move the items to.</param>
+        void MoveRange(int oldIndex, int count, int newIndex);
+
         /// <summary>
         /// Removes multiple items from the collection.
         /// </summary>

+ 7 - 0
src/Avalonia.Base/IAvaloniaObject.cs

@@ -31,6 +31,13 @@ namespace Avalonia
         /// <returns>The value.</returns>
         T GetValue<T>(AvaloniaProperty<T> property);
 
+        /// <summary>
+        /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <returns>True if the property is animating, otherwise false.</returns>
+        bool IsAnimating(AvaloniaProperty property);
+
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
         /// </summary>

+ 32 - 14
src/Avalonia.Base/PriorityBindingEntry.cs

@@ -3,6 +3,7 @@
 
 using System;
 using Avalonia.Data;
+using Avalonia.Threading;
 
 namespace Avalonia
 {
@@ -92,33 +93,50 @@ namespace Avalonia
 
         private void ValueChanged(object value)
         {
-            _owner.Owner.Owner?.VerifyAccess();
-
-            var notification = value as BindingNotification;
-
-            if (notification != null)
+            void Signal()
             {
-                if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
+                var notification = value as BindingNotification;
+
+                if (notification != null)
                 {
-                    Value = notification.Value;
-                    _owner.Changed(this);
+                    if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
+                    {
+                        Value = notification.Value;
+                        _owner.Changed(this);
+                    }
+
+                    if (notification.ErrorType != BindingErrorType.None)
+                    {
+                        _owner.Error(this, notification);
+                    }
                 }
-
-                if (notification.ErrorType != BindingErrorType.None)
+                else
                 {
-                    _owner.Error(this, notification);
+                    Value = value;
+                    _owner.Changed(this);
                 }
             }
+
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                Signal();
+            }
             else
             {
-                Value = value;
-                _owner.Changed(this);
+                Dispatcher.UIThread.InvokeAsync(Signal);
             }
         }
 
         private void Completed()
         {
-            _owner.Completed(this);
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                _owner.Completed(this);
+            }
+            else
+            {
+                Dispatcher.UIThread.InvokeAsync(() => _owner.Completed(this));
+            }
         }
     }
 }

+ 42 - 21
src/Avalonia.Base/PriorityValue.cs

@@ -28,8 +28,10 @@ namespace Avalonia
     {
         private readonly Type _valueType;
         private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
-        private object _value;
+
         private readonly Func<object, object> _validate;
+        private static readonly DeferredSetter<PriorityValue, (object value, int priority)> delayedSetter = new DeferredSetter<PriorityValue, (object, int)>();
+        private (object value, int priority) _value;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PriorityValue"/> class.
@@ -47,11 +49,22 @@ namespace Avalonia
             Owner = owner;
             Property = property;
             _valueType = valueType;
-            _value = AvaloniaProperty.UnsetValue;
-            ValuePriority = int.MaxValue;
+            _value = (AvaloniaProperty.UnsetValue, int.MaxValue);
             _validate = validate;
         }
 
+        /// <summary>
+        /// Gets a value indicating whether the property is animating.
+        /// </summary>
+        public bool IsAnimating
+        {
+            get
+            {
+                return ValuePriority <= (int)BindingPriority.Animation && 
+                    GetLevel(ValuePriority).ActiveBindingIndex != -1;
+            }
+        }
+
         /// <summary>
         /// Gets the owner of the value.
         /// </summary>
@@ -65,16 +78,12 @@ namespace Avalonia
         /// <summary>
         /// Gets the current value.
         /// </summary>
-        public object Value => _value;
+        public object Value => _value.value;
 
         /// <summary>
         /// Gets the priority of the binding that is currently active.
         /// </summary>
-        public int ValuePriority
-        {
-            get;
-            private set;
-        }
+        public int ValuePriority => _value.priority;
 
         /// <summary>
         /// Adds a new binding.
@@ -234,25 +243,36 @@ namespace Avalonia
         /// <param name="priority">The priority level that the value came from.</param>
         private void UpdateValue(object value, int priority)
         {
-            var notification = value as BindingNotification;
+            delayedSetter.SetAndNotify(this,
+                ref _value,
+                UpdateCore,
+                (value, priority));
+        }
+
+        private bool UpdateCore(
+            (object value, int priority) update,
+            ref (object value, int priority) backing,
+            Action<Action> notify)
+        {
+            var val = update.value;
+            var notification = val as BindingNotification;
             object castValue;
 
             if (notification != null)
             {
-                value = (notification.HasValue) ? notification.Value : null;
+                val = (notification.HasValue) ? notification.Value : null;
             }
 
-            if (TypeUtilities.TryConvertImplicit(_valueType, value, out castValue))
+            if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue))
             {
-                var old = _value;
+                var old = backing.value;
 
                 if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
                 {
                     castValue = _validate(castValue);
                 }
 
-                ValuePriority = priority;
-                _value = castValue;
+                backing = (castValue, update.priority);
 
                 if (notification?.HasValue == true)
                 {
@@ -261,7 +281,7 @@ namespace Avalonia
 
                 if (notification == null || notification.HasValue)
                 {
-                    Owner?.Changed(this, old, _value);
+                    notify(() => Owner?.Changed(this, old, Value));
                 }
 
                 if (notification != null)
@@ -272,14 +292,15 @@ namespace Avalonia
             else
             {
                 Logger.Error(
-                    LogArea.Binding, 
+                    LogArea.Binding,
                     Owner,
                     "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
-                    Property.Name, 
-                    _valueType, 
-                    value,
-                    value?.GetType());
+                    Property.Name,
+                    _valueType,
+                    val,
+                    val?.GetType());
             }
+            return true;
         }
     }
 }

+ 0 - 16
src/Avalonia.Base/Reactive/AnonymousSubject`1.cs

@@ -1,16 +0,0 @@
-// 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.Subjects;
-
-namespace Avalonia.Reactive
-{
-    public class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
-    {
-        public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
-            : base(observer, observable)
-        {
-        }
-    }
-}

+ 0 - 49
src/Avalonia.Base/Reactive/AnonymousSubject`2.cs

@@ -1,49 +0,0 @@
-// 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.Subjects;
-
-namespace Avalonia.Reactive
-{
-    public class AnonymousSubject<T, U> : ISubject<T, U>
-    {
-        private readonly IObserver<T> _observer;
-        private readonly IObservable<U> _observable;
-
-        public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
-        {
-            _observer = observer;
-            _observable = observable;
-        }
-
-        public void OnCompleted()
-        {
-            _observer.OnCompleted();
-        }
-
-        public void OnError(Exception error)
-        {
-            if (error == null)
-                throw new ArgumentNullException("error");
-
-            _observer.OnError(error);
-        }
-
-        public void OnNext(T value)
-        {
-            _observer.OnNext(value);
-        }
-
-        public IDisposable Subscribe(IObserver<U> observer)
-        {
-            if (observer == null)
-                throw new ArgumentNullException("observer");
-
-            //
-            // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
-            //
-            return _observable.Subscribe/*Unsafe*/(observer);
-        }
-    }
-}

+ 156 - 0
src/Avalonia.Base/Utilities/DeferredSetter.cs

@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Avalonia.Utilities
+{
+    /// <summary>
+    /// A utility class to enable deferring assignment until after property-changed notifications are sent.
+    /// </summary>
+    /// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
+    /// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
+    class DeferredSetter<TProperty, TSetRecord>
+        where TProperty: class
+    {
+        private struct NotifyDisposable : IDisposable
+        {
+            private readonly SettingStatus status;
+
+            internal NotifyDisposable(SettingStatus status)
+            {
+                this.status = status;
+                status.Notifying = true;
+            }
+
+            public void Dispose()
+            {
+                status.Notifying = false;
+            }
+        }
+
+        /// <summary>
+        /// Information on current setting/notification status of a property.
+        /// </summary>
+        private class SettingStatus
+        {
+            public bool Notifying { get; set; }
+
+            private Queue<TSetRecord> pendingValues;
+            
+            public Queue<TSetRecord> PendingValues
+            {
+                get
+                {
+                    return pendingValues ?? (pendingValues = new Queue<TSetRecord>());
+                }
+            }
+        }
+
+        private readonly ConditionalWeakTable<TProperty, SettingStatus> setRecords = new ConditionalWeakTable<TProperty, SettingStatus>();
+
+        /// <summary>
+        /// Mark the property as currently notifying.
+        /// </summary>
+        /// <param name="property">The property to mark as notifying.</param>
+        /// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
+        private NotifyDisposable MarkNotifying(TProperty property)
+        {
+            Contract.Requires<InvalidOperationException>(!IsNotifying(property));
+            
+            return new NotifyDisposable(setRecords.GetOrCreateValue(property));
+        }
+
+        /// <summary>
+        /// Check if the property is currently notifying listeners.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <returns>If the property is currently notifying listeners.</returns>
+        private bool IsNotifying(TProperty property)
+            => setRecords.TryGetValue(property, out var value) && value.Notifying;
+
+        /// <summary>
+        /// Add a pending assignment for the property.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="value">The value to assign.</param>
+        private void AddPendingSet(TProperty property, TSetRecord value)
+        {
+            Contract.Requires<InvalidOperationException>(IsNotifying(property));
+
+            setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value);
+        }
+
+        /// <summary>
+        /// Checks if there are any pending assignments for the property.
+        /// </summary>
+        /// <param name="property">The property to check.</param>
+        /// <returns>If the property has any pending assignments.</returns>
+        private bool HasPendingSet(TProperty property)
+        {
+            return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0;
+        }
+
+        /// <summary>
+        /// Gets the first pending assignment for the property.
+        /// </summary>
+        /// <param name="property">The property to check.</param>
+        /// <returns>The first pending assignment for the property.</returns>
+        private TSetRecord GetFirstPendingSet(TProperty property)
+        {
+            return setRecords.GetOrCreateValue(property).PendingValues.Dequeue();
+        }
+
+        public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback);
+
+        /// <summary>
+        /// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
+        /// </summary>
+        /// <param name="property">The property to set.</param>
+        /// <param name="backing">The backing field for the property</param>
+        /// <param name="setterCallback">
+        /// A callback that actually sets the property.
+        /// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification.
+        /// </param>
+        /// <param name="value">The value to try to set.</param>
+        public bool SetAndNotify<TValue>(
+            TProperty property,
+            ref TValue backing,
+            SetterDelegate<TValue> setterCallback,
+            TSetRecord value)
+        {
+            Contract.Requires<ArgumentNullException>(setterCallback != null);
+            if (!IsNotifying(property))
+            {
+                bool updated = false;
+                if (!object.Equals(value, backing))
+                {
+                    updated = setterCallback(value, ref backing, notification =>
+                    {
+                        using (MarkNotifying(property))
+                        {
+                            notification();
+                        }
+                    });
+                }
+                while (HasPendingSet(property))
+                {
+                    updated |= setterCallback(GetFirstPendingSet(property), ref backing, notification =>
+                    {
+                        using (MarkNotifying(property))
+                        {
+                            notification();
+                        }
+                    });
+                }
+                return updated;
+            }
+            else if(!object.Equals(value, backing))
+            {
+                AddPendingSet(property, value);
+            }
+            return false;
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Controls/Calendar/Calendar.cs

@@ -549,7 +549,7 @@ namespace Avalonia.Controls
                         }
                         else
                         {
-                            if (addedDate.HasValue && !(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
+                            if (!(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
                             {
                                 foreach (DateTime item in SelectedDates)
                                 {

+ 0 - 1
src/Avalonia.Controls/Control.cs

@@ -621,7 +621,6 @@ namespace Avalonia.Controls
             Contract.Requires<ArgumentNullException>(property != null);
             Contract.Requires<ArgumentNullException>(selector != null);
             Contract.Requires<ArgumentNullException>(className != null);
-            Contract.Requires<ArgumentNullException>(property != null);
 
             if (string.IsNullOrWhiteSpace(className))
             {

+ 32 - 1
src/Avalonia.Controls/DropDown.cs

@@ -96,6 +96,16 @@ namespace Avalonia.Controls
             this.UpdateSelectionBoxItem(this.SelectedItem);
         }
 
+        protected override void OnGotFocus(GotFocusEventArgs e)
+        {
+            base.OnGotFocus(e);
+
+            if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional)
+            {
+                e.Handled = UpdateSelectionFromEventSource(e.Source);
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnKeyDown(KeyEventArgs e)
         {
@@ -104,7 +114,7 @@ namespace Avalonia.Controls
             if (!e.Handled)
             {
                 if (e.Key == Key.F4 ||
-                    (e.Key == Key.Down && ((e.Modifiers & InputModifiers.Alt) != 0)))
+                    ((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
                 {
                     IsDropDownOpen = !IsDropDownOpen;
                     e.Handled = true;
@@ -114,6 +124,27 @@ namespace Avalonia.Controls
                     IsDropDownOpen = false;
                     e.Handled = true;
                 }
+
+                if (!IsDropDownOpen)
+                {
+                    if (e.Key == Key.Down)
+                    {
+                        if (SelectedIndex == -1)
+                            SelectedIndex = 0;
+                        
+                        if (++SelectedIndex >= ItemCount)
+                            SelectedIndex = 0;
+                        
+                        e.Handled = true;
+                    }
+                    else if (e.Key == Key.Up)
+                    {
+                        if (--SelectedIndex < 0)
+                            SelectedIndex = ItemCount - 1;
+                        
+                        e.Handled = true;
+                    }
+                }
             }
         }
 

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

@@ -9,8 +9,8 @@ namespace Avalonia.Controls
     public interface IPanel : IControl
     {
         /// <summary>
-        /// Gets or sets the children of the <see cref="Panel"/>.
+        /// Gets the children of the <see cref="Panel"/>.
         /// </summary>
-        Controls Children { get; set; }
+        Controls Children { get; }
     }
 }

+ 11 - 0
src/Avalonia.Controls/ItemsControl.cs

@@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Utils;
+using Avalonia.Input;
 using Avalonia.LogicalTree;
 using Avalonia.Metadata;
 
@@ -106,6 +107,12 @@ namespace Avalonia.Controls
             set { SetAndRaise(ItemsProperty, ref _items, value); }
         }
 
+        public int ItemCount
+        {
+            get;
+            private set;
+        }
+
         /// <summary>
         /// Gets or sets the panel used to display the items.
         /// </summary>
@@ -352,6 +359,10 @@ namespace Avalonia.Controls
                     RemoveControlItemsFromLogicalChildren(e.OldItems);
                     break;
             }
+            
+            int? count = (Items as IList)?.Count;
+            if (count != null)
+                ItemCount = (int)count;
 
             var collection = sender as ICollection;
             PseudoClasses.Set(":empty", collection == null || collection.Count == 0);

+ 9 - 34
src/Avalonia.Controls/Panel.cs

@@ -25,8 +25,6 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<IBrush> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<Panel>();
 
-        private readonly Controls _children = new Controls();
-
         /// <summary>
         /// Initializes static members of the <see cref="Panel"/> class.
         /// </summary>
@@ -40,38 +38,14 @@ namespace Avalonia.Controls
         /// </summary>
         public Panel()
         {
-            _children.CollectionChanged += ChildrenChanged;
+            Children.CollectionChanged += ChildrenChanged;
         }
 
         /// <summary>
-        /// Gets or sets the children of the <see cref="Panel"/>.
+        /// Gets the children of the <see cref="Panel"/>.
         /// </summary>
-        /// <remarks>
-        /// Even though this property can be set, the setter is only intended for use in object
-        /// initializers. Assigning to this property does not change the underlying collection,
-        /// it simply clears the existing collection and adds the contents of the assigned
-        /// collection.
-        /// </remarks>
         [Content]
-        public Controls Children
-        {
-            get
-            {
-                return _children;
-            }
-
-            set
-            {
-                Contract.Requires<ArgumentNullException>(value != null);
-
-                if (_children != value)
-                {
-                    VisualChildren.Clear();
-                    _children.Clear();
-                    _children.AddRange(value);
-                }
-            }
-        }
+        public Controls Children { get; } = new Controls();
 
         /// <summary>
         /// Gets or Sets Panel background brush.
@@ -115,6 +89,11 @@ namespace Avalonia.Controls
                     VisualChildren.AddRange(e.NewItems.OfType<Visual>());
                     break;
 
+                case NotifyCollectionChangedAction.Move:
+                    LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+                    VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+                    break;
+
                 case NotifyCollectionChangedAction.Remove:
                     controls = e.OldItems.OfType<Control>().ToList();
                     LogicalChildren.RemoveAll(controls);
@@ -132,11 +111,7 @@ namespace Avalonia.Controls
                     break;
 
                 case NotifyCollectionChangedAction.Reset:
-                    controls = e.OldItems.OfType<Control>().ToList();
-                    LogicalChildren.Clear();
-                    VisualChildren.Clear();
-                    VisualChildren.AddRange(_children);
-                    break;
+                    throw new NotSupportedException();
             }
 
             InvalidateMeasure();

+ 2 - 11
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -257,18 +257,9 @@ namespace Avalonia.Controls.Presenters
                     LogicalChildren.Remove(oldChild);
                 }
 
-                if (newChild.Parent == null)
+                if (newChild.Parent == null && TemplatedParent == null)
                 {
-                    var templatedLogicalParent = TemplatedParent as ILogical;
-
-                    if (templatedLogicalParent != null)
-                    {
-                        ((ISetLogicalParent)newChild).SetParent(templatedLogicalParent);
-                    }
-                    else
-                    {
-                        LogicalChildren.Add(newChild);
-                    }
+                    LogicalChildren.Add(newChild);
                 }
 
                 VisualChildren.Add(newChild);

+ 3 - 1
src/Avalonia.Controls/Primitives/RangeBase.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using Avalonia.Data;
 using Avalonia.Utilities;
 
 namespace Avalonia.Controls.Primitives
@@ -36,7 +37,8 @@ namespace Avalonia.Controls.Primitives
             AvaloniaProperty.RegisterDirect<RangeBase, double>(
                 nameof(Value),
                 o => o.Value,
-                (o, v) => o.Value = v);
+                (o, v) => o.Value = v,
+                defaultBindingMode: BindingMode.TwoWay);
 
         /// <summary>
         /// Defines the <see cref="SmallChange"/> property.

+ 45 - 27
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -151,15 +151,23 @@ namespace Avalonia.Controls.Primitives
             {
                 if (_updateCount == 0)
                 {
-                    var old = SelectedIndex;
-                    var effective = (value >= 0 && value < Items?.Cast<object>().Count()) ? value : -1;
-
-                    if (old != effective)
+                    SetAndRaise(SelectedIndexProperty, ref _selectedIndex, (int val, ref int backing, Action<Action> notifyWrapper) =>
                     {
-                        _selectedIndex = effective;
-                        RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue);
-                        SelectedItem = ElementAt(Items, effective);
-                    }
+                        var old = backing;
+                        var effective = (val >= 0 && val < Items?.Cast<object>().Count()) ? val : -1;
+
+                        if (old != effective)
+                        {
+                            backing = effective;
+                            notifyWrapper(() =>
+                                RaisePropertyChanged(
+                                    SelectedIndexProperty,
+                                    old,
+                                    effective,
+                                    BindingPriority.LocalValue));
+                            SelectedItem = ElementAt(Items, effective);
+                        }
+                    }, value);
                 }
                 else
                 {
@@ -183,31 +191,41 @@ namespace Avalonia.Controls.Primitives
             {
                 if (_updateCount == 0)
                 {
-                    var old = SelectedItem;
-                    var index = IndexOf(Items, value);
-                    var effective = index != -1 ? value : null;
-
-                    if (!object.Equals(effective, old))
+                    SetAndRaise(SelectedItemProperty, ref _selectedItem, (object val, ref object backing, Action<Action> notifyWrapper) =>
                     {
-                        _selectedItem = effective;
-                        RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue);
-                        SelectedIndex = index;
+                        var old = backing;
+                        var index = IndexOf(Items, val);
+                        var effective = index != -1 ? val : null;
 
-                        if (effective != null)
+                        if (!object.Equals(effective, old))
                         {
-                            if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
+                            backing = effective;
+
+                            notifyWrapper(() =>
+                                RaisePropertyChanged(
+                                    SelectedItemProperty,
+                                    old,
+                                    effective,
+                                    BindingPriority.LocalValue));
+
+                            SelectedIndex = index;
+
+                            if (effective != null)
+                            {
+                                if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
+                                {
+                                    _syncingSelectedItems = true;
+                                    SelectedItems.Clear();
+                                    SelectedItems.Add(effective);
+                                    _syncingSelectedItems = false;
+                                }
+                            }
+                            else if (SelectedItems.Count > 0)
                             {
-                                _syncingSelectedItems = true;
                                 SelectedItems.Clear();
-                                SelectedItems.Add(effective);
-                                _syncingSelectedItems = false;
                             }
                         }
-                        else if (SelectedItems.Count > 0)
-                        {
-                            SelectedItems.Clear();
-                        }
-                    }
+                    }, value);
                 }
                 else
                 {
@@ -297,7 +315,7 @@ namespace Avalonia.Controls.Primitives
                 .OfType<IControl>()
                 .FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
 
-            return item as IControl;
+            return item;
         }
 
         /// <inheritdoc/>

+ 10 - 2
src/Avalonia.Controls/Primitives/Track.cs

@@ -154,7 +154,11 @@ namespace Avalonia.Controls.Primitives
 
                 if (increaseButton != null)
                 {
-                    increaseButton.Arrange(new Rect(firstWidth + thumbWidth, 0, remaining - firstWidth, finalSize.Height));
+                    increaseButton.Arrange(new Rect(
+                        firstWidth + thumbWidth,
+                        0,
+                        Math.Max(0, remaining - firstWidth),
+                        finalSize.Height));
                 }
             }
             else
@@ -185,7 +189,11 @@ namespace Avalonia.Controls.Primitives
 
                 if (increaseButton != null)
                 {
-                    increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, Math.Max(remaining - firstHeight, 0)));
+                    increaseButton.Arrange(new Rect(
+                        0,
+                        firstHeight + thumbHeight,
+                        finalSize.Width,
+                        Math.Max(remaining - firstHeight, 0)));
                 }
             }
 

+ 0 - 3
src/Avalonia.Controls/ProgressBar.cs

@@ -28,9 +28,6 @@ namespace Avalonia.Controls
         {
             ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
 
-            HorizontalAlignmentProperty.OverrideDefaultValue<ProgressBar>(HorizontalAlignment.Left);
-            VerticalAlignmentProperty.OverrideDefaultValue<ProgressBar>(VerticalAlignment.Top);
-
             IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
                 (p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
             OrientationProperty.Changed.AddClassHandler<ProgressBar>(

+ 1 - 4
src/Avalonia.Controls/TreeView.cs

@@ -176,10 +176,7 @@ namespace Avalonia.Controls
 
                 SelectedItem = item;
 
-                if (SelectedItem != null)
-                {
-                    MarkContainerSelected(container, true);
-                }
+                MarkContainerSelected(container, true);
             }
         }
 

+ 5 - 3
src/Avalonia.Controls/VirtualizingStackPanel.cs

@@ -134,12 +134,14 @@ namespace Avalonia.Controls
 
         protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
         {
+            if (from == null)
+                return null;
+
             var logicalScrollable = Parent as ILogicalScrollable;
-            var fromControl = from as IControl;
 
-            if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
+            if (logicalScrollable?.IsLogicalScrollEnabled == true)
             {
-                return logicalScrollable.GetControlInDirection(direction, fromControl);
+                return logicalScrollable.GetControlInDirection(direction, from);
             }
             else
             {

+ 6 - 0
src/Avalonia.Diagnostics/Views/TreePage.xaml.cs

@@ -27,6 +27,12 @@ namespace Avalonia.Diagnostics.Views
 
             if (layer != null)
             {
+                if (_adorner != null)
+                {
+                    ((Panel)_adorner.Parent).Children.Remove(_adorner);
+                    _adorner = null;
+                }
+
                 _adorner = new Rectangle
                 {
                     Fill = new SolidColorBrush(0x80a0c5e8),

+ 1 - 1
src/Avalonia.Visuals/Media/PathMarkupParser.cs

@@ -320,7 +320,7 @@ namespace Avalonia.Media
                     if (c == 'E')
                     {
                         readSign = false;
-                        readExponent = c == 'E';
+                        readExponent = true;
                     }
                 }
                 else

+ 30 - 20
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -25,11 +25,9 @@ namespace Avalonia.Rendering
         private readonly IRenderLoop _renderLoop;
         private readonly IVisual _root;
         private readonly ISceneBuilder _sceneBuilder;
-        private readonly RenderLayers _layers;
 
         private bool _running;
         private Scene _scene;
-        private IRenderTarget _renderTarget;
         private DirtyVisuals _dirty;
         private IRenderTargetBitmapImpl _overlay;
         private bool _updateQueued;
@@ -56,7 +54,7 @@ namespace Avalonia.Rendering
             _dispatcher = dispatcher ?? Dispatcher.UIThread;
             _root = root;
             _sceneBuilder = sceneBuilder ?? new SceneBuilder();
-            _layers = new RenderLayers();
+            Layers = new RenderLayers();
             _renderLoop = renderLoop;
         }
 
@@ -78,9 +76,9 @@ namespace Avalonia.Rendering
             Contract.Requires<ArgumentNullException>(renderTarget != null);
 
             _root = root;
-            _renderTarget = renderTarget;
+            RenderTarget = renderTarget;
             _sceneBuilder = sceneBuilder ?? new SceneBuilder();
-            _layers = new RenderLayers();
+            Layers = new RenderLayers();
         }
 
         /// <inheritdoc/>
@@ -94,6 +92,16 @@ namespace Avalonia.Rendering
         /// </summary>
         public string DebugFramesPath { get; set; }
 
+        /// <summary>
+        /// Gets the render layers.
+        /// </summary>
+        internal RenderLayers Layers { get; }
+
+        /// <summary>
+        /// Gets the current render target.
+        /// </summary>
+        internal IRenderTarget RenderTarget { get; private set; }
+
         /// <inheritdoc/>
         public void AddDirty(IVisual visual)
         {
@@ -173,9 +181,9 @@ namespace Avalonia.Rendering
             bool renderOverlay = DrawDirtyRects || DrawFps;
             bool composite = false;
 
-            if (_renderTarget == null)
+            if (RenderTarget == null)
             {
-                _renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
+                RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
             }
 
             if (renderOverlay)
@@ -191,8 +199,8 @@ namespace Avalonia.Rendering
 
                     if (scene.Generation != _lastSceneId)
                     {
-                        context = _renderTarget.CreateDrawingContext(this);
-                        _layers.Update(scene, context);
+                        context = RenderTarget.CreateDrawingContext(this);
+                        Layers.Update(scene, context);
 
                         RenderToLayers(scene);
 
@@ -208,13 +216,13 @@ namespace Avalonia.Rendering
 
                     if (renderOverlay)
                     {
-                        context = context ?? _renderTarget.CreateDrawingContext(this);
+                        context = context ?? RenderTarget.CreateDrawingContext(this);
                         RenderOverlay(scene, context);
                         RenderComposite(scene, context);
                     }
                     else if (composite)
                     {
-                        context = context ?? _renderTarget.CreateDrawingContext(this);
+                        context = context ?? RenderTarget.CreateDrawingContext(this);
                         RenderComposite(scene, context);
                     }
 
@@ -224,8 +232,8 @@ namespace Avalonia.Rendering
             catch (RenderTargetCorruptedException ex)
             {
                 Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
-                _renderTarget?.Dispose();
-                _renderTarget = null;
+                RenderTarget?.Dispose();
+                RenderTarget = null;
             }
         }
 
@@ -235,9 +243,11 @@ namespace Avalonia.Rendering
             {
                 clipBounds = node.ClipBounds.Intersect(clipBounds);
 
-                if (!clipBounds.IsEmpty)
+                if (!clipBounds.IsEmpty && node.Opacity > 0)
                 {
-                    node.BeginRender(context);
+                    var isLayerRoot = node.Visual == layer;
+
+                    node.BeginRender(context, isLayerRoot);
 
                     foreach (var operation in node.DrawOperations)
                     {
@@ -251,7 +261,7 @@ namespace Avalonia.Rendering
                         Render(context, (VisualNode)child, layer, clipBounds);
                     }
 
-                    node.EndRender(context);
+                    node.EndRender(context, isLayerRoot);
                 }
             }
         }
@@ -262,7 +272,7 @@ namespace Avalonia.Rendering
             {
                 foreach (var layer in scene.Layers)
                 {
-                    var renderTarget = _layers[layer.LayerRoot].Bitmap;
+                    var renderTarget = Layers[layer.LayerRoot].Bitmap;
                     var node = (VisualNode)scene.FindNode(layer.LayerRoot);
 
                     if (node != null)
@@ -322,7 +332,7 @@ namespace Avalonia.Rendering
 
             foreach (var layer in scene.Layers)
             {
-                var bitmap = _layers[layer.LayerRoot].Bitmap;
+                var bitmap = Layers[layer.LayerRoot].Bitmap;
                 var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
 
                 if (layer.GeometryClip != null)
@@ -353,7 +363,7 @@ namespace Avalonia.Rendering
 
             if (DrawFps)
             {
-                RenderFps(context, clientRect, true);
+                RenderFps(context, clientRect, scene.Layers.Count);
             }
         }
 
@@ -442,7 +452,7 @@ namespace Avalonia.Rendering
         {
             var index = 0;
 
-            foreach (var layer in _layers)
+            foreach (var layer in Layers)
             {
                 var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
                 layer.Bitmap.Save(fileName);

+ 1 - 1
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@@ -69,7 +69,7 @@ namespace Avalonia.Rendering
 
                     if (DrawFps)
                     {
-                        RenderFps(context.PlatformImpl, _root.Bounds, true);
+                        RenderFps(context.PlatformImpl, _root.Bounds, null);
                     }
                 }
             }

+ 11 - 6
src/Avalonia.Visuals/Rendering/RendererBase.cs

@@ -22,15 +22,12 @@ namespace Avalonia.Rendering
             };
         }
 
-        protected void RenderFps(IDrawingContextImpl context, Rect clientRect, bool incrementFrameCount)
+        protected void RenderFps(IDrawingContextImpl context, Rect clientRect, int? layerCount)
         {
             var now = _stopwatch.Elapsed;
             var elapsed = now - _lastFpsUpdate;
 
-            if (incrementFrameCount)
-            {
-                ++_framesThisSecond;
-            }
+            ++_framesThisSecond;
 
             if (elapsed.TotalSeconds > 1)
             {
@@ -39,7 +36,15 @@ namespace Avalonia.Rendering
                 _lastFpsUpdate = now;
             }
 
-            _fpsText.Text = string.Format("FPS: {0:000}", _fps);
+            if (layerCount.HasValue)
+            {
+                _fpsText.Text = string.Format("Layers: {0} FPS: {1:000}", layerCount, _fps);
+            }
+            else
+            {
+                _fpsText.Text = string.Format("FPS: {0:000}", _fps);
+            }
+
             var size = _fpsText.Measure();
             var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height);
 

+ 4 - 2
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@@ -72,13 +72,15 @@ namespace Avalonia.Rendering.SceneGraph
         /// Sets up the drawing context for rendering the node's geometry.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        void BeginRender(IDrawingContextImpl context);
+        /// <param name="skipOpacity">Whether to skip pushing the control's opacity.</param>
+        void BeginRender(IDrawingContextImpl context, bool skipOpacity);
 
         /// <summary>
         /// Resets the drawing context after rendering the node's geometry.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        void EndRender(IDrawingContextImpl context);
+        /// <param name="skipOpacity">Whether to skip popping the control's opacity.</param>
+        void EndRender(IDrawingContextImpl context, bool skipOpacity);
 
         /// <summary>
         /// Hit test the geometry in this node.

+ 13 - 4
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@@ -167,7 +167,6 @@ namespace Avalonia.Rendering.SceneGraph
                 using (context.PushPostTransform(m))
                 using (context.PushTransformContainer())
                 {
-                    var startLayer = opacity < 1 || visual.OpacityMask != null;
                     var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
 
                     forceRecurse = forceRecurse ||
@@ -179,9 +178,11 @@ namespace Avalonia.Rendering.SceneGraph
                     node.ClipToBounds = clipToBounds;
                     node.GeometryClip = visual.Clip?.PlatformImpl;
                     node.Opacity = opacity;
-                    node.OpacityMask = visual.OpacityMask;
 
-                    if (startLayer)
+                    // TODO: Check equality between node.OpacityMask and visual.OpacityMask before assigning.
+                    node.OpacityMask = visual.OpacityMask?.ToImmutable();
+
+                    if (ShouldStartLayer(visual))
                     {
                         if (node.LayerRoot != visual)
                         {
@@ -192,7 +193,7 @@ namespace Avalonia.Rendering.SceneGraph
                             UpdateLayer(node, scene.Layers[node.LayerRoot]);
                         }
                     }
-                    else if (!startLayer && node.LayerRoot == node.Visual && node.Parent != null)
+                    else if (node.LayerRoot == node.Visual && node.Parent != null)
                     {
                         ClearLayer(scene, node);
                     }
@@ -366,6 +367,14 @@ namespace Avalonia.Rendering.SceneGraph
             }
         }
 
+        private static bool ShouldStartLayer(IVisual visual)
+        {
+            var o = visual as IAvaloniaObject;
+            return visual.VisualChildren.Count > 0 &&
+                o != null &&
+                o.IsAnimating(Visual.OpacityProperty);
+        }
+
         private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
         {
             IGeometryImpl result = null;

+ 28 - 2
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@@ -22,6 +22,7 @@ namespace Avalonia.Rendering.SceneGraph
         private List<IVisualNode> _children;
         private List<IDrawOperation> _drawOperations;
         private bool _drawOperationsCloned;
+        private Matrix transformRestore;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="VisualNode"/> class.
@@ -218,8 +219,10 @@ namespace Avalonia.Rendering.SceneGraph
         }
 
         /// <inheritdoc/>
-        public void BeginRender(IDrawingContextImpl context)
+        public void BeginRender(IDrawingContextImpl context, bool skipOpacity)
         {
+            transformRestore = context.Transform;
+
             if (ClipToBounds)
             {
                 context.Transform = Matrix.Identity;
@@ -228,24 +231,47 @@ namespace Avalonia.Rendering.SceneGraph
 
             context.Transform = Transform;
 
+            if (Opacity != 1 && !skipOpacity)
+            {
+                context.PushOpacity(Opacity);
+            }
+
             if (GeometryClip != null)
             {
                 context.PushGeometryClip(GeometryClip);
             }
+
+            if (OpacityMask != null)
+            {
+                context.PushOpacityMask(OpacityMask, ClipBounds);
+            }
         }
 
         /// <inheritdoc/>
-        public void EndRender(IDrawingContextImpl context)
+        public void EndRender(IDrawingContextImpl context, bool skipOpacity)
         {
+            if (OpacityMask != null)
+            {
+                context.PopOpacityMask();
+            }
+
             if (GeometryClip != null)
             {
                 context.PopGeometryClip();
             }
 
+            if (Opacity != 1 && !skipOpacity)
+            {
+                context.PopOpacity();
+            }
+
             if (ClipToBounds)
             {
+                context.Transform = Matrix.Identity;
                 context.PopClip();
             }
+
+            context.Transform = transformRestore;
         }
 
         private Rect CalculateBounds()

+ 13 - 0
src/Avalonia.Visuals/Visual.cs

@@ -537,6 +537,19 @@ namespace Avalonia
                         v.SetVisualParent(null);
                     }
 
+                    break;
+
+                case NotifyCollectionChangedAction.Replace:
+                    foreach (Visual v in e.OldItems)
+                    {
+                        v.SetVisualParent(null);
+                    }
+
+                    foreach (Visual v in e.NewItems)
+                    {
+                        v.SetVisualParent(this);
+                    }
+
                     break;
             }
         }

+ 36 - 73
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@@ -66,7 +66,7 @@ namespace Avalonia.Markup.Xaml.Data
         /// <summary>
         /// Gets or sets the binding path.
         /// </summary>
-        public string Path { get; set; }
+        public string Path { get; set; } = "";
 
         /// <summary>
         /// Gets or sets the binding priority.
@@ -93,53 +93,53 @@ namespace Avalonia.Markup.Xaml.Data
             bool enableDataValidation = false)
         {
             Contract.Requires<ArgumentNullException>(target != null);
-
             anchor = anchor ?? DefaultAnchor?.Target;
-
-            var pathInfo = ParsePath(Path);
-            ValidateState(pathInfo);
+            
             enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
-
+            
             ExpressionObserver observer;
 
-            if (pathInfo.ElementName != null || ElementName != null)
+            if (ElementName != null)
             {
                 observer = CreateElementObserver(
                     (target as IControl) ?? (anchor as IControl),
-                    pathInfo.ElementName ?? ElementName,
-                    pathInfo.Path);
+                    ElementName,
+                    Path,
+                    enableDataValidation);
             }
             else if (Source != null)
             {
-                observer = CreateSourceObserver(Source, pathInfo.Path, enableDataValidation);
+                observer = CreateSourceObserver(Source, Path, enableDataValidation);
             }
             else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
             {
                 observer = CreateDataContexObserver(
                     target,
-                    pathInfo.Path,
+                    Path,
                     targetProperty == Control.DataContextProperty,
                     anchor,
                     enableDataValidation);
             }
             else if (RelativeSource.Mode == RelativeSourceMode.Self)
             {
-                observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation);
+                observer = CreateSourceObserver(target, Path, enableDataValidation);
             }
             else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
             {
-                observer = CreateTemplatedParentObserver(target, pathInfo.Path);
+                observer = CreateTemplatedParentObserver(target, Path, enableDataValidation);
             }
             else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
             {
-                if (RelativeSource.AncestorType == null)
+                if (RelativeSource.Tree == TreeType.Visual && RelativeSource.AncestorType == null)
                 {
-                    throw new InvalidOperationException("AncestorType must be set for RelativeSourceModel.FindAncestor.");
+                    throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
                 }
 
                 observer = CreateFindAncestorObserver(
                     (target as IControl) ?? (anchor as IControl),
-                    pathInfo.Path);
+                    RelativeSource,
+                    Path,
+                    enableDataValidation);
             }
             else
             {
@@ -168,53 +168,6 @@ namespace Avalonia.Markup.Xaml.Data
             return new InstancedBinding(subject, Mode, Priority);
         }
 
-        private static PathInfo ParsePath(string path)
-        {
-            var result = new PathInfo();
-
-            if (string.IsNullOrWhiteSpace(path) || path == ".")
-            {
-                result.Path = string.Empty;
-            }
-            else if (path.StartsWith("#"))
-            {
-                var dot = path.IndexOf('.');
-
-                if (dot != -1)
-                {
-                    result.Path = path.Substring(dot + 1);
-                    result.ElementName = path.Substring(1, dot - 1);
-                }
-                else
-                {
-                    result.Path = string.Empty;
-                    result.ElementName = path.Substring(1);
-                }
-            }
-            else
-            {
-                result.Path = path;
-            }
-
-            return result;
-        }
-
-        private void ValidateState(PathInfo pathInfo)
-        {
-            if (pathInfo.ElementName != null && ElementName != null)
-            {
-                throw new InvalidOperationException(
-                    "ElementName property cannot be set when an #elementName path is provided.");
-            }
-
-            if ((pathInfo.ElementName != null || ElementName != null) &&
-                RelativeSource != null)
-            {
-                throw new InvalidOperationException(
-                    "ElementName property cannot be set with a RelativeSource.");
-            }
-        }
-
         private ExpressionObserver CreateDataContexObserver(
             IAvaloniaObject target,
             string path,
@@ -256,7 +209,11 @@ namespace Avalonia.Markup.Xaml.Data
             }
         }
 
-        private ExpressionObserver CreateElementObserver(IControl target, string elementName, string path)
+        private ExpressionObserver CreateElementObserver(
+            IControl target,
+            string elementName,
+            string path,
+            bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
@@ -264,35 +221,39 @@ namespace Avalonia.Markup.Xaml.Data
             var result = new ExpressionObserver(
                 ControlLocator.Track(target, elementName),
                 path,
-                false,
+                enableDataValidation,
                 description);
             return result;
         }
 
         private ExpressionObserver CreateFindAncestorObserver(
             IControl target,
-            string path)
+            RelativeSource relativeSource,
+            string path,
+            bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
             return new ExpressionObserver(
-                ControlLocator.Track(target, RelativeSource.AncestorType, RelativeSource.AncestorLevel -1),
-                path);
+                ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType),
+                path,
+                enableDataValidation);
         }
 
         private ExpressionObserver CreateSourceObserver(
             object source,
             string path,
-            bool enabledDataValidation)
+            bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(source != null);
 
-            return new ExpressionObserver(source, path, enabledDataValidation);
+            return new ExpressionObserver(source, path, enableDataValidation);
         }
 
         private ExpressionObserver CreateTemplatedParentObserver(
             IAvaloniaObject target,
-            string path)
+            string path,
+            bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
@@ -303,7 +264,8 @@ namespace Avalonia.Markup.Xaml.Data
             var result = new ExpressionObserver(
                 () => target.GetValue(Control.TemplatedParentProperty),
                 path,
-                update);
+                update,
+                enableDataValidation);
 
             return result;
         }
@@ -328,6 +290,7 @@ namespace Avalonia.Markup.Xaml.Data
         {
             public string Path { get; set; }
             public string ElementName { get; set; }
+            public RelativeSource RelativeSource { get; set; }
         }
     }
-}
+}

+ 2 - 0
src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs

@@ -87,5 +87,7 @@ namespace Avalonia.Markup.Xaml.Data
         /// Gets or sets a value that describes the type of relative source lookup.
         /// </summary>
         public RelativeSourceMode Mode { get; set; }
+
+        public TreeType Tree { get; set; } = TreeType.Visual;
     }
 }

+ 150 - 3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@@ -29,20 +29,167 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
         public override object ProvideValue(IServiceProvider serviceProvider)
         {
+            var descriptorContext = (ITypeDescriptorContext)serviceProvider;
+
+            var pathInfo = ParsePath(Path, descriptorContext);
+            ValidateState(pathInfo);
+
             return new Binding
             {
                 Converter = Converter,
                 ConverterParameter = ConverterParameter,
-                ElementName = ElementName,
+                ElementName = pathInfo.ElementName ?? ElementName,
                 FallbackValue = FallbackValue,
                 Mode = Mode,
-                Path = Path,
+                Path = pathInfo.Path,
                 Priority = Priority,
-                RelativeSource = RelativeSource,
+                RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
                 DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
             };
         }
 
+        private class PathInfo
+        {
+            public string Path { get; set; }
+            public string ElementName { get; set; }
+            public RelativeSource RelativeSource { get; set; }
+        }
+
+        private void ValidateState(PathInfo pathInfo)
+        {
+            if (pathInfo.ElementName != null && ElementName != null)
+            {
+                throw new InvalidOperationException(
+                    "ElementName property cannot be set when an #elementName path is provided.");
+            }
+
+            if (pathInfo.RelativeSource != null && RelativeSource != null)
+            {
+                throw new InvalidOperationException(
+                    "ElementName property cannot be set when a $self or $parent path is provided.");
+            }
+
+            if ((pathInfo.ElementName != null || ElementName != null) &&
+                (pathInfo.RelativeSource != null || RelativeSource != null))
+            {
+                throw new InvalidOperationException(
+                    "ElementName property cannot be set with a RelativeSource.");
+            }
+        }
+
+        private static PathInfo ParsePath(string path, ITypeDescriptorContext context)
+        {
+            var result = new PathInfo();
+
+            if (string.IsNullOrWhiteSpace(path) || path == ".")
+            {
+                result.Path = string.Empty;
+            }
+            else if (path.StartsWith("#"))
+            {
+                var dot = path.IndexOf('.');
+
+                if (dot != -1)
+                {
+                    result.Path = path.Substring(dot + 1);
+                    result.ElementName = path.Substring(1, dot - 1);
+                }
+                else
+                {
+                    result.Path = string.Empty;
+                    result.ElementName = path.Substring(1);
+                }
+            }
+            else if (path.StartsWith("$"))
+            {
+                var relativeSource = new RelativeSource
+                {
+                    Tree = TreeType.Logical
+                };
+                result.RelativeSource = relativeSource;
+                var dot = path.IndexOf('.');
+                string relativeSourceMode;
+                if (dot != -1)
+                {
+                    result.Path = path.Substring(dot + 1);
+                    relativeSourceMode = path.Substring(1, dot - 1);
+                }
+                else
+                {
+                    result.Path = string.Empty;
+                    relativeSourceMode = path.Substring(1);
+                }
+
+                if (relativeSourceMode == "self")
+                {
+                    relativeSource.Mode = RelativeSourceMode.Self;
+                }
+                else if (relativeSourceMode == "parent")
+                {
+                    relativeSource.Mode = RelativeSourceMode.FindAncestor;
+                    relativeSource.AncestorLevel = 1;
+                }
+                else if (relativeSourceMode.StartsWith("parent["))
+                {
+                    relativeSource.Mode = RelativeSourceMode.FindAncestor;
+                    var parentConfigStart = relativeSourceMode.IndexOf('[');
+                    if (!relativeSourceMode.EndsWith("]"))
+                    {
+                        throw new InvalidOperationException("Invalid RelativeSource binding syntax. Expected matching ']' for '['.");
+                    }
+                    var parentConfigParams = relativeSourceMode.Substring(parentConfigStart + 1).TrimEnd(']').Split(';');
+                    if (parentConfigParams.Length > 2 || parentConfigParams.Length == 0)
+                    {
+                        throw new InvalidOperationException("Expected either 1 or 2 parameters for RelativeSource binding syntax");
+                    }
+                    else if (parentConfigParams.Length == 1)
+                    {
+                        if (int.TryParse(parentConfigParams[0], out int level))
+                        {
+                            relativeSource.AncestorType = null;
+                            relativeSource.AncestorLevel = level + 1;
+                        }
+                        else
+                        {
+                            relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+                        }
+                    }
+                    else
+                    {
+                        relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+                        relativeSource.AncestorLevel = int.Parse(parentConfigParams[1]) + 1;
+                    }
+                }
+                else
+                {
+                    throw new InvalidOperationException($"Invalid RelativeSource binding syntax: {relativeSourceMode}");
+                }
+            }
+            else
+            {
+                result.Path = path;
+            }
+
+            return result;
+        }
+
+        private static Type LookupAncestorType(string ancestorTypeName, ITypeDescriptorContext context)
+        {
+            var parts = ancestorTypeName.Split(':');
+            if (parts.Length == 0 || parts.Length > 2)
+            {
+                throw new InvalidOperationException("Invalid type name");
+            }
+
+            if (parts.Length == 1)
+            {
+                return context.ResolveType(string.Empty, parts[0]);
+            }
+            else
+            {
+                return context.ResolveType(parts[0], parts[1]);
+            }
+        }
 
         private static object GetDefaultAnchor(ITypeDescriptorContext context)
         {

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
                 ElementName = ElementName,
                 Mode = Mode,
                 RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
-                Path = Path,
+                Path = Path ?? string.Empty,
                 Priority = Priority,
             };
         }

+ 72 - 19
src/Markup/Avalonia.Markup/ControlLocator.cs

@@ -11,6 +11,21 @@ using Avalonia.VisualTree;
 
 namespace Avalonia.Markup
 {
+    /// <summary>
+    /// The type of tree via which to track a control.
+    /// </summary>
+    public enum TreeType
+    {
+        /// <summary>
+        /// The visual tree.
+        /// </summary>
+        Visual,
+        /// <summary>
+        /// The logical tree.
+        /// </summary>
+        Logical,
+    }
+
     /// <summary>
     /// Locates controls relative to other controls.
     /// </summary>
@@ -27,13 +42,13 @@ namespace Avalonia.Markup
         {
             var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
                 x => relativeTo.AttachedToLogicalTree += x,
-                x => relativeTo.DetachedFromLogicalTree += x)
+                x => relativeTo.AttachedToLogicalTree -= x)
                 .Select(x => ((IControl)x.Sender).FindNameScope())
                 .StartWith(relativeTo.FindNameScope());
 
             var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
                 x => relativeTo.DetachedFromLogicalTree += x,
-                x => relativeTo.DetachedFromLogicalTree += x)
+                x => relativeTo.DetachedFromLogicalTree -= x)
                 .Select(x => (INameScope)null);
 
             return attached.Merge(detached).Select(nameScope =>
@@ -68,37 +83,75 @@ namespace Avalonia.Markup
         /// <param name="relativeTo">
         /// The control relative from which the other control should be found.
         /// </param>
-        /// <param name="ancestorType">The type of the ancestor to find.</param>
+        /// <param name="tree">The tree via which to track the control.</param>
         /// <param name="ancestorLevel">
         /// The level of ancestor control to look for. Use 0 for the first ancestor of the
         /// requested type.
         /// </param>
-        public static IObservable<IControl> Track(IControl relativeTo, Type ancestorType, int ancestorLevel)
+        /// <param name="ancestorType">The type of the ancestor to find.</param>
+        public static IObservable<IControl> Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null)
+        {
+            return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree =>
+            {
+                if (isAttachedToTree)
+                {
+                    if (tree == TreeType.Visual)
+                    {
+                        return relativeTo.GetVisualAncestors()
+                            .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+                            .ElementAtOrDefault(ancestorLevel) as IControl; 
+                    }
+                    else
+                    {
+                        return relativeTo.GetLogicalAncestors()
+                            .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+                            .ElementAtOrDefault(ancestorLevel) as IControl;
+                    }
+                }
+                else
+                {
+                    return null;
+                }
+            });
+        }
+
+        private static IObservable<bool> TrackAttachmentToTree(IControl relativeTo, TreeType tree)
+        {
+            return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo);
+        }
+
+        private static IObservable<bool> TrackAttachmentToVisualTree(IControl relativeTo)
         {
             var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
                 x => relativeTo.AttachedToVisualTree += x,
-                x => relativeTo.DetachedFromVisualTree += x)
+                x => relativeTo.AttachedToVisualTree -= x)
                 .Select(x => true)
                 .StartWith(relativeTo.IsAttachedToVisualTree);
 
             var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
                 x => relativeTo.DetachedFromVisualTree += x,
-                x => relativeTo.DetachedFromVisualTree += x)
+                x => relativeTo.DetachedFromVisualTree -= x)
                 .Select(x => false);
 
-            return attached.Merge(detached).Select(isAttachedToVisualTree =>
-            {
-                if (isAttachedToVisualTree)
-                {
-                    return relativeTo.GetVisualAncestors()
-                        .Where(x => ancestorType.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()))
-                        .ElementAtOrDefault(ancestorLevel) as IControl;
-                }
-                else
-                {
-                    return null;
-                }
-            });
+            var attachmentStatus = attached.Merge(detached);
+            return attachmentStatus;
+        }
+
+        private static IObservable<bool> TrackAttachmentToLogicalTree(IControl relativeTo)
+        {
+            var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
+                x => relativeTo.AttachedToLogicalTree += x,
+                x => relativeTo.AttachedToLogicalTree -= x)
+                .Select(x => true)
+                .StartWith(relativeTo.IsAttachedToLogicalTree);
+
+            var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
+                x => relativeTo.DetachedFromLogicalTree += x,
+                x => relativeTo.DetachedFromLogicalTree -= x)
+                .Select(x => false);
+
+            var attachmentStatus = attached.Merge(detached);
+            return attachmentStatus;
         }
     }
 }

+ 1 - 9
src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs

@@ -51,15 +51,7 @@ namespace Avalonia.Markup.Data.Parsers
                     }
                 }
 
-                if (!r.End)
-                {
-                    r.Take();
-                    return result;
-                }
-                else
-                {
-                    throw new ExpressionParseException(r.Position, "Expected ']'.");
-                }
+                throw new ExpressionParseException(r.Position, "Expected ']'.");
             }
 
             return null;

+ 0 - 1
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -54,7 +54,6 @@ namespace Avalonia.Direct2D1.Media
             _finishedCallback = finishedCallback;
             _directWriteFactory = directWriteFactory;
             _imagingFactory = imagingFactory;
-            _swapChain = swapChain;
             _renderTarget.BeginDraw();
         }
 

+ 127 - 12
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@@ -2,22 +2,22 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Collections;
-using System.Collections.Generic;
+using System.ComponentModel;
+using System.Reactive.Concurrency;
 using System.Reactive.Linq;
 using System.Reactive.Subjects;
-using Microsoft.Reactive.Testing;
+using System.Threading;
+using System.Threading.Tasks;
 using Avalonia.Data;
 using Avalonia.Logging;
-using Avalonia.UnitTests;
-using Xunit;
-using System.Threading.Tasks;
+using Avalonia.Markup.Xaml.Data;
 using Avalonia.Platform;
-using System.Threading;
-using Moq;
-using System.Reactive.Disposables;
-using System.Reactive.Concurrency;
 using Avalonia.Threading;
+using Avalonia.UnitTests;
+using Avalonia.Diagnostics;
+using Microsoft.Reactive.Testing;
+using Moq;
+using Xunit;
 
 namespace Avalonia.Base.UnitTests
 {
@@ -363,7 +363,7 @@ namespace Avalonia.Base.UnitTests
                 Assert.True(called);
             }
         }
-        
+
         [Fact]
         public async Task Bind_With_Scheduler_Executes_On_Scheduler()
         {
@@ -387,6 +387,77 @@ namespace Avalonia.Base.UnitTests
             }
         }
 
+        [Fact]
+        public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            var target = new Class1();
+
+            target.Bind(Class1.DoubleValueProperty,
+                new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
+
+            var child = new Class1();
+
+            child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            // Issues #855 and #824 were causing a StackOverflowException at this point.
+            target.DoubleValue = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.DoubleValue);
+            Assert.Equal(expected, child.DoubleValue);
+        }
+
+        [Fact]
+        public void IsAnimating_On_Property_With_No_Value_Returns_False()
+        {
+            var target = new Class1();
+
+            Assert.False(target.IsAnimating(Class1.FooProperty));
+        }
+
+        [Fact]
+        public void IsAnimating_On_Property_With_Animation_Value_Returns_False()
+        {
+            var target = new Class1();
+
+            target.SetValue(Class1.FooProperty, "foo", BindingPriority.Animation);
+
+            Assert.False(target.IsAnimating(Class1.FooProperty));
+        }
+
+        [Fact]
+        public void IsAnimating_On_Property_With_Non_Animation_Binding_Returns_False()
+        {
+            var target = new Class1();
+            var source = new Subject<string>();
+
+            target.Bind(Class1.FooProperty, source, BindingPriority.LocalValue);
+
+            Assert.False(target.IsAnimating(Class1.FooProperty));
+        }
+
+        [Fact]
+        public void IsAnimating_On_Property_With_Animation_Binding_Returns_True()
+        {
+            var target = new Class1();
+            var source = new BehaviorSubject<string>("foo");
+
+            target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
+
+            Assert.True(target.IsAnimating(Class1.FooProperty));
+        }
+
         /// <summary>
         /// Returns an observable that returns a single value but does not complete.
         /// </summary>
@@ -405,6 +476,15 @@ namespace Avalonia.Base.UnitTests
 
             public static readonly StyledProperty<double> QuxProperty =
                 AvaloniaProperty.Register<Class1, double>("Qux", 5.6);
+
+            public static readonly StyledProperty<double> DoubleValueProperty =
+                        AvaloniaProperty.Register<Class1, double>(nameof(DoubleValue));
+
+            public double DoubleValue
+            {
+                get { return GetValue(DoubleValueProperty); }
+                set { SetValue(DoubleValueProperty, value); }
+            }
         }
 
         private class Class2 : Class1
@@ -431,5 +511,40 @@ namespace Avalonia.Base.UnitTests
                 return InstancedBinding.OneTime(_source);
             }
         }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private double _value;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public double Value
+            {
+                get { return _value; }
+                set
+                {
+                    if (_value != value)
+                    {
+                        SetterInvokedCount++;
+                        if (SetterInvokedCount < MaxInvokedCount)
+                        {
+                            _value = (int)value;
+                            if (_value > 75) _value = 75;
+                            if (_value < 25) _value = 25;
+                        }
+                        else
+                        {
+                            _value = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+                    }
+                }
+            }
+        }
     }
-}
+}

+ 120 - 9
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@@ -3,11 +3,18 @@
 
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Reactive.Subjects;
+using System.Threading;
+using System.Threading.Tasks;
 using Avalonia;
 using Avalonia.Data;
 using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Threading;
+using Avalonia.Markup.Xaml.Data;
 using Avalonia.UnitTests;
+using Moq;
 using Xunit;
 
 namespace Avalonia.Base.UnitTests
@@ -208,7 +215,7 @@ namespace Avalonia.Base.UnitTests
         {
             var target = new Class1();
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.SetValue(Class1.BarProperty, "newvalue"));
         }
 
@@ -217,7 +224,7 @@ namespace Avalonia.Base.UnitTests
         {
             var target = new Class1();
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
         }
 
@@ -227,7 +234,7 @@ namespace Avalonia.Base.UnitTests
             var target = new Class1();
             var source = new Subject<string>();
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.Bind(Class1.BarProperty, source));
         }
 
@@ -411,6 +418,28 @@ namespace Avalonia.Base.UnitTests
             Assert.True(called);
         }
 
+        [Fact]
+        public async Task Bind_Executes_On_UIThread()
+        {
+            var target = new Class1();
+            var source = new Subject<object>();
+            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
+
+            var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
+            threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
+                .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
+
+            var services = new TestServices(
+                threadingInterface: threadingInterfaceMock.Object);
+
+            using (UnitTestApplication.Start(services))
+            {
+                target.Bind(Class1.FooProperty, source);
+
+                await Task.Run(() => source.OnNext("foobar"));
+            }
+        }
+
         [Fact]
         public void AddOwner_Should_Inherit_DefaultBindingMode()
         {
@@ -439,12 +468,46 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode);
         }
 
+        [Fact]
+        public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            var target = new Class1();
+
+            target.Bind(Class1.DoubleValueProperty, new Binding("Value")
+                                                    {
+                                                        Mode = BindingMode.TwoWay,
+                                                        Source = viewModel
+                                                    });
+
+            var child = new Class1();
+
+            child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            // Issues #855 and #824 were causing a StackOverflowException at this point.
+            target.DoubleValue = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.DoubleValue);
+            Assert.Equal(expected, child.DoubleValue);
+        }
+
         private class Class1 : AvaloniaObject
         {
             public static readonly DirectProperty<Class1, string> FooProperty =
                 AvaloniaProperty.RegisterDirect<Class1, string>(
-                    "Foo", 
-                    o => o.Foo, 
+                    "Foo",
+                    o => o.Foo,
                     (o, v) => o.Foo = v,
                     unsetValue: "unset");
 
@@ -453,14 +516,21 @@ namespace Avalonia.Base.UnitTests
 
             public static readonly DirectProperty<Class1, int> BazProperty =
                 AvaloniaProperty.RegisterDirect<Class1, int>(
-                    "Bar", 
-                    o => o.Baz, 
-                    (o,v) => o.Baz = v,
+                    "Bar",
+                    o => o.Baz,
+                    (o, v) => o.Baz = v,
                     unsetValue: -1);
 
+            public static readonly DirectProperty<Class1, double> DoubleValueProperty =
+                AvaloniaProperty.RegisterDirect<Class1, double>(
+                    nameof(DoubleValue),
+                    o => o.DoubleValue,
+                    (o, v) => o.DoubleValue = v);
+
             private string _foo = "initial";
             private readonly string _bar = "bar";
             private int _baz = 5;
+            private double _doubleValue;
 
             public string Foo
             {
@@ -478,6 +548,12 @@ namespace Avalonia.Base.UnitTests
                 get { return _baz; }
                 set { SetAndRaise(BazProperty, ref _baz, value); }
             }
+
+            public double DoubleValue
+            {
+                get { return _doubleValue; }
+                set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
+            }
         }
 
         private class Class2 : AvaloniaObject
@@ -497,5 +573,40 @@ namespace Avalonia.Base.UnitTests
                 set { SetAndRaise(FooProperty, ref _foo, value); }
             }
         }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private double _value;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public double Value
+            {
+                get { return _value; }
+                set
+                {
+                    if (_value != value)
+                    {
+                        SetterInvokedCount++;
+                        if (SetterInvokedCount < MaxInvokedCount)
+                        {
+                            _value = (int)value;
+                            if (_value > 75) _value = 75;
+                            if (_value < 25) _value = 25;
+                        }
+                        else
+                        {
+                            _value = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+                    }
+                }
+            }
+        }
     }
-}
+}

+ 22 - 0
tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs

@@ -83,6 +83,28 @@ namespace Avalonia.Base.UnitTests.Collections
             Assert.Equal(new[] { 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 }, target);
         }
 
+        [Fact]
+        public void MoveRange_Raises_Correct_CollectionChanged_Event()
+        {
+            var target = new AvaloniaList<int>(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+            var raised = false;
+
+            target.CollectionChanged += (s, e) =>
+            {
+                Assert.Equal(NotifyCollectionChangedAction.Move, e.Action);
+                Assert.Equal(0, e.OldStartingIndex);
+                Assert.Equal(10, e.NewStartingIndex);
+                Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.OldItems);
+                Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.NewItems);
+                raised = true;
+            };
+
+            target.MoveRange(0, 9, 10);
+
+            Assert.True(raised);
+            Assert.Equal(new[] { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, target);
+        }
+
         [Fact]
         public void Adding_Item_Should_Raise_CollectionChanged()
         {

+ 12 - 0
tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Reactive.Subjects;
 using Avalonia.Data;
 using Xunit;
 
@@ -70,6 +71,17 @@ namespace Avalonia.Base.UnitTests
             Assert.Same(p1.Initialized, p2.Initialized);
         }
 
+        [Fact]
+        public void IsAnimating_On_DirectProperty_With_Binding_Returns_False()
+        {
+            var target = new Class1();
+            var source = new BehaviorSubject<string>("foo");
+
+            target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
+
+            Assert.False(target.IsAnimating(Class1.FooProperty));
+        }
+
         private class Class1 : AvaloniaObject
         {
             public static readonly DirectProperty<Class1, string> FooProperty =

+ 1 - 0
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@@ -49,6 +49,7 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Base\Properties.cs" />
     <Compile Include="Layout\Measure.cs" />
     <Compile Include="Styling\ApplyStyling.cs" />
     <Compile Include="Program.cs" />

+ 43 - 0
tests/Avalonia.Benchmarks/Base/Properties.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Reactive.Subjects;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Base
+{
+    [MemoryDiagnoser]
+    public class AvaloniaObjectBenchmark
+    {
+        private Class1 target = new Class1();
+        private Subject<int> intBinding = new Subject<int>();
+
+        public AvaloniaObjectBenchmark()
+        {
+            target.SetValue(Class1.IntProperty, 123);
+        }
+
+        [Benchmark]
+        public void ClearAndSetIntProperty()
+        {
+            target.ClearValue(Class1.IntProperty);
+            target.SetValue(Class1.IntProperty, 123);
+        }
+
+        [Benchmark]
+        public void BindIntProperty()
+        {
+            using (target.Bind(Class1.IntProperty, intBinding))
+            {
+                for (var i = 0; i < 100; ++i)
+                {
+                    intBinding.OnNext(i);
+                }
+            }
+        }
+
+        class Class1 : AvaloniaObject
+        {
+            public static readonly AvaloniaProperty<int> IntProperty =
+                AvaloniaProperty.Register<Class1, int>("Int");
+        }
+    }
+}

+ 58 - 0
tests/Avalonia.Controls.UnitTests/ContentControlTests.cs

@@ -1,6 +1,7 @@
 // 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.Collections.Specialized;
 using System.Linq;
 using Moq;
@@ -11,6 +12,9 @@ using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Xunit;
+using Avalonia.Markup.Xaml.Data;
+using Avalonia.Data;
+using System.Collections.Generic;
 
 namespace Avalonia.Controls.UnitTests
 {
@@ -273,6 +277,60 @@ namespace Avalonia.Controls.UnitTests
             Assert.Null(target.Presenter.Child.DataContext);
         }
 
+        [Fact]
+        public void Binding_ContentTemplate_After_Content_Does_Not_Leave_Orpaned_TextBlock()
+        {
+            // Test for #1271.
+            var children = new List<IControl>();
+            var presenter = new ContentPresenter();
+
+            // The content and then the content template property need to be bound with delayed bindings
+            // as they are in Avalonia.Markup.Xaml.
+            DelayedBinding.Add(presenter, ContentPresenter.ContentProperty, new Binding("Content")
+            {
+                Priority = BindingPriority.TemplatedParent,
+                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
+            });
+
+            DelayedBinding.Add(presenter, ContentPresenter.ContentTemplateProperty, new Binding("ContentTemplate")
+            {
+                Priority = BindingPriority.TemplatedParent,
+                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
+            });
+
+            presenter.GetObservable(ContentPresenter.ChildProperty).Subscribe(children.Add);
+
+            var target = new ContentControl
+            {
+                Template = new FuncControlTemplate<ContentControl>(_ => presenter),
+                ContentTemplate = new FuncDataTemplate<string>(x => new Canvas()),
+                Content = "foo",
+            };
+                        
+            // The control must be rooted.
+            var root = new TestRoot
+            {
+                Child = target,
+            };
+
+            target.ApplyTemplate();
+
+            // When the template is applied, the Content property is bound before the ContentTemplate
+            // property, causing a TextBlock to be created by the default template before ContentTemplate
+            // is bound.
+            Assert.Collection(
+                children,
+                x => Assert.Null(x),
+                x => Assert.IsType<TextBlock>(x),
+                x => Assert.IsType<Canvas>(x));
+
+            var textBlock = (TextBlock)children[1];
+
+            // The leak in #1271 was caused by the TextBlock's logical parent not being cleared when
+            // it is replaced by the Canvas.
+            Assert.Null(textBlock.GetLogicalParent());
+        }
+
         private FuncControlTemplate GetTemplate()
         {
             return new FuncControlTemplate<ContentControl>(parent =>

+ 2 - 2
tests/Avalonia.Controls.UnitTests/DockPanelTests.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Controls.UnitTests
         {
             var target = new DockPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top },
                     new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom },
@@ -38,7 +38,7 @@ namespace Avalonia.Controls.UnitTests
         {
             var target = new DockPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left },
                     new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right },

+ 1 - 1
tests/Avalonia.Controls.UnitTests/DropDownTests.cs

@@ -89,7 +89,7 @@ namespace Avalonia.Controls.UnitTests
                 return new Panel
                 {
                     Name = "container",
-                    Children = new Controls
+                    Children =
                     {
                         new ContentControl
                         {

+ 42 - 42
tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs

@@ -22,14 +22,14 @@ namespace Avalonia.Controls.UnitTests
         {
             var grid = new Grid()
                        {
-                           RowDefinitions = new RowDefinitions("*,Auto,*"),
-                           ColumnDefinitions = new ColumnDefinitions("*,*"),
-                           Children = new Controls()
-                                      {
-                                          new Border { [Grid.RowProperty] = 0 },
-                                          new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
-                                          new Border { [Grid.RowProperty] = 2 }
-                                      }
+                            RowDefinitions = new RowDefinitions("*,Auto,*"),
+                            ColumnDefinitions = new ColumnDefinitions("*,*"),
+                            Children =
+                            {
+                                new Border { [Grid.RowProperty] = 0 },
+                                new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
+                                new Border { [Grid.RowProperty] = 2 }
+                            }
                        };
 
             var root = new TestRoot { Child = grid };
@@ -43,14 +43,14 @@ namespace Avalonia.Controls.UnitTests
         {
             var grid = new Grid()
                        {
-                           ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
-                           RowDefinitions = new RowDefinitions("*,*"),
-                           Children = new Controls()
-                                      {
-                                          new Border { [Grid.ColumnProperty] = 0 },
-                                          new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
-                                          new Border { [Grid.ColumnProperty] = 2 },
-                                      }
+                            ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
+                            RowDefinitions = new RowDefinitions("*,*"),
+                            Children =
+                            {
+                                new Border { [Grid.ColumnProperty] = 0 },
+                                new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
+                                new Border { [Grid.ColumnProperty] = 2 },
+                            }
                        };
 
             var root = new TestRoot { Child = grid };
@@ -64,14 +64,14 @@ namespace Avalonia.Controls.UnitTests
         {
             var grid = new Grid()
                        {
-                           ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
-                           RowDefinitions = new RowDefinitions("Auto,Auto"),
-                           Children = new Controls()
-                                      {
-                                          new Border { [Grid.ColumnProperty] = 0 },
-                                          new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
-                                          new Border { [Grid.ColumnProperty] = 2 },
-                                      }
+                            ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
+                            RowDefinitions = new RowDefinitions("Auto,Auto"),
+                            Children =
+                            {
+                                new Border { [Grid.ColumnProperty] = 0 },
+                                new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
+                                new Border { [Grid.ColumnProperty] = 2 },
+                            }
                        };
 
             var root = new TestRoot { Child = grid };
@@ -99,11 +99,11 @@ namespace Avalonia.Controls.UnitTests
 
             var grid = new Grid()
                        {
-                           RowDefinitions = rowDefinitions,
-                           Children = new Controls()
-                                      {
-                                          control1, splitter, control2
-                                      }
+                            RowDefinitions = rowDefinitions,
+                            Children =
+                            {
+                                control1, splitter, control2
+                            }
                        };
 
             var root = new TestRoot { Child = grid };
@@ -131,14 +131,14 @@ namespace Avalonia.Controls.UnitTests
         {
             var grid = new Grid()
                        {
-                           ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
-                           RowDefinitions = new RowDefinitions("*,*"),
-                           Children = new Controls()
-                                      {
-                                          new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
-                                          new Border { [Grid.ColumnProperty] = 1 },
-                                          new Border { [Grid.ColumnProperty] = 2 },
-                                      }
+                            ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
+                            RowDefinitions = new RowDefinitions("*,*"),
+                            Children =
+                            {
+                                new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
+                                new Border { [Grid.ColumnProperty] = 1 },
+                                new Border { [Grid.ColumnProperty] = 2 },
+                            }
                        };
 
             var root = new TestRoot { Child = grid };
@@ -171,11 +171,11 @@ namespace Avalonia.Controls.UnitTests
 
             var grid = new Grid()
                        {
-                           ColumnDefinitions = columnDefinitions,
-                           Children = new Controls()
-                                      {
-                                          control1, splitter, control2
-                                      }
+                            ColumnDefinitions = columnDefinitions,
+                            Children =
+                            {
+                                control1, splitter, control2
+                            }
                        };
 
             var root = new TestRoot { Child = grid };

+ 1 - 1
tests/Avalonia.Controls.UnitTests/GridTests.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Controls.UnitTests
                     new RowDefinition(GridLength.Auto),
                     new RowDefinition(GridLength.Auto),
                 },
-                Children = new Controls
+                Children =
                 {
                     new Border
                     {

+ 70 - 1
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@@ -1,11 +1,15 @@
 // 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.Collections.Generic;
+using System.ComponentModel;
 using System.Linq;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Templates;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml.Data;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 using Xunit;
@@ -199,6 +203,71 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(1, target.SelectedIndex);
         }
 
+        [Fact]
+        public void SelectedItem_Should_Not_Cause_StackOverflow()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Items = new List<string> { "foo", "bar", "baz" }
+            };
+
+            var target = new ListBox
+            {
+                Template = new FuncControlTemplate(CreateListBoxTemplate),
+                DataContext = viewModel,
+                Items = viewModel.Items
+            };
+
+            target.Bind(ListBox.SelectedItemProperty,
+                new Binding("SelectedItem") { Mode = BindingMode.TwoWay });
+
+            Assert.Equal(0, viewModel.SetterInvokedCount);
+
+            // In Issue #855, a Stackoverflow occured here.
+            target.SelectedItem = viewModel.Items[2];
+
+            Assert.Equal(viewModel.Items[1], target.SelectedItem);
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+        }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public List<string> Items { get; set; }
+
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private string _selectedItem;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public string SelectedItem
+            {
+                get { return _selectedItem; }
+                set
+                {
+                    if (_selectedItem != value)
+                    {
+                        SetterInvokedCount++;
+
+                        int index = Items.IndexOf(value);
+
+                        if (MaxInvokedCount > SetterInvokedCount && index > 0)
+                        {
+                            _selectedItem = Items[index - 1];
+                        }
+                        else
+                        {
+                            _selectedItem = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
+                    }
+                }
+            }
+        }
+
         private Control CreateListBoxTemplate(ITemplatedControl parent)
         {
             return new ScrollViewer
@@ -237,4 +306,4 @@ namespace Avalonia.Controls.UnitTests
             target.Presenter.ApplyTemplate();
         }
     }
-}
+}

+ 29 - 31
tests/Avalonia.Controls.UnitTests/PanelTests.cs

@@ -2,8 +2,8 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System.Linq;
-using Avalonia.Collections;
 using Avalonia.LogicalTree;
+using Avalonia.VisualTree;
 using Xunit;
 
 namespace Avalonia.Controls.UnitTests
@@ -18,20 +18,9 @@ namespace Avalonia.Controls.UnitTests
 
             panel.Children.Add(child);
 
-            Assert.Equal(child.Parent, panel);
-            Assert.Equal(child.GetLogicalParent(), panel);
-        }
-
-        [Fact]
-        public void Setting_Controls_Should_Set_Child_Controls_Parent()
-        {
-            var panel = new Panel();
-            var child = new Control();
-
-            panel.Children = new Controls { child };
-
-            Assert.Equal(child.Parent, panel);
-            Assert.Equal(child.GetLogicalParent(), panel);
+            Assert.Same(child.Parent, panel);
+            Assert.Same(child.GetLogicalParent(), panel);
+            Assert.Same(child.GetVisualParent(), panel);
         }
 
         [Fact]
@@ -45,6 +34,7 @@ namespace Avalonia.Controls.UnitTests
 
             Assert.Null(child.Parent);
             Assert.Null(child.GetLogicalParent());
+            Assert.Null(child.GetVisualParent());
         }
 
         [Fact]
@@ -60,62 +50,70 @@ namespace Avalonia.Controls.UnitTests
 
             Assert.Null(child1.Parent);
             Assert.Null(child1.GetLogicalParent());
+            Assert.Null(child1.GetVisualParent());
             Assert.Null(child2.Parent);
             Assert.Null(child2.GetLogicalParent());
+            Assert.Null(child2.GetVisualParent());
         }
 
         [Fact]
-        public void Resetting_Panel_Children_Should_Clear_Child_Controls_Parent()
+        public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent()
         {
             var panel = new Panel();
             var child1 = new Control();
             var child2 = new Control();
 
             panel.Children.Add(child1);
-            panel.Children.Add(child2);
-            panel.Children = new Controls();
+            panel.Children[0] = child2;
 
             Assert.Null(child1.Parent);
             Assert.Null(child1.GetLogicalParent());
-            Assert.Null(child2.Parent);
-            Assert.Null(child2.GetLogicalParent());
+            Assert.Null(child1.GetVisualParent());
+            Assert.Same(child2.Parent, panel);
+            Assert.Same(child2.GetLogicalParent(), panel);
+            Assert.Same(child2.GetVisualParent(), panel);
         }
 
         [Fact]
-        public void Setting_Children_Should_Make_Controls_Appear_In_Panel_Children()
+        public void Child_Control_Should_Appear_In_Panel_Logical_And_Visual_Children()
         {
             var panel = new Panel();
             var child = new Control();
 
-            panel.Children = new Controls { child };
+            panel.Children.Add(child);
 
             Assert.Equal(new[] { child }, panel.Children);
             Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+            Assert.Equal(new[] { child }, panel.GetVisualChildren());
         }
 
         [Fact]
-        public void Child_Control_Should_Appear_In_Panel_Children()
+        public void Removing_Child_Control_Should_Remove_From_Panel_Logical_And_Visual_Children()
         {
             var panel = new Panel();
             var child = new Control();
 
             panel.Children.Add(child);
+            panel.Children.Remove(child);
 
-            Assert.Equal(new[] { child }, panel.Children);
-            Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+            Assert.Equal(new Control[0], panel.Children);
+            Assert.Empty(panel.GetLogicalChildren());
+            Assert.Empty(panel.GetVisualChildren());
         }
 
         [Fact]
-        public void Removing_Child_Control_Should_Remove_From_Panel_Children()
+        public void Moving_Panel_Children_Should_Reoder_Logical_And_Visual_Children()
         {
             var panel = new Panel();
-            var child = new Control();
+            var child1 = new Control();
+            var child2 = new Control();
 
-            panel.Children.Add(child);
-            panel.Children.Remove(child);
+            panel.Children.Add(child1);
+            panel.Children.Add(child2);
+            panel.Children.Move(1, 0);
 
-            Assert.Equal(new Control[0], panel.Children);
-            Assert.Equal(new ILogical[0], panel.GetLogicalChildren());
+            Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
+            Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
         }
     }
 }

+ 108 - 0
tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs

@@ -2,7 +2,12 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.ComponentModel;
 using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+using Avalonia.Markup.Xaml.Data;
+using Avalonia.Styling;
 using Xunit;
 
 namespace Avalonia.Controls.UnitTests.Primitives
@@ -87,8 +92,111 @@ namespace Avalonia.Controls.UnitTests.Primitives
             Assert.Throws<ArgumentException>(() => target.Value = double.NegativeInfinity);
         }
 
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void SetValue_Should_Not_Cause_StackOverflow(bool useXamlBinding)
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            Track track = null;
+
+            var target = new TestRange()
+            {
+                Template = new FuncControlTemplate<RangeBase>(c =>
+                {
+                    track = new Track()
+                    {
+                        Width = 100,
+                        Orientation = Orientation.Horizontal,
+                        [~~Track.MinimumProperty] = c[~~RangeBase.MinimumProperty],
+                        [~~Track.MaximumProperty] = c[~~RangeBase.MaximumProperty],
+
+                        Name = "PART_Track",
+                        Thumb = new Thumb()
+                    };
+
+                    if (useXamlBinding)
+                    {
+                        track.Bind(Track.ValueProperty, new Binding("Value")
+                                                    {
+                                                        Mode = BindingMode.TwoWay,
+                                                        Source = c,
+                                                        Priority = BindingPriority.Style
+                                                    });
+                    }
+                    else
+                    {
+                        track[~~Track.ValueProperty] = c[~~RangeBase.ValueProperty];
+                    }
+
+                    return track;
+                }),
+                Minimum = 0,
+                Maximum = 100,
+                DataContext = viewModel
+            };
+
+            target.Bind(TestRange.ValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay });
+
+            target.ApplyTemplate();
+            track.Measure(new Size(100, 0));
+            track.Arrange(new Rect(0, 0, 100, 0));
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            // Issues #855 and #824 were causing a StackOverflowException at this point.
+            target.Value = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.Value);
+            Assert.Equal(expected, track.Value);
+        }
+
         private class TestRange : RangeBase
         {
         }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private double _value;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public double Value
+            {
+                get { return _value; }
+                set
+                {
+                    if (_value != value)
+                    {
+                        SetterInvokedCount++;
+                        if (SetterInvokedCount < MaxInvokedCount)
+                        {
+                            _value = (int)value;
+                            if (_value > 75) _value = 75;
+                            if (_value < 25) _value = 25;
+                        }
+                        else
+                        {
+                            _value = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+                    }
+                }
+            }
+        }
     }
 }

+ 4 - 4
tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@@ -67,7 +67,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 {
                     Child = new Panel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new TextBlock(),
                             new Border(),
@@ -101,7 +101,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 {
                     Child = new Panel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new TextBlock(),
                             new Border(),
@@ -124,7 +124,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 {
                     Child = new Panel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new TextBlock(),
                             new Border(),
@@ -189,7 +189,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                         {
                             return new StackPanel
                             {
-                                Children = new Controls
+                                Children =
                                 {
                                     new TextBlock
                                     {

+ 26 - 0
tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs

@@ -140,5 +140,31 @@ namespace Avalonia.Controls.UnitTests.Primitives
             Assert.Same(thumb.Parent, target);
             Assert.Equal(new[] { thumb }, ((ILogical)target).LogicalChildren);
         }
+
+        [Fact]
+        public void Should_Not_Pass_Invalid_Arrange_Rect()
+        {
+            var thumb = new Thumb { Width = 100.873106060606 };
+            var increaseButton = new Button { Width = 10 };
+            var decreaseButton = new Button { Width = 10 };
+
+            var target = new Track
+            {
+                Height = 12,
+                Thumb = thumb,
+                IncreaseButton = increaseButton,
+                DecreaseButton = decreaseButton,
+                Orientation = Orientation.Horizontal,
+                Minimum = 0,
+                Maximum = 287,
+                Value = 287,
+                ViewportSize = 241,
+            };
+
+            target.Measure(Size.Infinity);
+
+            // #1297 was occuring here.
+            target.Arrange(new Rect(0, 0, 221, 12));
+        }
     }
 }

+ 1 - 1
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
                     new RowDefinition(1, GridUnitType.Star),
                     new RowDefinition(GridLength.Auto),
                 },
-                Children = new Controls
+                Children =
                 {
                     new ScrollContentPresenter
                     {

+ 6 - 6
tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
         {
             var target = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new Border { Height = 20, Width = 120 },
                     new Border { Height = 30 },
@@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
             var target = new StackPanel
             {
                 Orientation = Orientation.Horizontal,
-                Children = new Controls
+                Children =
                 {
                     new Border { Width = 20, Height = 120 },
                     new Border { Width = 30 },
@@ -59,7 +59,7 @@ namespace Avalonia.Controls.UnitTests
             var target = new StackPanel
             {
                 Gap = 10,
-                Children = new Controls
+                Children =
                 {
                     new Border { Height = 20, Width = 120 },
                     new Border { Height = 30 },
@@ -83,7 +83,7 @@ namespace Avalonia.Controls.UnitTests
             {
                 Gap = 10,
                 Orientation = Orientation.Horizontal,
-                Children = new Controls
+                Children =
                 {
                     new Border { Width = 20, Height = 120 },
                     new Border { Width = 30 },
@@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests
             var target = new StackPanel
             {
                 Height = 60,
-                Children = new Controls
+                Children =
                 {
                     new Border { Height = 20, Width = 120 },
                     new Border { Height = 30 },
@@ -130,7 +130,7 @@ namespace Avalonia.Controls.UnitTests
             {
                 Width = 60,
                 Orientation = Orientation.Horizontal,
-                Children = new Controls
+                Children =
                 {
                     new Border { Width = 20, Height = 120 },
                     new Border { Width = 30 },

+ 1 - 1
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@@ -272,7 +272,7 @@ namespace Avalonia.Controls.UnitTests
         {
             return new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new TabStrip
                     {

+ 1 - 1
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@@ -432,7 +432,7 @@ namespace Avalonia.Controls.UnitTests
         {
             return new FuncControlTemplate<TreeViewItem>(parent => new Panel
             {
-                Children = new Controls
+                Children =
                 {
                     new ContentPresenter
                     {

+ 26 - 26
tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

@@ -12,12 +12,12 @@ namespace Avalonia.Controls.UnitTests
         {
             var target = new WrapPanel()
                          {
-                             Width = 100,
-                             Children = new Controls
-                                        {
-                                            new Border { Height = 50, Width = 100 },
-                                            new Border { Height = 50, Width = 100 },
-                                        }
+                            Width = 100,
+                            Children =
+                            {
+                                new Border { Height = 50, Width = 100 },
+                                new Border { Height = 50, Width = 100 },
+                            }
                          };
 
             target.Measure(Size.Infinity);
@@ -33,12 +33,12 @@ namespace Avalonia.Controls.UnitTests
         {
             var target = new WrapPanel()
                          {
-                             Width = 200,
-                             Children = new Controls
-                                        {
-                                            new Border { Height = 50, Width = 100 },
-                                            new Border { Height = 50, Width = 100 },
-                                        }
+                            Width = 200,
+                            Children =
+                            {
+                                new Border { Height = 50, Width = 100 },
+                                new Border { Height = 50, Width = 100 },
+                            }
                          };
 
             target.Measure(Size.Infinity);
@@ -54,13 +54,13 @@ namespace Avalonia.Controls.UnitTests
         {
             var target = new WrapPanel()
                          {
-                             Orientation = Orientation.Vertical,
-                             Height = 120,
-                             Children = new Controls
-                                        {
-                                            new Border { Height = 50, Width = 100 },
-                                            new Border { Height = 50, Width = 100 },
-                                        }
+                            Orientation = Orientation.Vertical,
+                            Height = 120,
+                            Children =
+                            {
+                                new Border { Height = 50, Width = 100 },
+                                new Border { Height = 50, Width = 100 },
+                            }
                          };
 
             target.Measure(Size.Infinity);
@@ -76,13 +76,13 @@ namespace Avalonia.Controls.UnitTests
         {
             var target = new WrapPanel()
                          {
-                             Orientation = Orientation.Vertical,
-                             Height = 60,
-                             Children = new Controls
-                                        {
-                                            new Border { Height = 50, Width = 100 },
-                                            new Border { Height = 50, Width = 100 },
-                                        }
+                            Orientation = Orientation.Vertical,
+                            Height = 60,
+                            Children =
+                            {
+                                new Border { Height = 50, Width = 100 },
+                                new Border { Height = 50, Width = 100 },
+                            }
                          };
 
             target.Measure(Size.Infinity);

+ 61 - 63
tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs

@@ -6,8 +6,6 @@ using Xunit;
 
 namespace Avalonia.Input.UnitTests
 {
-    using Controls = Controls.Controls;
-
     public class KeyboardNavigationTests_Arrows
     {
         [Fact]
@@ -18,12 +16,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -33,7 +31,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -56,12 +54,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -71,7 +69,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -94,12 +92,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -123,16 +121,16 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
                                 [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                                Children = new Controls
+                                Children =
                                 {
                                     new Button { Name = "Button1" },
                                     new Button { Name = "Button2" },
@@ -144,7 +142,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -167,7 +165,7 @@ namespace Avalonia.Input.UnitTests
             var top = new StackPanel
             {
                 [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                Children = new Controls
+                Children =
                 {
                     (next = new Button { Name = "Button1" }),
                 }
@@ -187,17 +185,17 @@ namespace Avalonia.Input.UnitTests
             var top = new StackPanel
             {
                 [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
                                 [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                                Children = new Controls
+                                Children =
                                 {
                                     (next = new Button { Name = "Button1" }),
                                     new Button { Name = "Button2" },
@@ -209,7 +207,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -232,12 +230,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -246,7 +244,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -269,12 +267,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button1" }),
                             new Button { Name = "Button2" },
@@ -283,7 +281,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -306,12 +304,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -321,7 +319,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -343,12 +341,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -358,7 +356,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -380,12 +378,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.None,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -395,7 +393,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -418,12 +416,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (next = new Button { Name = "Button2" }),
@@ -433,7 +431,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -456,12 +454,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -471,7 +469,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -495,12 +493,12 @@ namespace Avalonia.Input.UnitTests
             var top = new StackPanel
             {
                 [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -524,16 +522,16 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
                                 [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                                Children = new Controls
+                                Children =
                                 {
                                     new Button { Name = "Button1" },
                                     new Button { Name = "Button2" },
@@ -545,7 +543,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -568,16 +566,16 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
                                 [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                                Children = new Controls
+                                Children =
                                 {
                                     (current = new Button { Name = "Button1" }),
                                     new Button { Name = "Button2" },
@@ -589,7 +587,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -632,12 +630,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button1" }),
                             (current = new Button { Name = "Button2" }),
@@ -647,7 +645,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -670,12 +668,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button1" }),
                             new Button { Name = "Button2" },
@@ -685,7 +683,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -708,12 +706,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button1" }),
                             (current = new Button { Name = "Button2" }),
@@ -723,7 +721,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -745,12 +743,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button1" }),
                             new Button { Name = "Button2" },
@@ -760,7 +758,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -783,7 +781,7 @@ namespace Avalonia.Input.UnitTests
             var top = new StackPanel
             {
                 [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
-                Children = new Controls
+                Children =
                 {
                     (current = new Decorator
                     {

+ 83 - 85
tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs

@@ -6,8 +6,6 @@ using Xunit;
 
 namespace Avalonia.Input.UnitTests
 {
-    using Controls = Controls.Controls;
-
     public class KeyboardNavigationTests_Tab
     {
         [Fact]
@@ -18,11 +16,11 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -31,7 +29,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -54,11 +52,11 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -67,7 +65,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -90,11 +88,11 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button1" }),
                             new Button { Name = "Button2" },
@@ -104,11 +102,11 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
-                                Children = new Controls
+                                Children =
                                 {
                                     new Button { Name = "Button4" },
                                     new Button { Name = "Button5" },
@@ -133,11 +131,11 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -161,15 +159,15 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
-                                Children = new Controls
+                                Children =
                                 {
                                     new Button { Name = "Button1" },
                                     new Button { Name = "Button2" },
@@ -180,7 +178,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -202,7 +200,7 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     (next = new Button { Name = "Button1" }),
                 }
@@ -221,15 +219,15 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
-                                Children = new Controls
+                                Children =
                                 {
                                     (next = new Button { Name = "Button1" }),
                                     new Button { Name = "Button2" },
@@ -240,7 +238,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -263,12 +261,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -277,7 +275,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -300,12 +298,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button1" }),
                             new Button { Name = "Button2" },
@@ -314,7 +312,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -337,12 +335,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -351,7 +349,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -373,12 +371,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -387,7 +385,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -410,12 +408,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -424,7 +422,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -448,12 +446,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     (container = new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (next = new Button { Name = "Button2" }),
@@ -462,7 +460,7 @@ namespace Avalonia.Input.UnitTests
                     }),
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -487,12 +485,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (current = new Button { Name = "Button2" }),
@@ -501,7 +499,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -525,12 +523,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     (container = new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -539,7 +537,7 @@ namespace Avalonia.Input.UnitTests
                     }),
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -564,11 +562,11 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (next = new Button { Name = "Button2" }),
@@ -577,7 +575,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -600,11 +598,11 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -613,7 +611,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -636,11 +634,11 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -664,15 +662,15 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
-                                Children = new Controls
+                                Children =
                                 {
                                     new Button { Name = "Button1" },
                                     new Button { Name = "Button2" },
@@ -683,7 +681,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -706,15 +704,15 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new StackPanel
                             {
-                                Children = new Controls
+                                Children =
                                 {
                                     (current = new Button { Name = "Button1" }),
                                     new Button { Name = "Button2" },
@@ -725,7 +723,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -767,12 +765,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button1" }),
                             (current = new Button { Name = "Button2" }),
@@ -781,7 +779,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -804,12 +802,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button1" }),
                             new Button { Name = "Button2" },
@@ -818,7 +816,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -841,12 +839,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button1" }),
                             (current = new Button { Name = "Button2" }),
@@ -855,7 +853,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -877,12 +875,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button1" }),
                             new Button { Name = "Button2" },
@@ -891,7 +889,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             new Button { Name = "Button5" },
@@ -914,11 +912,11 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             new Button { Name = "Button2" },
@@ -928,7 +926,7 @@ namespace Avalonia.Input.UnitTests
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button4" },
                             (current = new Button { Name = "Button5" }),
@@ -952,12 +950,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     (container = new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
-                        Children = new Controls
+                        Children =
                         {
                             new Button { Name = "Button1" },
                             (next = new Button { Name = "Button2" }),
@@ -966,7 +964,7 @@ namespace Avalonia.Input.UnitTests
                     }),
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -991,12 +989,12 @@ namespace Avalonia.Input.UnitTests
 
             var top = new StackPanel
             {
-                Children = new Controls
+                Children =
                 {
                     new StackPanel
                     {
                         [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
-                        Children = new Controls
+                        Children =
                         {
                             (next = new Button { Name = "Button1" }),
                             new Button { Name = "Button2" },
@@ -1005,7 +1003,7 @@ namespace Avalonia.Input.UnitTests
                     },
                     new StackPanel
                     {
-                        Children = new Controls
+                        Children =
                         {
                             (current = new Button { Name = "Button4" }),
                             new Button { Name = "Button5" },
@@ -1028,7 +1026,7 @@ namespace Avalonia.Input.UnitTests
             var top = new StackPanel
             {
                 [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
-                Children = new Controls
+                Children =
                 {
                     (current = new Decorator
                     {

+ 4 - 4
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@@ -275,10 +275,10 @@ namespace Avalonia.Layout.UnitTests
                 {
                     Child = panel = new StackPanel
                     {
-                        Children = new Controls.Controls
-                    {
-                        (border = new Border())
-                    }
+                        Children =
+                        {
+                            (border = new Border())
+                        }
                     }
                 };
 

+ 5 - 5
tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Markup.UnitTests
             {
                 Child = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         (target = new TextBlock { Name = "target" }),
                         (relativeTo = new TextBlock { Name = "start" }),
@@ -49,7 +49,7 @@ namespace Avalonia.Markup.UnitTests
             {
                 Child = (panel = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         (relativeTo = new TextBlock
                         {
@@ -84,7 +84,7 @@ namespace Avalonia.Markup.UnitTests
             {
                 Child = panel = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         (target = new TextBlock { Name = "target" }),
                         (relativeTo = new TextBlock { Name = "start" }),
@@ -114,7 +114,7 @@ namespace Avalonia.Markup.UnitTests
             {
                 Child = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         (relativeTo = new TextBlock
                         {
@@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests
             {
                 Child = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         (target2 = new TextBlock { Name = "target" }),
                     }

+ 1 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@@ -21,6 +21,7 @@
     <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
+    <ProjectReference Include="..\Avalonia.Base.UnitTests\Avalonia.Base.UnitTests.csproj" />
     <ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
   </ItemGroup>
   <ItemGroup>

+ 162 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs

@@ -13,6 +13,7 @@ using Moq;
 using Xunit;
 using System.ComponentModel;
 using System.Runtime.CompilerServices;
+using Avalonia.UnitTests;
 
 namespace Avalonia.Markup.Xaml.UnitTests.Data
 {
@@ -337,6 +338,167 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             Assert.Equal("foo", target.Content);
         }
 
+        [Fact]
+        public void StyledProperty_SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            var target = new StyledPropertyClass();
+
+            target.Bind(StyledPropertyClass.DoubleValueProperty,
+                new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
+
+            var child = new StyledPropertyClass();
+
+            child.Bind(StyledPropertyClass.DoubleValueProperty,
+                new Binding("DoubleValue")
+                {
+                    Mode = BindingMode.TwoWay,
+                    Source = target
+                });
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            //here in real life stack overflow exception is thrown issue #855 and #824
+            target.DoubleValue = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.DoubleValue);
+            Assert.Equal(expected, child.DoubleValue);
+        }
+
+        [Fact]
+        public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            var target = new DirectPropertyClass();
+
+            target.Bind(DirectPropertyClass.DoubleValueProperty, new Binding("Value")
+            {
+                Mode = BindingMode.TwoWay,
+                Source = viewModel
+            });
+
+            var child = new DirectPropertyClass();
+
+            child.Bind(DirectPropertyClass.DoubleValueProperty,
+                new Binding("DoubleValue")
+                {
+                    Mode = BindingMode.TwoWay,
+                    Source = target
+                });
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            //here in real life stack overflow exception is thrown issue #855 and #824
+            target.DoubleValue = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.DoubleValue);
+            Assert.Equal(expected, child.DoubleValue);
+        }
+
+        private class StyledPropertyClass : AvaloniaObject
+        {
+            public static readonly StyledProperty<double> DoubleValueProperty =
+                        AvaloniaProperty.Register<StyledPropertyClass, double>(nameof(DoubleValue));
+
+            public double DoubleValue
+            {
+                get { return GetValue(DoubleValueProperty); }
+                set { SetValue(DoubleValueProperty, value); }
+            }
+        }
+
+        private class DirectPropertyClass : AvaloniaObject
+        {
+            public static readonly DirectProperty<DirectPropertyClass, double> DoubleValueProperty =
+                AvaloniaProperty.RegisterDirect<DirectPropertyClass, double>(
+                    nameof(DoubleValue),
+                    o => o.DoubleValue,
+                    (o, v) => o.DoubleValue = v);
+
+            private double _doubleValue;
+            public double DoubleValue
+            {
+                get { return _doubleValue; }
+                set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
+            }
+        }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private double _value;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public double Value
+            {
+                get { return _value; }
+                set
+                {
+                    if (_value != value)
+                    {
+                        SetterInvokedCount++;
+                        if (SetterInvokedCount < MaxInvokedCount)
+                        {
+                            _value = (int)value;
+                            if (_value > 75) _value = 75;
+                            if (_value < 25) _value = 25;
+                        }
+                        else
+                        {
+                            _value = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+                    }
+                }
+            }
+        }
+        
+
+        [Fact]
+        public void Binding_With_Null_Path_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <TextBlock Name='textBlock' Text='{Binding}'/>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                window.DataContext = "foo";
+                window.ApplyTemplate();
+
+                Assert.Equal("foo", textBlock.Text);
+            }
+        }
+
         private class TwoWayBindingTest : Control
         {
             public static readonly StyledProperty<string> TwoWayProperty =

+ 4 - 4
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             {
                 Child = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         new TextBlock
                         {
@@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             {
                 Child = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         (source = new TextBlock
                         {
@@ -89,7 +89,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             {
                 Child = stackPanel = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         (target = new TextBlock
                         {
@@ -126,7 +126,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             {
                 Child = stackPanel = new StackPanel
                 {
-                    Children = new Controls.Controls
+                    Children =
                     {
                         (target = new ContentControl
                         {

+ 32 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs

@@ -11,6 +11,9 @@ using Avalonia.Markup.Xaml.Data;
 using Avalonia.Styling;
 using Xunit;
 using System.Reactive.Disposables;
+using Avalonia.UnitTests;
+using Avalonia.VisualTree;
+using System.Linq;
 
 namespace Avalonia.Markup.Xaml.UnitTests.Data
 {
@@ -56,6 +59,35 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
                 BindingPriority.TemplatedParent));
         }
 
+        [Fact]
+        public void TemplateBinding_With_Null_Path_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Button Name='button'>
+       <Button.Template>
+         <ControlTemplate>
+           <TextBlock Text='{TemplateBinding}'/>
+         </ControlTemplate>
+       </Button.Template>
+    </Button>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var button = window.FindControl<Button>("button");
+
+                window.ApplyTemplate();
+                button.ApplyTemplate();
+
+                var textBlock = (TextBlock)button.GetVisualChildren().Single();
+                Assert.Equal("Avalonia.Controls.Button", textBlock.Text);
+            }
+        }
+
         private Mock<IControl> CreateTarget(
             ITemplatedControl templatedParent = null,
             string text = null)

+ 174 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs

@@ -3,6 +3,7 @@
 
 using Avalonia.Controls;
 using Avalonia.UnitTests;
+using System;
 using Xunit;
 
 namespace Avalonia.Markup.Xaml.UnitTests.Xaml
@@ -77,6 +78,77 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             }
         }
 
+        [Fact]
+        public void Binding_To_First_Ancestor_Without_AncestorType_Throws_Exception()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Border Name='border1'>
+      <ContentControl Name='contentControl'>
+        <Button Name='button' Content='{Binding Name, RelativeSource={RelativeSource AncestorLevel=1}}'/>
+      </ContentControl>
+    </Border>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                Assert.Throws<InvalidOperationException>( () => loader.Load(xaml));
+            }
+        }
+
+        [Fact]
+        public void Binding_To_First_Ancestor_With_Shorthand_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Border Name='border1'>
+      <Border Name='border2'>
+        <Button Name='button' Content='{Binding $parent.Name}'/>
+      </Border>
+    </Border>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var button = window.FindControl<Button>("button");
+
+                window.ApplyTemplate();
+
+                Assert.Equal("border2", button.Content);
+            }
+        }
+
+        [Fact]
+        public void Binding_To_First_Ancestor_With_Shorthand_Uses_LogicalTree()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Border Name='border'>
+      <ContentControl Name='contentControl'>
+        <Button Name='button' Content='{Binding $parent.Name}'/>
+      </ContentControl>
+    </Border>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var contentControl = window.FindControl<ContentControl>("contentControl");
+                var button = window.FindControl<Button>("button");
+
+                window.ApplyTemplate();
+                
+                Assert.Equal("contentControl", button.Content);
+            }
+        }
+
         [Fact]
         public void Binding_To_Second_Ancestor_Works()
         {
@@ -102,6 +174,108 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             }
         }
 
+        [Fact]
+        public void Binding_To_Second_Ancestor_With_Shorthand_Uses_LogicalTree()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <ContentControl Name='contentControl1'>
+      <ContentControl Name='contentControl2'>
+        <Button Name='button' Content='{Binding $parent[1].Name}'/>
+      </ContentControl>
+    </ContentControl>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var contentControl1 = window.FindControl<ContentControl>("contentControl1");
+                var contentControl2 = window.FindControl<ContentControl>("contentControl2");
+                var button = window.FindControl<Button>("button");
+
+                window.ApplyTemplate();
+
+                Assert.Equal("contentControl1", button.Content);
+            }
+        }
+
+        [Fact]
+        public void Binding_To_Ancestor_Of_Type_With_Shorthand_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Border Name='border1'>
+      <Border Name='border2'>
+        <Button Name='button' Content='{Binding $parent[Border].Name}'/>
+      </Border>
+    </Border>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var button = window.FindControl<Button>("button");
+
+                window.ApplyTemplate();
+
+                Assert.Equal("border2", button.Content);
+            }
+        }
+
+        [Fact]
+        public void Binding_To_Second_Ancestor_With_Shorthand_And_Type_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Border Name='border1'>
+      <Border Name='border2'>
+        <Button Name='button' Content='{Binding $parent[Border; 1].Name}'/>
+      </Border>
+    </Border>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var button = window.FindControl<Button>("button");
+
+                window.ApplyTemplate();
+
+                Assert.Equal("border1", button.Content);
+            }
+        }
+
+        [Fact]
+        public void Binding_To_Second_Ancestor_With_Shorthand_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Border Name='border1'>
+      <Border Name='border2'>
+        <Button Name='button' Content='{Binding $parent[1].Name}'/>
+      </Border>
+    </Border>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var button = window.FindControl<Button>("button");
+
+                window.ApplyTemplate();
+
+                Assert.Equal("border1", button.Content);
+            }
+        }
+
         [Fact]
         public void Binding_To_Ancestor_With_Namespace_Works()
         {

+ 1 - 1
tests/Avalonia.RenderTests/GeometryClippingTests.cs

@@ -29,7 +29,7 @@ namespace Avalonia.Direct2D1.RenderTests
                 Clip = StreamGeometry.Parse("F1 M 0,0  H 76 V 76 Z"),
                 Width = 76,
                 Height = 76,
-                Children = new Avalonia.Controls.Controls
+                Children =
                 {
                     new Path
                     {

+ 1 - 1
tests/Avalonia.RenderTests/Media/VisualBrushTests.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
             {
                 return new Panel
                 {
-                    Children = new Avalonia.Controls.Controls
+                    Children =
                     {
                         new Image
                         {

+ 1 - 1
tests/Avalonia.RenderTests/OpacityMaskTests.cs

@@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1.RenderTests
                 },
                 Width = 76,
                 Height = 76,
-                Children = new Avalonia.Controls.Controls
+                Children =
                 {
                     new Path
                     {

+ 1 - 1
tests/Avalonia.RenderTests/SVGPathTests.cs

@@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1.RenderTests
                 Background = Brushes.Yellow,
                 Width = 76,
                 Height = 76,
-                Children = new Avalonia.Controls.Controls
+                Children =
                 {
                     new Path
                     {

+ 5 - 0
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@@ -135,6 +135,11 @@ namespace Avalonia.Styling.UnitTests
                 throw new NotImplementedException();
             }
 
+            public bool IsAnimating(AvaloniaProperty property)
+            {
+                throw new NotImplementedException();
+            }
+
             public bool IsSet(AvaloniaProperty property)
             {
                 throw new NotImplementedException();

+ 5 - 0
tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

@@ -165,6 +165,11 @@ namespace Avalonia.Styling.UnitTests
                 throw new NotImplementedException();
             }
 
+            public bool IsAnimating(AvaloniaProperty property)
+            {
+                throw new NotImplementedException();
+            }
+
             public bool IsSet(AvaloniaProperty property)
             {
                 throw new NotImplementedException();

+ 5 - 0
tests/Avalonia.Styling.UnitTests/TestControlBase.cs

@@ -59,6 +59,11 @@ namespace Avalonia.Styling.UnitTests
             throw new NotImplementedException();
         }
 
+        public bool IsAnimating(AvaloniaProperty property)
+        {
+            throw new NotImplementedException();
+        }
+
         public bool IsSet(AvaloniaProperty property)
         {
             throw new NotImplementedException();

+ 5 - 0
tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs

@@ -67,6 +67,11 @@ namespace Avalonia.Styling.UnitTests
             throw new NotImplementedException();
         }
 
+        public bool IsAnimating(AvaloniaProperty property)
+        {
+            throw new NotImplementedException();
+        }
+
         public bool IsSet(AvaloniaProperty property)
         {
             throw new NotImplementedException();

+ 15 - 3
tests/Avalonia.UnitTests/TestRoot.cs

@@ -16,8 +16,6 @@ namespace Avalonia.UnitTests
     public class TestRoot : Decorator, IFocusScope, ILayoutRoot, IInputRoot, INameScope, IRenderRoot, IStyleRoot
     {
         private readonly NameScope _nameScope = new NameScope();
-        private readonly IRenderTarget _renderTarget = Mock.Of<IRenderTarget>(
-            x => x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>()) == Mock.Of<IDrawingContextImpl>());
 
         public TestRoot()
         {
@@ -65,7 +63,21 @@ namespace Avalonia.UnitTests
 
         IStyleHost IStyleHost.StylingParent => StylingParent;
 
-        public IRenderTarget CreateRenderTarget() => _renderTarget;
+        public IRenderTarget CreateRenderTarget()
+        {
+            var dc = new Mock<IDrawingContextImpl>();
+            dc.Setup(x => x.CreateLayer(It.IsAny<Size>())).Returns(() =>
+            {
+                var layerDc = new Mock<IDrawingContextImpl>();
+                var layer = new Mock<IRenderTargetBitmapImpl>();
+                layer.Setup(x => x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>())).Returns(layerDc.Object);
+                return layer.Object;
+            });
+
+            var result = new Mock<IRenderTarget>();
+            result.Setup(x => x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>())).Returns(dc.Object);
+            return result.Object;
+        }
 
         public void Invalidate(Rect rect)
         {

+ 6 - 6
tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Visuals.UnitTests
                 Width = 100,
                 Height = 100,
                 ClipToBounds = true,
-                Children = new Controls.Controls
+                Children =
                 {
                     (target = new TestControl
                     {
@@ -47,7 +47,7 @@ namespace Avalonia.Visuals.UnitTests
                 Width = 100,
                 Height = 100,
                 ClipToBounds = true,
-                Children = new Controls.Controls
+                Children =
                 {
                     (target = new TestControl
                     {
@@ -74,7 +74,7 @@ namespace Avalonia.Visuals.UnitTests
                 Width = 100,
                 Height = 100,
                 ClipToBounds = true,
-                Children = new Controls.Controls
+                Children =
                 {
                     new Canvas
                     {
@@ -82,7 +82,7 @@ namespace Avalonia.Visuals.UnitTests
                         Height = 100,
                         [Canvas.LeftProperty] = 50,
                         [Canvas.TopProperty] = 50,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             (target = new TestControl
                             {
@@ -111,7 +111,7 @@ namespace Avalonia.Visuals.UnitTests
                 Width = 100,
                 Height = 100,
                 ClipToBounds = true,
-                Children = new Controls.Controls
+                Children =
                 {
                     (target = new TestControl
                     {
@@ -138,7 +138,7 @@ namespace Avalonia.Visuals.UnitTests
                 Width = 100,
                 Height = 100,
                 ClipToBounds = true,
-                Children = new Controls.Controls
+                Children =
                 {
                     new Border
                     {

+ 206 - 67
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@@ -1,7 +1,9 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reactive.Subjects;
 using Avalonia.Controls;
+using Avalonia.Data;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Rendering;
@@ -19,28 +21,18 @@ namespace Avalonia.Visuals.UnitTests.Rendering
         [Fact]
         public void First_Frame_Calls_UpdateScene_On_Dispatcher()
         {
-            var loop = new Mock<IRenderLoop>();
             var root = new TestRoot();
 
             var dispatcher = new Mock<IDispatcher>();
             dispatcher.Setup(x => x.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render))
                 .Callback<Action, DispatcherPriority>((a, p) => a());
 
-            var target = new DeferredRenderer(
-                root,
-                loop.Object,
-                sceneBuilder: MockSceneBuilder(root).Object,
-                dispatcher: dispatcher.Object);
+            CreateTargetAndRunFrame(root, dispatcher: dispatcher.Object);
 
-            target.Start();
-            RunFrame(loop);
-
-#if !NETCOREAPP1_1 // Delegate.Method is not available in netcoreapp1.1
             dispatcher.Verify(x => 
                 x.InvokeAsync(
                     It.Is<Action>(a => a.Method.Name == "UpdateScene"),
                     DispatcherPriority.Render));
-#endif
         }
 
         [Fact]
@@ -49,15 +41,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
             var loop = new Mock<IRenderLoop>();
             var root = new TestRoot();
             var sceneBuilder = MockSceneBuilder(root);
-            var dispatcher = new ImmediateDispatcher();
-            var target = new DeferredRenderer(
-                root,
-                loop.Object,
-                sceneBuilder: sceneBuilder.Object,
-                dispatcher: dispatcher);
 
-            target.Start();
-            RunFrame(loop);
+            CreateTargetAndRunFrame(root, sceneBuilder: sceneBuilder.Object);
 
             sceneBuilder.Verify(x => x.UpdateAll(It.IsAny<Scene>()));
         }
@@ -68,12 +53,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering
             var loop = new Mock<IRenderLoop>();
             var root = new TestRoot();
             var sceneBuilder = MockSceneBuilder(root);
-            var dispatcher = new ImmediateDispatcher();
             var target = new DeferredRenderer(
                 root,
                 loop.Object,
-                sceneBuilder: sceneBuilder.Object,
-                dispatcher: dispatcher);
+                sceneBuilder: sceneBuilder.Object);
 
             target.Start();
             IgnoreFirstFrame(loop, sceneBuilder);
@@ -127,12 +110,93 @@ namespace Avalonia.Visuals.UnitTests.Rendering
         }
 
         [Fact]
-        public void Frame_Should_Create_Layer_For_Root()
+        public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity()
+        {
+            var root = new TestRoot
+            {
+                Width = 100,
+                Height = 100,
+                Child = new Border
+                {
+                    Background = Brushes.Red,
+                    Opacity = 0.5,
+                }
+            };
+
+            root.Measure(Size.Infinity);
+            root.Arrange(new Rect(root.DesiredSize));
+
+            var target = CreateTargetAndRunFrame(root);
+            var context = GetLayerContext(target, root);
+            var animation = new BehaviorSubject<double>(0.5);
+
+            context.Verify(x => x.PushOpacity(0.5), Times.Once);
+            context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
+            context.Verify(x => x.PopOpacity(), Times.Once);
+        }
+
+        [Fact]
+        public void Should_Not_Draw_Controls_With_0_Opacity()
+        {
+            var root = new TestRoot
+            {
+                Width = 100,
+                Height = 100,
+                Child = new Border
+                {
+                    Background = Brushes.Red,
+                    Opacity = 0,
+                    Child = new Border
+                    {
+                        Background = Brushes.Green,
+                    }
+                }
+            };
+
+            root.Measure(Size.Infinity);
+            root.Arrange(new Rect(root.DesiredSize));
+
+            var target = CreateTargetAndRunFrame(root);
+            var context = GetLayerContext(target, root);
+            var animation = new BehaviorSubject<double>(0.5);
+
+            context.Verify(x => x.PushOpacity(0.5), Times.Never);
+            context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Never);
+            context.Verify(x => x.PopOpacity(), Times.Never);
+        }
+
+        [Fact]
+        public void Should_Push_Opacity_Mask()
+        {
+            var root = new TestRoot
+            {
+                Width = 100,
+                Height = 100,
+                Child = new Border
+                {
+                    Background = Brushes.Red,
+                    OpacityMask = Brushes.Green,
+                }
+            };
+
+            root.Measure(Size.Infinity);
+            root.Arrange(new Rect(root.DesiredSize));
+
+            var target = CreateTargetAndRunFrame(root);
+            var context = GetLayerContext(target, root);
+            var animation = new BehaviorSubject<double>(0.5);
+
+            context.Verify(x => x.PushOpacityMask(Brushes.Green, new Rect(0, 0, 100, 100)), Times.Once);
+            context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
+            context.Verify(x => x.PopOpacityMask(), Times.Once);
+        }
+
+        [Fact]
+        public void Should_Create_Layer_For_Root()
         {
             var loop = new Mock<IRenderLoop>();
             var root = new TestRoot();
             var rootLayer = new Mock<IRenderTargetBitmapImpl>();
-            var dispatcher = new ImmediateDispatcher();
 
             var sceneBuilder = new Mock<ISceneBuilder>();
             sceneBuilder.Setup(x => x.UpdateAll(It.IsAny<Scene>()))
@@ -143,23 +207,53 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                 });
 
             var renderInterface = new Mock<IPlatformRenderInterface>();
+            var target = CreateTargetAndRunFrame(root, sceneBuilder: sceneBuilder.Object);
 
-            var target = new DeferredRenderer(
-                root,
-                loop.Object,
-                sceneBuilder: sceneBuilder.Object,
-                //layerFactory: layers.Object,
-                dispatcher: dispatcher);
+            Assert.Single(target.Layers);
+        }
 
-            target.Start();
+        [Fact]
+        public void Should_Create_And_Delete_Layers_For_Controls_With_Animated_Opacity()
+        {
+            Border border;
+            var root = new TestRoot
+            {
+                Width = 100,
+                Height = 100,
+                Child = new Border
+                {
+                    Background = Brushes.Red,
+                    Child = border = new Border
+                    {
+                        Background = Brushes.Green,
+                        Child = new Canvas(),
+                        Opacity = 0.9,
+                    }
+                }
+            };
+
+            root.Measure(Size.Infinity);
+            root.Arrange(new Rect(root.DesiredSize));
+
+            var loop = new Mock<IRenderLoop>();
+            var target = CreateTargetAndRunFrame(root, loop: loop);
+
+            Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot));
+
+            var animation = new BehaviorSubject<double>(0.5);
+            border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
             RunFrame(loop);
 
-            var context = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
-            context.Verify(x => x.CreateLayer(root.ClientSize));
+            Assert.Equal(new IVisual[] { root, border }, target.Layers.Select(x => x.LayerRoot));
+
+            animation.OnCompleted();
+            RunFrame(loop);
+
+            Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot));
         }
 
         [Fact]
-        public void Should_Create_And_Delete_Layers_For_Transparent_Controls()
+        public void Should_Not_Create_Layer_For_Childless_Control_With_Animated_Opacity()
         {
             Border border;
             var root = new TestRoot
@@ -176,51 +270,96 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                 }
             };
 
+            var animation = new BehaviorSubject<double>(0.5);
+            border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
             root.Measure(Size.Infinity);
             root.Arrange(new Rect(root.DesiredSize));
 
-            var rootLayer = CreateLayer();
-            var borderLayer = CreateLayer();
-            var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
-            renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny<Size>()))
-                .Returns(rootLayer)
-                .Returns(borderLayer);
-
             var loop = new Mock<IRenderLoop>();
-            var target = new DeferredRenderer(
-                root,
-                loop.Object,
-                dispatcher: new ImmediateDispatcher());
-            root.Renderer = target;
+            var target = CreateTargetAndRunFrame(root, loop: loop);
 
-            target.Start();
-            RunFrame(loop);
+            Assert.Single(target.Layers);
+        }
+
+        [Fact]
+        public void Should_Not_Push_Opacity_For_Transparent_Layer_Root_Control()
+        {
+            Border border;
+            var root = new TestRoot
+            {
+                Width = 100,
+                Height = 100,
+                Child = border = new Border
+                {
+                    Background = Brushes.Red,
+                    Child = new Canvas(),
+                }
+            };
+
+            var animation = new BehaviorSubject<double>(0.5);
+            border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
 
-            var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null));
-            var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null));
+            root.Measure(Size.Infinity);
+            root.Arrange(new Rect(root.DesiredSize));
 
-            rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
-            rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
-            borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never);
+            var target = CreateTargetAndRunFrame(root);
+            var context = GetLayerContext(target, border);
 
-            rootContext.ResetCalls();
-            borderContext.ResetCalls();
-            border.Opacity = 0.5;
-            RunFrame(loop);
+            context.Verify(x => x.PushOpacity(0.5), Times.Never);
+            context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
+            context.Verify(x => x.PopOpacity(), Times.Never);
+        }
+
+        [Fact]
+        public void Should_Draw_Transparent_Layer_With_Correct_Opacity()
+        {
+            Border border;
+            var root = new TestRoot
+            {
+                Width = 100,
+                Height = 100,
+                Child = border = new Border
+                {
+                    Background = Brushes.Red,
+                    Child = new Canvas(),
+                }
+            };
+
+            var animation = new BehaviorSubject<double>(0.5);
+            border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
+            root.Measure(Size.Infinity);
+            root.Arrange(new Rect(root.DesiredSize));
 
-            rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
-            rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never);
-            borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
+            var target = CreateTargetAndRunFrame(root);
+            var context = Mock.Get(target.RenderTarget.CreateDrawingContext(null));
+            var borderLayer = target.Layers[border].Bitmap;
 
-            rootContext.ResetCalls();
-            borderContext.ResetCalls();
-            border.Opacity = 1;
+            context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny<Rect>(), It.IsAny<Rect>()));
+        }
+
+        private DeferredRenderer CreateTargetAndRunFrame(
+            TestRoot root,
+            Mock<IRenderLoop> loop = null,
+            ISceneBuilder sceneBuilder = null,
+            IDispatcher dispatcher = null)
+        {
+            loop = loop ?? new Mock<IRenderLoop>();
+            var target = new DeferredRenderer(
+                root,
+                loop.Object,
+                sceneBuilder: sceneBuilder,
+                dispatcher: dispatcher ?? new ImmediateDispatcher());
+            root.Renderer = target;
+            target.Start();
             RunFrame(loop);
+            return target;
+        }
 
-            Mock.Get(borderLayer).Verify(x => x.Dispose());
-            rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
-            rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
-            borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never);
+        private Mock<IDrawingContextImpl> GetLayerContext(DeferredRenderer renderer, IControl layerRoot)
+        {
+            return Mock.Get(renderer.Layers[layerRoot].Bitmap.CreateDrawingContext(null));
         }
 
         private void IgnoreFirstFrame(Mock<IRenderLoop> loop, Mock<ISceneBuilder> sceneBuilder)

+ 7 - 7
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs

@@ -154,7 +154,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                     {
                         Width = 200,
                         Height = 200,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             new Border
                             {
@@ -198,7 +198,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                     {
                         Width = 200,
                         Height = 200,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             new Border
                             {
@@ -255,7 +255,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                         Height = 200,
                         Background = Brushes.Red,
                         ClipToBounds = false,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             new Border
                             {
@@ -303,7 +303,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                         Width = 100,
                         Height = 200,
                         Background = Brushes.Red,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             new Panel()
                             {
@@ -312,7 +312,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                                 Background = Brushes.Red,
                                 Margin = new Thickness(0, 100, 0, 0),
                                 ClipToBounds = true,
-                                Children = new Controls.Controls
+                                Children =
                                 {
                                     (target = new Border()
                                     {
@@ -354,7 +354,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                         Width = 100,
                         Height = 200,
                         Background = Brushes.Red,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             (target = new Border()
                             {
@@ -374,7 +374,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                                 {
                                     Content = new StackPanel()
                                     {
-                                        Children = new Controls.Controls
+                                        Children =
                                         {
                                             (item1 = new Border()
                                             {

+ 7 - 7
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs

@@ -126,7 +126,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                     {
                         Width = 200,
                         Height = 200,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             new Border
                             {
@@ -171,7 +171,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                     {
                         Width = 200,
                         Height = 200,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             new Border
                             {
@@ -238,7 +238,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                         Height = 200,
                         Background = Brushes.Red,
                         ClipToBounds = false,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             new Border
                             {
@@ -287,7 +287,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                         Width = 100,
                         Height = 200,
                         Background = Brushes.Red,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             new Panel()
                             {
@@ -296,7 +296,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                                 Background = Brushes.Red,
                                 Margin = new Thickness(0, 100, 0, 0),
                                 ClipToBounds = true,
-                                Children = new Controls.Controls
+                                Children =
                                 {
                                     (target = new Border()
                                     {
@@ -339,7 +339,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                         Width = 100,
                         Height = 200,
                         Background = Brushes.Red,
-                        Children = new Controls.Controls
+                        Children =
                         {
                             (target = new Border()
                             {
@@ -359,7 +359,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                                 {
                                     Content = new StackPanel()
                                     {
-                                        Children = new Controls.Controls
+                                        Children =
                                         {
                                             (item1 = new Border()
                                             {

+ 5 - 1
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@@ -9,6 +9,8 @@ using Xunit;
 using Avalonia.Layout;
 using Moq;
 using Avalonia.Platform;
+using System.Reactive.Subjects;
+using Avalonia.Data;
 
 namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
 {
@@ -620,13 +622,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                         Margin = new Thickness(0, 10, 0, 0),
                         Child = border = new Border
                         {
-                            Opacity = 0.5,
                             Background = Brushes.Red,
                             Child = canvas = new Canvas(),
                         }
                     }
                 };
 
+                var animation = new BehaviorSubject<double>(0.5);
+                border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();
                 sceneBuilder.UpdateAll(scene);

+ 29 - 46
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

@@ -7,14 +7,15 @@ using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Xunit;
 using Avalonia.Layout;
-using Avalonia.Rendering;
+using System.Reactive.Subjects;
+using Avalonia.Data;
 
 namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
 {
     public partial class SceneBuilderTests
     {
         [Fact]
-        public void Control_With_Transparency_Should_Start_New_Layer()
+        public void Control_With_Animated_Opacity_And_Children_Should_Start_New_Layer()
         {
             using (TestApplication())
             {
@@ -31,10 +32,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                         Padding = new Thickness(11),
                         Child = border = new Border
                         {
-                            Opacity = 0.5,
                             Background = Brushes.Red,
                             Padding = new Thickness(12),
-                            Child = canvas = new Canvas(),
+                            Child = canvas = new Canvas()
                         }
                     }
                 };
@@ -42,6 +42,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
                 layout.ExecuteInitialLayoutPass(tree);
 
+                var animation = new BehaviorSubject<double>(0.5);
+                border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();
                 sceneBuilder.UpdateAll(scene);
@@ -58,7 +61,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 Assert.Equal(2, scene.Layers.Count());
                 Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border }));
 
-                border.Opacity = 1;
+                animation.OnCompleted();
                 scene = scene.Clone();
 
                 sceneBuilder.Update(scene, border);
@@ -80,13 +83,12 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
         }
 
         [Fact]
-        public void Control_With_OpacityMask_Should_Start_New_Layer()
+        public void Control_With_Animated_Opacity_And_No_Children_Should_Not_Start_New_Layer()
         {
             using (TestApplication())
             {
                 Decorator decorator;
                 Border border;
-                Canvas canvas;
                 var tree = new TestRoot
                 {
                     Padding = new Thickness(10),
@@ -97,10 +99,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                         Padding = new Thickness(11),
                         Child = border = new Border
                         {
-                            OpacityMask = Brushes.Red,
                             Background = Brushes.Red,
-                            Padding = new Thickness(12),
-                            Child = canvas = new Canvas(),
                         }
                     }
                 };
@@ -108,45 +107,19 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
                 layout.ExecuteInitialLayoutPass(tree);
 
+                var animation = new BehaviorSubject<double>(0.5);
+                border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();
                 sceneBuilder.UpdateAll(scene);
 
-                var rootNode = (VisualNode)scene.Root;
-                var borderNode = (VisualNode)scene.FindNode(border);
-                var canvasNode = (VisualNode)scene.FindNode(canvas);
-
-                Assert.Same(tree, rootNode.LayerRoot);
-                Assert.Same(border, borderNode.LayerRoot);
-                Assert.Same(border, canvasNode.LayerRoot);
-                Assert.Equal(Brushes.Red, scene.Layers[border].OpacityMask);
-
-                Assert.Equal(2, scene.Layers.Count());
-                Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border }));
-
-                border.OpacityMask = null;
-                scene = scene.Clone();
-
-                sceneBuilder.Update(scene, border);
-
-                rootNode = (VisualNode)scene.Root;
-                borderNode = (VisualNode)scene.FindNode(border);
-                canvasNode = (VisualNode)scene.FindNode(canvas);
-
-                Assert.Same(tree, rootNode.LayerRoot);
-                Assert.Same(tree, borderNode.LayerRoot);
-                Assert.Same(tree, canvasNode.LayerRoot);
                 Assert.Single(scene.Layers);
-
-                var rootDirty = scene.Layers[tree].Dirty;
-
-                Assert.Single(rootDirty);
-                Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single());
             }
         }
 
         [Fact]
-        public void Removing_Transparent_Control_Should_Remove_Layers()
+        public void Removing_Control_With_Animated_Opacity_Should_Remove_Layers()
         {
             using (TestApplication())
             {
@@ -163,13 +136,12 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                         Padding = new Thickness(11),
                         Child = border = new Border
                         {
-                            Opacity = 0.5,
                             Background = Brushes.Red,
                             Padding = new Thickness(12),
                             Child = canvas = new Canvas
                             {
-                                Opacity = 0.75,
-                            },
+                                Children = { new TextBlock() },
+                            }
                         }
                     }
                 };
@@ -177,6 +149,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
                 layout.ExecuteInitialLayoutPass(tree);
 
+                var animation = new BehaviorSubject<double>(0.5);
+                border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+                canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation);
+
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();
                 sceneBuilder.UpdateAll(scene);
@@ -210,13 +186,12 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                         Padding = new Thickness(11),
                         Child = border = new Border
                         {
-                            Opacity = 0.5,
                             Background = Brushes.Red,
                             Padding = new Thickness(12),
                             Child = canvas = new Canvas
                             {
-                                Opacity = 0.75,
-                            },
+                                Children = { new TextBlock() },
+                            }
                         }
                     }
                 };
@@ -224,6 +199,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
                 layout.ExecuteInitialLayoutPass(tree);
 
+                var animation = new BehaviorSubject<double>(0.5);
+                border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+                canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation);
+
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();
                 sceneBuilder.UpdateAll(scene);
@@ -256,6 +235,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                         Child = border = new Border
                         {
                             Opacity = 0.5,
+                            Child = new Canvas(),
                         }
                     }
                 };
@@ -263,6 +243,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
                 layout.ExecuteInitialLayoutPass(tree);
 
+                var animation = new BehaviorSubject<double>(0.5);
+                border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();
                 sceneBuilder.UpdateAll(scene);