Browse Source

Merge branch 'master' into control-validation-message-fix

pull/4704/head
Kieran Devlin 6 years ago
committed by GitHub
parent
commit
383c81b047
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/Avalonia.Base/ApiCompatBaseline.txt
  2. 17
      src/Avalonia.Base/AvaloniaProperty.cs
  3. 48
      src/Avalonia.Base/AvaloniaProperty`1.cs
  4. 17
      src/Avalonia.Base/DirectPropertyBase.cs
  5. 4
      src/Avalonia.Controls/Mixins/SelectableMixin.cs
  6. 2
      src/Avalonia.Controls/NativeMenu.Export.cs
  7. 2
      src/Avalonia.Controls/NativeMenuItem.cs
  8. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  9. 3
      src/Avalonia.Controls/Primitives/IPopupHost.cs
  10. 2
      src/Avalonia.Controls/TextBlock.cs
  11. 4
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  12. 4
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  13. 62
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

3
src/Avalonia.Base/ApiCompatBaseline.txt

@ -0,0 +1,3 @@
Compat issues with assembly Avalonia.Base:
CannotAddAbstractMembers : Member 'protected System.IObservable<Avalonia.AvaloniaPropertyChangedEventArgs> Avalonia.AvaloniaProperty.GetChanged()' is abstract in the implementation but is missing in the contract.
Total Issues: 1

17
src/Avalonia.Base/AvaloniaProperty.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Subjects;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -18,7 +17,6 @@ namespace Avalonia
public static readonly object UnsetValue = new UnsetValueType(); public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId; private static int s_nextId;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _changed;
private readonly PropertyMetadata _defaultMetadata; private readonly PropertyMetadata _defaultMetadata;
private readonly Dictionary<Type, PropertyMetadata> _metadata; private readonly Dictionary<Type, PropertyMetadata> _metadata;
private readonly Dictionary<Type, PropertyMetadata> _metadataCache = new Dictionary<Type, PropertyMetadata>(); private readonly Dictionary<Type, PropertyMetadata> _metadataCache = new Dictionary<Type, PropertyMetadata>();
@ -50,7 +48,6 @@ namespace Avalonia
throw new ArgumentException("'name' may not contain periods."); throw new ArgumentException("'name' may not contain periods.");
} }
_changed = new Subject<AvaloniaPropertyChangedEventArgs>();
_metadata = new Dictionary<Type, PropertyMetadata>(); _metadata = new Dictionary<Type, PropertyMetadata>();
Name = name; Name = name;
@ -77,7 +74,6 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(source != null); Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(ownerType != null); Contract.Requires<ArgumentNullException>(ownerType != null);
_changed = source._changed;
_metadata = new Dictionary<Type, PropertyMetadata>(); _metadata = new Dictionary<Type, PropertyMetadata>();
Name = source.Name; Name = source.Name;
@ -139,7 +135,7 @@ namespace Avalonia
/// An observable that is fired when this property changes on any /// An observable that is fired when this property changes on any
/// <see cref="AvaloniaObject"/> instance. /// <see cref="AvaloniaObject"/> instance.
/// </value> /// </value>
public IObservable<AvaloniaPropertyChangedEventArgs> Changed => _changed; public IObservable<AvaloniaPropertyChangedEventArgs> Changed => GetChanged();
/// <summary> /// <summary>
/// Gets a method that gets called before and after the property starts being notified on an /// Gets a method that gets called before and after the property starts being notified on an
@ -474,15 +470,6 @@ namespace Avalonia
public abstract void Accept<TData>(IAvaloniaPropertyVisitor<TData> vistor, ref TData data) public abstract void Accept<TData>(IAvaloniaPropertyVisitor<TData> vistor, ref TData data)
where TData : struct; where TData : struct;
/// <summary>
/// Notifies the <see cref="Changed"/> observable.
/// </summary>
/// <param name="e">The observable arguments.</param>
internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e)
{
_changed.OnNext(e);
}
/// <summary> /// <summary>
/// Routes an untyped ClearValue call to a typed call. /// Routes an untyped ClearValue call to a typed call.
/// </summary> /// </summary>
@ -553,6 +540,8 @@ namespace Avalonia
_hasMetadataOverrides = true; _hasMetadataOverrides = true;
} }
protected abstract IObservable<AvaloniaPropertyChangedEventArgs> GetChanged();
private PropertyMetadata GetMetadataWithOverrides(Type type) private PropertyMetadata GetMetadataWithOverrides(Type type)
{ {
if (type is null) if (type is null)

48
src/Avalonia.Base/AvaloniaProperty`1.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Reactive.Subjects;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -10,6 +11,8 @@ namespace Avalonia
/// <typeparam name="TValue">The value type of the property.</typeparam> /// <typeparam name="TValue">The value type of the property.</typeparam>
public abstract class AvaloniaProperty<TValue> : AvaloniaProperty public abstract class AvaloniaProperty<TValue> : AvaloniaProperty
{ {
private readonly Subject<AvaloniaPropertyChangedEventArgs<TValue>> _changed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class. /// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
/// </summary> /// </summary>
@ -24,22 +27,61 @@ namespace Avalonia
Action<IAvaloniaObject, bool> notifying = null) Action<IAvaloniaObject, bool> notifying = null)
: base(name, typeof(TValue), ownerType, metadata, notifying) : base(name, typeof(TValue), ownerType, metadata, notifying)
{ {
_changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>();
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class. /// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
/// </summary> /// </summary>
/// <param name="source">The property to copy.</param> /// <param name="source">The property to copy.</param>
/// <param name="ownerType">The new owner type.</param> /// <param name="ownerType">The new owner type.</param>
/// <param name="metadata">Optional overridden metadata.</param> /// <param name="metadata">Optional overridden metadata.</param>
[Obsolete("Use constructor with AvaloniaProperty<TValue> instead.", true)]
protected AvaloniaProperty( protected AvaloniaProperty(
AvaloniaProperty source, AvaloniaProperty source,
Type ownerType, Type ownerType,
PropertyMetadata metadata)
: this(source as AvaloniaProperty<TValue> ?? throw new InvalidOperationException(), ownerType, metadata)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
/// </summary>
/// <param name="source">The property to copy.</param>
/// <param name="ownerType">The new owner type.</param>
/// <param name="metadata">Optional overridden metadata.</param>
protected AvaloniaProperty(
AvaloniaProperty<TValue> source,
Type ownerType,
PropertyMetadata metadata) PropertyMetadata metadata)
: base(source, ownerType, metadata) : base(source, ownerType, metadata)
{ {
_changed = source._changed;
}
/// <summary>
/// Gets an observable that is fired when this property changes on any
/// <see cref="AvaloniaObject"/> instance.
/// </summary>
/// <value>
/// An observable that is fired when this property changes on any
/// <see cref="AvaloniaObject"/> instance.
/// </value>
public new IObservable<AvaloniaPropertyChangedEventArgs<TValue>> Changed => _changed;
/// <summary>
/// Notifies the <see cref="Changed"/> observable.
/// </summary>
/// <param name="e">The observable arguments.</param>
internal void NotifyChanged(AvaloniaPropertyChangedEventArgs<TValue> e)
{
_changed.OnNext(e);
} }
protected override IObservable<AvaloniaPropertyChangedEventArgs> GetChanged() => Changed;
protected BindingValue<object> TryConvert(object value) protected BindingValue<object> TryConvert(object value)
{ {
if (value == UnsetValue) if (value == UnsetValue)

17
src/Avalonia.Base/DirectPropertyBase.cs

@ -32,15 +32,30 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class. /// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class.
/// </summary> /// </summary>
/// <param name="source">The property to copy.</param> /// <param name="source">The property to copy.</param>
/// <param name="ownerType">The new owner type.</param> /// <param name="ownerType">The new owner type.</param>
/// <param name="metadata">Optional overridden metadata.</param> /// <param name="metadata">Optional overridden metadata.</param>
[Obsolete("Use constructor with DirectPropertyBase<TValue> instead.", true)]
protected DirectPropertyBase( protected DirectPropertyBase(
AvaloniaProperty source, AvaloniaProperty source,
Type ownerType, Type ownerType,
PropertyMetadata metadata) PropertyMetadata metadata)
: this(source as DirectPropertyBase<TValue> ?? throw new InvalidOperationException(), ownerType, metadata)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class.
/// </summary>
/// <param name="source">The property to copy.</param>
/// <param name="ownerType">The new owner type.</param>
/// <param name="metadata">Optional overridden metadata.</param>
protected DirectPropertyBase(
DirectPropertyBase<TValue> source,
Type ownerType,
PropertyMetadata metadata)
: base(source, ownerType, metadata) : base(source, ownerType, metadata)
{ {
} }

4
src/Avalonia.Controls/Mixins/SelectableMixin.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls.Mixins
if (sender != null) if (sender != null)
{ {
((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue); ((IPseudoClasses)sender.Classes).Set(":selected", x.NewValue.GetValueOrDefault());
sender.RaiseEvent(new RoutedEventArgs sender.RaiseEvent(new RoutedEventArgs
{ {
@ -58,4 +58,4 @@ namespace Avalonia.Controls.Mixins
}); });
} }
} }
} }

2
src/Avalonia.Controls/NativeMenu.Export.cs

@ -77,7 +77,7 @@ namespace Avalonia.Controls
{ {
if (args.Sender is TopLevel tl) if (args.Sender is TopLevel tl)
{ {
GetInfo(tl).Exporter?.SetNativeMenu((NativeMenu)args.NewValue); GetInfo(tl).Exporter?.SetNativeMenu(args.NewValue.GetValueOrDefault());
} }
}); });
} }

2
src/Avalonia.Controls/NativeMenuItem.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls
MenuProperty.Changed.Subscribe(args => MenuProperty.Changed.Subscribe(args =>
{ {
var item = (NativeMenuItem)args.Sender; var item = (NativeMenuItem)args.Sender;
var value = (NativeMenu)args.NewValue; var value = args.NewValue.GetValueOrDefault();
if (value.Parent != null && value.Parent != item) if (value.Parent != null && value.Parent != item)
throw new InvalidOperationException("NativeMenu already has a parent"); throw new InvalidOperationException("NativeMenu already has a parent");
value.Parent = item; value.Parent = item;

2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -82,7 +82,7 @@ namespace Avalonia.Controls.Presenters
TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty, TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty,
TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty); TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty);
Observable.Merge(TextProperty.Changed, TextBlock.ForegroundProperty.Changed, Observable.Merge<AvaloniaPropertyChangedEventArgs>(TextProperty.Changed, TextBlock.ForegroundProperty.Changed,
TextAlignmentProperty.Changed, TextWrappingProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed,
TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed, TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed,
TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed, TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed,

3
src/Avalonia.Controls/Primitives/IPopupHost.cs

@ -1,6 +1,7 @@
using System; using System;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
@ -13,7 +14,7 @@ namespace Avalonia.Controls.Primitives
/// (<see cref="PopupRoot"/>) or an <see cref="OverlayPopupHost"/> which is created /// (<see cref="PopupRoot"/>) or an <see cref="OverlayPopupHost"/> which is created
/// on an <see cref="OverlayLayer"/>. /// on an <see cref="OverlayLayer"/>.
/// </remarks> /// </remarks>
public interface IPopupHost : IDisposable public interface IPopupHost : IDisposable, IFocusScope
{ {
/// <summary> /// <summary>
/// Sets the control to display in the popup. /// Sets the control to display in the popup.

2
src/Avalonia.Controls/TextBlock.cs

@ -138,7 +138,7 @@ namespace Avalonia.Controls
FontStyleProperty, TextWrappingProperty, FontFamilyProperty, FontStyleProperty, TextWrappingProperty, FontFamilyProperty,
TextTrimmingProperty, TextProperty, PaddingProperty, LineHeightProperty, MaxLinesProperty); TextTrimmingProperty, TextProperty, PaddingProperty, LineHeightProperty, MaxLinesProperty);
Observable.Merge(TextProperty.Changed, ForegroundProperty.Changed, Observable.Merge<AvaloniaPropertyChangedEventArgs>(TextProperty.Changed, ForegroundProperty.Changed,
TextAlignmentProperty.Changed, TextWrappingProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed,
TextTrimmingProperty.Changed, FontSizeProperty.Changed, TextTrimmingProperty.Changed, FontSizeProperty.Changed,
FontStyleProperty.Changed, FontWeightProperty.Changed, FontStyleProperty.Changed, FontWeightProperty.Changed,

4
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@ -94,8 +94,8 @@ namespace Avalonia.Base.UnitTests
Class1.FooProperty.Changed.Subscribe(e => Class1.FooProperty.Changed.Subscribe(e =>
raised = e.Property == Class1.FooProperty && raised = e.Property == Class1.FooProperty &&
(string)e.OldValue == "initial" && e.OldValue.GetValueOrDefault() == "initial" &&
(string)e.NewValue == "newvalue" && e.NewValue.GetValueOrDefault() == "newvalue" &&
e.Priority == BindingPriority.LocalValue); e.Priority == BindingPriority.LocalValue);
target.SetValue(Class1.FooProperty, "newvalue"); target.SetValue(Class1.FooProperty, "newvalue");

4
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@ -83,7 +83,7 @@ namespace Avalonia.Base.UnitTests
var target = new Class1(); var target = new Class1();
string value = null; string value = null;
Class1.FooProperty.Changed.Subscribe(x => value = (string)x.NewValue); Class1.FooProperty.Changed.Subscribe(x => value = x.NewValue.GetValueOrDefault());
target.SetValue(Class1.FooProperty, "newvalue"); target.SetValue(Class1.FooProperty, "newvalue");
Assert.Equal("newvalue", value); Assert.Equal("newvalue", value);
@ -95,7 +95,7 @@ namespace Avalonia.Base.UnitTests
var target = new Class1(); var target = new Class1();
var result = new List<string>(); var result = new List<string>();
Class1.FooProperty.Changed.Subscribe(x => result.Add((string)x.NewValue)); Class1.FooProperty.Changed.Subscribe(x => result.Add(x.NewValue.GetValueOrDefault()));
target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation);
target.SetValue(Class1.FooProperty, "local"); target.SetValue(Class1.FooProperty, "local");

62
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -395,6 +395,53 @@ namespace Avalonia.Controls.UnitTests.Primitives
} }
} }
[Fact]
public void Focusable_Controls_In_Popup_Should_Get_Focus()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
var tb = new TextBox();
var b = new Button();
var p = new Popup
{
PlacementTarget = window,
Child = new StackPanel
{
Children =
{
tb,
b
}
}
};
((ISetLogicalParent)p).SetParent(p.PlacementTarget);
window.Show();
p.Open();
if(p.Host is OverlayPopupHost host)
{
//Need to measure/arrange for visual children to show up
//in OverlayPopupHost
host.Measure(Size.Infinity);
host.Arrange(new Rect(host.DesiredSize));
}
tb.Focus();
Assert.True(FocusManager.Instance?.Current == tb);
//Ensure focus remains in the popup
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
Assert.True(nextFocus == b);
p.Close();
}
}
private IDisposable CreateServices() private IDisposable CreateServices()
{ {
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
@ -407,6 +454,21 @@ namespace Avalonia.Controls.UnitTests.Primitives
}))); })));
} }
private IDisposable CreateServicesWithFocus()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
new MockWindowingPlatform(null,
x =>
{
if (UsePopupHost)
return null;
return MockWindowingPlatform.CreatePopupMock(x).Object;
}),
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice()));
}
private PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p) private PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p)
{ {
var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);

Loading…
Cancel
Save