Browse Source

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

pull/4704/head
Kieran Devlin 5 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.Collections.Generic;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Utilities;
@ -18,7 +17,6 @@ namespace Avalonia
public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _changed;
private readonly PropertyMetadata _defaultMetadata;
private readonly Dictionary<Type, PropertyMetadata> _metadata;
private readonly Dictionary<Type, PropertyMetadata> _metadataCache = new Dictionary<Type, PropertyMetadata>();
@ -50,7 +48,6 @@ namespace Avalonia
throw new ArgumentException("'name' may not contain periods.");
}
_changed = new Subject<AvaloniaPropertyChangedEventArgs>();
_metadata = new Dictionary<Type, PropertyMetadata>();
Name = name;
@ -77,7 +74,6 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
_changed = source._changed;
_metadata = new Dictionary<Type, PropertyMetadata>();
Name = source.Name;
@ -139,7 +135,7 @@ namespace Avalonia
/// An observable that is fired when this property changes on any
/// <see cref="AvaloniaObject"/> instance.
/// </value>
public IObservable<AvaloniaPropertyChangedEventArgs> Changed => _changed;
public IObservable<AvaloniaPropertyChangedEventArgs> Changed => GetChanged();
/// <summary>
/// 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)
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>
/// Routes an untyped ClearValue call to a typed call.
/// </summary>
@ -553,6 +540,8 @@ namespace Avalonia
_hasMetadataOverrides = true;
}
protected abstract IObservable<AvaloniaPropertyChangedEventArgs> GetChanged();
private PropertyMetadata GetMetadataWithOverrides(Type type)
{
if (type is null)

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

@ -1,4 +1,5 @@
using System;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Utilities;
@ -10,6 +11,8 @@ namespace Avalonia
/// <typeparam name="TValue">The value type of the property.</typeparam>
public abstract class AvaloniaProperty<TValue> : AvaloniaProperty
{
private readonly Subject<AvaloniaPropertyChangedEventArgs<TValue>> _changed;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
/// </summary>
@ -24,22 +27,61 @@ namespace Avalonia
Action<IAvaloniaObject, bool> notifying = null)
: base(name, typeof(TValue), ownerType, metadata, notifying)
{
_changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>();
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
/// 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>
[Obsolete("Use constructor with AvaloniaProperty<TValue> instead.", true)]
protected AvaloniaProperty(
AvaloniaProperty source,
Type ownerType,
AvaloniaProperty source,
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)
: 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)
{
if (value == UnsetValue)

17
src/Avalonia.Base/DirectPropertyBase.cs

@ -32,15 +32,30 @@ namespace Avalonia
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
/// 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>
[Obsolete("Use constructor with DirectPropertyBase<TValue> instead.", true)]
protected DirectPropertyBase(
AvaloniaProperty source,
Type ownerType,
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)
{
}

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

@ -48,7 +48,7 @@ namespace Avalonia.Controls.Mixins
if (sender != null)
{
((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue);
((IPseudoClasses)sender.Classes).Set(":selected", x.NewValue.GetValueOrDefault());
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)
{
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 =>
{
var item = (NativeMenuItem)args.Sender;
var value = (NativeMenu)args.NewValue;
var value = args.NewValue.GetValueOrDefault();
if (value.Parent != null && value.Parent != item)
throw new InvalidOperationException("NativeMenu already has a parent");
value.Parent = item;

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

@ -82,7 +82,7 @@ namespace Avalonia.Controls.Presenters
TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty,
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,
TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed,
TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed,

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

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

2
src/Avalonia.Controls/TextBlock.cs

@ -138,7 +138,7 @@ namespace Avalonia.Controls
FontStyleProperty, TextWrappingProperty, FontFamilyProperty,
TextTrimmingProperty, TextProperty, PaddingProperty, LineHeightProperty, MaxLinesProperty);
Observable.Merge(TextProperty.Changed, ForegroundProperty.Changed,
Observable.Merge<AvaloniaPropertyChangedEventArgs>(TextProperty.Changed, ForegroundProperty.Changed,
TextAlignmentProperty.Changed, TextWrappingProperty.Changed,
TextTrimmingProperty.Changed, FontSizeProperty.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 =>
raised = e.Property == Class1.FooProperty &&
(string)e.OldValue == "initial" &&
(string)e.NewValue == "newvalue" &&
e.OldValue.GetValueOrDefault() == "initial" &&
e.NewValue.GetValueOrDefault() == "newvalue" &&
e.Priority == BindingPriority.LocalValue);
target.SetValue(Class1.FooProperty, "newvalue");

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

@ -83,7 +83,7 @@ namespace Avalonia.Base.UnitTests
var target = new Class1();
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");
Assert.Equal("newvalue", value);
@ -95,7 +95,7 @@ namespace Avalonia.Base.UnitTests
var target = new Class1();
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, "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()
{
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)
{
var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);

Loading…
Cancel
Save