From c82f80ea0fb80bd9e9872b2e859868eef2044883 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 3 May 2022 17:17:14 +0200 Subject: [PATCH] feat: Text input support in Uno Islands --- .../UnoIslands.Shared/MainPage.xaml | 1 + .../UnoIslands.WPF/PersonViewModel.cs | 2 ++ .../UI/Xaml/Controls/TextBoxViewExtension.cs | 12 +++++++- src/Uno.UI.Runtime.Skia.Wpf/IWpfHost.cs | 3 ++ .../Islands/UnoXamlHost.cs | 1 + .../Islands/UnoXamlHost.host.cs | 10 +++++-- .../Islands/UnoXamlHostBase.Focus.cs | 6 +++- .../Islands/UnoXamlHostBase.cs | 28 +++++++++++++++++-- .../Islands/WpfIslandsHost.cs | 3 ++ .../Themes/Generic.xaml | 15 ++++++++++ src/Uno.UI.Runtime.Skia.Wpf/WpfHost.cs | 7 +++-- 11 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/SamplesApp/UnoIslands.Shared/MainPage.xaml b/src/SamplesApp/UnoIslands.Shared/MainPage.xaml index 38617d3aeef5..ebde26993bbb 100644 --- a/src/SamplesApp/UnoIslands.Shared/MainPage.xaml +++ b/src/SamplesApp/UnoIslands.Shared/MainPage.xaml @@ -23,6 +23,7 @@ + diff --git a/src/SamplesApp/UnoIslands.WPF/PersonViewModel.cs b/src/SamplesApp/UnoIslands.WPF/PersonViewModel.cs index 66cbb3e79274..9d0ca25d7385 100644 --- a/src/SamplesApp/UnoIslands.WPF/PersonViewModel.cs +++ b/src/SamplesApp/UnoIslands.WPF/PersonViewModel.cs @@ -22,6 +22,8 @@ public class PersonViewModel public string Country { get; set; } + public string Note { get; set; } + public string EmailUrl => "mailto:" + Email; public string ImageUrl => $"https://www.gravatar.com/avatar/{Name.GetHashCode()}?s=128&d=identicon&r=PG"; diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/UI/Xaml/Controls/TextBoxViewExtension.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/UI/Xaml/Controls/TextBoxViewExtension.cs index b1b3849802fd..8d521be39542 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/UI/Xaml/Controls/TextBoxViewExtension.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/UI/Xaml/Controls/TextBoxViewExtension.cs @@ -9,6 +9,7 @@ using Uno.UI.Xaml.Controls.Extensions; using Point = Windows.Foundation.Point; using WpfCanvas = System.Windows.Controls.Canvas; +using Uno.UI.Runtime.Skia.Wpf.Hosting; namespace Uno.UI.Runtime.Skia.WPF.Extensions.UI.Xaml.Controls { @@ -23,7 +24,16 @@ public TextBoxViewExtension(TextBoxView owner) _owner = owner ?? throw new ArgumentNullException(nameof(owner)); } - private WpfCanvas? GetWindowTextInputLayer() => WpfHost.Current?.NativeOverlayLayer; + private WpfCanvas? GetWindowTextInputLayer() + { + if (_owner?.TextBox?.XamlRoot is not { } xamlRoot) + { + return null; + } + + var host = XamlRootMap.GetHostForRoot(xamlRoot); + return host?.NativeOverlayLayer; + } public void StartEntry() { diff --git a/src/Uno.UI.Runtime.Skia.Wpf/IWpfHost.cs b/src/Uno.UI.Runtime.Skia.Wpf/IWpfHost.cs index 26908658a33b..66c0c782a410 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/IWpfHost.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/IWpfHost.cs @@ -2,6 +2,7 @@ using Windows.Devices.Input; using Windows.UI.Xaml; +using WpfCanvas = System.Windows.Controls.Canvas; namespace Uno.UI.Runtime.Skia.Wpf { @@ -9,6 +10,8 @@ internal interface IWpfHost { XamlRoot? XamlRoot { get; } + WpfCanvas? NativeOverlayLayer { get;} + void ReleasePointerCapture(PointerIdentifier pointer); void SetPointerCapture(PointerIdentifier pointer); diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHost.cs b/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHost.cs index 3e81cd1c19ec..756c747b9136 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHost.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHost.cs @@ -16,6 +16,7 @@ public partial class UnoXamlHost : UnoXamlHostBase { public UnoXamlHost() { + this.DefaultStyleKey = typeof(UnoXamlHost); this.DataContextChanged += UnoXamlHost_DataContextChanged; this.Loaded += OnLoaded; } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHost.host.cs b/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHost.host.cs index 2ed3e3252640..9005b20dc7bc 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHost.host.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHost.host.cs @@ -10,19 +10,21 @@ using Windows.Graphics.Display; using WinUI = Windows.UI.Xaml; using WpfControl = global::System.Windows.Controls.Control; +using WpfCanvas = global::System.Windows.Controls.Canvas; namespace Uno.UI.XamlHost.Skia.Wpf { /// /// UnoXamlHost control hosts UWP XAML content inside the Windows Presentation Foundation /// - abstract partial class UnoXamlHostBase : WpfControl, WinUI.ISkiaHost, IWpfHost + partial class UnoXamlHostBase : WpfControl, WinUI.ISkiaHost, IWpfHost { private bool _designMode; private DisplayInformation _displayInformation; private bool _ignorePixelScaling; private WriteableBitmap _bitmap; private HostPointerHandler _hostPointerHandler; + private WpfCanvas _nativeOverlayLayer; public bool IgnorePixelScaling { @@ -34,8 +36,6 @@ public bool IgnorePixelScaling } } - WinUI.XamlRoot? IWpfHost.XamlRoot => ChildInternal?.XamlRoot; - private void InitializeHost() { _designMode = DesignerProperties.GetIsInDesignMode(this); @@ -123,5 +123,9 @@ protected override void OnRender(DrawingContext drawingContext) void IWpfHost.ReleasePointerCapture(PointerIdentifier pointer) => CaptureMouse(); //TODO:MZ:This should capture the correct type of pointer (stylus/mouse/touch) void IWpfHost.SetPointerCapture(PointerIdentifier pointer) => ReleaseMouseCapture(); + + WinUI.XamlRoot? IWpfHost.XamlRoot => ChildInternal?.XamlRoot; + + System.Windows.Controls.Canvas? IWpfHost.NativeOverlayLayer => _nativeOverlayLayer; } } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHostBase.Focus.cs b/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHostBase.Focus.cs index 92e3fc843449..37cbafecc1a6 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHostBase.Focus.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHostBase.Focus.cs @@ -8,12 +8,16 @@ using WF = Windows.Foundation; using WUX = Windows.UI.Xaml; +//TODO:MZ: We need to make sure that when the UnoXamlHost loses focus, focus is changed in the XamlRoot as well, +//so that for active input fields the native overlay is closed and changes are committed to underlying TextBox text +//before potential data binding changes. + namespace Uno.UI.XamlHost.Skia.Wpf { /// /// Focus and Keyboard handling for Focus integration with UWP XAML /// - public partial class UnoXamlHostBase + partial class UnoXamlHostBase { /// /// Dictionary that maps WPF (host framework) FocusNavigationDirection to UWP XAML XxamlSourceFocusNavigationReason diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHostBase.cs b/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHostBase.cs index b444ccc4bb2c..9b2ffe4f0cfc 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHostBase.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Islands/UnoXamlHostBase.cs @@ -5,7 +5,7 @@ using System; using Uno.UI.Runtime.Skia.Wpf.Hosting; using Uno.UI.Skia.Platform; -using Uno.UI.XamlHost; +using Windows.UI.Xaml; using WUX = Windows.UI.Xaml; namespace Uno.UI.XamlHost.Skia.Wpf @@ -13,7 +13,7 @@ namespace Uno.UI.XamlHost.Skia.Wpf /// /// UnoXamlHost control hosts UWP XAML content inside the Windows Presentation Foundation /// - abstract partial class UnoXamlHostBase + public abstract partial class UnoXamlHostBase { /// /// An instance of . Required to @@ -102,6 +102,14 @@ public UnoXamlHostBase(string typeName) ChildInternal.SetWrapper(this); } + // Uno specific: We need native overlay layer to show input for TextBoxes. + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _nativeOverlayLayer = GetTemplateChild("NativeOverlayLayer") as System.Windows.Controls.Canvas; + } + /// /// Gets the current instance of /// @@ -184,6 +192,16 @@ protected WUX.UIElement ChildInternal } _childInternal = value; + + if (_childInternal.XamlRoot is not null) + { + XamlRootMap.Register(_childInternal.XamlRoot, this); + } + else if (_childInternal is FrameworkElement element) + { + element.Loading += OnChildLoading; + } + SetContent(); var frameworkElement = ChildInternal as WUX.FrameworkElement; @@ -205,6 +223,12 @@ protected WUX.UIElement ChildInternal } } + private void OnChildLoading(FrameworkElement sender, object args) + { + // Ensure the XamlRoot is registered early. + XamlRootMap.Register(sender.XamlRoot, this); + } + private void XamlRoot_InvalidateRender() { //InvalidateOverlays(); diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Islands/WpfIslandsHost.cs b/src/Uno.UI.Runtime.Skia.Wpf/Islands/WpfIslandsHost.cs index 64c1a4c89a00..5a193aeb4575 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Islands/WpfIslandsHost.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Islands/WpfIslandsHost.cs @@ -42,6 +42,7 @@ namespace Uno.UI.Skia.Platform { + // TODO:MZ: Get rid of this class and use the one UnoXamlHost directly. [TemplatePart(Name = NativeOverlayLayerPart, Type = typeof(WpfCanvas))] public class WpfIslandsHost : WpfControl, WinUI.ISkiaHost, IWpfHost { @@ -191,6 +192,8 @@ public bool IgnorePixelScaling WinUI.XamlRoot? IWpfHost.XamlRoot => throw new NotImplementedException(); + WpfCanvas? IWpfHost.NativeOverlayLayer => NativeOverlayLayer; + protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Themes/Generic.xaml b/src/Uno.UI.Runtime.Skia.Wpf/Themes/Generic.xaml index c5b533807e28..cdee72dbde1a 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Themes/Generic.xaml +++ b/src/Uno.UI.Runtime.Skia.Wpf/Themes/Generic.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:platform="clr-namespace:Uno.UI.Skia.Platform" + xmlns:xamlHostWpf="clr-namespace:Uno.UI.XamlHost.Skia.Wpf" xmlns:controls="clr-namespace:Uno.UI.Runtime.Skia.WPF.Controls"> + +