Browse Source

Merge branch 'master' into bench-style-attach

pull/3560/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
573436ae9f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      readme.md
  2. 8
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  3. 1
      src/Avalonia.Base/AvaloniaObject.cs
  4. 39
      src/Avalonia.Base/AvaloniaProperty.cs
  5. 45
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  6. 15
      src/Avalonia.Base/DirectPropertyBase.cs
  7. 15
      src/Avalonia.Base/StyledPropertyBase.cs
  8. 13
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  9. 25
      src/Avalonia.Controls/Button.cs
  10. 28
      src/Avalonia.Controls/ButtonSpinner.cs
  11. 20
      src/Avalonia.Controls/ControlExtensions.cs
  12. 41
      src/Avalonia.Controls/Expander.cs
  13. 5
      src/Avalonia.Controls/ItemsControl.cs
  14. 30
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  15. 24
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  16. 24
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  17. 28
      src/Avalonia.Controls/Primitives/Track.cs
  18. 53
      src/Avalonia.Controls/ProgressBar.cs
  19. 24
      src/Avalonia.Controls/Slider.cs
  20. 42
      src/Avalonia.Input/InputElement.cs
  21. 12
      src/Avalonia.Layout/LayoutManager.cs
  22. 28
      src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs
  23. 77
      src/Avalonia.Styling/StyledElement.cs
  24. 21
      src/Avalonia.Themes.Default/ComboBox.xaml
  25. 86
      src/Avalonia.Themes.Default/ProgressBar.xaml
  26. 17
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
  27. 16
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  28. 23
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  29. 22
      tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
  30. 2
      tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs
  31. 22
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  32. 33
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

2
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:

8
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">

1
src/Avalonia.Base/AvaloniaObject.cs

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

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>

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; }

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)
{

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)
{

13
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

25
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);
}
}
}

28
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);
}
}
}

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>

41
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);
}
}
}

5
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;
}

30
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);
}
}

24
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);
}
}
}

24
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);
}
}
}

28
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);
}
}
}

53
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);
}
}
}
}

24
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);
}
}
}

42
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);
}
}
}
}

12
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
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);
}
}
}
}

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)

21
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>

86
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>

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
{
}
}
}

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()
{

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,

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

2
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
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
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);
}
}
}

Loading…
Cancel
Save