From 72bf6556a1ff27a2334bc3723097b51f97d98eaf Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 20 Aug 2024 11:29:47 +0200 Subject: [PATCH] chore: Selector improvements --- .../UI/Xaml/Controls/Primitives/Selector.cs | 180 ++++++------ .../Primitives/Selector.partial.mux.cs | 265 +----------------- .../Primitives/SelectorItem.partial.mux.cs | 2 +- 3 files changed, 102 insertions(+), 345 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Selector.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Selector.cs index 9f7fd434b8c0..44a20c6cb416 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Selector.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Selector.cs @@ -9,6 +9,7 @@ using Uno.Extensions; using Uno.Extensions.Specialized; using Uno.UI.DataBinding; +using Uno.UI.Extensions; using Uno.UI.Xaml.Input; using Windows.Foundation.Collections; using Windows.System; @@ -351,6 +352,9 @@ internal virtual void OnSelectedIndexChanged(int oldSelectedIndex, int newSelect } SelectedIndexPath = GetIndexPathFromIndex(SelectedIndex); + + OnSelectionChanged(oldSelectedIndex, newSelectedIndex, oldSelectedItem, newSelectedItem); + _isUpdatingSelection = false; if (shouldRaiseSelectionChanged) { @@ -510,6 +514,13 @@ protected override void PrepareContainerForItemOverride(DependencyObject element { selectorItem.IsSelected = IsSelected(IndexFromContainer(element)); } + + var newIndex = IndexFromContainer(element); + + if (newIndex == GetFocusedIndex()) + { + SetFocusedItem(newIndex, false); + } } internal virtual bool IsSelected(int index) @@ -824,67 +835,85 @@ protected virtual (Orientation PhysicalOrientation, Orientation LogicalOrientati return (physicalOrientation, logicalOrientation); } - protected void SetFocusedItem(int index, - bool shouldScrollIntoView, - bool forceFocus, - FocusState focusState, - bool animateIfBringIntoView) + private protected void SetFocusedItem( + int index, + bool shouldScrollIntoView, + bool animateIfBringIntoView = false, + FocusNavigationDirection focusNavigationDirection = FocusNavigationDirection.None, + InputActivationBehavior inputActivationBehavior = InputActivationBehavior.RequestActivation) // default to request activation to match legacy behavior { + FocusState focusState = FocusState.Programmatic; + + var hasFocus = HasFocus(); + if (hasFocus) + { + var spFocused = this.GetFocusedElement(); + var spFocusedAsElement = spFocused as UIElement; + if (spFocusedAsElement is { }) + { + focusState = spFocusedAsElement.FocusState; + global::System.Diagnostics.Debug.Assert(FocusState.Unfocused != focusState, "FocusState_Unfocused unexpected since spFocusedAsElement is focused"); + } + } + + SetFocusedItem(index, shouldScrollIntoView, false /*forceFocus*/, focusState, animateIfBringIntoView, focusNavigationDirection, inputActivationBehavior); } - protected void SetFocusedItem(int index, - bool shouldScrollIntoView, - bool forceFocus, - FocusState focusState, - bool animateIfBringIntoView, - FocusNavigationDirection focusNavigationDirection) + private protected void SetFocusedItem( + int index, + bool shouldScrollIntoView, + bool forceFocus, + FocusState focusState, + bool animateIfBringIntoView, + FocusNavigationDirection focusNavigationDirection = FocusNavigationDirection.None, + InputActivationBehavior inputActivationBehavior = InputActivationBehavior.RequestActivation) // default to request activation to match legacy behavior { - //bool bFocused = false; - //bool shouldFocus = false; + bool bFocused = false; + bool shouldFocus = false; - //var spItems = Items; - //var nCount = spItems?.Size; + var spItems = Items; + var nCount = spItems?.Size; - //if (index < 0 || nCount <= index) - //{ - // index = -1; - //} + if (index < 0 || nCount <= index) + { + index = -1; + } - //if (index >= 0) - //{ - // m_lastFocusedIndex = index; - //} + if (index >= 0) + { + // SetLastFocusedIndex(index); + } - //if (!forceFocus) - //{ - // //shouldFocus = HasFocus(); - //} - //else - //{ - // shouldFocus = true; - //} + if (!forceFocus) + { + shouldFocus = HasFocus(); + } + else + { + shouldFocus = true; + } - //if (shouldFocus) - //{ - // m_iFocusedIndex = index; - //} + if (shouldFocus) + { + SetFocusedIndex(index); + } - //if (m_iFocusedIndex == -1) - //{ - // if (shouldFocus) - // { - // // Since none of our child items have the focus, put the focus back on the main list box. - // // - // // This will happen e.g. when the focused item is being removed but is still in the visual tree at the time of this call. - // // Note that this call may fail e.g. if IsTabStop is false, which is OK; it will just set focus - // // to the next focusable element (or clear focus if none is found). - // bFocused = Focus(focusState); - // } - - // return; - //} + if (GetFocusedIndex() == -1) + { + if (shouldFocus) + { + // Since none of our child items have the focus, put the focus back on the main list box. + // + // This will happen e.g. when the focused item is being removed but is still in the visual tree at the time of this call. + // Note that this call may fail e.g. if IsTabStop is false, which is OK; it will just set focus + // to the next focusable element (or clear focus if none is found). + bFocused = Focus(focusState); + } + + return; + } //if (shouldScrollIntoView) //{ @@ -904,15 +933,15 @@ protected void SetFocusedItem(int index, // ScrollIntoViewAlignment.Default); //} - //if (shouldFocus) - //{ - // var spContainer = ContainerFromIndex(index); + if (shouldFocus) + { + var spContainer = ContainerFromIndex(index); - // if (spContainer is SelectorItem spSelectorItem) - // { - // //spSelectorItem.FocusSelfOrChild(focusState, animateIfBringIntoView, &bFocused, focusNavigationDirection); - // } - //} + if (spContainer is SelectorItem spSelectorItem) + { + spSelectorItem.FocusSelfOrChild(focusState, animateIfBringIntoView, out bFocused, focusNavigationDirection, inputActivationBehavior); + } + } } #if false @@ -922,43 +951,6 @@ private void ScrollIntoView(int index, bool isGroupItemIndex, bool isHeader, boo } #endif - protected void SetFocusedItem(int index, - bool shouldScrollIntoView, - bool animateIfBringIntoView, - FocusNavigationDirection focusNavigationDirection) - { - - //bool hasFocus = false; - FocusState focusState = FocusState.Programmatic; - - //hasFocus = HasFocus(); - - //if (hasFocus) - //{ - // DependencyObject spFocused; - - // //spFocused = GetFocusedElement(); - - // if (spFocused is UIElement spFocusedAsElement) - // { - // focusState = spFocusedAsElement.FocusState; - // } - //} - - SetFocusedItem(index, - shouldScrollIntoView, - forceFocus: false, - focusState, - animateIfBringIntoView, - focusNavigationDirection); - } - - protected void SetFocusedItem(int index, - bool shouldScrollIntoView) - { - - } - #if false bool CanScrollIntoView() { diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/Selector.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/Selector.partial.mux.cs index d646fbd36577..94f2beb91e5f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/Selector.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/Selector.partial.mux.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Linq; using Microsoft.UI.Xaml.Input; -using Windows.Foundation.Collections; +using Uno.UI.Extensions; +using Uno.UI.Xaml.Input; using Windows.System; -using static Uno.UI.FeatureConfiguration; namespace Microsoft.UI.Xaml.Controls.Primitives; @@ -87,253 +83,22 @@ internal void HandleNavigationKey( bool scrollViewport, ref int newFocusedIndex) { - var spItems = Items; - var nCount = spItems.Count; - - var direction = FlowDirection; - var bInvertForRTL = (direction == FlowDirection.RightToLeft); - var (physicalOrientation, _) = GetItemsHostOrientations(); - var isVertical = (physicalOrientation == Orientation.Vertical); - switch (key) - { - case VirtualKey.Left: - if (isVertical && scrollViewport) - { - ElementScrollViewerScrollInDirection(VirtualKey.Left); - } - else - { - if (bInvertForRTL) - { - SelectNext(ref newFocusedIndex); - } - else - { - SelectPrev(ref newFocusedIndex); - } - - if (GetFocusedIndex() == newFocusedIndex && scrollViewport) - { - ElementScrollViewerScrollInDirection(VirtualKey.Left); - } - } - break; - case VirtualKey.Up: - if (!isVertical && scrollViewport) - { - ElementScrollViewerScrollInDirection(VirtualKey.Up); - } - else - { - SelectPrev(newFocusedIndex); - } - - if (GetFocusedIndex() == newFocusedIndex && scrollViewport) - { - ElementScrollViewerScrollInDirection(VirtualKey.Up); - } - break; - case VirtualKey.Right: - if (isVertical && scrollViewport) - { - ElementScrollViewerScrollInDirection(VirtualKey.Right); - } - else - { - if (bInvertForRTL) - { - SelectPrev(newFocusedIndex); - } - else - { - SelectNext(newFocusedIndex); - } - - if (GetFocusedIndex() == newFocusedIndex && scrollViewport) - { - ElementScrollViewerScrollInDirection(VirtualKey.Right); - } - } - break; - case VirtualKey.Down: - if (!isVertical && scrollViewport) - { - ElementScrollViewerScrollInDirection(VirtualKey.Down); - } - else - { - SelectNext(ref newFocusedIndex); - } - - if (GetFocusedIndex() == newFocusedIndex && scrollViewport) - { - ElementScrollViewerScrollInDirection(VirtualKey.Down); - } - break; - case VirtualKey.Home: - newFocusedIndex = 0; - break; - case VirtualKey.End: - newFocusedIndex = nCount - 1; - break; - case VirtualKey.PageUp: - NavigateByPage(/*forward*/false, newFocusedIndex); - break; - case VirtualKey.GamepadLeftTrigger: - if (isVertical) - { - NavigateByPage(/*forward*/false, newFocusedIndex); - } - break; - case VirtualKey.GamepadLeftShoulder: - if (!isVertical) - { - NavigateByPage(/*forward*/false, newFocusedIndex); - } - break; - case VirtualKey.PageDown: - NavigateByPage(/*forward*/true, newFocusedIndex); - break; - case VirtualKey.GamepadRightTrigger: - if (isVertical) - { - NavigateByPage(/*forward*/true, newFocusedIndex); - } - break; - case VirtualKey.GamepadRightShoulder: - if (!isVertical) - { - NavigateByPage(/*forward*/true, newFocusedIndex); - } - break; - } - newFocusedIndex = (int)(Math.Min(newFocusedIndex, (int)(nCount) - 1)); - newFocusedIndex = (int)(Math.Max(newFocusedIndex, -1)); - + // TODO Uno: Not implemented yet. } - SetFocusedItem( - INT index, - bool shouldScrollIntoView, - bool animateIfBringIntoView, - xaml_input.FocusNavigationDirection focusNavigationDirection, - InputActivationBehavior inputActivationBehavior) + private void OnSelectionChanged( + int oldSelectedIndex, + int newSelectedIndex, + object pOldSelectedItem, + object pNewSelectedItem, + bool animateIfBringIntoView = false, + FocusNavigationDirection focusNavigationDirection = FocusNavigationDirection.None) { - - bool hasFocus = false; - FocusState focusState = FocusState_Programmatic; - - HasFocus(&hasFocus); - if (hasFocus) + if (newSelectedIndex != -1) { - DependencyObject spFocused; - UIElement spFocusedAsElement; - - GetFocusedElement(&spFocused); - spFocusedAsElement = spFocused.AsOrNull(); - if (spFocusedAsElement) - { - focusState = spFocusedAsElement.FocusState; - MUX_ASSERT(FocusState_Unfocused != focusState, "FocusState_Unfocused unexpected since spFocusedAsElement is focused"); - } - } - - SetFocusedItem(index, shouldScrollIntoView, false /*forceFocus*/, focusState, animateIfBringIntoView, focusNavigationDirection, inputActivationBehavior); - - Cleanup: - RRETURN(hr); - } - - // Set the SelectorItem at index to be focused using an explicit FocusState - // The focus is only set if forceFocus is true or the Selector already has focus. - // ScrollIntoView is always called if shouldScrollIntoView is set (regardless of focus). - private void SetFocusedItem( - int index, - bool shouldScrollIntoView, - bool forceFocus, - FocusState focusState, - bool animateIfBringIntoView, - FocusNavigationDirection focusNavigationDirection, - InputActivationBehavior inputActivationBehavior) - { -#if SLTR_DEBUG - IGNOREHR(gps.DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "SLTR[0x%p]: SetFocusedItem. index=%d, shouldScrollIntoView=%d, forceFocus=%d, focusState=%d, animateIfBringIntoView=%d, focusNavigationDirection=%d", - this, index, shouldScrollIntoView, forceFocus, focusState, animateIfBringIntoView, focusNavigationDirection)); -#endif - - uint nCount = 0; - bool bFocused = false; - bool shouldFocus = false; - - var spItems = Items; - nCount = spItems.Cast().Size; - if (index < 0 || (INT)(nCount) <= index) - { - index = -1; - } - - if (index >= 0) - { - SetLastFocusedIndex(index); - } - - if (!forceFocus) - { - shouldFocus = HasFocus(); - } - else - { - shouldFocus = true; - } - - if (shouldFocus) - { - SetFocusedIndex(index); - } - - if (GetFocusedIndex() == -1) - { - if (shouldFocus) - { - // Since none of our child items have the focus, put the focus back on the main list box. - // - // This will happen e.g. when the focused item is being removed but is still in the visual tree at the time of this call. - // Note that this call may fail e.g. if IsTabStop is false, which is OK; it will just set focus - // to the next focusable element (or clear focus if none is found). - Focus(focusState); // FUTURE: should handle inputActivationBehavior - } - return; - } - - if (shouldScrollIntoView) - { - CanScrollIntoView(shouldScrollIntoView); - } - - if (shouldScrollIntoView) - { - ScrollIntoView( - index, - false /*isGroupItemIndex*/, - false /*isHeader*/, - false /*isFooter*/, - false /*isFromPublicAPI*/, - true /*ensureContainerRealized*/, - animateIfBringIntoView, - ScrollIntoViewAlignment.Default); - } - - if (shouldFocus) - { - SelectorItem spSelectorItem; - DependencyObject spContainer; - - spContainer = ContainerFromIndex(index); - spSelectorItem = spContainer as SelectorItem; - if (spSelectorItem is not null) - { - spSelectorItem.FocusSelfOrChild(focusState, animateIfBringIntoView, &bFocused, focusNavigationDirection, inputActivationBehavior); - } + // Only change the focus if there is a selected item. + // Use InputActivationBehavior.NoActivate because just changing selected item by default shouldn't steal activation from another window/island. + SetFocusedItem(newSelectedIndex, true /*shouldScrollIntoView*/, animateIfBringIntoView, focusNavigationDirection, InputActivationBehavior.NoActivate); } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/SelectorItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/SelectorItem.partial.mux.cs index 6366387a7585..5d801a52ea5c 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/SelectorItem.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/SelectorItem.partial.mux.cs @@ -11,7 +11,7 @@ partial class SelectorItem // If this item is unfocused, sets focus on the SelectorItem. // Otherwise, sets focus to whichever element currently has focus // (so focusState can be propagated). - private void FocusSelfOrChild( + internal void FocusSelfOrChild( FocusState focusState, bool animateIfBringIntoView, out bool pFocused,