diff --git a/src/Perspex.Input/FocusManager.cs b/src/Perspex.Input/FocusManager.cs index 1ce645fd13..78935dc13e 100644 --- a/src/Perspex.Input/FocusManager.cs +++ b/src/Perspex.Input/FocusManager.cs @@ -39,7 +39,7 @@ namespace Perspex.Input /// /// Gets the currently focused . /// - public IInputElement Current => KeyboardDevice.Instance.FocusedElement; + public IInputElement Current => KeyboardDevice.Instance?.FocusedElement; /// /// Gets the current focus scope. @@ -82,9 +82,12 @@ namespace Perspex.Input if (_focusScopes.TryGetValue(scope, out element)) { Focus(element, method); - break; + return; } } + + // Couldn't find a focus scope, clear focus. + SetFocusedElement(Scope, null); } } @@ -111,7 +114,7 @@ namespace Perspex.Input if (Scope == scope) { - KeyboardDevice.Instance.SetFocusedElement(element, method, modifiers); + KeyboardDevice.Instance?.SetFocusedElement(element, method, modifiers); } } @@ -176,7 +179,7 @@ namespace Perspex.Input if (sender == e.Source) { var ev = (PointerPressedEventArgs)e; - var element = (ev.Device.Captured as IInputElement) ?? (e.Source as IInputElement); + var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement); if (element == null || !CanFocus(element)) { diff --git a/src/Perspex.Input/InputElement.cs b/src/Perspex.Input/InputElement.cs index 00e53df646..8c976bb155 100644 --- a/src/Perspex.Input/InputElement.cs +++ b/src/Perspex.Input/InputElement.cs @@ -376,9 +376,9 @@ namespace Perspex.Input } /// - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { - base.OnDetachedFromVisualTree(e); + base.OnDetachedFromVisualTreeCore(e); if (IsFocused) { @@ -387,9 +387,9 @@ namespace Perspex.Input } /// - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { - base.OnAttachedToVisualTree(e); + base.OnAttachedToVisualTreeCore(e); UpdateIsEnabledCore(); } diff --git a/tests/Perspex.Input.UnitTests/InputElement_Focus.cs b/tests/Perspex.Input.UnitTests/InputElement_Focus.cs new file mode 100644 index 0000000000..a3016cdd8a --- /dev/null +++ b/tests/Perspex.Input.UnitTests/InputElement_Focus.cs @@ -0,0 +1,49 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Perspex.Controls; +using Perspex.UnitTests; +using Xunit; + +namespace Perspex.Input.UnitTests +{ + public class InputElement_Focus + { + [Fact] + public void Focus_Should_Set_FocusManager_Current() + { + Button target; + + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var root = new TestRoot + { + Child = target = new Button() + }; + + target.Focus(); + + Assert.Same(target, FocusManager.Instance.Current); + } + } + + [Fact] + public void Focus_Should_Be_Cleared_When_Control_Is_Removed_From_VisualTree() + { + Button target; + + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var root = new TestRoot + { + Child = target = new Button() + }; + + target.Focus(); + root.Child = null; + + Assert.Null(FocusManager.Instance.Current); + } + } + } +} diff --git a/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj b/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj index e41c379026..d1b4bba60b 100644 --- a/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj +++ b/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj @@ -60,6 +60,7 @@ + @@ -103,6 +104,10 @@ {F1BAA01A-F176-4C6A-B39D-5B40BB1B148F} Perspex.Styling + + {88060192-33D5-4932-B0F9-8BD2763E857D} + Perspex.UnitTests + diff --git a/tests/Perspex.UnitTests/TestRoot.cs b/tests/Perspex.UnitTests/TestRoot.cs index 84a4a15aa7..48b8beaeba 100644 --- a/tests/Perspex.UnitTests/TestRoot.cs +++ b/tests/Perspex.UnitTests/TestRoot.cs @@ -3,6 +3,7 @@ using System; using Perspex.Controls; +using Perspex.Input; using Perspex.Layout; using Perspex.Platform; using Perspex.Rendering; @@ -10,7 +11,7 @@ using Perspex.Styling; namespace Perspex.UnitTests { - public class TestRoot : Decorator, ILayoutRoot, INameScope, IRenderRoot, IStyleRoot + public class TestRoot : Decorator, IFocusScope, ILayoutRoot, INameScope, IRenderRoot, IStyleRoot { private readonly NameScope _nameScope = new NameScope(); diff --git a/tests/Perspex.UnitTests/TestServices.cs b/tests/Perspex.UnitTests/TestServices.cs index 6383aeb516..bac9cc1c24 100644 --- a/tests/Perspex.UnitTests/TestServices.cs +++ b/tests/Perspex.UnitTests/TestServices.cs @@ -36,12 +36,19 @@ namespace Perspex.UnitTests public static readonly TestServices MockThreadingInterface = new TestServices( threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)); + public static readonly TestServices RealFocus = new TestServices( + focusManager: new FocusManager(), + keyboardDevice: () => new KeyboardDevice(), + inputManager: new InputManager()); + public static readonly TestServices RealStyler = new TestServices( styler: new Styler()); public TestServices( IAssetLoader assetLoader = null, + IFocusManager focusManager = null, IInputManager inputManager = null, + Func keyboardDevice = null, ILayoutManager layoutManager = null, IPclPlatformWrapper platformWrapper = null, IPlatformRenderInterface renderInterface = null, @@ -53,7 +60,9 @@ namespace Perspex.UnitTests IWindowingPlatform windowingPlatform = null) { AssetLoader = assetLoader; + FocusManager = focusManager; InputManager = inputManager; + KeyboardDevice = keyboardDevice; LayoutManager = layoutManager; PlatformWrapper = platformWrapper; RenderInterface = renderInterface; @@ -67,6 +76,8 @@ namespace Perspex.UnitTests public IAssetLoader AssetLoader { get; } public IInputManager InputManager { get; } + public IFocusManager FocusManager { get; } + public Func KeyboardDevice { get; } public ILayoutManager LayoutManager { get; } public IPclPlatformWrapper PlatformWrapper { get; } public IPlatformRenderInterface RenderInterface { get; } @@ -79,7 +90,9 @@ namespace Perspex.UnitTests public TestServices With( IAssetLoader assetLoader = null, + IFocusManager focusManager = null, IInputManager inputManager = null, + Func keyboardDevice = null, ILayoutManager layoutManager = null, IPclPlatformWrapper platformWrapper = null, IPlatformRenderInterface renderInterface = null, @@ -92,7 +105,9 @@ namespace Perspex.UnitTests { return new TestServices( assetLoader: assetLoader ?? AssetLoader, + focusManager: focusManager ?? FocusManager, inputManager: inputManager ?? InputManager, + keyboardDevice: keyboardDevice ?? KeyboardDevice, layoutManager: layoutManager ?? LayoutManager, platformWrapper: platformWrapper ?? PlatformWrapper, renderInterface: renderInterface ?? RenderInterface, diff --git a/tests/Perspex.UnitTests/UnitTestApplication.cs b/tests/Perspex.UnitTests/UnitTestApplication.cs index 04fd5daf68..3807a35fdb 100644 --- a/tests/Perspex.UnitTests/UnitTestApplication.cs +++ b/tests/Perspex.UnitTests/UnitTestApplication.cs @@ -40,8 +40,10 @@ namespace Perspex.UnitTests { PerspexLocator.CurrentMutable .Bind().ToConstant(Services.AssetLoader) + .Bind().ToConstant(Services.FocusManager) .BindToSelf(this) .Bind().ToConstant(Services.InputManager) + .Bind().ToConstant(Services.KeyboardDevice?.Invoke()) .Bind().ToConstant(Services.LayoutManager) .Bind().ToConstant(Services.PlatformWrapper) .Bind().ToConstant(Services.RenderInterface)