From acab1102081e3d09c359b79174e8706ac127f34b Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 8 Sep 2020 16:57:38 -0500 Subject: [PATCH 1/8] PopupRoot IFocusScope --- src/Avalonia.Controls/Primitives/PopupRoot.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index da7352b77f..2721ab879f 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Reactive.Disposables; using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Platform; @@ -14,7 +15,7 @@ namespace Avalonia.Controls.Primitives /// /// The root window of a . /// - public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost + public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost, IFocusScope { private readonly TopLevel _parent; private PopupPositionerParameters _positionerParameters; From 4e2dc7f5dbb9a2c38ce9e24a75318b5b32b38758 Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 8 Sep 2020 17:41:25 -0500 Subject: [PATCH 2/8] Add test --- .../Primitives/PopupTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index d9176ca55d..a0b7368a4e 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -395,6 +395,34 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Focusable_Controls_In_Popup_Should_Get_Focus() + { + using (CreateServicesWithFocus()) + { + var tb = new TextBox(); + var b = new Button(); + var p = new Popup + { + PlacementTarget = PreparedWindow(), + Child = new StackPanel + { + Children = + { + tb, + b + } + } + }; + ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + + p.Open(); + tb.Focus(); + + Assert.True(FocusManager.Instance?.Current == tb); + } + } + private IDisposable CreateServices() { return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: @@ -407,6 +435,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); From 05c6978617d87c30ae7cfb6a541c397b1547e400 Mon Sep 17 00:00:00 2001 From: amwx Date: Thu, 10 Sep 2020 16:29:16 -0500 Subject: [PATCH 3/8] Try to get test to pass on CI --- tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index a0b7368a4e..e5dcba9912 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -416,8 +416,12 @@ namespace Avalonia.Controls.UnitTests.Primitives }; ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + p.Opened += (s, e) => + { + tb.Focus(); + }; + p.Open(); - tb.Focus(); Assert.True(FocusManager.Instance?.Current == tb); } From 7782261ec3b3148fac846e1b0e77c36672b4f0b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 11 Sep 2020 03:26:58 +0100 Subject: [PATCH 4/8] Added typed AvaloniaProperty.Changed. --- src/Avalonia.Base/ApiCompatBaseline.txt | 3 +++ src/Avalonia.Base/AvaloniaProperty.cs | 17 +++--------- src/Avalonia.Base/AvaloniaProperty`1.cs | 27 +++++++++++++++++++ .../Mixins/SelectableMixin.cs | 4 +-- src/Avalonia.Controls/NativeMenu.Export.cs | 2 +- src/Avalonia.Controls/NativeMenuItem.cs | 2 +- .../Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 2 +- .../AvaloniaObjectTests_Direct.cs | 2 +- .../AvaloniaPropertyTests.cs | 4 +-- 10 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 src/Avalonia.Base/ApiCompatBaseline.txt diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt new file mode 100644 index 0000000000..4668a572c5 --- /dev/null +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +Compat issues with assembly Avalonia.Base: +CannotAddAbstractMembers : Member 'protected System.IObservable Avalonia.AvaloniaProperty.GetChanged()' is abstract in the implementation but is missing in the contract. +Total Issues: 1 diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 39391490b0..3ae0445e9b 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/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 _changed; private readonly PropertyMetadata _defaultMetadata; private readonly Dictionary _metadata; private readonly Dictionary _metadataCache = new Dictionary(); @@ -50,7 +48,6 @@ namespace Avalonia throw new ArgumentException("'name' may not contain periods."); } - _changed = new Subject(); _metadata = new Dictionary(); Name = name; @@ -77,7 +74,6 @@ namespace Avalonia Contract.Requires(source != null); Contract.Requires(ownerType != null); - _changed = source._changed; _metadata = new Dictionary(); Name = source.Name; @@ -139,7 +135,7 @@ namespace Avalonia /// An observable that is fired when this property changes on any /// instance. /// - public IObservable Changed => _changed; + public IObservable Changed => GetChanged(); /// /// 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(IAvaloniaPropertyVisitor vistor, ref TData data) where TData : struct; - /// - /// Notifies the observable. - /// - /// The observable arguments. - internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e) - { - _changed.OnNext(e); - } - /// /// Routes an untyped ClearValue call to a typed call. /// @@ -553,6 +540,8 @@ namespace Avalonia _hasMetadataOverrides = true; } + protected abstract IObservable GetChanged(); + private PropertyMetadata GetMetadataWithOverrides(Type type) { if (type is null) diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 2f26d855f2..7480d9c9c5 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/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 /// The value type of the property. public abstract class AvaloniaProperty : AvaloniaProperty { + private readonly Subject> _changed; + /// /// Initializes a new instance of the class. /// @@ -24,6 +27,7 @@ namespace Avalonia Action notifying = null) : base(name, typeof(TValue), ownerType, metadata, notifying) { + _changed = new Subject>(); } /// @@ -38,8 +42,31 @@ namespace Avalonia PropertyMetadata metadata) : base(source, ownerType, metadata) { + _changed = source is AvaloniaProperty p ? p._changed : new Subject>(); } + /// + /// Gets an observable that is fired when this property changes on any + /// instance. + /// + /// + /// An observable that is fired when this property changes on any + /// instance. + /// + + public new IObservable> Changed => _changed; + + /// + /// Notifies the observable. + /// + /// The observable arguments. + internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e) + { + _changed.OnNext(e); + } + + protected override IObservable GetChanged() => Changed; + protected BindingValue TryConvert(object value) { if (value == UnsetValue) diff --git a/src/Avalonia.Controls/Mixins/SelectableMixin.cs b/src/Avalonia.Controls/Mixins/SelectableMixin.cs index d2586ab6e8..e7dbecb06e 100644 --- a/src/Avalonia.Controls/Mixins/SelectableMixin.cs +++ b/src/Avalonia.Controls/Mixins/SelectableMixin.cs @@ -42,7 +42,7 @@ namespace Avalonia.Controls.Mixins { Contract.Requires(isSelected != null); - isSelected.Changed.Subscribe(x => + isSelected.Changed.Subscribe((AvaloniaPropertyChangedEventArgs x) => { var sender = x.Sender as TControl; @@ -58,4 +58,4 @@ namespace Avalonia.Controls.Mixins }); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index 776e9d2171..89e4c9e492 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -73,7 +73,7 @@ namespace Avalonia.Controls throw new InvalidOperationException("IsNativeMenuExported property is read-only"); info.ChangingIsExported = false; }); - MenuProperty.Changed.Subscribe(args => + MenuProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs args) => { if (args.Sender is TopLevel tl) { diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 4c94d82eb4..d4badbc559 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -20,7 +20,7 @@ namespace Avalonia.Controls static NativeMenuItem() { - MenuProperty.Changed.Subscribe(args => + MenuProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs args) => { var item = (NativeMenuItem)args.Sender; var value = (NativeMenu)args.NewValue; diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index cb7bee1d33..f5115a2f7c 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/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(TextProperty.Changed, TextBlock.ForegroundProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed, TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed, TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed, diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 3b9e9c4751..d8477840af 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/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(TextProperty.Changed, ForegroundProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed, TextTrimmingProperty.Changed, FontSizeProperty.Changed, FontStyleProperty.Changed, FontWeightProperty.Changed, diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 81a8de1046..83ae663419 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -92,7 +92,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); bool raised = false; - Class1.FooProperty.Changed.Subscribe(e => + Class1.FooProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs e) => raised = e.Property == Class1.FooProperty && (string)e.OldValue == "initial" && (string)e.NewValue == "newvalue" && diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index d7f927372e..19040ff584 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/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((AvaloniaPropertyChangedEventArgs x) => value = (string)x.NewValue); 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(); - Class1.FooProperty.Changed.Subscribe(x => result.Add((string)x.NewValue)); + Class1.FooProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs x) => result.Add((string)x.NewValue)); target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); target.SetValue(Class1.FooProperty, "local"); From 82240033dc12d476deb333cff99f9239abb06662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 16 Sep 2020 20:09:52 +0100 Subject: [PATCH 5/8] Changed property copy constructors to require same property type. --- src/Avalonia.Base/AvaloniaProperty`1.cs | 23 +++++++++++++++++++---- src/Avalonia.Base/DirectPropertyBase.cs | 17 ++++++++++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 7480d9c9c5..d5549e979b 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -31,18 +31,33 @@ namespace Avalonia } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. + /// + /// The property to copy. + /// The new owner type. + /// Optional overridden metadata. + [Obsolete("Use constructor with AvaloniaProperty instead.", true)] + protected AvaloniaProperty( + AvaloniaProperty source, + Type ownerType, + PropertyMetadata metadata) + : this(source as AvaloniaProperty ?? throw new InvalidOperationException(), ownerType, metadata) + { + } + + /// + /// Initializes a new instance of the class. /// /// The property to copy. /// The new owner type. /// Optional overridden metadata. protected AvaloniaProperty( - AvaloniaProperty source, - Type ownerType, + AvaloniaProperty source, + Type ownerType, PropertyMetadata metadata) : base(source, ownerType, metadata) { - _changed = source is AvaloniaProperty p ? p._changed : new Subject>(); + _changed = source._changed; } /// diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index dbc2625b86..a2f113adb7 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -32,15 +32,30 @@ namespace Avalonia } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The property to copy. /// The new owner type. /// Optional overridden metadata. + [Obsolete("Use constructor with DirectPropertyBase instead.", true)] protected DirectPropertyBase( AvaloniaProperty source, Type ownerType, PropertyMetadata metadata) + : this(source as DirectPropertyBase ?? throw new InvalidOperationException(), ownerType, metadata) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The property to copy. + /// The new owner type. + /// Optional overridden metadata. + protected DirectPropertyBase( + DirectPropertyBase source, + Type ownerType, + PropertyMetadata metadata) : base(source, ownerType, metadata) { } From 32fe0e60a7f9ff20d1c3173e4471638c64f6f0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Thu, 17 Sep 2020 23:30:34 +0100 Subject: [PATCH 6/8] Use typed property changed args instead of changing handler args type. --- src/Avalonia.Controls/Mixins/SelectableMixin.cs | 4 ++-- src/Avalonia.Controls/NativeMenu.Export.cs | 4 ++-- src/Avalonia.Controls/NativeMenuItem.cs | 4 ++-- tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs | 6 +++--- tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls/Mixins/SelectableMixin.cs b/src/Avalonia.Controls/Mixins/SelectableMixin.cs index e7dbecb06e..c9e2b684cb 100644 --- a/src/Avalonia.Controls/Mixins/SelectableMixin.cs +++ b/src/Avalonia.Controls/Mixins/SelectableMixin.cs @@ -42,13 +42,13 @@ namespace Avalonia.Controls.Mixins { Contract.Requires(isSelected != null); - isSelected.Changed.Subscribe((AvaloniaPropertyChangedEventArgs x) => + isSelected.Changed.Subscribe(x => { var sender = x.Sender as TControl; if (sender != null) { - ((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue); + ((IPseudoClasses)sender.Classes).Set(":selected", x.NewValue.GetValueOrDefault()); sender.RaiseEvent(new RoutedEventArgs { diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index 89e4c9e492..0349df842b 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -73,11 +73,11 @@ namespace Avalonia.Controls throw new InvalidOperationException("IsNativeMenuExported property is read-only"); info.ChangingIsExported = false; }); - MenuProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs args) => + MenuProperty.Changed.Subscribe(args => { if (args.Sender is TopLevel tl) { - GetInfo(tl).Exporter?.SetNativeMenu((NativeMenu)args.NewValue); + GetInfo(tl).Exporter?.SetNativeMenu(args.NewValue.GetValueOrDefault()); } }); } diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index d4badbc559..a0fec9e677 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -20,10 +20,10 @@ namespace Avalonia.Controls static NativeMenuItem() { - MenuProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs args) => + 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; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 83ae663419..20172eea88 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -92,10 +92,10 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); bool raised = false; - Class1.FooProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs e) => + 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"); diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index 19040ff584..8e5d8b7be2 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/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((AvaloniaPropertyChangedEventArgs 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(); - Class1.FooProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs 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"); From 6c52a7cb64ca660d1b03abfca22918e0a4704199 Mon Sep 17 00:00:00 2001 From: amwx Date: Wed, 23 Sep 2020 16:16:09 -0500 Subject: [PATCH 7/8] Move IFocusScope to IPopupHost --- src/Avalonia.Controls/Primitives/IPopupHost.cs | 3 ++- src/Avalonia.Controls/Primitives/PopupRoot.cs | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index e424bf683d..82a49c4189 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/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 /// () or an which is created /// on an . /// - public interface IPopupHost : IDisposable + public interface IPopupHost : IDisposable, IFocusScope { /// /// Sets the control to display in the popup. diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 2721ab879f..da7352b77f 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Reactive.Disposables; using Avalonia.Controls.Primitives.PopupPositioning; -using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Platform; @@ -15,7 +14,7 @@ namespace Avalonia.Controls.Primitives /// /// The root window of a . /// - public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost, IFocusScope + public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost { private readonly TopLevel _parent; private PopupPositionerParameters _positionerParameters; From 52075d6cd98527d7eb75c890c3d1f0a91fe6e203 Mon Sep 17 00:00:00 2001 From: amwx Date: Wed, 23 Sep 2020 16:16:54 -0500 Subject: [PATCH 8/8] Fix Test --- .../Primitives/PopupTests.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index e5dcba9912..53a8db2176 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -400,11 +400,13 @@ namespace Avalonia.Controls.UnitTests.Primitives { using (CreateServicesWithFocus()) { + var window = PreparedWindow(); + var tb = new TextBox(); var b = new Button(); var p = new Popup { - PlacementTarget = PreparedWindow(), + PlacementTarget = window, Child = new StackPanel { Children = @@ -415,15 +417,28 @@ namespace Avalonia.Controls.UnitTests.Primitives } }; ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + window.Show(); - p.Opened += (s, e) => + p.Open(); + + if(p.Host is OverlayPopupHost host) { - tb.Focus(); - }; + //Need to measure/arrange for visual children to show up + //in OverlayPopupHost + host.Measure(Size.Infinity); + host.Arrange(new Rect(host.DesiredSize)); + } - p.Open(); + 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(); } }