From 5c341587f753b1327abc67147750cf9ac3eb0a87 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 19 Mar 2024 00:06:38 +0200 Subject: [PATCH 01/16] feat: add support for PreviewKey on skia --- .../Keyboard/Keyboard_Events.xaml.cs | 2 +- .../Microsoft.UI.Xaml.Controls/Control.cs | 8 +-- .../3.0.0.0/Microsoft.UI.Xaml/UIElement.cs | 24 +++---- .../UI/Xaml/Controls/Control/Control.cs | 6 +- .../Internal/InputManager.Keyboard.skia.cs | 51 ++++----------- src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 62 ++++++++++++++++--- 6 files changed, 84 insertions(+), 69 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs index 21f4b5fcfab9..d6b13191895c 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs @@ -36,7 +36,7 @@ private void SetupEvent(FrameworkElement elt) global::System.Diagnostics.Debug.WriteLine($"{elt.Name} - [KEYUP] {e.Key}"); _output.Text += $"{elt.Name} - [KEYUP] {e.Key}\r\n"; }; -#if __WASM__ +#if __WASM__ || __SKIA__ elt.PreviewKeyDown += (snd, e) => { Console.WriteLine($"{elt.Name} - [PREVIEWKEYDOWN] {e.Key}"); diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/Control.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/Control.cs index 07ec7176e588..f27a4632bcc2 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/Control.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/Control.cs @@ -274,15 +274,15 @@ public int CharacterSpacing // Skipping already declared method Microsoft.UI.Xaml.Controls.Control.OnManipulationCompleted(Microsoft.UI.Xaml.Input.ManipulationCompletedRoutedEventArgs) // Skipping already declared method Microsoft.UI.Xaml.Controls.Control.OnKeyUp(Microsoft.UI.Xaml.Input.KeyRoutedEventArgs) // Skipping already declared method Microsoft.UI.Xaml.Controls.Control.OnKeyDown(Microsoft.UI.Xaml.Input.KeyRoutedEventArgs) -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || false || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] protected virtual void OnPreviewKeyDown(global::Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e) { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.Control", "void Control.OnPreviewKeyDown(KeyRoutedEventArgs e)"); } #endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || false || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] protected virtual void OnPreviewKeyUp(global::Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e) { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.Control", "void Control.OnPreviewKeyUp(KeyRoutedEventArgs e)"); diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs index 37c8d517c26a..13b542589476 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs @@ -695,8 +695,8 @@ public double RasterizationScale // Skipping already declared property PointerPressedEvent // Skipping already declared property PointerReleasedEvent // Skipping already declared property PointerWheelChangedEvent -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || false || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] public static global::Microsoft.UI.Xaml.RoutedEvent PreviewKeyDownEvent { get @@ -705,8 +705,8 @@ public double RasterizationScale } } #endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || false || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] public static global::Microsoft.UI.Xaml.RoutedEvent PreviewKeyUpEvent { get @@ -1277,32 +1277,32 @@ public static bool TryStartDirectManipulation(global::Microsoft.UI.Xaml.Input.Po // Skipping already declared event Microsoft.UI.Xaml.UIElement.PointerPressed // Skipping already declared event Microsoft.UI.Xaml.UIElement.PointerReleased // Skipping already declared event Microsoft.UI.Xaml.UIElement.PointerWheelChanged -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || false || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] public event global::Microsoft.UI.Xaml.Input.KeyEventHandler PreviewKeyDown { - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] add { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.UIElement", "event KeyEventHandler UIElement.PreviewKeyDown"); } - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] remove { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.UIElement", "event KeyEventHandler UIElement.PreviewKeyDown"); } } #endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || false || false || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] public event global::Microsoft.UI.Xaml.Input.KeyEventHandler PreviewKeyUp { - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] add { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.UIElement", "event KeyEventHandler UIElement.PreviewKeyUp"); } - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__NETSTD_REFERENCE__", "__MACOS__")] remove { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.UIElement", "event KeyEventHandler UIElement.PreviewKeyUp"); diff --git a/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs b/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs index 3c9fd314d5fb..ad6a2d075c51 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs @@ -1010,7 +1010,7 @@ protected virtual void OnDragEnter(global::Microsoft.UI.Xaml.DragEventArgs e) { protected virtual void OnDragOver(global::Microsoft.UI.Xaml.DragEventArgs e) { } protected virtual void OnDragLeave(global::Microsoft.UI.Xaml.DragEventArgs e) { } protected virtual void OnDrop(global::Microsoft.UI.Xaml.DragEventArgs e) { } -#if __WASM__ +#if __WASM__ || __SKIA__ protected virtual void OnPreviewKeyDown(KeyRoutedEventArgs e) { } protected virtual void OnPreviewKeyUp(KeyRoutedEventArgs e) { } #endif @@ -1081,7 +1081,7 @@ protected virtual void OnLostFocus(RoutedEventArgs e) { } private static readonly DragEventHandler OnDropHandler = (object sender, global::Microsoft.UI.Xaml.DragEventArgs args) => ((Control)sender).OnDrop(args); -#if __WASM__ +#if __WASM__ || __SKIA__ private static readonly KeyEventHandler OnPreviewKeyDownHandler = (object sender, KeyRoutedEventArgs args) => ((Control)sender).OnPreviewKeyDown(args); @@ -1223,7 +1223,7 @@ internal static RoutedEventFlag EvaluateImplementedControlRoutedEvents(Type type { result |= RoutedEventFlag.Drop; } -#if __WASM__ +#if __WASM__ || __SKIA__ if (GetIsEventOverrideImplemented(type, nameof(OnPreviewKeyDown), _keyArgsType)) { result |= RoutedEventFlag.PreviewKeyDown; diff --git a/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs b/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs index 92ec2283c324..559cfbe38df5 100644 --- a/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs +++ b/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs @@ -44,60 +44,33 @@ public void Init(object host) CoreWindow.GetForCurrentThreadSafe()?.SetKeyboardInputSource(_source); } - _source.KeyDown += (s, e) => InitiateKeyDownBubblingFlow(e); - _source.KeyUp += (s, e) => InitiateKeyUpBubblingFlow(e); + _source.KeyDown += (_, e) => OnKey(e, true); + _source.KeyUp += (_, e) => OnKey(e, false); } - private void InitiateKeyDownBubblingFlow(KeyEventArgs args) + private void OnKey(KeyEventArgs args, bool down) { var originalSource = FocusManager.GetFocusedElement(_inputManager.ContentRoot.XamlRoot) as UIElement ?? _inputManager.ContentRoot.VisualTree.RootElement; - if (originalSource is null) - { - return; - } - - var krea = new KeyRoutedEventArgs(originalSource, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) + var args1 = new KeyRoutedEventArgs(originalSource, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) { CanBubbleNatively = false }; - originalSource.RaiseEvent(UIElement.KeyDownEvent, krea); - args.Handled = krea.Handled; - - if (this.Log().IsEnabled(LogLevel.Trace)) + var args2 = new KeyRoutedEventArgs(originalSource, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) { - this.Log().Trace( - $"CoreWindow_KeyDown(vk: {args.VirtualKey}, " + - $"IsExtendedKey: {args.KeyStatus.IsExtendedKey}, " + - $"IsKeyReleased: {args.KeyStatus.IsKeyReleased}, " + - $"IsMenuKeyDown: {args.KeyStatus.IsMenuKeyDown}, " + - $"RepeatCount: {args.KeyStatus.RepeatCount}, " + - $"ScanCode: {args.KeyStatus.ScanCode})" - ); - } - } + CanBubbleNatively = false + }; - private void InitiateKeyUpBubblingFlow(KeyEventArgs args) - { - var originalSource = FocusManager.GetFocusedElement(_inputManager.ContentRoot.XamlRoot) as UIElement ?? _inputManager.ContentRoot.VisualTree.RootElement; + originalSource.RaiseTunnelingEvent(down ? UIElement.PreviewKeyDownEvent : UIElement.PreviewKeyUpEvent, args1); + args2.Handled = args1.Handled; // WinUI doesn't reuse the same args object, but copies the Handled value + originalSource.RaiseEvent(down ? UIElement.KeyDownEvent : UIElement.KeyUpEvent, args2); - if (originalSource is null) - { - return; - } - - originalSource.RaiseEvent( - UIElement.KeyUpEvent, - new KeyRoutedEventArgs(originalSource, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) - { - CanBubbleNatively = false - } - ); + args.Handled = args2.Handled; if (this.Log().IsEnabled(LogLevel.Trace)) { this.Log().Trace( - $"CoreWindow_KeyUp(vk: {args.VirtualKey}, " + + $"CoreWindow_KeyDown(vk: {args.VirtualKey}, " + $"IsExtendedKey: {args.KeyStatus.IsExtendedKey}, " + $"IsKeyReleased: {args.KeyStatus.IsKeyReleased}, " + $"IsMenuKeyDown: {args.KeyStatus.IsMenuKeyDown}, " + diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index c496922ccef1..17f5101b788d 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -148,7 +148,8 @@ partial class UIElement /* ** */ internal /* ** */ static RoutedEvent DropCompletedEvent { get; } = new RoutedEvent(RoutedEventFlag.DropCompleted); -#if __WASM__ + +#if __WASM__ || __SKIA__ public static RoutedEvent PreviewKeyDownEvent { get; } = new RoutedEvent(RoutedEventFlag.PreviewKeyDown); public static RoutedEvent PreviewKeyUpEvent { get; } = new RoutedEvent(RoutedEventFlag.PreviewKeyUp); @@ -440,7 +441,7 @@ public event TypedEventHandler DropCompleted remove => RemoveHandler(DropCompletedEvent, value); } -#if __WASM__ +#if __WASM__ || __SKIA__ public event KeyEventHandler PreviewKeyDown { add => AddHandler(PreviewKeyDownEvent, value, false); @@ -674,6 +675,13 @@ internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, Bubbling throw new InvalidOperationException($"Flag not defined for routed event {routedEvent.Name}."); } +#if !__WASM__ + if (routedEvent.IsTunnelingEvent) + { + throw new InvalidOperationException($"Tunneling event {routedEvent.Name} should be raised through {nameof(RaiseTunnelingEvent)}"); + } +#endif + // TODO: This is just temporary workaround before proper // keyboard event infrastructure is implemented everywhere // (issue #6074) @@ -763,23 +771,57 @@ internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, Bubbling return RaiseOnParent(routedEvent, args, parent, ctx); } - private static void TrackKeyState(RoutedEvent routedEvent, RoutedEventArgs args) + /// + /// Raise a tunneling routed event starting from the root and tunneling down to this element. + /// + internal void RaiseTunnelingEvent(RoutedEvent routedEvent, RoutedEventArgs args) { - if (args is KeyRoutedEventArgs keyArgs) + if (!routedEvent.Flag.IsTunnelingEvent()) + { + throw new InvalidOperationException($"Event {routedEvent.Name} is not marked as a tunneling event."); + } + + // TODO: This is just temporary workaround before proper + // keyboard event infrastructure is implemented everywhere + // (issue #6074) + // The key states will be tracked again in an accompanying bubbling event (e.g. KeyDown for PreviewKeyDown), + // but this is fine, since it's idempotent. + if (routedEvent.IsKeyEvent) { - if (routedEvent == KeyDownEvent) + TrackKeyState(routedEvent, args); + } + + // On WinUI, if one of the event handlers reparents this element, the tunneling still goes through the + // original path. + foreach (var p in this.GetAllParents().Reverse()) + { + if (p is not UIElement parent) { - KeyboardStateTracker.OnKeyDown(keyArgs.OriginalKey); + break; } - else if (routedEvent == KeyUpEvent) + + if (parent._eventHandlerStore.TryGetValue(routedEvent, out var handlers)) { - KeyboardStateTracker.OnKeyUp(keyArgs.OriginalKey); + foreach (var handler in handlers.ToArray()) + { + if (IsHandled(args) || handler.HandledEventsToo) + { + parent.InvokeHandler(handler.Handler, args); + } + } } - else if (routedEvent == PreviewKeyDownEvent) + } + } + + private static void TrackKeyState(RoutedEvent routedEvent, RoutedEventArgs args) + { + if (args is KeyRoutedEventArgs keyArgs) + { + if (routedEvent == KeyDownEvent || routedEvent == PreviewKeyDownEvent) { KeyboardStateTracker.OnKeyDown(keyArgs.OriginalKey); } - else if (routedEvent == PreviewKeyUpEvent) + else if (routedEvent == KeyUpEvent || routedEvent == PreviewKeyUpEvent) { KeyboardStateTracker.OnKeyUp(keyArgs.OriginalKey); } From ee3c5669adb7638b77501cf73c5999d9ebfe17ea Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 19 Mar 2024 17:25:53 +0200 Subject: [PATCH 02/16] chore: remove unnecessary and buggy SubscribedToHandledEventsToo --- src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 123 +++---------------- 1 file changed, 16 insertions(+), 107 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index 17f5101b788d..8cdf071c1e80 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -194,9 +194,9 @@ internal RoutedEventHandlerInfo(object handler, bool handledEventsToo) typeof(UIElement), new FrameworkPropertyMetadata( RoutedEventFlag.None, - FrameworkPropertyMetadataOptions.Inherits) + FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.KeepCoercedWhenEquals) { - CoerceValueCallback = (dependencyObject, value, _) => CoerceRoutedEventFlag(dependencyObject, value) + CoerceValueCallback = CoerceRoutedEventFlag } ); @@ -208,54 +208,30 @@ public RoutedEventFlag EventsBubblingInManagedCode #endregion - #region SubscribedToHandledEventsToo DependencyProperty - - private static DependencyProperty SubscribedToHandledEventsTooProperty { get; } = - DependencyProperty.Register( - "SubscribedToHandledEventsToo", - typeof(RoutedEventFlag), - typeof(UIElement), - new FrameworkPropertyMetadata( - RoutedEventFlag.None, - FrameworkPropertyMetadataOptions.Inherits) - { - CoerceValueCallback = (dependencyObject, value, _) => CoerceRoutedEventFlag(dependencyObject, value) - } - ); - - private RoutedEventFlag SubscribedToHandledEventsToo - { - get => (RoutedEventFlag)GetValue(SubscribedToHandledEventsTooProperty); - set => SetValue(SubscribedToHandledEventsTooProperty, value); - } - - #endregion - - private static object CoerceRoutedEventFlag(DependencyObject dependencyObject, object baseValue) + private static object CoerceRoutedEventFlag(DependencyObject dependencyObject, object baseValue, DependencyPropertyValuePrecedences precedence) { - // This is a Coerce method for both EventsBubblingInManagedCodeProperty and SubscribedToHandledEventsTooProperty - var @this = (UIElement)dependencyObject; - var localValue = @this.GetPrecedenceSpecificValue( - SubscribedToHandledEventsTooProperty, - DependencyPropertyValuePrecedences.Local); // should be the same than localValue on first assignment + // GetPrecedenceSpecificValue will read an outdated value for the precedence currently being set + var localValue = precedence is DependencyPropertyValuePrecedences.Local ? + baseValue : + @this.GetPrecedenceSpecificValue(EventsBubblingInManagedCodeProperty, DependencyPropertyValuePrecedences.Local); + + var inheritedValue = precedence is DependencyPropertyValuePrecedences.Inheritance ? + baseValue : + @this.GetPrecedenceSpecificValue(EventsBubblingInManagedCodeProperty, DependencyPropertyValuePrecedences.Inheritance); - if (!(localValue is RoutedEventFlag local)) + var combinedFlag = RoutedEventFlag.None; + if (localValue is RoutedEventFlag local) { - return baseValue; // local not set, no coerced value to set + combinedFlag |= local; } - - var inheritedValue = @this.GetPrecedenceSpecificValue( - SubscribedToHandledEventsTooProperty, - DependencyPropertyValuePrecedences.Inheritance); - if (inheritedValue is RoutedEventFlag inherited) { - return local | inherited; // coerced value is a merge between local and inherited + combinedFlag |= inherited; } - return baseValue; // no inherited value, nothing to do + return combinedFlag; } private readonly Dictionary> _eventHandlerStore @@ -493,12 +469,6 @@ private protected void InsertHandler(RoutedEvent routedEvent, object handler, bo } AddHandler(routedEvent, handlers.Count, handler, handledEventsToo); - - if (handledEventsToo - && !routedEvent.IsAlwaysBubbled) // This event is always bubbled, no needs to update the flag - { - UpdateSubscribedToHandledEventsToo(); - } } public void AddHandler(RoutedEvent routedEvent, object handler, bool handledEventsToo) @@ -507,12 +477,6 @@ public void AddHandler(RoutedEvent routedEvent, object handler, bool handledEven handlers.Add(new RoutedEventHandlerInfo(handler, handledEventsToo)); AddHandler(routedEvent, handlers.Count, handler, handledEventsToo); - - if (handledEventsToo - && !routedEvent.IsAlwaysBubbled) // This event is always bubbled, no needs to update the flag - { - UpdateSubscribedToHandledEventsToo(); - } } private void AddHandler(RoutedEvent routedEvent, int handlersCount, object handler, bool handledEventsToo) @@ -559,12 +523,6 @@ public void RemoveHandler(RoutedEvent routedEvent, object handler) if (!matchingHandler.Equals(default(RoutedEventHandlerInfo))) { handlers.Remove(matchingHandler); - - if (matchingHandler.HandledEventsToo - && !routedEvent.IsAlwaysBubbled) // This event is always bubbled, no need to update the flag - { - UpdateSubscribedToHandledEventsToo(); - } } RemoveHandler(routedEvent, handlers.Count, handler); @@ -615,31 +573,6 @@ private int CountHandler(RoutedEvent routedEvent) ? handlers.Count : 0; - private void UpdateSubscribedToHandledEventsToo() - { - var subscribedToHandledEventsToo = RoutedEventFlag.None; - - foreach (var eventHandlers in _eventHandlerStore) - { - if (eventHandlers.Key.IsAlwaysBubbled) - { - // This event is always bubbled, no need to include it in the SubscribedToHandledEventsToo - continue; - } - - foreach (var handler in eventHandlers.Value) - { - if (handler.HandledEventsToo) - { - subscribedToHandledEventsToo |= eventHandlers.Key.Flag; - break; - } - } - } - - SubscribedToHandledEventsToo = subscribedToHandledEventsToo; - } - internal bool SafeRaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, BubblingContext ctx = default) { try @@ -710,14 +643,6 @@ internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, Bubbling // [5] Event handled by local handlers? if (isHandled) { - // [9] Any parent interested ? - var anyParentInterested = AnyParentInterested(routedEvent); - if (!anyParentInterested) - { - // [12] Event processing finished - return true; // reported has handled in managed - } - // Make sure the event is marked as not bubbling natively anymore // --> [10] if (args != null) @@ -991,22 +916,6 @@ private bool IsBubblingInManagedCode(RoutedEvent routedEvent, RoutedEventArgs ar return eventsBubblingInManagedCode.HasFlag(flag); } - private bool AnyParentInterested(RoutedEvent routedEvent) - { - // Pointer events must always be dispatched to all parents in order to update visual states, - // update manipulation, detect gestures, etc. - // (They are then interpreted by each parent in the PrepareManagedPointerEventBubbling) - if (routedEvent.IsAlwaysBubbled) - { - return true; - } - - // [9] Any parent interested? - var subscribedToHandledEventsToo = SubscribedToHandledEventsToo; - var flag = routedEvent.Flag; - return subscribedToHandledEventsToo.HasFlag(flag); - } - private void InvokeHandler(object handler, RoutedEventArgs args) { // TODO: WPF calls a virtual RoutedEventArgs.InvokeEventHandler(Delegate handler, object target) method, From fdf354e5a27637fb639560cfca2667179423fcea Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 19 Mar 2024 17:26:41 +0200 Subject: [PATCH 03/16] chore: fix KeyboardManager to create different event args for PreviewKey and Key events --- .../Internal/InputManager.Keyboard.skia.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs b/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs index 559cfbe38df5..705ffd39f8d8 100644 --- a/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs +++ b/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs @@ -50,20 +50,25 @@ public void Init(object host) private void OnKey(KeyEventArgs args, bool down) { - var originalSource = FocusManager.GetFocusedElement(_inputManager.ContentRoot.XamlRoot) as UIElement ?? _inputManager.ContentRoot.VisualTree.RootElement; + var originalSource1 = FocusManager.GetFocusedElement(_inputManager.ContentRoot.XamlRoot) as UIElement ?? _inputManager.ContentRoot.VisualTree.RootElement; - var args1 = new KeyRoutedEventArgs(originalSource, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) + var args1 = new KeyRoutedEventArgs(originalSource1, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) { CanBubbleNatively = false }; - var args2 = new KeyRoutedEventArgs(originalSource, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) + + originalSource1.RaiseTunnelingEvent(down ? UIElement.PreviewKeyDownEvent : UIElement.PreviewKeyUpEvent, args1); + + // On WinUI, if the focus changes during PreviewKey, the Key event bubbles from the new focused element. + var originalSource2 = FocusManager.GetFocusedElement(_inputManager.ContentRoot.XamlRoot) as UIElement ?? _inputManager.ContentRoot.VisualTree.RootElement; + + var args2 = new KeyRoutedEventArgs(originalSource2, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) { - CanBubbleNatively = false + CanBubbleNatively = false, + Handled = args1.Handled // WinUI doesn't reuse the same args object, but copies the Handled value }; - originalSource.RaiseTunnelingEvent(down ? UIElement.PreviewKeyDownEvent : UIElement.PreviewKeyUpEvent, args1); - args2.Handled = args1.Handled; // WinUI doesn't reuse the same args object, but copies the Handled value - originalSource.RaiseEvent(down ? UIElement.KeyDownEvent : UIElement.KeyUpEvent, args2); + originalSource2.RaiseEvent(down ? UIElement.KeyDownEvent : UIElement.KeyUpEvent, args2); args.Handled = args2.Handled; @@ -79,5 +84,10 @@ private void OnKey(KeyEventArgs args, bool down) ); } } + + /// + /// ONLY USE THIS FOR TESTS + /// + internal void OnKeyTestingOnly(KeyEventArgs args, bool down) => OnKey(args, down); } } From 06e9145b02b063caaa6caa9ba5047746e0d0c54a Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 19 Mar 2024 17:31:18 +0200 Subject: [PATCH 04/16] test: add tests for PreviewKey --- .../Tests/Windows_UI_Xaml/Given_UIElement.cs | 461 ++++++++++++++++++ 1 file changed, 461 insertions(+) diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs index d266bdf7e02f..8563edfface3 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs @@ -8,15 +8,19 @@ using System; using System.Linq; using System.Numerics; +using System.Text; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Execution; using Private.Infrastructure; using Windows.Foundation; using Windows.Foundation.Metadata; +using Windows.System; +using Windows.UI.Core; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Shapes; using Uno.UI.RuntimeTests.Helpers; @@ -230,6 +234,463 @@ public async Task When_Nested_Child_ActualOffset() Assert.AreEqual(new Vector3(110, 60, 0), button.ActualOffset); } +#if __SKIA__ + private async Task TapKey(VirtualKey key) + { + TestServices.WindowHelper.XamlRoot.VisualTree.ContentRoot.InputManager.Keyboard.OnKeyTestingOnly( + new KeyEventArgs("test", key, VirtualKeyModifiers.None, new CorePhysicalKeyStatus()), true); + + await TestServices.WindowHelper.WaitForIdle(); + + TestServices.WindowHelper.XamlRoot.VisualTree.ContentRoot.InputManager.Keyboard.OnKeyTestingOnly( + new KeyEventArgs("test", key, VirtualKeyModifiers.None, new CorePhysicalKeyStatus()), false); + + await TestServices.WindowHelper.WaitForIdle(); + } + + [TestMethod] + [RunsOnUIThread] + public async Task When_PreviewKeyDown_Basic() + { + StackPanel sp2, sp3; + Button btn1, btn2, btn3; + var sp1 = new StackPanel + { + Name = "sp1", + Children = + { + (sp2 = new StackPanel + { + Name = "sp2", + Children = + { + (sp3 = new StackPanel + { + Name = "sp3", + Children = + { + (btn1 = new Button { Name = "btn1" }), + (btn2 = new Button { Name = "btn2" }) + } + }), + (btn3 = new Button { Name = "btn3" }) + } + }) + } + }; + + var result = new StringBuilder(); + + btn1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + btn2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + btn3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + btn1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + await UITestHelper.Load(sp1); + + btn1.Focus(FocusState.Programmatic); + await TapKey(VirtualKey.A); // any key that doesn't get handled by Button + await TestServices.WindowHelper.WaitForIdle(); + + Assert.AreEqual( + """ + sp1 PreviewKeyDown False + sp2 PreviewKeyDown False + sp3 PreviewKeyDown False + btn1 PreviewKeyDown False + btn1 KeyDown False + sp3 KeyDown False + sp2 KeyDown False + sp1 KeyDown False + + """.ReplaceLineEndings("\n") + , result.ToString()); + + void OnKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} KeyDown {e.Handled}\n"); + } + + void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} PreviewKeyDown {e.Handled}\n"); + } + } + + [TestMethod] + [RunsOnUIThread] + public async Task When_PreviewKeyDown_KeyDown_DifferentArgs() + { + var button = new Button(); + + KeyRoutedEventArgs keyDownArgs = default, previewKeyDownArgs = default; + button.KeyDown += (_, args) => keyDownArgs = args; + button.PreviewKeyDown += (_, args) => previewKeyDownArgs = args; + + await TapKey(VirtualKey.A); + + Assert.IsNotNull(keyDownArgs); + Assert.IsNotNull(previewKeyDownArgs); + Assert.AreNotEqual(keyDownArgs, previewKeyDownArgs); + } + + [TestMethod] + [RunsOnUIThread] + public async Task When_PreviewKeyDown_Handled() + { + StackPanel sp2, sp3; + Button btn1, btn2, btn3; + var sp1 = new StackPanel + { + Name = "sp1", + Children = + { + (sp2 = new StackPanel + { + Name = "sp2", + Children = + { + (sp3 = new StackPanel + { + Name = "sp3", + Children = + { + (btn1 = new Button { Name = "btn1" }), + (btn2 = new Button { Name = "btn2" }) + } + }), + (btn3 = new Button { Name = "btn3" }) + } + }) + } + }; + + var result = new StringBuilder(); + + btn1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), false); // Change from other tests, true -> false + btn2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + btn3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + btn1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + await UITestHelper.Load(sp1); + + btn1.Focus(FocusState.Programmatic); + await TapKey(VirtualKey.A); // any key that doesn't get handled by Button + await TestServices.WindowHelper.WaitForIdle(); + + Assert.AreEqual( + """ + sp1 PreviewKeyDown False + sp2 PreviewKeyDown False + sp3 PreviewKeyDown True + btn1 PreviewKeyDown True + sp3 KeyDown True + sp2 KeyDown True + sp1 KeyDown True + + """.ReplaceLineEndings("\n") + , result.ToString()); + + void OnKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} KeyDown {e.Handled}\n"); + } + + void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} PreviewKeyDown {e.Handled}\n"); + + if (((FrameworkElement)sender).Name == "sp2") + { + e.Handled = true; + } + } + } + + [TestMethod] + [RunsOnUIThread] + public async Task When_PreviewKeyDown_Handled_Then_Unhandled() + { + StackPanel sp2, sp3; + Button btn1, btn2, btn3; + var sp1 = new StackPanel + { + Name = "sp1", + Children = + { + (sp2 = new StackPanel + { + Name = "sp2", + Children = + { + (sp3 = new StackPanel + { + Name = "sp3", + Children = + { + (btn1 = new Button { Name = "btn1" }), + (btn2 = new Button { Name = "btn2" }) + } + }), + (btn3 = new Button { Name = "btn3" }) + } + }) + } + }; + + var result = new StringBuilder(); + + btn1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), false); // change from other tests, true -> false + btn2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + btn3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + btn1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + await UITestHelper.Load(sp1); + + btn1.Focus(FocusState.Programmatic); + await TapKey(VirtualKey.A); // any key that doesn't get handled by Button + await TestServices.WindowHelper.WaitForIdle(); + + Assert.AreEqual( + """ + sp1 PreviewKeyDown False + sp2 PreviewKeyDown False + sp3 PreviewKeyDown True + btn1 PreviewKeyDown False + btn1 KeyDown False + sp3 KeyDown False + sp2 KeyDown False + sp1 KeyDown False + + """.ReplaceLineEndings("\n") + , result.ToString()); + + void OnKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} KeyDown {e.Handled}\n"); + } + + void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} PreviewKeyDown {e.Handled}\n"); + + if (((FrameworkElement)sender).Name == "sp2") + { + e.Handled = true; + } + + if (((FrameworkElement)sender).Name == "sp3") + { + e.Handled = false; + } + } + } + + [TestMethod] + [RunsOnUIThread] + [Ignore("Failing due to #15942")] + public async Task When_PreviewKeyDown_Reparenting() + { + StackPanel sp2, sp3; + Button btn1, btn2, btn3; + var sp1 = new StackPanel + { + Name = "sp1", + Children = + { + (sp2 = new StackPanel + { + Name = "sp2", + Children = + { + (sp3 = new StackPanel + { + Name = "sp3", + Children = + { + (btn1 = new Button { Name = "btn1" }), + (btn2 = new Button { Name = "btn2" }) + } + }), + (btn3 = new Button { Name = "btn3" }) + } + }) + } + }; + + var result = new StringBuilder(); + + btn1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + btn2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + btn3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + btn1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + await UITestHelper.Load(sp1); + + btn1.Focus(FocusState.Programmatic); + await TapKey(VirtualKey.A); // any key that doesn't get handled by Button + await TestServices.WindowHelper.WaitForIdle(); + + Assert.AreEqual( + """ + sp1 PreviewKeyDown False + sp2 PreviewKeyDown False + sp3 PreviewKeyDown False + btn1 PreviewKeyDown False + btn3 KeyDown False + sp2 KeyDown False + sp1 KeyDown False + + """.ReplaceLineEndings("\n") + , result.ToString()); + + void OnKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} KeyDown {e.Handled}\n"); + } + + void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} PreviewKeyDown {e.Handled}\n"); + + if (((FrameworkElement)sender).Name == "sp2") + { + sp3.Children.Remove(btn1); + sp2.Children.Add(btn1); + } + } + } + + [TestMethod] + [RunsOnUIThread] + public async Task When_PreviewKeyDown_FocusChanged() + { + StackPanel sp2, sp3; + Button btn1, btn2, btn3; + var sp1 = new StackPanel + { + Name = "sp1", + Children = + { + (sp2 = new StackPanel + { + Name = "sp2", + Children = + { + (sp3 = new StackPanel + { + Name = "sp3", + Children = + { + (btn1 = new Button { Name = "btn1" }), + (btn2 = new Button { Name = "btn2" }) + } + }), + (btn3 = new Button { Name = "btn3" }) + } + }) + } + }; + + var result = new StringBuilder(); + + btn1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + btn2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + btn3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + btn1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + btn3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp2.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + sp3.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDown), true); + + sp1.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp2.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + sp3.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); + + await UITestHelper.Load(sp1); + + btn1.Focus(FocusState.Programmatic); + await TapKey(VirtualKey.A); // any key that doesn't get handled by Button + await TestServices.WindowHelper.WaitForIdle(); + + Assert.AreEqual( + """ + sp1 PreviewKeyDown False + sp2 PreviewKeyDown False + sp3 PreviewKeyDown False + btn1 PreviewKeyDown False + btn2 KeyDown False + sp3 KeyDown False + sp2 KeyDown False + sp1 KeyDown False + + """.ReplaceLineEndings("\n") + , result.ToString()); + + void OnKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} KeyDown {e.Handled}\n"); + } + + void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + result.Append($"{((FrameworkElement)sender).Name} PreviewKeyDown {e.Handled}\n"); + + if (((FrameworkElement)sender).Name == "sp2") + { + btn2.Focus(FocusState.Programmatic); + } + } + } +#endif + #if HAS_UNO // Cannot Set the LayoutInformation on UWP [TestMethod] [RunsOnUIThread] From 002ee029c054f82872abc657a0db62f0dd2b146e Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 20 Mar 2024 16:00:08 +0200 Subject: [PATCH 05/16] chore: fix test --- .../Tests/Windows_UI_Xaml/Given_UIElement.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs index 8563edfface3..2920c4a7cba2 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs @@ -338,6 +338,8 @@ public async Task When_PreviewKeyDown_KeyDown_DifferentArgs() button.KeyDown += (_, args) => keyDownArgs = args; button.PreviewKeyDown += (_, args) => previewKeyDownArgs = args; + await UITestHelper.Load(button); + button.Focus(FocusState.Programmatic); await TapKey(VirtualKey.A); Assert.IsNotNull(keyDownArgs); From aacbd75577e490b90d0cb5ed16b6b6bba88f1201 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 20 Mar 2024 16:03:41 +0200 Subject: [PATCH 06/16] chore: remove unused EventsBubblingInManagedCodeProperty --- src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 66 +------------------- 1 file changed, 1 insertion(+), 65 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index 8cdf071c1e80..79e709b3b5fa 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -186,54 +186,6 @@ internal RoutedEventHandlerInfo(object handler, bool handledEventsToo) internal bool HandledEventsToo { get; } } - #region EventsBubblingInManagedCode DependencyProperty - - public static DependencyProperty EventsBubblingInManagedCodeProperty { get; } = DependencyProperty.Register( - "EventsBubblingInManagedCode", - typeof(RoutedEventFlag), - typeof(UIElement), - new FrameworkPropertyMetadata( - RoutedEventFlag.None, - FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.KeepCoercedWhenEquals) - { - CoerceValueCallback = CoerceRoutedEventFlag - } - ); - - public RoutedEventFlag EventsBubblingInManagedCode - { - get => (RoutedEventFlag)GetValue(EventsBubblingInManagedCodeProperty); - set => SetValue(EventsBubblingInManagedCodeProperty, value); - } - - #endregion - - private static object CoerceRoutedEventFlag(DependencyObject dependencyObject, object baseValue, DependencyPropertyValuePrecedences precedence) - { - var @this = (UIElement)dependencyObject; - - // GetPrecedenceSpecificValue will read an outdated value for the precedence currently being set - var localValue = precedence is DependencyPropertyValuePrecedences.Local ? - baseValue : - @this.GetPrecedenceSpecificValue(EventsBubblingInManagedCodeProperty, DependencyPropertyValuePrecedences.Local); - - var inheritedValue = precedence is DependencyPropertyValuePrecedences.Inheritance ? - baseValue : - @this.GetPrecedenceSpecificValue(EventsBubblingInManagedCodeProperty, DependencyPropertyValuePrecedences.Inheritance); - - var combinedFlag = RoutedEventFlag.None; - if (localValue is RoutedEventFlag local) - { - combinedFlag |= local; - } - if (inheritedValue is RoutedEventFlag inherited) - { - combinedFlag |= inherited; - } - - return combinedFlag; - } - private readonly Dictionary> _eventHandlerStore = new Dictionary>(); @@ -658,7 +610,7 @@ internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, Bubbling } // [6] & [7] Will the event bubbling natively or in managed code? - var isBubblingInManagedCode = IsBubblingInManagedCode(routedEvent, args); + var isBubblingInManagedCode = args == null || !args.CanBubbleNatively; if (!isBubblingInManagedCode) { return false; // [8] Return for native bubbling @@ -900,22 +852,6 @@ private static bool IsHandled(RoutedEventArgs args) return args is IHandleableRoutedEventArgs cancellable && cancellable.Handled; } - private bool IsBubblingInManagedCode(RoutedEvent routedEvent, RoutedEventArgs args) - { - if (args == null || !args.CanBubbleNatively) // [6] From platform? - { - // Not from platform - - return true; // -> [10] bubble in managed to parents - } - - // [7] Event set to bubble in managed code? - var eventsBubblingInManagedCode = EventsBubblingInManagedCode; - var flag = routedEvent.Flag; - - return eventsBubblingInManagedCode.HasFlag(flag); - } - private void InvokeHandler(object handler, RoutedEventArgs args) { // TODO: WPF calls a virtual RoutedEventArgs.InvokeEventHandler(Delegate handler, object target) method, From 4a5039272a3fa18000cb42c73bd7d0d8431bfa43 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 20 Mar 2024 16:14:47 +0200 Subject: [PATCH 07/16] chore: typo --- src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index 79e709b3b5fa..b65cc6a389fb 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -681,7 +681,7 @@ internal void RaiseTunnelingEvent(RoutedEvent routedEvent, RoutedEventArgs args) { foreach (var handler in handlers.ToArray()) { - if (IsHandled(args) || handler.HandledEventsToo) + if (!IsHandled(args) || handler.HandledEventsToo) { parent.InvokeHandler(handler.Handler, args); } From d041c0134fa2a471891ecbb25d2ea77d191437dc Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 2 Apr 2024 11:57:19 +0200 Subject: [PATCH 08/16] chore: remove EventsBubblingInManagedCode-related unit test --- .../Given_TappedRoutedEvent.cs | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/src/Uno.UI.Tests/RoutedEventTests/Given_TappedRoutedEvent.cs b/src/Uno.UI.Tests/RoutedEventTests/Given_TappedRoutedEvent.cs index 86522fe7fb62..38235769d88c 100644 --- a/src/Uno.UI.Tests/RoutedEventTests/Given_TappedRoutedEvent.cs +++ b/src/Uno.UI.Tests/RoutedEventTests/Given_TappedRoutedEvent.cs @@ -5,7 +5,6 @@ using Microsoft.UI.Xaml.Input; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Uno.UI.Xaml; namespace Uno.UI.Tests.RoutedEventTests { @@ -297,46 +296,5 @@ void OnTapped(object snd, TappedRoutedEventArgs evt) events.Should().HaveCount(0); } - - [TestMethod] - public void When_SubscribingUsingAddHandler_WithHandlesToo_And_BubblingInNativeCode_SetToBubbleInManaged_EatenByPlatform() - { - var events = new List<(object sender, TappedRoutedEventArgs args)>(); - - var child2 = new Border - { - Name = "child2" - }; - var child1 = new Border - { - Child = child2, - Name = "child1" - }; - var root = new Border - { - Child = child1, - Name = "root", - EventsBubblingInManagedCode = RoutedEventFlag.Tapped - }; - - void OnTapped(object snd, TappedRoutedEventArgs evt) - { - events.Add((snd, evt)); - evt.Handled = true; - } - - root.AddHandler(UIElement.TappedEvent, (TappedEventHandler)OnTapped, true); - child1.AddHandler(UIElement.TappedEvent, (TappedEventHandler)OnTapped, true); - - root.Measure(new Size(1, 1)); - - var evt1 = new TappedRoutedEventArgs() - { - CanBubbleNatively = true - }; - child2.RaiseEvent(UIElement.TappedEvent, evt1).Should().BeTrue(); - - events.Should().HaveCount(2); // bubbling in managed code - } } } From 613d5548e3b439d6ed58e360b97e932fb931b8a9 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 2 Apr 2024 12:04:01 +0200 Subject: [PATCH 09/16] revert: "chore: remove unused EventsBubblingInManagedCodeProperty" This reverts commit aacbd75577e490b90d0cb5ed16b6b6bba88f1201. --- .../Given_TappedRoutedEvent.cs | 42 ++++++++++++ src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 66 ++++++++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/Uno.UI.Tests/RoutedEventTests/Given_TappedRoutedEvent.cs b/src/Uno.UI.Tests/RoutedEventTests/Given_TappedRoutedEvent.cs index 38235769d88c..86522fe7fb62 100644 --- a/src/Uno.UI.Tests/RoutedEventTests/Given_TappedRoutedEvent.cs +++ b/src/Uno.UI.Tests/RoutedEventTests/Given_TappedRoutedEvent.cs @@ -5,6 +5,7 @@ using Microsoft.UI.Xaml.Input; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Uno.UI.Xaml; namespace Uno.UI.Tests.RoutedEventTests { @@ -296,5 +297,46 @@ void OnTapped(object snd, TappedRoutedEventArgs evt) events.Should().HaveCount(0); } + + [TestMethod] + public void When_SubscribingUsingAddHandler_WithHandlesToo_And_BubblingInNativeCode_SetToBubbleInManaged_EatenByPlatform() + { + var events = new List<(object sender, TappedRoutedEventArgs args)>(); + + var child2 = new Border + { + Name = "child2" + }; + var child1 = new Border + { + Child = child2, + Name = "child1" + }; + var root = new Border + { + Child = child1, + Name = "root", + EventsBubblingInManagedCode = RoutedEventFlag.Tapped + }; + + void OnTapped(object snd, TappedRoutedEventArgs evt) + { + events.Add((snd, evt)); + evt.Handled = true; + } + + root.AddHandler(UIElement.TappedEvent, (TappedEventHandler)OnTapped, true); + child1.AddHandler(UIElement.TappedEvent, (TappedEventHandler)OnTapped, true); + + root.Measure(new Size(1, 1)); + + var evt1 = new TappedRoutedEventArgs() + { + CanBubbleNatively = true + }; + child2.RaiseEvent(UIElement.TappedEvent, evt1).Should().BeTrue(); + + events.Should().HaveCount(2); // bubbling in managed code + } } } diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index b65cc6a389fb..9f7f9d9d9c9f 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -186,6 +186,54 @@ internal RoutedEventHandlerInfo(object handler, bool handledEventsToo) internal bool HandledEventsToo { get; } } + #region EventsBubblingInManagedCode DependencyProperty + + public static DependencyProperty EventsBubblingInManagedCodeProperty { get; } = DependencyProperty.Register( + "EventsBubblingInManagedCode", + typeof(RoutedEventFlag), + typeof(UIElement), + new FrameworkPropertyMetadata( + RoutedEventFlag.None, + FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.KeepCoercedWhenEquals) + { + CoerceValueCallback = CoerceRoutedEventFlag + } + ); + + public RoutedEventFlag EventsBubblingInManagedCode + { + get => (RoutedEventFlag)GetValue(EventsBubblingInManagedCodeProperty); + set => SetValue(EventsBubblingInManagedCodeProperty, value); + } + + #endregion + + private static object CoerceRoutedEventFlag(DependencyObject dependencyObject, object baseValue, DependencyPropertyValuePrecedences precedence) + { + var @this = (UIElement)dependencyObject; + + // GetPrecedenceSpecificValue will read an outdated value for the precedence currently being set + var localValue = precedence is DependencyPropertyValuePrecedences.Local ? + baseValue : + @this.GetPrecedenceSpecificValue(EventsBubblingInManagedCodeProperty, DependencyPropertyValuePrecedences.Local); + + var inheritedValue = precedence is DependencyPropertyValuePrecedences.Inheritance ? + baseValue : + @this.GetPrecedenceSpecificValue(EventsBubblingInManagedCodeProperty, DependencyPropertyValuePrecedences.Inheritance); + + var combinedFlag = RoutedEventFlag.None; + if (localValue is RoutedEventFlag local) + { + combinedFlag |= local; + } + if (inheritedValue is RoutedEventFlag inherited) + { + combinedFlag |= inherited; + } + + return combinedFlag; + } + private readonly Dictionary> _eventHandlerStore = new Dictionary>(); @@ -610,7 +658,7 @@ internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, Bubbling } // [6] & [7] Will the event bubbling natively or in managed code? - var isBubblingInManagedCode = args == null || !args.CanBubbleNatively; + var isBubblingInManagedCode = IsBubblingInManagedCode(routedEvent, args); if (!isBubblingInManagedCode) { return false; // [8] Return for native bubbling @@ -852,6 +900,22 @@ private static bool IsHandled(RoutedEventArgs args) return args is IHandleableRoutedEventArgs cancellable && cancellable.Handled; } + private bool IsBubblingInManagedCode(RoutedEvent routedEvent, RoutedEventArgs args) + { + if (args == null || !args.CanBubbleNatively) // [6] From platform? + { + // Not from platform + + return true; // -> [10] bubble in managed to parents + } + + // [7] Event set to bubble in managed code? + var eventsBubblingInManagedCode = EventsBubblingInManagedCode; + var flag = routedEvent.Flag; + + return eventsBubblingInManagedCode.HasFlag(flag); + } + private void InvokeHandler(object handler, RoutedEventArgs args) { // TODO: WPF calls a virtual RoutedEventArgs.InvokeEventHandler(Delegate handler, object target) method, From 73b4b40fc35d8432d0cc91a8e3cc4f69d8e13c4d Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 2 Apr 2024 12:54:53 +0200 Subject: [PATCH 10/16] chore: only check for PreviewKeyDownEvent on skia and wasm --- src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index 9f7f9d9d9c9f..1c8521f1f9bc 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -742,11 +742,19 @@ private static void TrackKeyState(RoutedEvent routedEvent, RoutedEventArgs args) { if (args is KeyRoutedEventArgs keyArgs) { - if (routedEvent == KeyDownEvent || routedEvent == PreviewKeyDownEvent) + if (routedEvent == KeyDownEvent +#if __WASM__ || __SKIA__ + || routedEvent == PreviewKeyDownEvent +#endif + ) { KeyboardStateTracker.OnKeyDown(keyArgs.OriginalKey); } - else if (routedEvent == KeyUpEvent || routedEvent == PreviewKeyUpEvent) + else if (routedEvent == KeyUpEvent +#if __WASM__ || __SKIA__ + || routedEvent == PreviewKeyUpEvent +#endif + ) { KeyboardStateTracker.OnKeyUp(keyArgs.OriginalKey); } From 7c9959794ccb6f2e8780c04f13123d4a3397e8e5 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 4 Apr 2024 12:56:22 +0200 Subject: [PATCH 11/16] chore: adjust comments to match changes --- src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index 1c8521f1f9bc..ea94cc09c61e 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -47,10 +47,10 @@ This partial file handles the registration and bubbling of routed events of a UI [1]---------------------+ | An event is fired | +--------+--------------+ - | - [2]------v--------------+ - | Event is dispatched | - | to corresponding | [12] + | [12]--------------------+ + [2]------v--------------+ | Processing finished | + | Event is dispatched | | for this event. | + | to corresponding | +--------+--------------+ | element | ^ +-------yes-------------+ | | [11]---no--------------+ @@ -69,15 +69,15 @@ This partial file handles the registration and bubbling of routed events of a UI | by local handlers? no------------>| Event is coming from | | +-------yes-------------+ | platform? | | | +------yes-------------+ | - [9]------v--------------+ | | - | Any parent interested | [7]-----v--------------+ | - | by this event? yes-+ | Is the event | | - +-------no--------------+ | | bubbling natively? no-+ - | | +------yes-------------+ - [12]-----v--------------+ | | - | Processing finished | v [8]-----v--------------+ - | for this event. | [10] | Event is returned | - +-----------------------+ | for native | + v | | + [10] [7]-----v--------------+ | + | Is the event | | + | bubbling natively? no-+ + +------yes-------------+ + | + [8]-----v--------------+ + | Event is returned | + | for native | | bubbling in platform | +----------------------+ From bf85404908f1b594629143d5fa055111a965fbe4 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 4 Apr 2024 13:02:00 +0200 Subject: [PATCH 12/16] chore: remove IsAlwaysBubbled --- src/Uno.UI/UI/Xaml/RoutedEvent.cs | 3 --- src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/RoutedEvent.cs b/src/Uno.UI/UI/Xaml/RoutedEvent.cs index bf1831fae935..37a230a6ff60 100644 --- a/src/Uno.UI/UI/Xaml/RoutedEvent.cs +++ b/src/Uno.UI/UI/Xaml/RoutedEvent.cs @@ -22,7 +22,6 @@ internal RoutedEvent( IsGestureEvent = flag.IsGestureEvent(); IsDragAndDropEvent = flag.IsDragAndDropEvent(); - IsAlwaysBubbled = IsPointerEvent || IsGestureEvent || IsManipulationEvent || IsDragAndDropEvent; IsTunnelingEvent = flag.IsTunnelingEvent(); } @@ -47,8 +46,6 @@ internal RoutedEvent( /// state should opt-in for that. /// [Pure] - internal bool IsAlwaysBubbled { get; } - [Pure] internal bool IsTunnelingEvent { get; } [Pure] internal bool IsPointerEvent { get; } diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index ea94cc09c61e..cc7c2154d050 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -812,7 +812,6 @@ private BubblingMode PrepareManagedEventBubbling(RoutedEvent routedEvent, Routed } #nullable enable - // WARNING: When implementing one of those methods to maintain a local state, you should also opt-in for RoutedEvent.IsAlwaysBubbled partial void PrepareManagedPointerEventBubbling(RoutedEvent routedEvent, ref RoutedEventArgs args, ref BubblingMode bubblingMode); partial void PrepareManagedKeyEventBubbling(RoutedEvent routedEvent, ref RoutedEventArgs args, ref BubblingMode bubblingMode); partial void PrepareManagedFocusEventBubbling(RoutedEvent routedEvent, ref RoutedEventArgs args, ref BubblingMode bubblingMode); @@ -875,7 +874,6 @@ public override string ToString() /// Defines the mode used to bubble an event. /// /// - /// This takes priority over the . /// Preventing default bubble behavior of an event is meant to be used only when the event has already been raised/bubbled, /// but we need to sent it also to some specific elements (e.g. implicit captures). /// From f2ffac701f2d456a8ddd0eac6fabe449b288f563 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Sat, 6 Apr 2024 18:20:12 +0200 Subject: [PATCH 13/16] chore: fix comment --- src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs b/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs index 705ffd39f8d8..14c5193cde08 100644 --- a/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs +++ b/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs @@ -74,8 +74,9 @@ private void OnKey(KeyEventArgs args, bool down) if (this.Log().IsEnabled(LogLevel.Trace)) { + var methodName = down ? "CoreWindow_KeyDown" : "CoreWindow_KeyUp"; this.Log().Trace( - $"CoreWindow_KeyDown(vk: {args.VirtualKey}, " + + $"{methodName}(vk: {args.VirtualKey}, " + $"IsExtendedKey: {args.KeyStatus.IsExtendedKey}, " + $"IsKeyReleased: {args.KeyStatus.IsKeyReleased}, " + $"IsMenuKeyDown: {args.KeyStatus.IsMenuKeyDown}, " + From 518f77c536a0dc77f2dac943134080f76a209f56 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Sun, 7 Apr 2024 10:23:20 +0200 Subject: [PATCH 14/16] chore: remove conditionals around the keyboard events sample --- .../Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs index d6b13191895c..b179681bac9f 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Events.xaml.cs @@ -36,21 +36,19 @@ private void SetupEvent(FrameworkElement elt) global::System.Diagnostics.Debug.WriteLine($"{elt.Name} - [KEYUP] {e.Key}"); _output.Text += $"{elt.Name} - [KEYUP] {e.Key}\r\n"; }; -#if __WASM__ || __SKIA__ + elt.PreviewKeyDown += (snd, e) => { Console.WriteLine($"{elt.Name} - [PREVIEWKEYDOWN] {e.Key}"); global::System.Diagnostics.Debug.WriteLine($"{elt.Name} - [PREVIEWKEYDOWN] {e.Key}"); _output.Text += $"{elt.Name} - [PREVIEWKEYDOWN] {e.Key}\r\n"; }; - elt.PreviewKeyUp += (snd, e) => { Console.WriteLine($"{elt.Name} - [PREVIEWKEYUP] {e.Key}"); global::System.Diagnostics.Debug.WriteLine($"{elt.Name} - [PREVIEWKEYUP] {e.Key}"); _output.Text += $"{elt.Name} - [PREVIEWKEYUP] {e.Key}\r\n"; }; -#endif } private void SetupEvent(CoreWindow window) From 8a2787a5b31b5274ba838b595dd62b1e041378f6 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 16 Apr 2024 10:49:17 +0200 Subject: [PATCH 15/16] chore: reuse the same routed args object for PreviewKeyXX and KeyXX --- .../Tests/Windows_UI_Xaml/Given_UIElement.cs | 3 ++- .../Xaml/Internal/InputManager.Keyboard.skia.cs | 16 +++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs index 2920c4a7cba2..f9d28c9b6643 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs @@ -344,7 +344,8 @@ public async Task When_PreviewKeyDown_KeyDown_DifferentArgs() Assert.IsNotNull(keyDownArgs); Assert.IsNotNull(previewKeyDownArgs); - Assert.AreNotEqual(keyDownArgs, previewKeyDownArgs); + // We use the same args object twice to reduce allocations, which is different from WinUI. + // Assert.AreNotEqual(keyDownArgs, previewKeyDownArgs); } [TestMethod] diff --git a/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs b/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs index 14c5193cde08..a21920019414 100644 --- a/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs +++ b/src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs @@ -52,25 +52,19 @@ private void OnKey(KeyEventArgs args, bool down) { var originalSource1 = FocusManager.GetFocusedElement(_inputManager.ContentRoot.XamlRoot) as UIElement ?? _inputManager.ContentRoot.VisualTree.RootElement; - var args1 = new KeyRoutedEventArgs(originalSource1, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) + var routedArgs = new KeyRoutedEventArgs(originalSource1, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) { CanBubbleNatively = false }; - originalSource1.RaiseTunnelingEvent(down ? UIElement.PreviewKeyDownEvent : UIElement.PreviewKeyUpEvent, args1); + originalSource1.RaiseTunnelingEvent(down ? UIElement.PreviewKeyDownEvent : UIElement.PreviewKeyUpEvent, routedArgs); // On WinUI, if the focus changes during PreviewKey, the Key event bubbles from the new focused element. var originalSource2 = FocusManager.GetFocusedElement(_inputManager.ContentRoot.XamlRoot) as UIElement ?? _inputManager.ContentRoot.VisualTree.RootElement; - var args2 = new KeyRoutedEventArgs(originalSource2, args.VirtualKey, args.KeyboardModifiers, args.KeyStatus, args.UnicodeKey) - { - CanBubbleNatively = false, - Handled = args1.Handled // WinUI doesn't reuse the same args object, but copies the Handled value - }; - - originalSource2.RaiseEvent(down ? UIElement.KeyDownEvent : UIElement.KeyUpEvent, args2); - - args.Handled = args2.Handled; + // WinUI doesn't reuse the same args object, but creates a new routed args object and copies the Handled value + // To reduce allocations, we reuse the same routed args object twice. + originalSource2.RaiseEvent(down ? UIElement.KeyDownEvent : UIElement.KeyUpEvent, routedArgs); if (this.Log().IsEnabled(LogLevel.Trace)) { From 2439283e6311b252b5994165d8e77cb72fda7436 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 18 Apr 2024 11:23:36 +0200 Subject: [PATCH 16/16] chore: make checks debug-only --- src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs index cc7c2154d050..3291d32bf7d3 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs @@ -602,17 +602,10 @@ internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, Bubbling #if TRACE_ROUTED_EVENT_BUBBLING global::System.Diagnostics.Debug.Write($"{this.GetDebugIdentifier()} - [{routedEvent.Name.TrimEnd("Event")}-{args?.GetHashCode():X8}] (ctx: {ctx}){(this is Microsoft.UI.Xaml.Controls.ContentControl ctrl ? ctrl.DataContext : "")}\r\n"); #endif - - if (routedEvent.Flag == RoutedEventFlag.None) - { - throw new InvalidOperationException($"Flag not defined for routed event {routedEvent.Name}."); - } + global::System.Diagnostics.Debug.Assert(routedEvent.Flag == RoutedEventFlag.None, $"Flag not defined for routed event {routedEvent.Name}."); #if !__WASM__ - if (routedEvent.IsTunnelingEvent) - { - throw new InvalidOperationException($"Tunneling event {routedEvent.Name} should be raised through {nameof(RaiseTunnelingEvent)}"); - } + global::System.Diagnostics.Debug.Assert(routedEvent.IsTunnelingEvent, $"Tunneling event {routedEvent.Name} should be raised through {nameof(RaiseTunnelingEvent)}"); #endif // TODO: This is just temporary workaround before proper @@ -701,10 +694,7 @@ internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, Bubbling /// internal void RaiseTunnelingEvent(RoutedEvent routedEvent, RoutedEventArgs args) { - if (!routedEvent.Flag.IsTunnelingEvent()) - { - throw new InvalidOperationException($"Event {routedEvent.Name} is not marked as a tunneling event."); - } + global::System.Diagnostics.Debug.Assert(!routedEvent.IsTunnelingEvent, $"Event {routedEvent.Name} is not marked as a tunneling event."); // TODO: This is just temporary workaround before proper // keyboard event infrastructure is implemented everywhere