Browse Source

Merge branch 'master' into bench-style-attach

Dariusz Komosiński 5 years ago
parent
commit
573436ae9f
32 changed files with 461 additions and 380 deletions
  1. 2 0
      readme.md
  2. 6 2
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  3. 0 1
      src/Avalonia.Base/AvaloniaObject.cs
  4. 0 39
      src/Avalonia.Base/AvaloniaProperty.cs
  5. 0 45
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  6. 0 15
      src/Avalonia.Base/DirectPropertyBase.cs
  7. 0 15
      src/Avalonia.Base/StyledPropertyBase.cs
  8. 10 3
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  9. 24 1
      src/Avalonia.Controls/Button.cs
  10. 26 2
      src/Avalonia.Controls/ButtonSpinner.cs
  11. 0 20
      src/Avalonia.Controls/ControlExtensions.cs
  12. 33 8
      src/Avalonia.Controls/Expander.cs
  13. 4 1
      src/Avalonia.Controls/ItemsControl.cs
  14. 25 5
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  15. 21 3
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  16. 18 6
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  17. 26 2
      src/Avalonia.Controls/Primitives/Track.cs
  18. 49 4
      src/Avalonia.Controls/ProgressBar.cs
  19. 22 2
      src/Avalonia.Controls/Slider.cs
  20. 38 4
      src/Avalonia.Input/InputElement.cs
  21. 10 2
      src/Avalonia.Layout/LayoutManager.cs
  22. 28 0
      src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs
  23. 0 77
      src/Avalonia.Styling/StyledElement.cs
  24. 11 10
      src/Avalonia.Themes.Default/ComboBox.xaml
  25. 52 34
      src/Avalonia.Themes.Default/ProgressBar.xaml
  26. 0 17
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
  27. 0 16
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  28. 0 23
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  29. 0 22
      tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
  30. 1 1
      tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs
  31. 22 0
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  32. 33 0
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

+ 2 - 0
readme.md

@@ -24,6 +24,8 @@ Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?it
 
 For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
 
+If you need to develop Avalonia app with JetBrains Rider, go and *vote* on [this issue](https://youtrack.jetbrains.com/issue/RIDER-39247) in their tracker. JetBrains won't do things without their users telling them that they want the feature,  so only **YOU** can make it happen.
+
 Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/)
 
 Use these commands in the Package Manager console to install Avalonia manually:

+ 6 - 2
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@@ -6,15 +6,19 @@
     <TextBlock Classes="h2">A progress bar control</TextBlock>
 
     <StackPanel>
+      <CheckBox
+          x:Name="showProgress"
+          Margin="10,16,0,0"
+          Content="Show Progress Text" />
       <StackPanel Orientation="Horizontal"
                   Margin="0,16,0,0"
                   HorizontalAlignment="Center"
                   Spacing="16">
         <StackPanel Spacing="16">
-          <ProgressBar Value="{Binding #hprogress.Value}" />
+          <ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
           <ProgressBar IsIndeterminate="True"/>
         </StackPanel>
-        <ProgressBar Value="{Binding #vprogress.Value}" Orientation="Vertical" />
+        <ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
         <ProgressBar Orientation="Vertical" IsIndeterminate="True" />
       </StackPanel>
       <StackPanel Margin="16">

+ 0 - 1
src/Avalonia.Base/AvaloniaObject.cs

@@ -34,7 +34,6 @@ namespace Avalonia
         public AvaloniaObject()
         {
             VerifyAccess();
-            AvaloniaPropertyRegistry.Instance.NotifyInitialized(this);
         }
 
         /// <summary>

+ 0 - 39
src/Avalonia.Base/AvaloniaProperty.cs

@@ -20,7 +20,6 @@ namespace Avalonia
         public static readonly object UnsetValue = new UnsetValueType();
 
         private static int s_nextId;
-        private readonly Subject<AvaloniaPropertyChangedEventArgs> _initialized;
         private readonly Subject<AvaloniaPropertyChangedEventArgs> _changed;
         private readonly PropertyMetadata _defaultMetadata;
         private readonly Dictionary<Type, PropertyMetadata> _metadata;
@@ -53,7 +52,6 @@ namespace Avalonia
                 throw new ArgumentException("'name' may not contain periods.");
             }
 
-            _initialized = new Subject<AvaloniaPropertyChangedEventArgs>();
             _changed = new Subject<AvaloniaPropertyChangedEventArgs>();
             _metadata = new Dictionary<Type, PropertyMetadata>();
 
@@ -81,7 +79,6 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(source != null);
             Contract.Requires<ArgumentNullException>(ownerType != null);
 
-            _initialized = source._initialized;
             _changed = source._changed;
             _metadata = new Dictionary<Type, PropertyMetadata>();
 
@@ -136,22 +133,6 @@ namespace Avalonia
         /// </summary>
         public virtual bool IsReadOnly => false;
 
-        /// <summary>
-        /// Gets an observable that is fired when this property is initialized on a
-        /// new <see cref="AvaloniaObject"/> instance.
-        /// </summary>
-        /// <remarks>
-        /// This observable is fired each time a new <see cref="AvaloniaObject"/> is constructed
-        /// for all properties registered on the object's type. The default value of the property
-        /// for the object is passed in the args' NewValue (OldValue will always be
-        /// <see cref="UnsetValue"/>.
-        /// </remarks>
-        /// <value>
-        /// An observable that is fired when this property is initialized on a new
-        /// <see cref="AvaloniaObject"/> instance.
-        /// </value>
-        public IObservable<AvaloniaPropertyChangedEventArgs> Initialized => _initialized;
-
         /// <summary>
         /// Gets an observable that is fired when this property changes on any
         /// <see cref="AvaloniaObject"/> instance.
@@ -488,26 +469,6 @@ namespace Avalonia
             return Name;
         }
 
-        /// <summary>
-        /// True if <see cref="Initialized"/> has any observers.
-        /// </summary>
-        internal bool HasNotifyInitializedObservers => _initialized.HasObservers;
-
-        /// <summary>
-        /// Notifies the <see cref="Initialized"/> observable.
-        /// </summary>
-        /// <param name="o">The object being initialized.</param>
-        internal abstract void NotifyInitialized(IAvaloniaObject o);
-
-        /// <summary>
-        /// Notifies the <see cref="Initialized"/> observable.
-        /// </summary>
-        /// <param name="e">The observable arguments.</param>
-        internal void NotifyInitialized(AvaloniaPropertyChangedEventArgs e)
-        {
-            _initialized.OnNext(e);
-        }
-
         /// <summary>
         /// Notifies the <see cref="Changed"/> observable.
         /// </summary>

+ 0 - 45
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@@ -415,51 +415,6 @@ namespace Avalonia
             _inheritedCache.Clear();
         }
 
-        internal void NotifyInitialized(AvaloniaObject o)
-        {
-            Contract.Requires<ArgumentNullException>(o != null);
-
-            var type = o.GetType();
-
-            if (!_initializedCache.TryGetValue(type, out var initializationData))
-            {
-                var visited = new HashSet<AvaloniaProperty>();
-
-                initializationData = new List<PropertyInitializationData>();
-
-                foreach (AvaloniaProperty property in GetRegistered(type))
-                {
-                    if (property.IsDirect)
-                    {
-                        initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property));
-                    }
-                    else
-                    {
-                        initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
-                    }
-
-                    visited.Add(property);
-                }
-
-                foreach (AvaloniaProperty property in GetRegisteredAttached(type))
-                {
-                    if (!visited.Contains(property))
-                    {
-                        initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
-
-                        visited.Add(property);
-                    }
-                }
-
-                _initializedCache.Add(type, initializationData);
-            }
-
-            foreach (PropertyInitializationData data in initializationData)
-            {
-                data.Property.NotifyInitialized(o);
-            }
-        }
-
         private readonly struct PropertyInitializationData
         {
             public AvaloniaProperty Property { get; }

+ 0 - 15
src/Avalonia.Base/DirectPropertyBase.cs

@@ -101,21 +101,6 @@ namespace Avalonia
             return (DirectPropertyMetadata<TValue>)base.GetMetadata(type);
         }
 
-        /// <inheritdoc/>
-        internal override void NotifyInitialized(IAvaloniaObject o)
-        {
-            if (HasNotifyInitializedObservers)
-            {
-                var e = new AvaloniaPropertyChangedEventArgs<TValue>(
-                    o,
-                    this,
-                    default,
-                    InvokeGetter(o),
-                    BindingPriority.Unset);
-                NotifyInitialized(e);
-            }
-        }
-
         /// <inheritdoc/>
         internal override void RouteClearValue(IAvaloniaObject o)
         {

+ 0 - 15
src/Avalonia.Base/StyledPropertyBase.cs

@@ -181,21 +181,6 @@ namespace Avalonia
         /// <inheritdoc/>
         object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type);
 
-        /// <inheritdoc/>
-        internal override void NotifyInitialized(IAvaloniaObject o)
-        {
-            if (HasNotifyInitializedObservers)
-            {
-                var e = new AvaloniaPropertyChangedEventArgs<TValue>(
-                    o,
-                    this,
-                    default,
-                    o.GetValue(this),
-                    BindingPriority.Unset);
-                NotifyInitialized(e);
-            }
-        }
-
         /// <inheritdoc/>
         internal override void RouteClearValue(IAvaloniaObject o)
         {

+ 10 - 3
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -149,6 +149,9 @@ namespace Avalonia.Controls
 
         private IEnumerable _items;
 
+        public event EventHandler<ScrollEventArgs> HorizontalScroll;
+        public event EventHandler<ScrollEventArgs> VerticalScroll;
+
         /// <summary>
         /// Identifies the CanUserReorderColumns dependency property.
         /// </summary>
@@ -373,7 +376,11 @@ namespace Avalonia.Controls
         public bool IsValid
         {
             get { return _isValid; }
-            internal set { SetAndRaise(IsValidProperty, ref _isValid, value); }
+            internal set 
+            { 
+                SetAndRaise(IsValidProperty, ref _isValid, value);
+                PseudoClasses.Set(":invalid", !value);
+            }
         }
 
         public static readonly StyledProperty<double> MaxColumnWidthProperty =
@@ -656,8 +663,6 @@ namespace Avalonia.Controls
                 HorizontalScrollBarVisibilityProperty,
                 VerticalScrollBarVisibilityProperty);
 
-            PseudoClass<DataGrid, bool>(IsValidProperty, x => !x, ":invalid");
-
             ItemsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnItemsPropertyChanged(e));
             CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnCanUserResizeColumnsChanged(e));
             ColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnColumnWidthChanged(e));
@@ -4223,6 +4228,7 @@ namespace Avalonia.Controls
         private void HorizontalScrollBar_Scroll(object sender, ScrollEventArgs e)
         {
             ProcessHorizontalScroll(e.ScrollEventType);
+            HorizontalScroll?.Invoke(sender, e);
         }
 
         private bool IsColumnOutOfBounds(int columnIndex)
@@ -5555,6 +5561,7 @@ namespace Avalonia.Controls
         private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e)
         {
             ProcessVerticalScroll(e.ScrollEventType);
+            VerticalScroll?.Invoke(sender, e);
         }
 
         //TODO: Ensure left button is checked for

+ 24 - 1
src/Avalonia.Controls/Button.cs

@@ -91,7 +91,11 @@ namespace Avalonia.Controls
             CommandProperty.Changed.Subscribe(CommandChanged);
             IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
             IsCancelProperty.Changed.Subscribe(IsCancelChanged);
-            PseudoClass<Button>(IsPressedProperty, ":pressed");
+        }
+
+        public Button()
+        {
+            UpdatePseudoClasses(IsPressed);
         }
 
         /// <summary>
@@ -312,6 +316,20 @@ namespace Avalonia.Controls
             IsPressed = false;
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == IsPressedProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>());
+            }
+        }
+
         protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
         {
             base.UpdateDataValidation(property, value);
@@ -474,5 +492,10 @@ namespace Avalonia.Controls
                 OnClick();
             }
         }
+
+        private void UpdatePseudoClasses(bool isPressed)
+        {
+            PseudoClasses.Set(":pressed", isPressed);
+        }
     }
 }

+ 26 - 2
src/Avalonia.Controls/ButtonSpinner.cs

@@ -1,5 +1,6 @@
 using System;
 using Avalonia.Controls.Primitives;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 
@@ -34,6 +35,11 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
             AvaloniaProperty.Register<ButtonSpinner, Location>(nameof(ButtonSpinnerLocation), Location.Right);
 
+        public ButtonSpinner()
+        {
+            UpdatePseudoClasses(ButtonSpinnerLocation);
+        }
+
         private Button _decreaseButton;
         /// <summary>
         /// Gets or sets the DecreaseButton template part.
@@ -85,8 +91,6 @@ namespace Avalonia.Controls
         static ButtonSpinner()
         {
             AllowSpinProperty.Changed.Subscribe(AllowSpinChanged);
-            PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
-            PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
         }
 
         /// <summary>
@@ -201,6 +205,20 @@ namespace Avalonia.Controls
             }
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == ButtonSpinnerLocationProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Location>());
+            }
+        }
+
         protected override void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue)
         {
             SetButtonUsage();
@@ -259,5 +277,11 @@ namespace Avalonia.Controls
                 OnSpin(new SpinEventArgs(SpinEvent, direction));
             }
         }
+
+        private void UpdatePseudoClasses(Location location)
+        {
+            PseudoClasses.Set(":left", location == Location.Left);
+            PseudoClasses.Set(":right", location == Location.Right);
+        }
     }
 }

+ 0 - 20
src/Avalonia.Controls/ControlExtensions.cs

@@ -69,26 +69,6 @@ namespace Avalonia.Controls
             return nameScope.Find<T>(name);
         }
 
-        /// <summary>
-        /// Adds or removes a pseudoclass depending on a boolean value.
-        /// </summary>
-        /// <param name="classes">The pseudoclasses collection.</param>
-        /// <param name="name">The name of the pseudoclass to set.</param>
-        /// <param name="value">True to add the pseudoclass or false to remove.</param>
-        public static void Set(this IPseudoClasses classes, string name, bool value)
-        {
-            Contract.Requires<ArgumentNullException>(classes != null);
-
-            if (value)
-            {
-                classes.Add(name);
-            }
-            else
-            {
-                classes.Remove(name);
-            }
-        }
-
         /// <summary>
         /// Sets a pseudoclass depending on an observable trigger.
         /// </summary>

+ 33 - 8
src/Avalonia.Controls/Expander.cs

@@ -1,5 +1,6 @@
 using Avalonia.Animation;
 using Avalonia.Controls.Primitives;
+using Avalonia.Data;
 
 namespace Avalonia.Controls
 {
@@ -30,16 +31,14 @@ namespace Avalonia.Controls
 
         static Expander()
         {
-            PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down");
-            PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Up, ":up");
-            PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Left, ":left");
-            PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Right, ":right");
-
-            PseudoClass<Expander>(IsExpandedProperty, ":expanded");
-
             IsExpandedProperty.Changed.AddClassHandler<Expander>((x, e) => x.OnIsExpandedChanged(e));
         }
 
+        public Expander()
+        {
+            UpdatePseudoClasses(ExpandDirection);
+        }
+
         public IPageTransition ContentTransition
         {
             get => GetValue(ContentTransitionProperty);
@@ -55,7 +54,11 @@ namespace Avalonia.Controls
         public bool IsExpanded
         {
             get { return _isExpanded; }
-            set { SetAndRaise(IsExpandedProperty, ref _isExpanded, value); }
+            set 
+            { 
+                SetAndRaise(IsExpandedProperty, ref _isExpanded, value);
+                PseudoClasses.Set(":expanded", value);
+            }
         }
 
         protected virtual void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
@@ -74,5 +77,27 @@ namespace Avalonia.Controls
                 }
             }
         }
+
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == ExpandDirectionProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<ExpandDirection>());
+            }
+        }
+
+        private void UpdatePseudoClasses(ExpandDirection d)
+        {
+            PseudoClasses.Set(":up", d == ExpandDirection.Up);
+            PseudoClasses.Set(":down", d == ExpandDirection.Down);
+            PseudoClasses.Set(":left", d == ExpandDirection.Left);
+            PseudoClasses.Set(":right", d == ExpandDirection.Right);
+        }
     }
 }

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

@@ -472,7 +472,10 @@ namespace Avalonia.Controls
                 result = container.GetControl(direction, c, wrap);
                 from = from ?? result;
 
-                if (result?.Focusable == true)
+                if (result != null &&
+                    result.Focusable &&
+                    result.IsEffectivelyEnabled &&
+                    result.IsEffectivelyVisible)
                 {
                     return result;
                 }

+ 25 - 5
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@@ -8,6 +8,7 @@ using System.Reactive.Linq;
 using System.Threading.Tasks;
 using Avalonia.Controls.Primitives;
 using Avalonia.Rendering;
+using Avalonia.Data;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Controls.Notifications
@@ -68,15 +69,12 @@ namespace Avalonia.Controls.Notifications
                         Install(host);
                     });
             }
+
+            UpdatePseudoClasses(Position);
         }
 
         static WindowNotificationManager()
         {
-            PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopLeft, ":topleft");
-            PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopRight, ":topright");
-            PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomLeft, ":bottomleft");
-            PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomRight, ":bottomright");
-
             HorizontalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.HorizontalAlignment.Stretch);
             VerticalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.VerticalAlignment.Stretch);
         }
@@ -143,6 +141,20 @@ namespace Avalonia.Controls.Notifications
             notificationControl.Close();
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == PositionProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<NotificationPosition>());
+            }
+        }
+
         /// <summary>
         /// Installs the <see cref="WindowNotificationManager"/> within the <see cref="AdornerLayer"/>
         /// of the host <see cref="Window"/>.
@@ -155,6 +167,14 @@ namespace Avalonia.Controls.Notifications
             adornerLayer?.Children.Add(this);
         }
 
+        private void UpdatePseudoClasses(NotificationPosition position)
+        {
+            PseudoClasses.Set(":topleft", position == NotificationPosition.TopLeft);
+            PseudoClasses.Set(":topright", position == NotificationPosition.TopRight);
+            PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
+            PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
+        }
+
         public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
     }
 }

+ 21 - 3
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -55,9 +55,6 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         static ScrollBar()
         {
-            PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
-            PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
-
             Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
             Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragComplete(e), RoutingStrategies.Bubble);
         }
@@ -74,6 +71,7 @@ namespace Avalonia.Controls.Primitives
                 this.GetObservable(VisibilityProperty).Select(_ => Unit.Default))
                 .Select(_ => CalculateIsVisible());
             this.Bind(IsVisibleProperty, isVisible, BindingPriority.Style);
+            UpdatePseudoClasses(Orientation);
         }
 
         /// <summary>
@@ -143,6 +141,20 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == OrientationProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+            }
+        }
+
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
             base.OnTemplateApplied(e);
@@ -252,5 +264,11 @@ namespace Avalonia.Controls.Primitives
         {
             Scroll?.Invoke(this, new ScrollEventArgs(scrollEventType, Value));
         }
+
+        private void UpdatePseudoClasses(Orientation o)
+        {
+            PseudoClasses.Set(":vertical", o == Orientation.Vertical);
+            PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
+        }
     }
 }

+ 18 - 6
src/Avalonia.Controls/Primitives/ToggleButton.cs

@@ -2,8 +2,8 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using Avalonia.Interactivity;
 using Avalonia.Data;
+using Avalonia.Interactivity;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -51,13 +51,14 @@ namespace Avalonia.Controls.Primitives
 
         static ToggleButton()
         {
-            PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == true, ":checked");
-            PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == false, ":unchecked");
-            PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == null, ":indeterminate");
-
             IsCheckedProperty.Changed.AddClassHandler<ToggleButton>((x, e) => x.OnIsCheckedChanged(e));
         }
 
+        public ToggleButton()
+        {
+            UpdatePseudoClasses(IsChecked);
+        }
+
         /// <summary>
         /// Raised when a <see cref="ToggleButton"/> is checked.
         /// </summary>
@@ -91,7 +92,11 @@ namespace Avalonia.Controls.Primitives
         public bool? IsChecked
         {
             get => _isChecked;
-            set => SetAndRaise(IsCheckedProperty, ref _isChecked, value);
+            set 
+            { 
+                SetAndRaise(IsCheckedProperty, ref _isChecked, value);
+                UpdatePseudoClasses(value);
+            }
         }
 
         /// <summary>
@@ -182,5 +187,12 @@ namespace Avalonia.Controls.Primitives
                     break;
             }
         }
+
+        private void UpdatePseudoClasses(bool? isChecked)
+        {
+            PseudoClasses.Set(":checked", isChecked == true);
+            PseudoClasses.Set(":unchecked", isChecked == false);
+            PseudoClasses.Set(":indeterminate", isChecked == null);
+        }
     }
 }

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

@@ -4,6 +4,7 @@
 // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
 
 using System;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.Metadata;
@@ -46,14 +47,17 @@ namespace Avalonia.Controls.Primitives
 
         static Track()
         {
-            PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
-            PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
             ThumbProperty.Changed.AddClassHandler<Track>((x,e) => x.ThumbChanged(e));
             IncreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
             DecreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
             AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
         }
 
+        public Track()
+        {
+            UpdatePseudoClasses(Orientation);
+        }
+
         public double Minimum
         {
             get { return _minimum; }
@@ -276,6 +280,20 @@ namespace Avalonia.Controls.Primitives
             return arrangeSize;
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == OrientationProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+            }
+        }
+
         private static void CoerceLength(ref double componentLength, double trackLength)
         {
             if (componentLength < 0)
@@ -433,5 +451,11 @@ namespace Avalonia.Controls.Primitives
                 DecreaseButton.IsVisible = visible;
             }
         }
+
+        private void UpdatePseudoClasses(Orientation o)
+        {
+            PseudoClasses.Set(":vertical", o == Orientation.Vertical);
+            PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
+        }
     }
 }

+ 49 - 4
src/Avalonia.Controls/ProgressBar.cs

@@ -4,6 +4,7 @@
 
 using System;
 using Avalonia.Controls.Primitives;
+using Avalonia.Data;
 using Avalonia.Layout;
 
 namespace Avalonia.Controls
@@ -16,6 +17,9 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<bool> IsIndeterminateProperty =
             AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
 
+        public static readonly StyledProperty<bool> ShowProgressTextProperty =
+            AvaloniaProperty.Register<ProgressBar, bool>(nameof(ShowProgressText));
+
         public static readonly StyledProperty<Orientation> OrientationProperty =
             AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
 
@@ -35,20 +39,27 @@ namespace Avalonia.Controls
 
         static ProgressBar()
         {
-            PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
-            PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
-            PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
-
             ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
             IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
         }
 
+        public ProgressBar()
+        {
+            UpdatePseudoClasses(IsIndeterminate, Orientation);
+        }
+
         public bool IsIndeterminate
         {
             get => GetValue(IsIndeterminateProperty);
             set => SetValue(IsIndeterminateProperty, value);
         }
 
+        public bool ShowProgressText
+        {
+            get => GetValue(ShowProgressTextProperty);
+            set => SetValue(ShowProgressTextProperty, value);
+        }
+
         public Orientation Orientation
         {
             get => GetValue(OrientationProperty);
@@ -75,6 +86,24 @@ namespace Avalonia.Controls
             return base.ArrangeOverride(finalSize);
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == IsIndeterminateProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
+            }
+            else if (property == OrientationProperty)
+            {
+                UpdatePseudoClasses(null, newValue.GetValueOrDefault<Orientation>());
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
@@ -121,5 +150,21 @@ namespace Avalonia.Controls
         {
             UpdateIndicator(Bounds.Size);
         }
+
+        private void UpdatePseudoClasses(
+            bool? isIndeterminate,
+            Orientation? o)
+        {
+            if (isIndeterminate.HasValue)
+            {
+                PseudoClasses.Set(":indeterminate", isIndeterminate.Value);
+            }
+
+            if (o.HasValue)
+            {
+                PseudoClasses.Set(":vertical", o == Orientation.Vertical);
+                PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
+            }
+        }
     }
 }

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

@@ -3,6 +3,7 @@
 
 using System;
 using Avalonia.Controls.Primitives;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
@@ -43,8 +44,6 @@ namespace Avalonia.Controls
         static Slider()
         {
             OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
-            PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
-            PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
             Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
             Thumb.DragDeltaEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
             Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble);
@@ -55,6 +54,7 @@ namespace Avalonia.Controls
         /// </summary>
         public Slider()
         {
+            UpdatePseudoClasses(Orientation);
         }
 
         /// <summary>
@@ -137,6 +137,20 @@ namespace Avalonia.Controls
             }
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == OrientationProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+            }
+        }
+
         /// <summary>
         /// Called when user start dragging the <see cref="Thumb"/>.
         /// </summary>
@@ -190,5 +204,11 @@ namespace Avalonia.Controls
 
             return value;
         }
+
+        private void UpdatePseudoClasses(Orientation o)
+        {
+            PseudoClasses.Set(":vertical", o == Orientation.Vertical);
+            PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
+        }
     }
 }

+ 38 - 4
src/Avalonia.Input/InputElement.cs

@@ -4,6 +4,8 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Data;
 using Avalonia.Input.GestureRecognizers;
 using Avalonia.Interactivity;
 using Avalonia.VisualTree;
@@ -181,10 +183,11 @@ namespace Avalonia.Input
             PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
             PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
             PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
+        }
 
-            PseudoClass<InputElement, bool>(IsEffectivelyEnabledProperty, x => !x, ":disabled");
-            PseudoClass<InputElement>(IsFocusedProperty, ":focus");
-            PseudoClass<InputElement>(IsPointerOverProperty, ":pointerover");
+        public InputElement()
+        {
+            UpdatePseudoClasses(IsFocused, IsPointerOver);
         }
 
         /// <summary>
@@ -372,7 +375,11 @@ namespace Avalonia.Input
         public bool IsEffectivelyEnabled
         {
             get => _isEffectivelyEnabled;
-            private set => SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
+            private set
+            {
+                SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
+                PseudoClasses.Set(":disabled", !value);
+            }
         }
 
         public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
@@ -522,6 +529,20 @@ namespace Avalonia.Input
         {
         }
 
+        protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == IsFocusedProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
+            }
+            else if (property == IsPointerOverProperty)
+            {
+                UpdatePseudoClasses(null, newValue.GetValueOrDefault<bool>());
+            }
+        }
+
         /// <summary>
         /// Updates the <see cref="IsEffectivelyEnabled"/> property value according to the parent
         /// control's enabled state and <see cref="IsEnabledCore"/>.
@@ -578,5 +599,18 @@ namespace Avalonia.Input
                 child?.UpdateIsEffectivelyEnabled(this);
             }
         }
+
+        private void UpdatePseudoClasses(bool? isFocused, bool? isPointerOver)
+        {
+            if (isFocused.HasValue)
+            {
+                PseudoClasses.Set(":focus", isFocused.Value);
+            }
+            
+            if (isPointerOver.HasValue)
+            {
+                PseudoClasses.Set(":pointerover", isPointerOver.Value);
+            }
+        }
     }
 }

+ 10 - 2
src/Avalonia.Layout/LayoutManager.cs

@@ -132,8 +132,16 @@ namespace Avalonia.Layout
         /// <inheritdoc/>
         public void ExecuteInitialLayoutPass(ILayoutRoot root)
         {
-            Measure(root);
-            Arrange(root);
+            try
+            {
+                _running = true;
+                Measure(root);
+                Arrange(root);
+            }
+            finally
+            {
+                _running = false;
+            }
 
             // Running the initial layout pass may have caused some control to be invalidated
             // so run a full layout pass now (this usually due to scrollbars; its not known

+ 28 - 0
src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs

@@ -0,0 +1,28 @@
+using System;
+
+namespace Avalonia.Controls
+{
+    public static class PseudolassesExtensions
+    {
+        /// <summary>
+        /// Adds or removes a pseudoclass depending on a boolean value.
+        /// </summary>
+        /// <param name="classes">The pseudoclasses collection.</param>
+        /// <param name="name">The name of the pseudoclass to set.</param>
+        /// <param name="value">True to add the pseudoclass or false to remove.</param>
+        public static void Set(this IPseudoClasses classes, string name, bool value)
+        {
+            Contract.Requires<ArgumentNullException>(classes != null);
+
+            if (value)
+            {
+                classes.Add(name);
+            }
+            else
+            {
+                classes.Remove(name);
+            }
+        }
+
+    }
+}

+ 0 - 77
src/Avalonia.Styling/StyledElement.cs

@@ -488,83 +488,6 @@ namespace Avalonia
             InheritanceParent = parent;
         }
 
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property is true.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <param name="className">The pseudo-class.</param>
-        [Obsolete("Use PseudoClass<TOwner> and specify the control type.")]
-        protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
-        {
-            PseudoClass<StyledElement>(property, className);
-        }
-
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property is true.
-        /// </summary>
-        /// <typeparam name="TOwner">The type to apply the pseudo-class to.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="className">The pseudo-class.</param>
-        protected static void PseudoClass<TOwner>(AvaloniaProperty<bool> property, string className)
-            where TOwner : class, IStyledElement
-        {
-            PseudoClass<TOwner, bool>(property, x => x, className);
-        }
-
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property equals a certain value.
-        /// </summary>
-        /// <typeparam name="TProperty">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="selector">Returns a boolean value based on the property value.</param>
-        /// <param name="className">The pseudo-class.</param>
-        [Obsolete("Use PseudoClass<TOwner, TProperty> and specify the control type.")]
-        protected static void PseudoClass<TProperty>(
-            AvaloniaProperty<TProperty> property,
-            Func<TProperty, bool> selector,
-            string className)
-        {
-            PseudoClass<StyledElement, TProperty>(property, selector, className);
-        }
-
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property equals a certain value.
-        /// </summary>
-        /// <typeparam name="TProperty">The type of the property.</typeparam>
-        /// <typeparam name="TOwner">The type to apply the pseudo-class to.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="selector">Returns a boolean value based on the property value.</param>
-        /// <param name="className">The pseudo-class.</param>
-        protected static void PseudoClass<TOwner, TProperty>(
-            AvaloniaProperty<TProperty> property,
-            Func<TProperty, bool> selector,
-            string className)
-                where TOwner : class, IStyledElement
-        {
-            Contract.Requires<ArgumentNullException>(property != null);
-            Contract.Requires<ArgumentNullException>(selector != null);
-            Contract.Requires<ArgumentNullException>(className != null);
-
-            if (string.IsNullOrWhiteSpace(className))
-            {
-                throw new ArgumentException("Cannot supply an empty className.");
-            }
-
-            property.Changed.Merge(property.Initialized)
-                .Where(e => e.Sender is TOwner)
-                .Subscribe(e =>
-                {
-                    if (selector((TProperty)e.NewValue))
-                    {
-                        ((StyledElement)e.Sender).PseudoClasses.Add(className);
-                    }
-                    else
-                    {
-                        ((StyledElement)e.Sender).PseudoClasses.Remove(className);
-                    }
-                });
-        }
-
         protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
         {
             switch (e.Action)

+ 11 - 10
src/Avalonia.Themes.Default/ComboBox.xaml

@@ -4,6 +4,7 @@
     <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
     <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
     <Setter Property="Padding" Value="4"/>
+    <Setter Property="MinHeight" Value="20"/>
     <Setter Property="Template">
       <ControlTemplate>
         <Border Name="border"
@@ -40,14 +41,14 @@
                    StaysOpen="False">
               <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                       BorderThickness="1">
-                  <ScrollViewer>
-                      <ItemsPresenter Name="PART_ItemsPresenter"
-                                      Items="{TemplateBinding Items}"
-                                      ItemsPanel="{TemplateBinding ItemsPanel}"
-                                      ItemTemplate="{TemplateBinding ItemTemplate}"
-                                      VirtualizationMode="{TemplateBinding VirtualizationMode}"
+                <ScrollViewer>
+                  <ItemsPresenter Name="PART_ItemsPresenter"
+                                  Items="{TemplateBinding Items}"
+                                  ItemsPanel="{TemplateBinding ItemsPanel}"
+                                  ItemTemplate="{TemplateBinding ItemTemplate}"
+                                  VirtualizationMode="{TemplateBinding VirtualizationMode}"
                               />
-                  </ScrollViewer>
+                </ScrollViewer>
               </Border>
             </Popup>
           </Grid>
@@ -58,7 +59,7 @@
   <Style Selector="ComboBox:pointerover /template/ Border#border">
     <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
   </Style>
-    <Style Selector="ComboBox:disabled /template/ Border#border">
-        <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
-    </Style>
+  <Style Selector="ComboBox:disabled /template/ Border#border">
+    <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
+  </Style>
 </Styles>

+ 52 - 34
src/Avalonia.Themes.Default/ProgressBar.xaml

@@ -1,14 +1,25 @@
 <Styles xmlns="https://github.com/avaloniaui">
   <Style Selector="ProgressBar">
     <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
-    <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/> 
+    <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
     <Setter Property="Template">
       <ControlTemplate>
-        <Border Background="{TemplateBinding Background}"
-                BorderBrush="{TemplateBinding BorderBrush}"
-                BorderThickness="{TemplateBinding BorderThickness}">
-          <Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/>
-        </Border>
+        <Grid>
+          <Border Background="{TemplateBinding Background}"
+                  BorderBrush="{TemplateBinding BorderBrush}"
+                  BorderThickness="{TemplateBinding BorderThickness}">
+            <Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/>
+          </Border>
+          <LayoutTransformControl
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                IsVisible="{Binding ShowProgressText, RelativeSource={RelativeSource TemplatedParent}}"
+                Name="PART_LayoutTransformControl">
+            <TextBlock
+                Foreground="{DynamicResource ThemeForegroundBrush}"
+                Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
+          </LayoutTransformControl>
+        </Grid>
       </ControlTemplate>
     </Setter>
   </Style>
@@ -22,42 +33,49 @@
   </Style>
   <Style Selector="ProgressBar:horizontal">
     <Setter Property="MinWidth" Value="200"/>
-    <Setter Property="MinHeight" Value="14"/>
+    <Setter Property="MinHeight" Value="16"/>
   </Style>
   <Style Selector="ProgressBar:vertical">
-    <Setter Property="MinWidth" Value="14"/>
+    <Setter Property="MinWidth" Value="16"/>
     <Setter Property="MinHeight" Value="200"/>
   </Style>
+  <Style Selector="ProgressBar:vertical /template/ LayoutTransformControl#PART_LayoutTransformControl">
+    <Setter Property="LayoutTransform">
+      <Setter.Value>
+        <RotateTransform Angle="90"/>
+      </Setter.Value>
+    </Setter>
+  </Style>
   <Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator">
-      <Style.Animations>
-          <Animation Duration="0:0:3"
-                     IterationCount="Infinite"
-                     Easing="LinearEasing">
-              <KeyFrame Cue="0%">
-                  <Setter Property="TranslateTransform.X"
-                          Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
-              </KeyFrame>
-              <KeyFrame Cue="100%">
-                  <Setter Property="TranslateTransform.X"
-                          Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
-              </KeyFrame>
+    <Style.Animations>
+      <Animation Duration="0:0:3"
+                 IterationCount="Infinite"
+                 Easing="LinearEasing">
+        <KeyFrame Cue="0%">
+          <Setter Property="TranslateTransform.X"
+                  Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+        </KeyFrame>
+        <KeyFrame Cue="100%">
+          <Setter Property="TranslateTransform.X"
+                  Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+        </KeyFrame>
       </Animation>
-      </Style.Animations>
+    </Style.Animations>
   </Style>
   <Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator">
-      <Style.Animations>
-          <Animation Duration="0:0:3"
-                     IterationCount="Infinite"
-                     Easing="LinearEasing">
-              <KeyFrame Cue="0%">
-                  <Setter Property="TranslateTransform.Y"
-                          Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
-              </KeyFrame>
-              <KeyFrame Cue="100%">
-                  <Setter Property="TranslateTransform.Y"
-                          Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
-              </KeyFrame>
+    <Style.Animations>
+      <Animation Duration="0:0:3"
+                 IterationCount="Infinite"
+                 Easing="LinearEasing">
+        <KeyFrame Cue="0%">
+          <Setter Property="TranslateTransform.Y"
+                  Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+        </KeyFrame>
+        <KeyFrame Cue="100%">
+          <Setter Property="TranslateTransform.Y"
+                  Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+        </KeyFrame>
       </Animation>
-      </Style.Animations>
+    </Style.Animations>
   </Style>
 </Styles>

+ 0 - 17
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs

@@ -16,19 +16,6 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("foodefault", target.GetValue(Class2.FooProperty));
         }
 
-        [Fact]
-        public void AvaloniaProperty_Initialized_Is_Called_For_Attached_Property()
-        {
-            bool raised = false;
-
-            using (Class1.FooProperty.Initialized.Subscribe(x => raised = true))
-            {
-                new Class3();
-            }
-
-            Assert.True(raised);
-        }
-
         private class Base : AvaloniaObject
         {
         }
@@ -46,9 +33,5 @@ namespace Avalonia.Base.UnitTests
             public static readonly AttachedProperty<string> FooProperty =
                 Class1.FooProperty.AddOwner<Class2>();
         }
-
-        private class Class3 : Base
-        {
-        }
     }
 }

+ 0 - 16
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@@ -424,22 +424,6 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("second", target.Foo);
         }
 
-        [Fact]
-        public void Property_Notifies_Initialized()
-        {
-            bool raised = false;
-
-            Class1.FooProperty.Initialized.Subscribe(e =>
-                raised = e.Property == Class1.FooProperty &&
-                         e.OldValue == AvaloniaProperty.UnsetValue &&
-                         (string)e.NewValue == "initial" &&
-                         e.Priority == BindingPriority.Unset);
-
-            var target = new Class1();
-
-            Assert.True(raised);
-        }
-
         [Fact]
         public void Binding_Error_Reverts_To_Default_Value()
         {

+ 0 - 23
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@@ -78,24 +78,6 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal(BindingMode.TwoWay, result.DefaultBindingMode);
         }
 
-        [Fact]
-        public void Initialized_Observable_Fired()
-        {
-            bool invoked = false;
-
-            Class1.FooProperty.Initialized.Subscribe(x =>
-            {
-                Assert.Equal(AvaloniaProperty.UnsetValue, x.OldValue);
-                Assert.Equal("default", x.NewValue);
-                Assert.Equal(BindingPriority.Unset, x.Priority);
-                invoked = true;
-            });
-
-            var target = new Class1();
-
-            Assert.True(invoked);
-        }
-
         [Fact]
         public void Changed_Observable_Fired()
         {
@@ -141,11 +123,6 @@ namespace Avalonia.Base.UnitTests
                 OverrideMetadata(typeof(T), metadata);
             }
 
-            internal override void NotifyInitialized(IAvaloniaObject o)
-            {
-                throw new NotImplementedException();
-            }
-
             internal override IDisposable RouteBind(
                 IAvaloniaObject o,
                 IObservable<BindingValue<object>> source,

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

@@ -1,33 +1,12 @@
 // 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;
-using Avalonia.Data;
 using Xunit;
 
 namespace Avalonia.Base.UnitTests
 {
     public class DirectPropertyTests
     {
-        [Fact]
-        public void Initialized_Observable_Fired()
-        {
-            bool invoked = false;
-
-            Class1.FooProperty.Initialized.Subscribe(x =>
-            {
-                Assert.Equal(AvaloniaProperty.UnsetValue, x.OldValue);
-                Assert.Equal("foo", x.NewValue);
-                Assert.Equal(BindingPriority.Unset, x.Priority);
-                invoked = true;
-            });
-
-            var target = new Class1();
-
-            Assert.True(invoked);
-        }
-
         [Fact]
         public void IsDirect_Property_Returns_True()
         {
@@ -69,7 +48,6 @@ namespace Avalonia.Base.UnitTests
             var p2 = p1.AddOwner<Class2>(o => null, (o, v) => { });
 
             Assert.Same(p1.Changed, p2.Changed);
-            Assert.Same(p1.Initialized, p2.Initialized);
         }
 
         private class Class1 : AvaloniaObject

+ 1 - 1
tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Benchmarks.Base
     [MemoryDiagnoser]
     public class AvaloniaObjectInitializationBenchmark
     {
-        [Benchmark(OperationsPerInvoke = 1000)]
+        [Benchmark]
         public Button InitializeButton()
         {
             return new Button();

+ 22 - 0
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@@ -1202,6 +1202,28 @@ namespace Avalonia.Controls.UnitTests.Primitives
             target.MoveSelection(NavigationDirection.Next, true);
         }
 
+        [Fact]
+        public void MoveSelection_Does_Select_Disabled_Controls()
+        {
+            // Issue #3426.
+            var target = new TestSelector
+            {
+                Template = Template(),
+                Items = new[]
+                {
+                    new ListBoxItem(),
+                    new ListBoxItem { IsEnabled = false },
+                },
+                SelectedIndex = 0,
+            };
+
+            target.Measure(new Size(100, 100));
+            target.Arrange(new Rect(0, 0, 100, 100));
+            target.MoveSelection(NavigationDirection.Next, true);
+
+            Assert.Equal(0, target.SelectedIndex);
+        }
+
         [Fact]
         public void Pre_Selecting_Item_Should_Set_Selection_After_It_Was_Added_When_AlwaysSelected()
         {

+ 33 - 0
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@@ -374,5 +374,38 @@ namespace Avalonia.Layout.UnitTests
             Assert.True(control.Measured);
             Assert.True(control.IsMeasureValid);
         }
+
+        [Fact]
+        public void Calling_ExecuteLayoutPass_From_ExecuteInitialLayoutPass_Does_Not_Break_Measure()
+        {
+            // Test for issue #3550.
+            var control = new LayoutTestControl();
+            var root = new LayoutTestRoot { Child = control };
+            var count = 0;
+
+            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            control.Measured = false;
+
+            control.DoMeasureOverride = (l, s) =>
+            {
+                if (count++ == 0)
+                {
+                    control.InvalidateMeasure();
+                    root.LayoutManager.ExecuteLayoutPass();
+                    return new Size(100, 100);
+                }
+                else
+                {
+                    return new Size(200, 200);
+                }
+            };
+
+            root.InvalidateMeasure();
+            control.InvalidateMeasure();
+            root.LayoutManager.ExecuteInitialLayoutPass(root);
+
+            Assert.Equal(new Size(200, 200), control.Bounds.Size);
+            Assert.Equal(new Size(200, 200), control.DesiredSize);
+        }
     }
 }