From e43e53d7a38efbc8536e2133fd03b042e8ee940f Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 13 Jun 2024 14:36:42 +0200 Subject: [PATCH 01/28] chore: MenuFlyout adjustments prep --- .../UI/Xaml/Controls/Flyout/FlyoutBase.cs | 3 +- .../MenuFlyout/MenuFlyout.Properties.cs | 32 + .../UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs | 606 +++++---- .../Controls/MenuFlyout/MenuFlyout.mux.cs | 72 + .../Controls/MenuFlyout/MenuFlyoutSubItem.cs | 1171 ++++++++--------- .../MenuFlyout/MenuFlyoutSubItem.mux.cs | 27 + .../UI/Xaml/Controls/Popup/Popup.mux.cs | 2 +- .../UI/Xaml/IDependencyObjectInternal.cs | 19 +- 8 files changed, 1048 insertions(+), 884 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Properties.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs index be0698e33bbf..bae5c415b04f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs @@ -61,11 +61,10 @@ public partial class FlyoutBase : DependencyObject internal bool IsTargetPositionSet => m_isTargetPositionSet; - private bool m_isPositionedForDateTimePicker; + private protected bool m_isPositionedForDateTimePicker; private bool m_openingCanceled; - [NotImplemented] private InputDeviceType m_inputDeviceTypeUsedToOpen; internal FlyoutPlacementMode EffectivePlacement => m_hasPlacementOverride ? m_placementOverride : Placement; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Properties.cs new file mode 100644 index 000000000000..e1d2f681cace --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Properties.cs @@ -0,0 +1,32 @@ +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyout +{ + public IList Items + { + get => (IList)this.GetValue(ItemsProperty); + private set => this.SetValue(ItemsProperty, value); + } + + public static DependencyProperty ItemsProperty { get; } = + DependencyProperty.Register( + nameof(Items), + typeof(IList), + typeof(MenuFlyout), + new FrameworkPropertyMetadata(defaultValue: null)); + + public Style MenuFlyoutPresenterStyle + { + get => (Style)this.GetValue(MenuFlyoutPresenterStyleProperty); + set => SetValue(MenuFlyoutPresenterStyleProperty, value); + } + + public static DependencyProperty MenuFlyoutPresenterStyleProperty { get; } = + DependencyProperty.Register( + nameof(MenuFlyoutPresenterStyle), + typeof(Style), + typeof(MenuFlyout), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext)); +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs index efc0e11d4ed1..84505974260e 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs @@ -19,379 +19,415 @@ using Microsoft.UI.Xaml.Markup; using Microsoft.UI.Xaml.Media.Animation; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +[ContentProperty(Name = nameof(Items))] +public partial class MenuFlyout : FlyoutBase, IMenu { - [ContentProperty(Name = nameof(Items))] - public partial class MenuFlyout : FlyoutBase, IMenu - { - private readonly ObservableVector m_tpItems; + private readonly MenuFlyoutItemBaseCollection m_tpItems; - // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. - // UNO TODO private Transition m_tpMenuPopupThemeTransition = null; + // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. + // UNO TODO private Transition m_tpMenuPopupThemeTransition = null; - private WeakReference m_wrParentMenu; + private WeakReference m_wrParentMenu; - private bool m_openWindowed = true; + private bool m_openWindowed = false; // TODO Uno specific: Always false in Uno for now. - //private bool m_openingWindowedInProgress; + //private bool m_openingWindowedInProgress; - public MenuFlyout() - { - m_isPositionedAtPoint = true; - InputDeviceTypeUsedToOpen = FocusInputDeviceKind.None; + public MenuFlyout() + { + m_isPositionedAtPoint = true; + m_inputDeviceTypeUsedToOpen = FocusInputDeviceKind.None; - m_tpItems = new ObservableVector(); + PrepareState(); + } - Items = m_tpItems; - } + // Prepares object's state + private void PrepareState() + { + base.PrepareState(); - internal FocusInputDeviceKind InputDeviceTypeUsedToOpen { get; set; } + MenuFlyoutItemBaseCollection spItems = new(); + CoreImports.Collection_SetOwner(spItems, this); + m_tpItems = spItems; - internal protected override void OnDataContextChanged(DependencyPropertyChangedEventArgs e) - { - base.OnDataContextChanged(e); + Items = spItems; + } - SetFlyoutItemsDataContext(); - } + /* + _Check_return_ HRESULT MenuFlyout::DisconnectFrameworkPeerCore() +{ + HRESULT hr = S_OK; + + // + // DXAML Structure + // --------------- + // DXAML::MenuFlyout (this) --------------> DXAML::MenuFlyoutItemBase + // | | + // | +---------> DXAML::MenuFlyoutItemBase + // V + // DXAML::MenuFlyoutItemBaseCollection (m_tpItems) + // + // CORE Structure + // -------------- + // Core::CControl (this) < - - + < - - + + // | : : + // V : : + // Core::CMenuFlyoutItemBaseCollection - - + (m_pOwner) : + // | | : + // V V : + // Core::CControl Core::CControl - - - - - - - - - - + (m_pParent) + // + // To clear the m_pParent association of the MenuFlyoutItemBase, we have to clear the + // Core::CMenuFlyoutItemBaseCollection's children, which calls SetParent(NULL) on each of its + // children. Once this association to MenuFlyout is broken, we can safely destroy MenuFlyout. + // + + // clear the children in the MenuFlyoutItemBaseCollection + if (m_tpItems.GetAsCoreDO() != nullptr) + { + IFC(CoreImports::Collection_Clear(static_cast(m_tpItems.GetAsCoreDO()))); + IFC(CoreImports::Collection_SetOwner(static_cast(m_tpItems.GetAsCoreDO()), nullptr)); + } + + IFC(MenuFlyoutGenerated::DisconnectFrameworkPeerCore()); + +Cleanup: + RRETURN(hr); +} + */ - private void SetFlyoutItemsDataContext() + internal void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) + { + if (e.Property == MenuFlyoutPresenterStyleProperty && + GetPresenter() != null) { - // This is present to force the dataContext to be passed to the popup of the flyout since it is not directly a child in the visual tree of the flyout. - Items?.ForEach(item => item?.SetValue( - UIElement.DataContextProperty, - this.DataContext, - precedence: DependencyPropertyValuePrecedences.Inheritance - )); + SetPresenterStyle(GetPresenter(), e.NewValue as Style); } + } + + protected override Control CreatePresenter() + { + MenuFlyoutPresenter presenter = new(); + presenter.SetParentMenuFlyout(this); + + var style = MenuFlyoutPresenterStyle; + SetPresenterStyle(presenter, style); - #region Items DependencyProperty + return presenter; + } - public IList Items + private protected override void ShowAtCore(FrameworkElement pPlacementTarget, FlyoutShowOptions showOptions) + { + openDelayed = false; + if (m_openWindowed) { - get => (IList)this.GetValue(ItemsProperty); - private set => this.SetValue(ItemsProperty, value); + SetIsWindowedPopup(); + } + else + { + m_openWindowed = true; } - public static DependencyProperty ItemsProperty { get; } = - DependencyProperty.Register( - "Items", - typeof(IList), - typeof(MenuFlyout), - new FrameworkPropertyMetadata(defaultValue: null) - ); + var placementTarget = pPlacementTarget; + CacheInputDeviceTypeUsedToOpen(placementTarget); - #endregion + base.ShowAtCore(pPlacementTarget, showOptions); + } - public Style MenuFlyoutPresenterStyle + // Raise Opening event. + private protected override void OnOpening() + { + // Update the TemplateSettings as it is about to open. + Control presenter = GetPresenter(); + MenuFlyoutPresenter menuFlyoutPresenter = presenter as MenuFlyoutPresenter; + + if (menuFlyoutPresenter is not null) { - get => (Style)this.GetValue(MenuFlyoutPresenterStyleProperty); - set => SetValue(MenuFlyoutPresenterStyleProperty, value); + IMenu parentMenu = (this as IMenu).ParentMenu as IMenu; + + (menuFlyoutPresenter as IMenuPresenter).OwningMenu = parentMenu != null ? parentMenu : this; + menuFlyoutPresenter.UpdateTemplateSettings(); } - public static DependencyProperty MenuFlyoutPresenterStyleProperty { get; } = - DependencyProperty.Register( - "MenuFlyoutPresenterStyle", - typeof(Style), - typeof(MenuFlyout), - new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext, (s, e) => (s as MenuFlyout).OnPropertyChanged2(s, e))); + base.OnOpening(); - public void ShowAt(UIElement targetElement, Point point) + if (menuFlyoutPresenter is not null) { - ShowAtCore((FrameworkElement)targetElement, new FlyoutShowOptions { Position = point }); + // Reset the presenter's ItemsSource. Since Items is not an IObservableVector, we don't + // automatically respond to changes within the vector. Clearing the property when the presenter + // unloads and resetting it before we reopen ensures any changes to Items are reflected + // when the MenuFlyoutPresenter shows. It also allows sharing of MenuFlyouts; since MenuFlyoutItemBases + // are UIElements they must be unparented when leaving the tree before they can be inserted elsewhere. + menuFlyoutPresenter.ItemsSource = m_tpItems; + + AutomationPeer.RaiseEventIfListener(menuFlyoutPresenter, AutomationEvents.MenuOpened); } + } - private void OnPropertyChanged2(DependencyObject s, DependencyPropertyChangedEventArgs e) + internal override void OnClosing(ref bool cancel) + { + base.OnClosing(ref cancel); + + if (!cancel) { - if (e.Property == MenuFlyoutPresenterStyleProperty && - GetPresenter() != null) - { - SetPresenterStyle(GetPresenter(), e.NewValue as Style); - } + CloseSubMenu(); } + } - protected override Control CreatePresenter() - { - MenuFlyoutPresenter presenter = new MenuFlyoutPresenter(); - Style style; + private protected override void OnClosed() + { + CloseSubMenu(); - presenter.SetParentMenuFlyout(this); + AutomationPeer.RaiseEventIfListener(GetPresenter(), AutomationEvents.MenuClosed); - style = MenuFlyoutPresenterStyle; - SetPresenterStyle(presenter, style); + base.OnClosed(); - return presenter; + if (GetPresenter() is MenuFlyoutPresenter presenter) + { + presenter.m_iFocusedIndex = -1; + presenter.ItemsSource = null; } + } + + private void CloseSubMenu() + { + var presenter = (MenuFlyoutPresenter)GetPresenter(); - private protected override void ShowAtCore(FrameworkElement pPlacementTarget, FlyoutShowOptions showOptions) + if (presenter is not null) { - m_openWindowed = false; - - base.ShowAtCore(pPlacementTarget, showOptions); + var subPresenter = (presenter as IMenuPresenter).SubPresenter; - if (m_openWindowed) + if (subPresenter != null) { - // UNO TODO - // SetIsWindowedPopup(); + subPresenter.CloseSubMenu(); } - else - { - m_openWindowed = true; - } - - var placementTarget = pPlacementTarget; - CacheInputDeviceTypeUsedToOpen(placementTarget); } + } - // Raise Opening event. - private protected override void OnOpening() - { - // Update the TemplateSettings as it is about to open. - Control presenter = GetPresenter(); - MenuFlyoutPresenter menuFlyoutPresenter = presenter as MenuFlyoutPresenter; + internal FocusInputDeviceKind InputDeviceTypeUsedToOpen => m_inputDeviceTypeUsedToOpen; - if (menuFlyoutPresenter is not null) - { - IMenu parentMenu = (this as IMenu).ParentMenu as IMenu; + internal protected override void OnDataContextChanged(DependencyPropertyChangedEventArgs e) + { + base.OnDataContextChanged(e); - (menuFlyoutPresenter as IMenuPresenter).OwningMenu = parentMenu != null ? parentMenu : this; - menuFlyoutPresenter.UpdateTemplateSettings(); - } + SetFlyoutItemsDataContext(); + } - base.OnOpening(); + private void SetFlyoutItemsDataContext() + { + // This is present to force the dataContext to be passed to the popup of the flyout since it is not directly a child in the visual tree of the flyout. + Items?.ForEach(item => item?.SetValue( + UIElement.DataContextProperty, + this.DataContext, + precedence: DependencyPropertyValuePrecedences.Inheritance + )); + } - if (menuFlyoutPresenter is not null) - { - // Reset the presenter's ItemsSource. Since Items is not an IObservableVector, we don't - // automatically respond to changes within the vector. Clearing the property when the presenter - // unloads and resetting it before we reopen ensures any changes to Items are reflected - // when the MenuFlyoutPresenter shows. It also allows sharing of MenuFlyouts; since MenuFlyoutItemBases - // are UIElements they must be unparented when leaving the tree before they can be inserted elsewhere. - menuFlyoutPresenter.ItemsSource = m_tpItems; - - AutomationPeer.RaiseEventIfListener(menuFlyoutPresenter, AutomationEvents.MenuOpened); - } - } + + public void ShowAt(UIElement targetElement, Point point) + { + ShowAtCore((FrameworkElement)targetElement, new FlyoutShowOptions { Position = point }); + } - private protected override void OnClosed() - { - base.OnClosed(); - CloseSubMenu(); + - AutomationPeer.RaiseEventIfListener(GetPresenter(), AutomationEvents.MenuClosed); - if (GetPresenter() is MenuFlyoutPresenter presenter) - { - presenter.m_iFocusedIndex = -1; - presenter.ItemsSource = null; - } - } + - void CloseSubMenu() - { - MenuFlyoutPresenter presenter = (MenuFlyoutPresenter)GetPresenter(); + - if (presenter != null) - { - IMenuPresenter subPresenter = (presenter as IMenuPresenter).SubPresenter; +#if false + void PreparePopupTheme( + Popup pPopup, + MajorPlacementMode placementMode, + FrameworkElement pPlacementTarget) + { + bool areOpenCloseAnimationsEnabled = false; + areOpenCloseAnimationsEnabled = AreOpenCloseAnimationsEnabled; - if (subPresenter != null) - { - subPresenter.CloseSubMenu(); - } - } + if (!areOpenCloseAnimationsEnabled) + { + return; } -#if false - void PreparePopupTheme( - Popup pPopup, - MajorPlacementMode placementMode, - FrameworkElement pPlacementTarget) - { - bool areOpenCloseAnimationsEnabled = false; - areOpenCloseAnimationsEnabled = AreOpenCloseAnimationsEnabled; + // On Threshold, we use the MenuPopupThemeTransition for MenuFlyouts. + double openedLength = 0; - if (!areOpenCloseAnimationsEnabled) - { - return; - } + // UNO TODO + //if (m_tpMenuPopupThemeTransition == null) + //{ + // Transition spMenuPopupChildTransition; + // spMenuPopupChildTransition = PreparePopupThemeTransitionsAndShadows(pPopup, 0.5 /* closedRatioConstant */, 0 /* depth */); + // m_tpMenuPopupThemeTransition = spMenuPopupChildTransition; + //} - // On Threshold, we use the MenuPopupThemeTransition for MenuFlyouts. - double openedLength = 0; + openedLength = ((Control)GetPresenter()).ActualHeight; - // UNO TODO - //if (m_tpMenuPopupThemeTransition == null) - //{ - // Transition spMenuPopupChildTransition; - // spMenuPopupChildTransition = PreparePopupThemeTransitionsAndShadows(pPopup, 0.5 /* closedRatioConstant */, 0 /* depth */); - // m_tpMenuPopupThemeTransition = spMenuPopupChildTransition; - //} + // UNO TODO + // (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; + var direction = (placementMode == MajorPlacementMode.Top) ? AnimationDirection.Bottom : AnimationDirection.Top; - openedLength = ((Control)GetPresenter()).ActualHeight; + // UNO TODO + // (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).Direction = direction); + } - // UNO TODO - // (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; - var direction = (placementMode == MajorPlacementMode.Top) ? AnimationDirection.Bottom : AnimationDirection.Top; + ///*static*/ + //Transition PreparePopupThemeTransitionsAndShadows( + // Popup popup, + // double closedRatioConstant, + // uint depth) + //{ + // Transition spTransition; + // TransitionCollection spTransitionCollection; + // MenuPopupThemeTransition spMenuPopupChildTransition; + + // *transition = null; + + // (ctl.make(&spMenuPopupChildTransition)); + // (ctl.make(&spTransitionCollection)); + + // spMenuPopupChildTransition.ClosedRatio = closedRatioConstant; + + // FrameworkElement overlayElement; + // overlayElement = popup.OverlayElement; + // spMenuPopupChildTransition.SetOverlayElement(overlayElement); + + // spTransition = spMenuPopupChildTransition; + // spTransitionCollection.Append(spTransition)); + + // // UNO TODO Windowed Popups are not supported + // // + // // For windowed popups, the transition needs to target the grandchild of the popup. + // // Otherwise, the transition LTE is going to live in the main window and get clipped. + // //if ((CPopup*)(popup.GetHandle()).IsWindowed()) + // //{ + // // UIElement popupChild; + // // popupChild = popup.Child; + + // // if (popupChild) + // // { + // // int childrenCount; + // // (VisualTreeHelper.GetChildrenCountStatic((UIElement*)(popupChild), &childrenCount)); + + // // if (childrenCount == 1) + // // { + // // xaml.IDependencyObject popupGrandChildAsDO; + // // (VisualTreeHelper.GetChildStatic((UIElement*)(popupChild), 0, &popupGrandChildAsDO)); + + // // if (popupGrandChildAsDO) + // // { + // // UIElement popupGrandChildAsUE; + // // (popupGrandChildAsDO.As(&popupGrandChildAsUE)); + // // (popupGrandChildAsUE.Transitions = spTransitionCollection); + // // (popupGrandChildAsUE.InvalidateMeasure()); + // // } + // // } + // // } + // //} + // //else + // { + // popup.ChildTransitions = spTransitionCollection; + // } + //} + + void AutoAdjustPlacement(MajorPlacementMode pPlacement) + { + // UNO TODO + // Rect windowRect = default; + // (DXamlCore.GetCurrent().GetContentBoundsForElement(GetHandle(), &windowRect)); + } - // UNO TODO - // (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).Direction = direction); - } + void ShowAtImpl(UIElement pTargetElement, Point targetPoint) + { + var targetElement = pTargetElement; - ///*static*/ - //Transition PreparePopupThemeTransitionsAndShadows( - // Popup popup, - // double closedRatioConstant, - // uint depth) - //{ - // Transition spTransition; - // TransitionCollection spTransitionCollection; - // MenuPopupThemeTransition spMenuPopupChildTransition; - - // *transition = null; - - // (ctl.make(&spMenuPopupChildTransition)); - // (ctl.make(&spTransitionCollection)); - - // spMenuPopupChildTransition.ClosedRatio = closedRatioConstant; - - // FrameworkElement overlayElement; - // overlayElement = popup.OverlayElement; - // spMenuPopupChildTransition.SetOverlayElement(overlayElement); - - // spTransition = spMenuPopupChildTransition; - // spTransitionCollection.Append(spTransition)); - - // // UNO TODO Windowed Popups are not supported - // // - // // For windowed popups, the transition needs to target the grandchild of the popup. - // // Otherwise, the transition LTE is going to live in the main window and get clipped. - // //if ((CPopup*)(popup.GetHandle()).IsWindowed()) - // //{ - // // UIElement popupChild; - // // popupChild = popup.Child; - - // // if (popupChild) - // // { - // // int childrenCount; - // // (VisualTreeHelper.GetChildrenCountStatic((UIElement*)(popupChild), &childrenCount)); - - // // if (childrenCount == 1) - // // { - // // xaml.IDependencyObject popupGrandChildAsDO; - // // (VisualTreeHelper.GetChildStatic((UIElement*)(popupChild), 0, &popupGrandChildAsDO)); - - // // if (popupGrandChildAsDO) - // // { - // // UIElement popupGrandChildAsUE; - // // (popupGrandChildAsDO.As(&popupGrandChildAsUE)); - // // (popupGrandChildAsUE.Transitions = spTransitionCollection); - // // (popupGrandChildAsUE.InvalidateMeasure()); - // // } - // // } - // // } - // //} - // //else - // { - // popup.ChildTransitions = spTransitionCollection; - // } - //} + var showOptions = new FlyoutShowOptions(); + showOptions.Position = targetPoint; - void AutoAdjustPlacement(MajorPlacementMode pPlacement) + try { - // UNO TODO - // Rect windowRect = default; - // (DXamlCore.GetCurrent().GetContentBoundsForElement(GetHandle(), &windowRect)); - } + m_openingWindowedInProgress = true; - void ShowAtImpl(UIElement pTargetElement, Point targetPoint) + ShowAt(targetElement, showOptions); + } + finally { - var targetElement = pTargetElement; - - var showOptions = new FlyoutShowOptions(); - showOptions.Position = targetPoint; - - try - { - m_openingWindowedInProgress = true; - - ShowAt(targetElement, showOptions); - } - finally - { - m_openingWindowedInProgress = false; - } + m_openingWindowedInProgress = false; } + } #endif - void CacheInputDeviceTypeUsedToOpen(UIElement pTargetElement) - { - // UNO TODO - //CContentRoot* contentRoot = VisualTree.GetContentRootForElement(pTargetElement); - //InputDeviceTypeUsedToOpen = contentRoot.GetInputManager().GetLastInputDeviceType(); - } + void CacheInputDeviceTypeUsedToOpen(UIElement pTargetElement) + { + // UNO TODO + //CContentRoot* contentRoot = VisualTree.GetContentRootForElement(pTargetElement); + //InputDeviceTypeUsedToOpen = contentRoot.GetInputManager().GetLastInputDeviceType(); + } #if false - // Callback for ShowAt() from core layer - void ShowAtStatic(MenuFlyout pCoreMenuFlyout, - UIElement pCoreTarget, - Point point) - { - Debug.Assert(pCoreMenuFlyout != null); - Debug.Assert(pCoreTarget != null); + // Callback for ShowAt() from core layer + void ShowAtStatic(MenuFlyout pCoreMenuFlyout, + UIElement pCoreTarget, + Point point) + { + Debug.Assert(pCoreMenuFlyout != null); + Debug.Assert(pCoreTarget != null); - DependencyObject target = pCoreTarget; + DependencyObject target = pCoreTarget; - pCoreMenuFlyout.ShowAtImpl(target as FrameworkElement, point); - } -#endif + pCoreMenuFlyout.ShowAtImpl(target as FrameworkElement, point); + } - protected override void OnProcessKeyboardAccelerators(ProcessKeyboardAcceleratorEventArgs args) + void OnProcessKeyboardAcceleratorsImpl(ProcessKeyboardAcceleratorEventArgs pArgs) + { + if (m_tpItems != null) { - if (m_tpItems != null) + var itemCount = m_tpItems.Count; + for (int i = 0; i < itemCount; i++) { - var itemCount = m_tpItems.Count; - for (int i = 0; i < itemCount; i++) - { - MenuFlyoutItemBase spItem = m_tpItems[i]; - (spItem as MenuFlyoutItemBase).TryInvokeKeyboardAccelerator(args); - } + MenuFlyoutItemBase spItem = m_tpItems[i]; + (spItem as MenuFlyoutItemBase).TryInvokeKeyboardAccelerator(pArgs); } } + } +#endif - IMenu IMenu.ParentMenu + IMenu IMenu.ParentMenu + { + get => m_wrParentMenu?.Target as IMenu; + set { - get => m_wrParentMenu?.Target as IMenu; - set - { - m_wrParentMenu = new WeakReference(value); + m_wrParentMenu = new WeakReference(value); - // If we have a parent menu, then we want to disable the light-dismiss overlay - - // in that circumstance, the parent menu will have a light-dismiss overlay that we'll use instead. - IsLightDismissOverlayEnabled = value == null; + // If we have a parent menu, then we want to disable the light-dismiss overlay - + // in that circumstance, the parent menu will have a light-dismiss overlay that we'll use instead. + IsLightDismissOverlayEnabled = value == null; - Control presenter = GetPresenter(); - MenuFlyoutPresenter menuFlyoutPresenter = presenter as MenuFlyoutPresenter; + Control presenter = GetPresenter(); + MenuFlyoutPresenter menuFlyoutPresenter = presenter as MenuFlyoutPresenter; - if (menuFlyoutPresenter is { }) - { - menuFlyoutPresenter.IsSubPresenter = value != null; - } + if (menuFlyoutPresenter is { }) + { + menuFlyoutPresenter.IsSubPresenter = value != null; } } + } - void IMenu.Close() - { - Hide(); - } + void IMenu.Close() + { + Hide(); + } #if false - bool IsWindowedPopup() - { - return false; + bool IsWindowedPopup() + { + return false; - // UNO TODO - // return CPopup.DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle()) && (FlyoutBase.IsWindowedPopup() || m_openingWindowedInProgress); - } -#endif + // UNO TODO + // return CPopup.DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle()) && (FlyoutBase.IsWindowedPopup() || m_openingWindowedInProgress); } +#endif } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs new file mode 100644 index 000000000000..45c2579f60a1 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs @@ -0,0 +1,72 @@ +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyout +{ + internal static void KeyboardAcceleratorFlyoutItemEnter( + DependencyObject* element, + DependencyObject* pNamescopeOwner, + KnownPropertyIndex collectionPropertyIndex, + EnterParams parameters) + { + CValue value; + IFC_RETURN(element->GetValueByIndex(collectionPropertyIndex, &value)); + + if (CMenuFlyoutItemBaseCollection * const items = do_pointer_cast(value.AsObject())) + + { + // This is a dead enter to register any keyboard accelerators that may be present in the MenuFlyout items + // to the list of live accelerators + params.fIsForKeyboardAccelerator = true; + params.fIsLive = false; + params.fSkipNameRegistration = true; + params.fUseLayoutRounding = false; + params.fCoercedIsEnabled = false; + + for (CDependencyObject* item : *items) + { + IFC_RETURN(item->Enter(pNamescopeOwner, params)); + } + } + + return S_OK; + } + + internal static void KeyboardAcceleratorFlyoutItemLeave( + DependencyObject element, + DependencyObject pNamescopeOwner, + KnownPropertyIndex collectionPropertyIndex, + LeaveParams parameters) + { + CValue value; + IFC_RETURN(element->GetValueByIndex(collectionPropertyIndex, &value)); + + if (CMenuFlyoutItemBaseCollection * const items = do_pointer_cast(value.AsObject())) + + { + // This is a dead leave to remove any keyboard accelerators that may be present in the MenuFlyout items + // from the list of live accelerators + params.fIsForKeyboardAccelerator = true; + params.fIsLive = false; + params.fSkipNameRegistration = true; + params.fUseLayoutRounding = false; + params.fCoercedIsEnabled = false; + + for (CDependencyObject* item : *items) + { + IFC_RETURN(item->Leave(pNamescopeOwner, params)); + } + } + } + + private void EnterImpl(DependencyObject namescopeOwner, EnterParams parameters) + { + //base.EnterImpl(pNamescopeOwner, params)); + IFC_RETURN(KeyboardAcceleratorFlyoutItemEnter(this, pNamescopeOwner, KnownPropertyIndex::MenuFlyout_Items, params)); + } + + private void LeaveImpl(DependencyObject namescopeOwner, LeaveParams parameters) + { + //base.LeaveImpl(pNamescopeOwner, params); + IFC_RETURN(KeyboardAcceleratorFlyoutItemLeave(this, pNamescopeOwner, KnownPropertyIndex::MenuFlyout_Items, params)); + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs index a28dbf7de656..29f1a3ab6933 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs @@ -10,488 +10,488 @@ using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Markup; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +[ContentProperty(Name = nameof(Items))] +public partial class MenuFlyoutSubItem : MenuFlyoutItemBase, ISubMenuOwner { - [ContentProperty(Name = nameof(Items))] - public partial class MenuFlyoutSubItem : MenuFlyoutItemBase, ISubMenuOwner - { - // Popup for the MenuFlyoutSubItem - Popup m_tpPopup; + // Popup for the MenuFlyoutSubItem + Popup m_tpPopup; - // Presenter for the MenuFlyoutSubItem - Control m_tpPresenter; + // Presenter for the MenuFlyoutSubItem + Control m_tpPresenter; - // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. - // UNO TODO Transition m_tpMenuPopupThemeTransition = null; + // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. + // UNO TODO Transition m_tpMenuPopupThemeTransition = null; - // Event pointer for the Loaded event - // IDisposable m_epLoadedHandler; + // Event pointer for the Loaded event + // IDisposable m_epLoadedHandler; - // Event pointer for the size changed on the MenuFlyoutSubItem's presenter - IDisposable m_epPresenterSizeChangedHandler; + // Event pointer for the size changed on the MenuFlyoutSubItem's presenter + IDisposable m_epPresenterSizeChangedHandler; - // Helper to which to delegate cascading menu functionality. - CascadingMenuHelper m_menuHelper; + // Helper to which to delegate cascading menu functionality. + CascadingMenuHelper m_menuHelper; - // Weak reference the parent that owns the menu that this item belongs to. - private WeakReference m_wrParentOwner; + // Weak reference the parent that owns the menu that this item belongs to. + private WeakReference m_wrParentOwner; - DependencyObjectCollection m_tpItems; + DependencyObjectCollection m_tpItems; - public string Text - { - get => (string)this.GetValue(TextProperty) ?? ""; - set => SetValue(TextProperty, value); - } + public string Text + { + get => (string)this.GetValue(TextProperty) ?? ""; + set => SetValue(TextProperty, value); + } - public IList Items => m_tpItems; + public IList Items => m_tpItems; - public IconElement Icon - { - get => (IconElement)this.GetValue(IconProperty); - set => this.SetValue(IconProperty, value); - } + public IconElement Icon + { + get => (IconElement)this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); + } - public static Microsoft.UI.Xaml.DependencyProperty TextProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - "Text", - typeof(string), - typeof(MenuFlyoutSubItem), - new FrameworkPropertyMetadata(default(string))); - - public static Microsoft.UI.Xaml.DependencyProperty IconProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - "Icon", - typeof(IconElement), - typeof(MenuFlyoutSubItem), - new FrameworkPropertyMetadata(default(IconElement))); - - public MenuFlyoutSubItem() - { + public static Microsoft.UI.Xaml.DependencyProperty TextProperty { get; } = + Microsoft.UI.Xaml.DependencyProperty.Register( + "Text", + typeof(string), + typeof(MenuFlyoutSubItem), + new FrameworkPropertyMetadata(default(string))); + + public static Microsoft.UI.Xaml.DependencyProperty IconProperty { get; } = + Microsoft.UI.Xaml.DependencyProperty.Register( + "Icon", + typeof(IconElement), + typeof(MenuFlyoutSubItem), + new FrameworkPropertyMetadata(default(IconElement))); + + public MenuFlyoutSubItem() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ", this)); #endif // MFSI_DEBUG - PrepareState(); + PrepareState(); - DefaultStyleKey = typeof(MenuFlyoutSubItem); - } + DefaultStyleKey = typeof(MenuFlyoutSubItem); + } - void PrepareState() - { + void PrepareState() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareState.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareState.", this)); #endif // MFSI_DEBUG - // Create the sub menu items collection and set the owner - m_tpItems = new DependencyObjectCollection(this); + // Create the sub menu items collection and set the owner + m_tpItems = new DependencyObjectCollection(this); - m_menuHelper = new CascadingMenuHelper(); - m_menuHelper.Initialize(this); - } + m_menuHelper = new CascadingMenuHelper(); + m_menuHelper.Initialize(this); + } #if false - void DisconnectFrameworkPeerCore() - { - // Ensure the clean up the items whenever MenuFlyoutSubItem is disconnected - //if (m_tpItems.GetAsCoreDO() != null) - //{ - // (Collection_Clear((CCollection*)(m_tpItems.GetAsCoreDO()))); - // (Collection_SetOwner((CCollection*)(m_tpItems.GetAsCoreDO()), null)); - //} - - // (MenuFlyoutSubItemGenerated.DisconnectFrameworkPeerCore()); - } + void DisconnectFrameworkPeerCore() + { + // Ensure the clean up the items whenever MenuFlyoutSubItem is disconnected + //if (m_tpItems.GetAsCoreDO() != null) + //{ + // (Collection_Clear((CCollection*)(m_tpItems.GetAsCoreDO()))); + // (Collection_SetOwner((CCollection*)(m_tpItems.GetAsCoreDO()), null)); + //} + + // (MenuFlyoutSubItemGenerated.DisconnectFrameworkPeerCore()); + } #endif - protected override void OnApplyTemplate() - { + protected override void OnApplyTemplate() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnApplyTemplate.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnApplyTemplate.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnApplyTemplate(); - } + m_menuHelper.OnApplyTemplate(); + } - // PointerEntered event handler that shows the MenuFlyoutSubItem - // whenever the pointer is over to the - // In case of touch, the MenuFlyoutSubItem will be shown by - // PointerReleased event. + // PointerEntered event handler that shows the MenuFlyoutSubItem + // whenever the pointer is over to the + // In case of touch, the MenuFlyoutSubItem will be shown by + // PointerReleased event. - protected override void OnPointerEntered(PointerRoutedEventArgs args) - { - base.OnPointerEntered(args); + protected override void OnPointerEntered(PointerRoutedEventArgs args) + { + base.OnPointerEntered(args); #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerEntered.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerEntered.", this)); #endif // MFSI_DEBUG - UpdateParentOwner(null /*parentMenuFlyoutPresenter*/); - m_menuHelper.OnPointerEntered(args); - } + UpdateParentOwner(null /*parentMenuFlyoutPresenter*/); + m_menuHelper.OnPointerEntered(args); + } - // PointerExited event handler that ensures the close MenuFlyoutSubItem - // whenever the pointer over is out of the current MenuFlyoutSubItem or - // out of the main presenter. If the exited point is on MenuFlyoutSubItem - // or sub presenter position, we want to keep the opened + // PointerExited event handler that ensures the close MenuFlyoutSubItem + // whenever the pointer over is out of the current MenuFlyoutSubItem or + // out of the main presenter. If the exited point is on MenuFlyoutSubItem + // or sub presenter position, we want to keep the opened - protected override void OnPointerExited(PointerRoutedEventArgs args) - { - base.OnPointerExited(args); + protected override void OnPointerExited(PointerRoutedEventArgs args) + { + base.OnPointerExited(args); #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerExited.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerExited.", this)); #endif // MFSI_DEBUG - bool parentIsSubMenu = false; - MenuFlyoutPresenter parentPresenter = GetParentMenuFlyoutPresenter(); - - if (parentPresenter != null) - { - parentIsSubMenu = parentPresenter.IsSubPresenter; - } + bool parentIsSubMenu = false; + MenuFlyoutPresenter parentPresenter = GetParentMenuFlyoutPresenter(); - m_menuHelper.OnPointerExited(args, parentIsSubMenu); + if (parentPresenter != null) + { + parentIsSubMenu = parentPresenter.IsSubPresenter; } - // PointerPressed event handler that ensures the pressed state. + m_menuHelper.OnPointerExited(args, parentIsSubMenu); + } - protected override void OnPointerPressed(PointerRoutedEventArgs args) - { - base.OnPointerPressed(args); + // PointerPressed event handler that ensures the pressed state. + + protected override void OnPointerPressed(PointerRoutedEventArgs args) + { + base.OnPointerPressed(args); #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerPressed.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerPressed.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnPointerPressed(args); - } + m_menuHelper.OnPointerPressed(args); + } - // PointerReleased event handler that shows MenuFlyoutSubItem in - // case of touch input. + // PointerReleased event handler that shows MenuFlyoutSubItem in + // case of touch input. - protected override void OnPointerReleased(PointerRoutedEventArgs args) - { - base.OnPointerReleased(args); + protected override void OnPointerReleased(PointerRoutedEventArgs args) + { + base.OnPointerReleased(args); #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerReleased.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerReleased.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnPointerReleased(args); + m_menuHelper.OnPointerReleased(args); - } + } - protected override void OnGotFocus(RoutedEventArgs args) - { - base.OnGotFocus(args); + protected override void OnGotFocus(RoutedEventArgs args) + { + base.OnGotFocus(args); #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnGotFocus.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnGotFocus.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnGotFocus(args); - } + m_menuHelper.OnGotFocus(args); + } - protected override void OnLostFocus(RoutedEventArgs args) - { - base.OnLostFocus(args); + protected override void OnLostFocus(RoutedEventArgs args) + { + base.OnLostFocus(args); #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnLostFocus.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnLostFocus.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnLostFocus(args); - } + m_menuHelper.OnLostFocus(args); + } - // KeyDown event handler that handles the keyboard navigation between - // the menu items and shows the MenuFlyoutSubItem in case of hitting - // the enter or right arrow key. + // KeyDown event handler that handles the keyboard navigation between + // the menu items and shows the MenuFlyoutSubItem in case of hitting + // the enter or right arrow key. - protected override void OnKeyDown(KeyRoutedEventArgs args) - { - base.OnKeyDown(args); + protected override void OnKeyDown(KeyRoutedEventArgs args) + { + base.OnKeyDown(args); #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyDown.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyDown.", this)); #endif // MFSI_DEBUG - bool handled = args.Handled; - bool shouldHandleEvent = false; + bool handled = args.Handled; + bool shouldHandleEvent = false; - if (!handled) + if (!handled) + { + MenuFlyoutPresenter spParentPresenter = GetParentMenuFlyoutPresenter(); + + if (spParentPresenter != null) { - MenuFlyoutPresenter spParentPresenter = GetParentMenuFlyoutPresenter(); + var key = args.Key; - if (spParentPresenter != null) + // Navigate each item with the arrow down or up key + if (key == VirtualKey.Down || key == VirtualKey.Up) { - var key = args.Key; + spParentPresenter.HandleUpOrDownKey(key == VirtualKey.Down); + UpdateVisualState(); - // Navigate each item with the arrow down or up key - if (key == VirtualKey.Down || key == VirtualKey.Up) - { - spParentPresenter.HandleUpOrDownKey(key == VirtualKey.Down); - UpdateVisualState(); - - // If we handle the event here, it won't get handled in m_menuHelper.OnKeyDown, - // so we'll do that afterwards. - shouldHandleEvent = true; - } + // If we handle the event here, it won't get handled in m_menuHelper.OnKeyDown, + // so we'll do that afterwards. + shouldHandleEvent = true; } } - - m_menuHelper.OnKeyDown(args); - args.Handled = shouldHandleEvent; } + m_menuHelper.OnKeyDown(args); + args.Handled = shouldHandleEvent; + } + - protected override void OnKeyUp(KeyRoutedEventArgs args) - { - base.OnKeyUp(args); + protected override void OnKeyUp(KeyRoutedEventArgs args) + { + base.OnKeyUp(args); #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyUp.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyUp.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnKeyUp(args); - } + m_menuHelper.OnKeyUp(args); + } - // Ensure the creating the popup and menu presenter to show the - void EnsurePopupAndPresenter() - { + // Ensure the creating the popup and menu presenter to show the + void EnsurePopupAndPresenter() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsurePopupAndPresenter.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsurePopupAndPresenter.", this)); #endif // MFSI_DEBUG - if (m_tpPopup == null) - { - MenuFlyoutPresenter spParentMenuFlyoutPresenter = null; - Control spPresenter; - UIElement spPresenterAsUI; - FrameworkElement spPresenterAsFE; - Popup spPopup; + if (m_tpPopup == null) + { + MenuFlyoutPresenter spParentMenuFlyoutPresenter = null; + Control spPresenter; + UIElement spPresenterAsUI; + FrameworkElement spPresenterAsFE; + Popup spPopup; - spPopup = new Popup(); - spPopup.IsSubMenu = true; - spPopup.IsLightDismissEnabled = false; + spPopup = new Popup(); + spPopup.IsSubMenu = true; + spPopup.IsLightDismissEnabled = false; - spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); #if false // UNO TODO Windowed Popup is not available - if (spParentMenuFlyoutPresenter != null) + if (spParentMenuFlyoutPresenter != null) + { + spParentMenuFlyout = spParentMenuFlyoutPresenter.GetParentMenuFlyout(); + // Set the windowed Popup if the MenuFlyout is set the windowed Popup + if (spParentMenuFlyout && spParentMenuFlyout.IsWindowedPopup()) { - spParentMenuFlyout = spParentMenuFlyoutPresenter.GetParentMenuFlyout(); - // Set the windowed Popup if the MenuFlyout is set the windowed Popup - if (spParentMenuFlyout && spParentMenuFlyout.IsWindowedPopup()) - { - ASSERT((CPopup*)(spPopup as Popup.GetHandle()).DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle())); + ASSERT((CPopup*)(spPopup as Popup.GetHandle()).DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle())); - ((CPopup*)(spPopup as Popup.GetHandle()).SetIsWindowed()); + ((CPopup*)(spPopup as Popup.GetHandle()).SetIsWindowed()); - // Ensure the sub menu is the windowed Popup - ASSERT((CPopup*)(spPopup as Popup.GetHandle()).IsWindowed()); + // Ensure the sub menu is the windowed Popup + ASSERT((CPopup*)(spPopup as Popup.GetHandle()).IsWindowed()); - xaml.IXamlRoot xamlRoot = XamlRoot.GetForElementStatic(spParentMenuFlyoutPresenter); - if (xamlRoot) - { - (spPopup as Popup.XamlRoot = xamlRoot); - } + xaml.IXamlRoot xamlRoot = XamlRoot.GetForElementStatic(spParentMenuFlyoutPresenter); + if (xamlRoot) + { + (spPopup as Popup.XamlRoot = xamlRoot); } } + } #endif - spPresenter = CreateSubPresenter(); - spPresenterAsUI = spPresenter; + spPresenter = CreateSubPresenter(); + spPresenterAsUI = spPresenter; - if (spParentMenuFlyoutPresenter != null) - { - int parentDepth = spParentMenuFlyoutPresenter.GetDepth(); - (spPresenter as MenuFlyoutPresenter).SetDepth(parentDepth + 1); - } + if (spParentMenuFlyoutPresenter != null) + { + int parentDepth = spParentMenuFlyoutPresenter.GetDepth(); + (spPresenter as MenuFlyoutPresenter).SetDepth(parentDepth + 1); + } - spPopup.Child = spPresenterAsUI as FrameworkElement; + spPopup.Child = spPresenterAsUI as FrameworkElement; - m_tpPresenter = spPresenter; - m_tpPopup = spPopup; + m_tpPresenter = spPresenter; + m_tpPopup = spPopup; - ((ItemsControl)m_tpPresenter).ItemsSource = m_tpItems; + ((ItemsControl)m_tpPresenter).ItemsSource = m_tpItems; - spPresenterAsFE = spPresenter; + spPresenterAsFE = spPresenter; - spPresenterAsFE.SizeChanged += OnPresenterSizeChanged; - m_epPresenterSizeChangedHandler = Disposable.Create(() => spPresenterAsFE.SizeChanged -= OnPresenterSizeChanged); + spPresenterAsFE.SizeChanged += OnPresenterSizeChanged; + m_epPresenterSizeChangedHandler = Disposable.Create(() => spPresenterAsFE.SizeChanged -= OnPresenterSizeChanged); - m_menuHelper.SetSubMenuPresenter(spPresenter as Control); - } + m_menuHelper.SetSubMenuPresenter(spPresenter as Control); } + } - void ForwardPresenterProperties( - MenuFlyout pOwnerMenuFlyout, - MenuFlyoutPresenter pParentMenuFlyoutPresenter, - MenuFlyoutPresenter pSubMenuFlyoutPresenter) - { - Style spStyle; - ElementTheme parentPresenterTheme; - object spDataContext; - FrameworkElement spPopupAsFE; - MenuFlyoutPresenter spSubMenuFlyoutPresenter = pSubMenuFlyoutPresenter; - Control spSubMenuFlyoutPresenterAsControl; - DependencyObject spThisAsDO = this; + void ForwardPresenterProperties( + MenuFlyout pOwnerMenuFlyout, + MenuFlyoutPresenter pParentMenuFlyoutPresenter, + MenuFlyoutPresenter pSubMenuFlyoutPresenter) + { + Style spStyle; + ElementTheme parentPresenterTheme; + object spDataContext; + FrameworkElement spPopupAsFE; + MenuFlyoutPresenter spSubMenuFlyoutPresenter = pSubMenuFlyoutPresenter; + Control spSubMenuFlyoutPresenterAsControl; + DependencyObject spThisAsDO = this; - global::System.Diagnostics.Debug.Assert(pOwnerMenuFlyout != null && pParentMenuFlyoutPresenter != null && pSubMenuFlyoutPresenter != null); + global::System.Diagnostics.Debug.Assert(pOwnerMenuFlyout != null && pParentMenuFlyoutPresenter != null && pSubMenuFlyoutPresenter != null); - spSubMenuFlyoutPresenterAsControl = spSubMenuFlyoutPresenter; + spSubMenuFlyoutPresenterAsControl = spSubMenuFlyoutPresenter; - // Set the sub presenter style from the MenuFlyout's presenter style - spStyle = pOwnerMenuFlyout.MenuFlyoutPresenterStyle; + // Set the sub presenter style from the MenuFlyout's presenter style + spStyle = pOwnerMenuFlyout.MenuFlyoutPresenterStyle; - if (spStyle != null) - { - ((Control)pSubMenuFlyoutPresenter).Style = spStyle; - } - else - { - ((Control)pSubMenuFlyoutPresenter).ClearValue(FrameworkElement.StyleProperty); - } + if (spStyle != null) + { + ((Control)pSubMenuFlyoutPresenter).Style = spStyle; + } + else + { + ((Control)pSubMenuFlyoutPresenter).ClearValue(FrameworkElement.StyleProperty); + } - // Set the sub presenter's RequestTheme from the parent presenter's RequestTheme - parentPresenterTheme = pParentMenuFlyoutPresenter.RequestedTheme; - pSubMenuFlyoutPresenter.RequestedTheme = parentPresenterTheme; + // Set the sub presenter's RequestTheme from the parent presenter's RequestTheme + parentPresenterTheme = pParentMenuFlyoutPresenter.RequestedTheme; + pSubMenuFlyoutPresenter.RequestedTheme = parentPresenterTheme; - // Set the sub presenter's DataContext from the parent presenter's DataContext - spDataContext = pParentMenuFlyoutPresenter.DataContext; - pSubMenuFlyoutPresenter.DataContext = spDataContext; + // Set the sub presenter's DataContext from the parent presenter's DataContext + spDataContext = pParentMenuFlyoutPresenter.DataContext; + pSubMenuFlyoutPresenter.DataContext = spDataContext; - // Set the sub presenter's FlowDirection from the current sub menu item's FlowDirection - var flowDirection = FlowDirection; - pSubMenuFlyoutPresenter.FlowDirection = flowDirection; + // Set the sub presenter's FlowDirection from the current sub menu item's FlowDirection + var flowDirection = FlowDirection; + pSubMenuFlyoutPresenter.FlowDirection = flowDirection; - // Set the popup's FlowDirection from the current FlowDirection - spPopupAsFE = m_tpPopup; - spPopupAsFE.FlowDirection = flowDirection; + // Set the popup's FlowDirection from the current FlowDirection + spPopupAsFE = m_tpPopup; + spPopupAsFE.FlowDirection = flowDirection; - // Set the sub presenter's Language from the parent presenter's Language - pSubMenuFlyoutPresenter.Language = pParentMenuFlyoutPresenter.Language; + // Set the sub presenter's Language from the parent presenter's Language + pSubMenuFlyoutPresenter.Language = pParentMenuFlyoutPresenter.Language; - // Set the sub presenter's IsTextScaleFactorEnabledInternal from the parent presenter's IsTextScaleFactorEnabledInternal - var isTextScaleFactorEnabled = pParentMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal; - pSubMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal = isTextScaleFactorEnabled; + // Set the sub presenter's IsTextScaleFactorEnabledInternal from the parent presenter's IsTextScaleFactorEnabledInternal + var isTextScaleFactorEnabled = pParentMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal; + pSubMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal = isTextScaleFactorEnabled; - ElementSoundMode soundMode = ElementSoundPlayerService.Instance.GetEffectiveSoundMode(spThisAsDO as DependencyObject); + ElementSoundMode soundMode = ElementSoundPlayerService.Instance.GetEffectiveSoundMode(spThisAsDO as DependencyObject); - (spSubMenuFlyoutPresenterAsControl as Control).ElementSoundMode = soundMode; - } + (spSubMenuFlyoutPresenterAsControl as Control).ElementSoundMode = soundMode; + } - // Ensure that any currently open MenuFlyoutSubItems are closed - void EnsureCloseExistingSubItems() - { + // Ensure that any currently open MenuFlyoutSubItems are closed + void EnsureCloseExistingSubItems() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsureCloseExistingSubItems.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsureCloseExistingSubItems.", this)); #endif // MFSI_DEBUG - MenuFlyoutPresenter spParentPresenter; + MenuFlyoutPresenter spParentPresenter; - spParentPresenter = GetParentMenuFlyoutPresenter(); - if (spParentPresenter != null) + spParentPresenter = GetParentMenuFlyoutPresenter(); + if (spParentPresenter != null) + { + IMenuPresenter openedSubPresenter; + + openedSubPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; + if (openedSubPresenter != null) { - IMenuPresenter openedSubPresenter; + ISubMenuOwner subMenuOwner; - openedSubPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; - if (openedSubPresenter != null) + subMenuOwner = openedSubPresenter.Owner; + if (subMenuOwner != null && subMenuOwner != this) { - ISubMenuOwner subMenuOwner; - - subMenuOwner = openedSubPresenter.Owner; - if (subMenuOwner != null && subMenuOwner != this) - { - openedSubPresenter.CloseSubMenu(); - } + openedSubPresenter.CloseSubMenu(); } } + } - } + } - bool IsOpen => m_tpPopup?.IsOpen ?? false; + bool IsOpen => m_tpPopup?.IsOpen ?? false; - Control - CreateSubPresenter() - { + Control + CreateSubPresenter() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CreateSubPresenter.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CreateSubPresenter.", this)); #endif // MFSI_DEBUG - MenuFlyoutPresenter spPresenter = new MenuFlyoutPresenter(); + MenuFlyoutPresenter spPresenter = new MenuFlyoutPresenter(); - // Specify the sub MenuFlyoutPresenter - (spPresenter as MenuFlyoutPresenter).IsSubPresenter = true; - (spPresenter as IMenuPresenter).Owner = this; + // Specify the sub MenuFlyoutPresenter + (spPresenter as MenuFlyoutPresenter).IsSubPresenter = true; + (spPresenter as IMenuPresenter).Owner = this; - return spPresenter; - } + return spPresenter; + } - void UpdateParentOwner(MenuFlyoutPresenter parentMenuFlyoutPresenter) + void UpdateParentOwner(MenuFlyoutPresenter parentMenuFlyoutPresenter) + { + MenuFlyoutPresenter parentPresenter = parentMenuFlyoutPresenter; + if (parentPresenter == null) { - MenuFlyoutPresenter parentPresenter = parentMenuFlyoutPresenter; - if (parentPresenter == null) - { - parentPresenter = GetParentMenuFlyoutPresenter(); - } + parentPresenter = GetParentMenuFlyoutPresenter(); + } #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentPresenter=0x%p.", this, parentPresenter)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentPresenter=0x%p.", this, parentPresenter)); #endif // MFSI_DEBUG - if (parentPresenter != null) - { - ISubMenuOwner parentSubMenuOwner; - parentSubMenuOwner = (parentPresenter as IMenuPresenter).Owner; + if (parentPresenter != null) + { + ISubMenuOwner parentSubMenuOwner; + parentSubMenuOwner = (parentPresenter as IMenuPresenter).Owner; #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentSubMenuOwner=0x%p.", this, parentSubMenuOwner)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentSubMenuOwner=0x%p.", this, parentSubMenuOwner)); #endif // MFSI_DEBUG - if (parentSubMenuOwner != null) - { - ((ISubMenuOwner)this).ParentOwner = parentSubMenuOwner; - } + if (parentSubMenuOwner != null) + { + ((ISubMenuOwner)this).ParentOwner = parentSubMenuOwner; } } + } - // Set the popup open or close status for MenuFlyoutSubItem and ensure the - // focus to the current presenter. - void SetIsOpen(bool isOpen) - { - bool isOpened = false; + // Set the popup open or close status for MenuFlyoutSubItem and ensure the + // focus to the current presenter. + void SetIsOpen(bool isOpen) + { + bool isOpened = false; - isOpened = m_tpPopup.IsOpen; + isOpened = m_tpPopup.IsOpen; #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: SetIsOpen isOpen=%d, isOpened=%d.", this, isOpen, isOpened)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: SetIsOpen isOpen=%d, isOpened=%d.", this, isOpen, isOpened)); #endif // MFSI_DEBUG - if (isOpen != isOpened) - { - (m_tpPresenter as IMenuPresenter).Owner = isOpen ? this : null; + if (isOpen != isOpened) + { + (m_tpPresenter as IMenuPresenter).Owner = isOpen ? this : null; - MenuFlyoutPresenter parentPresenter; - parentPresenter = GetParentMenuFlyoutPresenter(); + MenuFlyoutPresenter parentPresenter; + parentPresenter = GetParentMenuFlyoutPresenter(); - if (parentPresenter != null) - { - (parentPresenter as IMenuPresenter).SubPresenter = isOpen ? m_tpPresenter as MenuFlyoutPresenter : null; - - IMenu owningMenu; - owningMenu = (parentPresenter as IMenuPresenter).OwningMenu; + if (parentPresenter != null) + { + (parentPresenter as IMenuPresenter).SubPresenter = isOpen ? m_tpPresenter as MenuFlyoutPresenter : null; - if (owningMenu != null) - { - (m_tpPresenter as IMenuPresenter).OwningMenu = isOpen ? owningMenu : null; - } + IMenu owningMenu; + owningMenu = (parentPresenter as IMenuPresenter).OwningMenu; - UpdateParentOwner(parentPresenter); + if (owningMenu != null) + { + (m_tpPresenter as IMenuPresenter).OwningMenu = isOpen ? owningMenu : null; } + UpdateParentOwner(parentPresenter); + } + // UNO TODO VisualTree visualTree = VisualTree.GetForElement(this); if (visualTree is not null) @@ -500,386 +500,385 @@ void SetIsOpen(bool isOpen) m_tpPopup.SetVisualTree(visualTree); } - // Set the popup open or close state - m_tpPopup.IsOpen = isOpen; + // Set the popup open or close state + m_tpPopup.IsOpen = isOpen; - // Set the focus to the displayed sub menu presenter when MenuFlyoutSubItem is opened and - // set the focus back to the original sub item when the displayed sub menu presenter is closed. - if (isOpen) - { - // Set the focus to the displayed sub menu presenter to navigate the each sub items - m_tpPresenter.Focus(FocusState.Programmatic); - - // UNO TODO - // (DependencyObject.SetFocusedElement( - // spPresenterAsDO as DependencyObject, - // xaml.FocusState_Programmatic, - // false /*animateIfBringIntoView*/, - // &focusUpdated)); - } - else - { - // Set the focus to the sub menu item - this.Focus(FocusState.Programmatic); - - // UNO TODO - //(DependencyObject.SetFocusedElement( - // spThisAsDO as DependencyObject, - // xaml.FocusState_Programmatic, - // false /*animateIfBringIntoView*/, - // &focusUpdated)); - } + // Set the focus to the displayed sub menu presenter when MenuFlyoutSubItem is opened and + // set the focus back to the original sub item when the displayed sub menu presenter is closed. + if (isOpen) + { + // Set the focus to the displayed sub menu presenter to navigate the each sub items + m_tpPresenter.Focus(FocusState.Programmatic); - UpdateVisualState(); + // UNO TODO + // (DependencyObject.SetFocusedElement( + // spPresenterAsDO as DependencyObject, + // xaml.FocusState_Programmatic, + // false /*animateIfBringIntoView*/, + // &focusUpdated)); } + else + { + // Set the focus to the sub menu item + this.Focus(FocusState.Programmatic); + // UNO TODO + //(DependencyObject.SetFocusedElement( + // spThisAsDO as DependencyObject, + // xaml.FocusState_Programmatic, + // false /*animateIfBringIntoView*/, + // &focusUpdated)); + } + UpdateVisualState(); } - internal void Open() - { + + } + + internal void Open() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Open.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Open.", this)); #endif // MFSI_DEBUG - m_menuHelper.OpenSubMenu(); - } + m_menuHelper.OpenSubMenu(); + } - internal void Close() - { + internal void Close() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Close.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Close.", this)); #endif // MFSI_DEBUG - m_menuHelper.CloseSubMenu(); + m_menuHelper.CloseSubMenu(); - } + } - private protected override void ChangeVisualState(bool bUseTransitions) - { - bool hasToggleMenuItem = false; - bool hasIconMenuItem = false; - bool bIsPopupOpened = false; - MenuFlyoutPresenter spPresenter; + private protected override void ChangeVisualState(bool bUseTransitions) + { + bool hasToggleMenuItem = false; + bool hasIconMenuItem = false; + bool bIsPopupOpened = false; + MenuFlyoutPresenter spPresenter; - var bIsEnabled = IsEnabled; - var focusState = FocusState; - var shouldBeNarrow = GetShouldBeNarrow(); + var bIsEnabled = IsEnabled; + var focusState = FocusState; + var shouldBeNarrow = GetShouldBeNarrow(); - spPresenter = GetParentMenuFlyoutPresenter(); - if (spPresenter != null) - { - hasToggleMenuItem = spPresenter.GetContainsToggleItems(); - hasIconMenuItem = spPresenter.GetContainsIconItems(); - } + spPresenter = GetParentMenuFlyoutPresenter(); + if (spPresenter != null) + { + hasToggleMenuItem = spPresenter.GetContainsToggleItems(); + hasIconMenuItem = spPresenter.GetContainsIconItems(); + } - if (m_tpPopup != null) - { - bIsPopupOpened = m_tpPopup.IsOpen; - } + if (m_tpPopup != null) + { + bIsPopupOpened = m_tpPopup.IsOpen; + } - // CommonStates - if (!bIsEnabled) - { - VisualStateManager.GoToState(this, "Disabled", bUseTransitions); - } - else if (bIsPopupOpened) - { - VisualStateManager.GoToState(this, "SubMenuOpened", bUseTransitions); - } - else if (m_menuHelper.IsPressed) - { - VisualStateManager.GoToState(this, "Pressed", bUseTransitions); - } - else if (m_menuHelper.IsPointerOver) - { - VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Normal", bUseTransitions); - } + // CommonStates + if (!bIsEnabled) + { + VisualStateManager.GoToState(this, "Disabled", bUseTransitions); + } + else if (bIsPopupOpened) + { + VisualStateManager.GoToState(this, "SubMenuOpened", bUseTransitions); + } + else if (m_menuHelper.IsPressed) + { + VisualStateManager.GoToState(this, "Pressed", bUseTransitions); + } + else if (m_menuHelper.IsPointerOver) + { + VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Normal", bUseTransitions); + } - // FocusStates - if (FocusState.Unfocused != focusState && bIsEnabled) + // FocusStates + if (FocusState.Unfocused != focusState && bIsEnabled) + { + if (FocusState.Pointer == focusState) { - if (FocusState.Pointer == focusState) - { - VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Focused", bUseTransitions); - } + VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); } else { - VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); + VisualStateManager.GoToState(this, "Focused", bUseTransitions); } + } + else + { + VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); + } - // CheckPlaceholderStates - if (hasToggleMenuItem && hasIconMenuItem) - { - VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); - } - else if (hasToggleMenuItem) - { - VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); - } - else if (hasIconMenuItem) - { - VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); - } + // CheckPlaceholderStates + if (hasToggleMenuItem && hasIconMenuItem) + { + VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); + } + else if (hasToggleMenuItem) + { + VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); + } + else if (hasIconMenuItem) + { + VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); + } - // PaddingSizeStates - if (shouldBeNarrow) - { - VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); - } + // PaddingSizeStates + if (shouldBeNarrow) + { + VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); + } - } + } - // MenuFlyoutSubItem's presenter size changed event handler that - // adjust the sub presenter position to the proper space area - // on the available window rect. - void OnPresenterSizeChanged( - object pSender, - SizeChangedEventArgs args) - { + // MenuFlyoutSubItem's presenter size changed event handler that + // adjust the sub presenter position to the proper space area + // on the available window rect. + void OnPresenterSizeChanged( + object pSender, + SizeChangedEventArgs args) + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPresenterSizeChanged.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPresenterSizeChanged.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnPresenterSizeChanged(pSender, args, m_tpPopup as Popup); + m_menuHelper.OnPresenterSizeChanged(pSender, args, m_tpPopup as Popup); #if false // UNO TODO - if (m_tpMenuPopupThemeTransition == null) - { - - MenuFlyoutPresenter parentMenuFlyoutPresenter; - parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + if (m_tpMenuPopupThemeTransition == null) + { - // Get how many sub menus deep we are. We need this number to know what kind of Z - // offset to use for displaying elevation. The menus aren't parented in the visual - // hierarchy so that has to be applied with an additional transform. - int depth = 1; - if (parentMenuFlyoutPresenter != null) - { - depth = parentMenuFlyoutPresenter.GetDepth() + 1; - } + MenuFlyoutPresenter parentMenuFlyoutPresenter; + parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); - Transition spMenuPopupChildTransition; - (MenuFlyout.PreparePopupThemeTransitionsAndShadows((Popup*)(m_tpPopup), 0.67 /* closedRatioConstant */, depth, &spMenuPopupChildTransition)); - (spMenuPopupChildTransition as MenuPopupThemeTransition.Direction = xaml_primitives.AnimationDirection_Top); - m_tpMenuPopupThemeTransition = spMenuPopupChildTransition; + // Get how many sub menus deep we are. We need this number to know what kind of Z + // offset to use for displaying elevation. The menus aren't parented in the visual + // hierarchy so that has to be applied with an additional transform. + int depth = 1; + if (parentMenuFlyoutPresenter != null) + { + depth = parentMenuFlyoutPresenter.GetDepth() + 1; } - // Update the OpenedLength property of the ThemeTransition. - double openedLength = (m_tpPresenter as Control).ActualHeight; + Transition spMenuPopupChildTransition; + (MenuFlyout.PreparePopupThemeTransitionsAndShadows((Popup*)(m_tpPopup), 0.67 /* closedRatioConstant */, depth, &spMenuPopupChildTransition)); + (spMenuPopupChildTransition as MenuPopupThemeTransition.Direction = xaml_primitives.AnimationDirection_Top); + m_tpMenuPopupThemeTransition = spMenuPopupChildTransition; + } + + // Update the OpenedLength property of the ThemeTransition. + double openedLength = (m_tpPresenter as Control).ActualHeight; - (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; + (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; #endif - } + } #if false - void ClearStateFlags() - { + void ClearStateFlags() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClearStateFlags.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClearStateFlags.", this)); #endif // MFSI_DEBUG - m_menuHelper.ClearStateFlags(); + m_menuHelper.ClearStateFlags(); - } + } - void OnIsEnabledChanged(/*IsEnabledChangedEventArgs* args*/) - { + void OnIsEnabledChanged(/*IsEnabledChangedEventArgs* args*/) + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnIsEnabledChanged.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnIsEnabledChanged.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnIsEnabledChanged(); - } + m_menuHelper.OnIsEnabledChanged(); + } - void OnVisibilityChanged() - { + void OnVisibilityChanged() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnVisibilityChanged.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnVisibilityChanged.", this)); #endif // MFSI_DEBUG - m_menuHelper.OnVisibilityChanged(); + m_menuHelper.OnVisibilityChanged(); - } + } #endif - protected override AutomationPeer OnCreateAutomationPeer() - { - //*ppAutomationPeer = null; - //MenuFlyoutSubItemAutomationPeer spAutomationPeer; - //(ActivationAPI.ActivateAutomationInstance(KnownTypeIndex.MenuFlyoutSubItemAutomationPeer, GetHandle(), spAutomationPeer.GetAddressOf())); - //(spAutomationPeer.Owner = this); - //*ppAutomationPeer = spAutomationPeer.Detach(); + protected override AutomationPeer OnCreateAutomationPeer() + { + //*ppAutomationPeer = null; + //MenuFlyoutSubItemAutomationPeer spAutomationPeer; + //(ActivationAPI.ActivateAutomationInstance(KnownTypeIndex.MenuFlyoutSubItemAutomationPeer, GetHandle(), spAutomationPeer.GetAddressOf())); + //(spAutomationPeer.Owner = this); + //*ppAutomationPeer = spAutomationPeer.Detach(); - return null; - } + return null; + } - private protected override string GetPlainText() => Text; + private protected override string GetPlainText() => Text; - bool ISubMenuOwner.IsSubMenuOpen => IsOpen; + bool ISubMenuOwner.IsSubMenuOpen => IsOpen; - ISubMenuOwner ISubMenuOwner.ParentOwner - { - get => m_wrParentOwner?.Target as ISubMenuOwner; - set => m_wrParentOwner = new WeakReference(value); - } + ISubMenuOwner ISubMenuOwner.ParentOwner + { + get => m_wrParentOwner?.Target as ISubMenuOwner; + set => m_wrParentOwner = new WeakReference(value); + } - bool ISubMenuOwner.IsSubMenuPositionedAbsolutely => true; + bool ISubMenuOwner.IsSubMenuPositionedAbsolutely => true; - void ISubMenuOwner.PrepareSubMenu() - { + void ISubMenuOwner.PrepareSubMenu() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareSubMenu.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareSubMenu.", this)); #endif // MFSI_DEBUG - EnsurePopupAndPresenter(); + EnsurePopupAndPresenter(); - global::System.Diagnostics.Debug.Assert(m_tpPopup != null); - global::System.Diagnostics.Debug.Assert(m_tpPresenter != null); - } + global::System.Diagnostics.Debug.Assert(m_tpPopup != null); + global::System.Diagnostics.Debug.Assert(m_tpPresenter != null); + } - void ISubMenuOwner.OpenSubMenu(Point position) - { + void ISubMenuOwner.OpenSubMenu(Point position) + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OpenSubMenu.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OpenSubMenu.", this)); #endif // MFSI_DEBUG - EnsurePopupAndPresenter(); - EnsureCloseExistingSubItems(); + EnsurePopupAndPresenter(); + EnsureCloseExistingSubItems(); - MenuFlyoutPresenter parentMenuFlyoutPresenter; - parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + MenuFlyoutPresenter parentMenuFlyoutPresenter; + parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); - if (parentMenuFlyoutPresenter != null) - { - IMenu owningMenu; - owningMenu = (parentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; - (m_tpPresenter as IMenuPresenter).OwningMenu = owningMenu; + if (parentMenuFlyoutPresenter != null) + { + IMenu owningMenu; + owningMenu = (parentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; + (m_tpPresenter as IMenuPresenter).OwningMenu = owningMenu; - MenuFlyout parentMenuFlyout; - parentMenuFlyout = parentMenuFlyoutPresenter.GetParentMenuFlyout(); + MenuFlyout parentMenuFlyout; + parentMenuFlyout = parentMenuFlyoutPresenter.GetParentMenuFlyout(); - if (parentMenuFlyout != null) - { - // Update the TemplateSettings before it is opened. - (m_tpPresenter as MenuFlyoutPresenter).SetParentMenuFlyout(parentMenuFlyout); - (m_tpPresenter as MenuFlyoutPresenter).UpdateTemplateSettings(); - - // Forward the parent presenter's properties to the sub presenter - ForwardPresenterProperties( - parentMenuFlyout, - parentMenuFlyoutPresenter, - m_tpPresenter as MenuFlyoutPresenter); - } + if (parentMenuFlyout != null) + { + // Update the TemplateSettings before it is opened. + (m_tpPresenter as MenuFlyoutPresenter).SetParentMenuFlyout(parentMenuFlyout); + (m_tpPresenter as MenuFlyoutPresenter).UpdateTemplateSettings(); + + // Forward the parent presenter's properties to the sub presenter + ForwardPresenterProperties( + parentMenuFlyout, + parentMenuFlyoutPresenter, + m_tpPresenter as MenuFlyoutPresenter); } + } - m_tpPopup.HorizontalOffset = position.X; - m_tpPopup.VerticalOffset = position.Y; - SetIsOpen(true); + m_tpPopup.HorizontalOffset = position.X; + m_tpPopup.VerticalOffset = position.Y; + SetIsOpen(true); - } + } - void ISubMenuOwner.PositionSubMenu(Point position) - { + void ISubMenuOwner.PositionSubMenu(Point position) + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PositionSubMenu - (%f, %f).", this, position.X, position.Y)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PositionSubMenu - (%f, %f).", this, position.X, position.Y)); #endif // MFSI_DEBUG - if (position.X != float.NegativeInfinity) - { - m_tpPopup.HorizontalOffset = position.X; - } + if (position.X != float.NegativeInfinity) + { + m_tpPopup.HorizontalOffset = position.X; + } - if (position.Y != float.NegativeInfinity) - { - m_tpPopup.VerticalOffset = position.Y; - } + if (position.Y != float.NegativeInfinity) + { + m_tpPopup.VerticalOffset = position.Y; + } - } + } - void ISubMenuOwner.ClosePeerSubMenus() - { + void ISubMenuOwner.ClosePeerSubMenus() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClosePeerSubMenus.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClosePeerSubMenus.", this)); #endif // MFSI_DEBUG - EnsureCloseExistingSubItems(); + EnsureCloseExistingSubItems(); - } + } - void ISubMenuOwner.CloseSubMenu() - { + void ISubMenuOwner.CloseSubMenu() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CloseSubMenu.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CloseSubMenu.", this)); #endif // MFSI_DEBUG - SetIsOpen(false); + SetIsOpen(false); - } + } - void ISubMenuOwner.CloseSubMenuTree() - { + void ISubMenuOwner.CloseSubMenuTree() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CloseSubMenuTree.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CloseSubMenuTree.", this)); #endif // MFSI_DEBUG - m_menuHelper.CloseChildSubMenus(); + m_menuHelper.CloseChildSubMenus(); - } + } - void ISubMenuOwner.DelayCloseSubMenu() - { + void ISubMenuOwner.DelayCloseSubMenu() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: DelayCloseSubMenu.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: DelayCloseSubMenu.", this)); #endif // MFSI_DEBUG - m_menuHelper.DelayCloseSubMenu(); - } + m_menuHelper.DelayCloseSubMenu(); + } - void ISubMenuOwner.CancelCloseSubMenu() - { + void ISubMenuOwner.CancelCloseSubMenu() + { #if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CancelCloseSubMenu.", this)); + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CancelCloseSubMenu.", this)); #endif // MFSI_DEBUG - m_menuHelper.CancelCloseSubMenu(); - } + m_menuHelper.CancelCloseSubMenu(); + } - void ISubMenuOwner.RaiseAutomationPeerExpandCollapse(bool isOpen) - { - // UNO TODO - //AutomationPeer spAutomationPeer; - //bool isListener = false; - - //AutomationPeer.ListenerExistsHelper(xaml_automation_peers.AutomationEvents_PropertyChanged, &isListener); - //if (isListener) - //{ - // (GetOrCreateAutomationPeer(&spAutomationPeer)); - // if (spAutomationPeer) - // { - // (spAutomationPeer as MenuFlyoutSubItemAutomationPeer.RaiseExpandCollapseAutomationEvent(isOpen)); - // } - //} - } + void ISubMenuOwner.RaiseAutomationPeerExpandCollapse(bool isOpen) + { + // UNO TODO + //AutomationPeer spAutomationPeer; + //bool isListener = false; + + //AutomationPeer.ListenerExistsHelper(xaml_automation_peers.AutomationEvents_PropertyChanged, &isListener); + //if (isListener) + //{ + // (GetOrCreateAutomationPeer(&spAutomationPeer)); + // if (spAutomationPeer) + // { + // (spAutomationPeer as MenuFlyoutSubItemAutomationPeer.RaiseExpandCollapseAutomationEvent(isOpen)); + // } + //} } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs new file mode 100644 index 000000000000..db5800e8790f --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyoutSubItem +{ + private void EnterImpl(DependencyObject pNamescopeOwner, EnterParams parameters) + { + + base.EnterImpl(pNamescopeOwner, parameters); + MenuFlyout.KeyboardAcceleratorFlyoutItemEnter(this, pNamescopeOwner, MenuFlyoutSubItem.ItemsProperty, parameters); + + return S_OK; + } + + private void LeaveImpl(DependencyObject pNamescopeOwner, LeaveParams parameters) + { + base.LeaveImpl(pNamescopeOwner, parameters); + MenuFlyout.KeyboardAcceleratorFlyoutItemLeave(this, pNamescopeOwner, MenuFlyoutSubItem.ItemsProperty, parameters); + + return S_OK; + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.mux.cs b/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.mux.cs index 242586e09edc..e88b28708c9e 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.mux.cs @@ -6,7 +6,7 @@ namespace Microsoft.UI.Xaml.Controls.Primitives; public partial class Popup { - internal void OnClosing(ref bool cancel) + internal virtual void OnClosing(ref bool cancel) { // If this popup is associated with a flyout, then give it a chance to cancel closing. if (AssociatedFlyout is { } flyout) diff --git a/src/Uno.UI/UI/Xaml/IDependencyObjectInternal.cs b/src/Uno.UI/UI/Xaml/IDependencyObjectInternal.cs index 2f186277cedd..7381a7d68045 100644 --- a/src/Uno.UI/UI/Xaml/IDependencyObjectInternal.cs +++ b/src/Uno.UI/UI/Xaml/IDependencyObjectInternal.cs @@ -1,14 +1,13 @@ -namespace Microsoft.UI.Xaml +namespace Microsoft.UI.Xaml; + +/// +/// Internal implemenation for +/// +internal partial interface IDependencyObjectInternal { /// - /// Internal implemenation for + /// Invoked on every changes, automatically generated for every implementing type. /// - internal partial interface IDependencyObjectInternal - { - /// - /// Invoked on every changes, automatically generated for every implementing type. - /// - /// - void OnPropertyChanged2(DependencyPropertyChangedEventArgs args); - } + /// + void OnPropertyChanged2(DependencyPropertyChangedEventArgs args); } From 93dddd422c029b7cfa537412ad8cf6f6ac215c2f Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Fri, 14 Jun 2024 08:43:59 +0200 Subject: [PATCH 02/28] chore: Start MenuFlyout update for KA --- .../MenuFlyout/MenuFlyoutItem.Properties.cs | 828 ++++++++++++++++ .../MenuFlyout/MenuFlyoutItem.h.mux.cs | 828 ++++++++++++++++ .../Controls/MenuFlyout/MenuFlyoutItem.mux.cs | 828 ++++++++++++++++ .../MenuFlyoutSubItem.Properties.cs | 43 + .../Controls/MenuFlyout/MenuFlyoutSubItem.cs | 866 ----------------- .../MenuFlyoutSubItem.partial.h.mux.cs | 22 + .../MenuFlyoutSubItem.partial.mux.cs | 882 ++++++++++++++++++ 7 files changed, 3431 insertions(+), 866 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs new file mode 100644 index 000000000000..9c3e701fd3a5 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs @@ -0,0 +1,828 @@ +using System; +using Uno.Client; +using Uno.Disposables; +using Windows.Foundation; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using ICommand = System.Windows.Input.ICommand; +using Microsoft.UI.Xaml.Markup; + +#if HAS_UNO_WINUI +using Microsoft.UI.Input; +#else +using Windows.Devices.Input; +using Windows.UI.Input; +#endif + +namespace Microsoft.UI.Xaml.Controls +{ + [ContentProperty(Name = nameof(Text))] + public partial class MenuFlyoutItem : MenuFlyoutItemBase + { + // Whether the pointer is currently over the + bool m_bIsPointerOver = true; + + // Whether the pointer is currently pressed over the + internal bool m_bIsPressed = true; + + // Whether the pointer's left button is currently down. + internal bool m_bIsPointerLeftButtonDown = true; + + // True if the SPACE or ENTER key is currently pressed, false otherwise. + internal bool m_bIsSpaceOrEnterKeyDown = true; + + // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. + internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; + + // On pointer released we perform some actions depending on control. We decide to whether to perform them + // depending on some parameters including but not limited to whether released is followed by a pressed, which + // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. + bool m_shouldPerformActions = true; + + // Event pointer for the ICommand.CanExecuteChanged event. + IDisposable m_epCanExecuteChangedHandler; + + // UNO TODO + // Event pointer for the Loaded event. + // IDisposable m_epLoadedHandler; + + // UNO TODO + // IDisposable m_epMenuFlyoutItemClickEventCallback; + + double m_maxKeyboardAcceleratorTextWidth; + TextBlock m_tpKeyboardAcceleratorTextBlock; + + bool m_isTemplateApplied; + + #region CommandParameter + + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public static DependencyProperty CommandParameterProperty { get; } = + DependencyProperty.Register( + "CommandParameter", typeof(object), + typeof(Controls.MenuFlyoutItem), + new FrameworkPropertyMetadata(default(object))); + + #endregion + + #region Command + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public static DependencyProperty CommandProperty { get; } = + DependencyProperty.Register( + name: nameof(Command), + propertyType: typeof(ICommand), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); + + #endregion + + #region Text + + public string Text + { + get { return (string)GetValue(TextProperty) ?? ""; } + set { SetValue(TextProperty, value); } + } + + public static DependencyProperty TextProperty { get; } = + DependencyProperty.Register( + name: nameof(Text), + propertyType: typeof(string), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(string))); + + #endregion + + public IconElement Icon + { + get => (IconElement)this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); + } + + public static DependencyProperty IconProperty { get; } = + DependencyProperty.Register( + name: nameof(Icon), + propertyType: typeof(IconElement), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); + + public string KeyboardAcceleratorTextOverride + { + get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; + set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); + } + + public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = + DependencyProperty.Register( + name: nameof(KeyboardAcceleratorTextOverride), + propertyType: typeof(string), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(string))); + + public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } + +#pragma warning disable CS0108 + public event RoutedEventHandler Click; +#pragma warning restore CS0108 + + internal void InvokeClick() + { + Click?.Invoke(this, new RoutedEventArgs(this)); + Command.ExecuteIfPossible(this.CommandParameter); + } + + public MenuFlyoutItem() + { + m_bIsPointerOver = false; + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + m_shouldPerformActions = false; + + DefaultStyleKey = typeof(MenuFlyoutItem); + + Initialize(); + } + + // Prepares object's state + void Initialize() + { + Loaded += (s, e) => ClearStateFlags(); + + this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); + } + + // Apply a template to the + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; + m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; + + SuppressIsEnabled(false); + UpdateCanExecute(); + + // Sync the logical and visual states of the control + UpdateVisualState(); + } + + // PointerPressed event handler. + protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) + { + base.OnPointerPressed(pArgs); + var handled = pArgs.Handled; + if (!handled) + { + PointerPoint spPointerPoint; + PointerPointProperties spPointerProperties; + spPointerPoint = pArgs.GetCurrentPoint(this); + spPointerProperties = spPointerPoint.Properties; + var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; + if (bIsLeftButtonPressed) + { + m_bIsPointerLeftButtonDown = true; + m_bIsPressed = true; + + pArgs.Handled = true; + UpdateVisualState(); + } + } + } + + // PointerReleased event handler. + protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) + { + base.OnPointerReleased(pArgs); + + bool handled = false; + + handled = pArgs.Handled; + if (!handled) + { + m_bIsPointerLeftButtonDown = false; + + m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; + +#if false + // UNO TODO + if (m_shouldPerformActions) + { + GestureModes gestureFollowing = GestureModes.None; + + m_bIsPressed = false; + + gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); + + // Note that we are intentionally NOT handling the args + // if we do not fall through here because basically we are no_opting in that case. + if (gestureFollowing != GestureModes.RightTapped) + { + pArgs.Handled = true; + PerformPointerUpAction(); + } + } +#else + PerformPointerUpAction(); +#endif + } + } + + private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + PerformPointerUpAction(); + pArgs.Handled = true; + } + } + + // Contains the logic to be employed if we decide to handle pointer released. + void PerformPointerUpAction() + { + if (m_shouldPerformActions) + { + Focus(FocusState.Pointer); + Invoke(); + } + } + + // Performs appropriate actions upon a mouse/keyboard invocation of a + internal virtual void Invoke() + { + RoutedEventArgs spArgs; + MenuFlyoutPresenter spParentMenuFlyoutPresenter; + + // Create the args + spArgs = new RoutedEventArgs(); + spArgs.OriginalSource = this; + + // Raise the event + Click?.Invoke(this, spArgs); + + // UNO TODO + //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); + + // Execute command associated with the button + ExecuteCommand(); + + bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; + if (!shouldPreventDismissOnPointer) + { + // Close the MenuFlyout. + spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + if (spParentMenuFlyoutPresenter != null) + { + IMenu owningMenu; + + owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; + if (owningMenu != null) + { + // We need to make close all menu flyout sub items before we hide the parent menu flyout, + // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a + // significant amount of time before closing the sub menu. + (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); + owningMenu.Close(); + } + } + } + + ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); + } + + // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) + //{ + // DependencyObject peer = pMenuFlyoutItem; + + // if (peer == null) + // { + // return; + // } + + // MenuFlyoutItem peerAsMenuFlyoutItem; + // (peer.As(&peerAsMenuFlyoutItem)); + // IFCPTR_RETURN(peerAsMenuFlyoutItem); + + // (peerAsAddProofingItemHandler(eventHandler)); + + // return S_OK; + //} + + // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) + //{ + // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) + // { + // DependencyObject spSender; + // EventArgs spArgs; + + // IFCPTR_RETURN(pSender); + // IFCPTR_RETURN(pArgs); + + // (ctl.do_query_interface(spSender, pSender)); + // (ctl.do_query_interface(spArgs, pArgs)); + + // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); + + // return S_OK; + // })); + + // return S_OK; + //} + + // Executes Command if CanExecute() returns true. + void ExecuteCommand() + { + ICommand spCommand = Command; + + if (spCommand != null) + { + object spCommandParameter; + bool canExecute; + + spCommandParameter = CommandParameter; + canExecute = spCommand.CanExecute(spCommandParameter); + if (canExecute) + { + spCommand.Execute(spCommandParameter); + } + } + } + + // PointerEnter event handler. + protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) + { + base.OnPointerEntered(pArgs); + + MenuFlyoutPresenter spParentPresenter; + + m_bIsPointerOver = true; + + spParentPresenter = GetParentMenuFlyoutPresenter(); + if (spParentPresenter != null) + { + IMenuPresenter subPresenter; + + subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; + if (subPresenter != null) + { + ISubMenuOwner subPresenterOwner; + + subPresenterOwner = subPresenter.Owner; + + if (subPresenterOwner != null) + { + subPresenterOwner.DelayCloseSubMenu(); + } + } + } + + UpdateVisualState(); + } + + // PointerExited event handler. + protected override void OnPointerExited(PointerRoutedEventArgs pArgs) + { + base.OnPointerExited(pArgs); + + // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + UpdateVisualState(); + } + + // PointerCaptureLost event handler. + protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) + { + base.OnPointerCaptureLost(pArgs); + + // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + UpdateVisualState(); + } + + // Called when the IsEnabled property changes. + private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) + { + if (!e.NewValue) + { + ClearStateFlags(); + } + else + { + UpdateVisualState(); + } + + base.OnIsEnabledChanged(e); + } + + // Called when the control got focus. + protected override void OnGotFocus(RoutedEventArgs pArgs) + { + UpdateVisualState(); + } + + // LostFocus event handler. + protected override void OnLostFocus(RoutedEventArgs pArgs) + { + if (!m_bIsPointerLeftButtonDown) + { + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + m_bIsPressed = false; + } + + UpdateVisualState(); + } + + // KeyDown event handler. + protected override void OnKeyDown(KeyRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + var key = pArgs.Key; + handled = KeyPressMenuFlyout.KeyDown(key, this); + pArgs.Handled = handled; + } + } + + // KeyUp event handler. + protected override void OnKeyUp(KeyRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + var key = (pArgs.Key); + KeyPressMenuFlyout.KeyUp(key, this); + pArgs.Handled = true; + } + } + + // Handle the custom property changed event and call the OnPropertyChanged2 methods. + internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) + { + if (args.Property == UIElement.VisibilityProperty) + { + OnVisibilityChanged(); + } + else if (args.Property == MenuFlyoutItem.CommandProperty) + { + OnCommandChanged(args.OldValue, args.NewValue); + } + else if (args.Property == MenuFlyoutItem.CommandParameterProperty) + { + UpdateCanExecute(); + } + } + + // Update the visual states when the Visibility property is changed. + void OnVisibilityChanged() + { + Visibility visibility = Visibility; + + if (Visibility.Visible != visibility) + { + ClearStateFlags(); + } + } + + private protected override void OnLoaded() + { + base.OnLoaded(); + + if (m_epCanExecuteChangedHandler == null) + { + ICommand spCommand = Command; + + if (spCommand != null) + { + void OnCanExecuteChanged(object sender, object args) + { + UpdateCanExecute(); + } + + spCommand.CanExecuteChanged += OnCanExecuteChanged; + + m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); + } + } + + // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, + // we need to update our value now. + UpdateCanExecute(); + } + + private protected override void OnUnloaded() + { + base.OnUnloaded(); + + if (m_epCanExecuteChangedHandler != null) + { + ICommand spCommand = Command; + + if (spCommand != null) + { + m_epCanExecuteChangedHandler.Dispose(); + } + } + } + + // Called when the Command property changes. + void + OnCommandChanged( + object pOldValue, + object pNewValue) + { + // Remove handler for CanExecuteChanged from the old value + m_epCanExecuteChangedHandler?.Dispose(); + + if (pOldValue != null) + { + XamlUICommand oldCommand = pOldValue as XamlUICommand; + + if (oldCommand != null) + { + CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); + } + } + + // Subscribe to the CanExecuteChanged event on the new value + if (pNewValue != null) + { + var spNewCommand = pNewValue as ICommand; + + void OnCanExecuteUpdated(object sender, object args) + { + UpdateCanExecute(); + } + + spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; + m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); + + var newCommandAsUICommand = spNewCommand as XamlUICommand; + + if (newCommandAsUICommand != null) + { + CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); + CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); + CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); + CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); + CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); + } + } + + // Coerce the button enabled state with the CanExecute state of the command. + UpdateCanExecute(); + } + + // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. + void UpdateCanExecute() + { + ICommand spCommand; + object spCommandParameter; + bool canExecute = true; + + spCommand = Command; + if (spCommand != null) + { + spCommandParameter = CommandParameter; + canExecute = spCommand.CanExecute(spCommandParameter); + } + + SuppressIsEnabled(!canExecute); + } + + // Change to the correct visual state for the + private protected override void ChangeVisualState( + // true to use transitions when updating the visual state, false + // to snap directly to the new visual state. + bool bUseTransitions) + { + bool hasToggleMenuItem = false; + bool hasIconMenuItem = false; + bool hasMenuItemWithKeyboardAcceleratorText = false; + bool isKeyboardPresent = false; + + MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); + if (spPresenter != null) + { + hasToggleMenuItem = spPresenter.GetContainsToggleItems(); + hasIconMenuItem = spPresenter.GetContainsIconItems(); + hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); + } + + var bIsEnabled = IsEnabled; + var focusState = FocusState; + var shouldBeNarrow = GetShouldBeNarrow(); + + // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, + // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. + if (hasMenuItemWithKeyboardAcceleratorText) + { + // UNO TODO + // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); + isKeyboardPresent = true; + } + + // CommonStates + if (!bIsEnabled) + { + VisualStateManager.GoToState(this, "Disabled", bUseTransitions); + } + else if (m_bIsPressed) + { + VisualStateManager.GoToState(this, "Pressed", bUseTransitions); + } + else if (m_bIsPointerOver) + { + VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Normal", bUseTransitions); + } + + // FocusStates + if (FocusState.Unfocused != focusState && bIsEnabled) + { + if (FocusState.Pointer == focusState) + { + VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Focused", bUseTransitions); + } + } + else + { + VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); + } + + // CheckPlaceholderStates + if (hasToggleMenuItem && hasIconMenuItem) + { + VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); + } + else if (hasToggleMenuItem) + { + VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); + } + else if (hasIconMenuItem) + { + VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); + } + + // PaddingSizeStates + if (shouldBeNarrow) + { + VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); + } + + // We'll make the accelerator text visible if any item has accelerator text, + // as this causes the margin to be applied which reserves space, ensuring that accelerator text + // in one item won't be at the same horizontal position as label text in another item. + if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); + } + } + + // Clear flags relating to the visual state. Called when IsEnabled is set to false + // or when Visibility is set to Hidden or Collapsed. + void ClearStateFlags() + { + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + + UpdateVisualState(); + } + + // Create MenuFlyoutItemAutomationPeer to represent the + protected override AutomationPeer OnCreateAutomationPeer() + { + return new MenuFlyoutItemAutomationPeer(this); + } + + private protected override string GetPlainText() => Text; + + internal string KeyboardAcceleratorTextOverrideImpl + { + get + { + var pValue = KeyboardAcceleratorTextOverride; + + // If we have no keyboard accelerator text already provided by the app, + // then we'll see if we can ruct it ourselves based on keyboard accelerators + // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" + // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". + if (pValue == null) + { + pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); + + // If we were able to get a string representation from keyboard accelerators, + // then we should now set that as the value of KeyboardAcceleratorText. + if (pValue != null) + { + KeyboardAcceleratorTextOverrideImpl = pValue; + } + } + + return pValue; + } + set + { + KeyboardAcceleratorTextOverride = value; + } + } + + internal Size GetKeyboardAcceleratorTextDesiredSize() + { + var desiredSize = new Size(0, 0); + + if (!m_isTemplateApplied) + { + bool templateApplied = ApplyTemplate(); + m_isTemplateApplied = templateApplied; + } + + if (m_tpKeyboardAcceleratorTextBlock != null) + { + Thickness margin; + + m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; + margin = m_tpKeyboardAcceleratorTextBlock.Margin; + + desiredSize.Width -= (float)(margin.Left + margin.Right); + desiredSize.Height -= (float)(margin.Top + margin.Bottom); + } + + return desiredSize; + } + + internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) + { + if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) + { + m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; + + MenuFlyoutItemTemplateSettings templateSettings; + templateSettings = TemplateSettings; + + if (templateSettings == null) + { + MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); + TemplateSettings = templateSettingsImplementation; + templateSettings = templateSettingsImplementation; + } + + templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; + } + } + + internal virtual bool HasToggle() + { + return false; + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs new file mode 100644 index 000000000000..9c3e701fd3a5 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs @@ -0,0 +1,828 @@ +using System; +using Uno.Client; +using Uno.Disposables; +using Windows.Foundation; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using ICommand = System.Windows.Input.ICommand; +using Microsoft.UI.Xaml.Markup; + +#if HAS_UNO_WINUI +using Microsoft.UI.Input; +#else +using Windows.Devices.Input; +using Windows.UI.Input; +#endif + +namespace Microsoft.UI.Xaml.Controls +{ + [ContentProperty(Name = nameof(Text))] + public partial class MenuFlyoutItem : MenuFlyoutItemBase + { + // Whether the pointer is currently over the + bool m_bIsPointerOver = true; + + // Whether the pointer is currently pressed over the + internal bool m_bIsPressed = true; + + // Whether the pointer's left button is currently down. + internal bool m_bIsPointerLeftButtonDown = true; + + // True if the SPACE or ENTER key is currently pressed, false otherwise. + internal bool m_bIsSpaceOrEnterKeyDown = true; + + // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. + internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; + + // On pointer released we perform some actions depending on control. We decide to whether to perform them + // depending on some parameters including but not limited to whether released is followed by a pressed, which + // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. + bool m_shouldPerformActions = true; + + // Event pointer for the ICommand.CanExecuteChanged event. + IDisposable m_epCanExecuteChangedHandler; + + // UNO TODO + // Event pointer for the Loaded event. + // IDisposable m_epLoadedHandler; + + // UNO TODO + // IDisposable m_epMenuFlyoutItemClickEventCallback; + + double m_maxKeyboardAcceleratorTextWidth; + TextBlock m_tpKeyboardAcceleratorTextBlock; + + bool m_isTemplateApplied; + + #region CommandParameter + + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public static DependencyProperty CommandParameterProperty { get; } = + DependencyProperty.Register( + "CommandParameter", typeof(object), + typeof(Controls.MenuFlyoutItem), + new FrameworkPropertyMetadata(default(object))); + + #endregion + + #region Command + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public static DependencyProperty CommandProperty { get; } = + DependencyProperty.Register( + name: nameof(Command), + propertyType: typeof(ICommand), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); + + #endregion + + #region Text + + public string Text + { + get { return (string)GetValue(TextProperty) ?? ""; } + set { SetValue(TextProperty, value); } + } + + public static DependencyProperty TextProperty { get; } = + DependencyProperty.Register( + name: nameof(Text), + propertyType: typeof(string), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(string))); + + #endregion + + public IconElement Icon + { + get => (IconElement)this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); + } + + public static DependencyProperty IconProperty { get; } = + DependencyProperty.Register( + name: nameof(Icon), + propertyType: typeof(IconElement), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); + + public string KeyboardAcceleratorTextOverride + { + get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; + set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); + } + + public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = + DependencyProperty.Register( + name: nameof(KeyboardAcceleratorTextOverride), + propertyType: typeof(string), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(string))); + + public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } + +#pragma warning disable CS0108 + public event RoutedEventHandler Click; +#pragma warning restore CS0108 + + internal void InvokeClick() + { + Click?.Invoke(this, new RoutedEventArgs(this)); + Command.ExecuteIfPossible(this.CommandParameter); + } + + public MenuFlyoutItem() + { + m_bIsPointerOver = false; + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + m_shouldPerformActions = false; + + DefaultStyleKey = typeof(MenuFlyoutItem); + + Initialize(); + } + + // Prepares object's state + void Initialize() + { + Loaded += (s, e) => ClearStateFlags(); + + this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); + } + + // Apply a template to the + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; + m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; + + SuppressIsEnabled(false); + UpdateCanExecute(); + + // Sync the logical and visual states of the control + UpdateVisualState(); + } + + // PointerPressed event handler. + protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) + { + base.OnPointerPressed(pArgs); + var handled = pArgs.Handled; + if (!handled) + { + PointerPoint spPointerPoint; + PointerPointProperties spPointerProperties; + spPointerPoint = pArgs.GetCurrentPoint(this); + spPointerProperties = spPointerPoint.Properties; + var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; + if (bIsLeftButtonPressed) + { + m_bIsPointerLeftButtonDown = true; + m_bIsPressed = true; + + pArgs.Handled = true; + UpdateVisualState(); + } + } + } + + // PointerReleased event handler. + protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) + { + base.OnPointerReleased(pArgs); + + bool handled = false; + + handled = pArgs.Handled; + if (!handled) + { + m_bIsPointerLeftButtonDown = false; + + m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; + +#if false + // UNO TODO + if (m_shouldPerformActions) + { + GestureModes gestureFollowing = GestureModes.None; + + m_bIsPressed = false; + + gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); + + // Note that we are intentionally NOT handling the args + // if we do not fall through here because basically we are no_opting in that case. + if (gestureFollowing != GestureModes.RightTapped) + { + pArgs.Handled = true; + PerformPointerUpAction(); + } + } +#else + PerformPointerUpAction(); +#endif + } + } + + private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + PerformPointerUpAction(); + pArgs.Handled = true; + } + } + + // Contains the logic to be employed if we decide to handle pointer released. + void PerformPointerUpAction() + { + if (m_shouldPerformActions) + { + Focus(FocusState.Pointer); + Invoke(); + } + } + + // Performs appropriate actions upon a mouse/keyboard invocation of a + internal virtual void Invoke() + { + RoutedEventArgs spArgs; + MenuFlyoutPresenter spParentMenuFlyoutPresenter; + + // Create the args + spArgs = new RoutedEventArgs(); + spArgs.OriginalSource = this; + + // Raise the event + Click?.Invoke(this, spArgs); + + // UNO TODO + //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); + + // Execute command associated with the button + ExecuteCommand(); + + bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; + if (!shouldPreventDismissOnPointer) + { + // Close the MenuFlyout. + spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + if (spParentMenuFlyoutPresenter != null) + { + IMenu owningMenu; + + owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; + if (owningMenu != null) + { + // We need to make close all menu flyout sub items before we hide the parent menu flyout, + // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a + // significant amount of time before closing the sub menu. + (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); + owningMenu.Close(); + } + } + } + + ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); + } + + // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) + //{ + // DependencyObject peer = pMenuFlyoutItem; + + // if (peer == null) + // { + // return; + // } + + // MenuFlyoutItem peerAsMenuFlyoutItem; + // (peer.As(&peerAsMenuFlyoutItem)); + // IFCPTR_RETURN(peerAsMenuFlyoutItem); + + // (peerAsAddProofingItemHandler(eventHandler)); + + // return S_OK; + //} + + // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) + //{ + // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) + // { + // DependencyObject spSender; + // EventArgs spArgs; + + // IFCPTR_RETURN(pSender); + // IFCPTR_RETURN(pArgs); + + // (ctl.do_query_interface(spSender, pSender)); + // (ctl.do_query_interface(spArgs, pArgs)); + + // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); + + // return S_OK; + // })); + + // return S_OK; + //} + + // Executes Command if CanExecute() returns true. + void ExecuteCommand() + { + ICommand spCommand = Command; + + if (spCommand != null) + { + object spCommandParameter; + bool canExecute; + + spCommandParameter = CommandParameter; + canExecute = spCommand.CanExecute(spCommandParameter); + if (canExecute) + { + spCommand.Execute(spCommandParameter); + } + } + } + + // PointerEnter event handler. + protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) + { + base.OnPointerEntered(pArgs); + + MenuFlyoutPresenter spParentPresenter; + + m_bIsPointerOver = true; + + spParentPresenter = GetParentMenuFlyoutPresenter(); + if (spParentPresenter != null) + { + IMenuPresenter subPresenter; + + subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; + if (subPresenter != null) + { + ISubMenuOwner subPresenterOwner; + + subPresenterOwner = subPresenter.Owner; + + if (subPresenterOwner != null) + { + subPresenterOwner.DelayCloseSubMenu(); + } + } + } + + UpdateVisualState(); + } + + // PointerExited event handler. + protected override void OnPointerExited(PointerRoutedEventArgs pArgs) + { + base.OnPointerExited(pArgs); + + // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + UpdateVisualState(); + } + + // PointerCaptureLost event handler. + protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) + { + base.OnPointerCaptureLost(pArgs); + + // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + UpdateVisualState(); + } + + // Called when the IsEnabled property changes. + private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) + { + if (!e.NewValue) + { + ClearStateFlags(); + } + else + { + UpdateVisualState(); + } + + base.OnIsEnabledChanged(e); + } + + // Called when the control got focus. + protected override void OnGotFocus(RoutedEventArgs pArgs) + { + UpdateVisualState(); + } + + // LostFocus event handler. + protected override void OnLostFocus(RoutedEventArgs pArgs) + { + if (!m_bIsPointerLeftButtonDown) + { + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + m_bIsPressed = false; + } + + UpdateVisualState(); + } + + // KeyDown event handler. + protected override void OnKeyDown(KeyRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + var key = pArgs.Key; + handled = KeyPressMenuFlyout.KeyDown(key, this); + pArgs.Handled = handled; + } + } + + // KeyUp event handler. + protected override void OnKeyUp(KeyRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + var key = (pArgs.Key); + KeyPressMenuFlyout.KeyUp(key, this); + pArgs.Handled = true; + } + } + + // Handle the custom property changed event and call the OnPropertyChanged2 methods. + internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) + { + if (args.Property == UIElement.VisibilityProperty) + { + OnVisibilityChanged(); + } + else if (args.Property == MenuFlyoutItem.CommandProperty) + { + OnCommandChanged(args.OldValue, args.NewValue); + } + else if (args.Property == MenuFlyoutItem.CommandParameterProperty) + { + UpdateCanExecute(); + } + } + + // Update the visual states when the Visibility property is changed. + void OnVisibilityChanged() + { + Visibility visibility = Visibility; + + if (Visibility.Visible != visibility) + { + ClearStateFlags(); + } + } + + private protected override void OnLoaded() + { + base.OnLoaded(); + + if (m_epCanExecuteChangedHandler == null) + { + ICommand spCommand = Command; + + if (spCommand != null) + { + void OnCanExecuteChanged(object sender, object args) + { + UpdateCanExecute(); + } + + spCommand.CanExecuteChanged += OnCanExecuteChanged; + + m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); + } + } + + // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, + // we need to update our value now. + UpdateCanExecute(); + } + + private protected override void OnUnloaded() + { + base.OnUnloaded(); + + if (m_epCanExecuteChangedHandler != null) + { + ICommand spCommand = Command; + + if (spCommand != null) + { + m_epCanExecuteChangedHandler.Dispose(); + } + } + } + + // Called when the Command property changes. + void + OnCommandChanged( + object pOldValue, + object pNewValue) + { + // Remove handler for CanExecuteChanged from the old value + m_epCanExecuteChangedHandler?.Dispose(); + + if (pOldValue != null) + { + XamlUICommand oldCommand = pOldValue as XamlUICommand; + + if (oldCommand != null) + { + CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); + } + } + + // Subscribe to the CanExecuteChanged event on the new value + if (pNewValue != null) + { + var spNewCommand = pNewValue as ICommand; + + void OnCanExecuteUpdated(object sender, object args) + { + UpdateCanExecute(); + } + + spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; + m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); + + var newCommandAsUICommand = spNewCommand as XamlUICommand; + + if (newCommandAsUICommand != null) + { + CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); + CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); + CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); + CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); + CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); + } + } + + // Coerce the button enabled state with the CanExecute state of the command. + UpdateCanExecute(); + } + + // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. + void UpdateCanExecute() + { + ICommand spCommand; + object spCommandParameter; + bool canExecute = true; + + spCommand = Command; + if (spCommand != null) + { + spCommandParameter = CommandParameter; + canExecute = spCommand.CanExecute(spCommandParameter); + } + + SuppressIsEnabled(!canExecute); + } + + // Change to the correct visual state for the + private protected override void ChangeVisualState( + // true to use transitions when updating the visual state, false + // to snap directly to the new visual state. + bool bUseTransitions) + { + bool hasToggleMenuItem = false; + bool hasIconMenuItem = false; + bool hasMenuItemWithKeyboardAcceleratorText = false; + bool isKeyboardPresent = false; + + MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); + if (spPresenter != null) + { + hasToggleMenuItem = spPresenter.GetContainsToggleItems(); + hasIconMenuItem = spPresenter.GetContainsIconItems(); + hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); + } + + var bIsEnabled = IsEnabled; + var focusState = FocusState; + var shouldBeNarrow = GetShouldBeNarrow(); + + // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, + // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. + if (hasMenuItemWithKeyboardAcceleratorText) + { + // UNO TODO + // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); + isKeyboardPresent = true; + } + + // CommonStates + if (!bIsEnabled) + { + VisualStateManager.GoToState(this, "Disabled", bUseTransitions); + } + else if (m_bIsPressed) + { + VisualStateManager.GoToState(this, "Pressed", bUseTransitions); + } + else if (m_bIsPointerOver) + { + VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Normal", bUseTransitions); + } + + // FocusStates + if (FocusState.Unfocused != focusState && bIsEnabled) + { + if (FocusState.Pointer == focusState) + { + VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Focused", bUseTransitions); + } + } + else + { + VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); + } + + // CheckPlaceholderStates + if (hasToggleMenuItem && hasIconMenuItem) + { + VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); + } + else if (hasToggleMenuItem) + { + VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); + } + else if (hasIconMenuItem) + { + VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); + } + + // PaddingSizeStates + if (shouldBeNarrow) + { + VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); + } + + // We'll make the accelerator text visible if any item has accelerator text, + // as this causes the margin to be applied which reserves space, ensuring that accelerator text + // in one item won't be at the same horizontal position as label text in another item. + if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); + } + } + + // Clear flags relating to the visual state. Called when IsEnabled is set to false + // or when Visibility is set to Hidden or Collapsed. + void ClearStateFlags() + { + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + + UpdateVisualState(); + } + + // Create MenuFlyoutItemAutomationPeer to represent the + protected override AutomationPeer OnCreateAutomationPeer() + { + return new MenuFlyoutItemAutomationPeer(this); + } + + private protected override string GetPlainText() => Text; + + internal string KeyboardAcceleratorTextOverrideImpl + { + get + { + var pValue = KeyboardAcceleratorTextOverride; + + // If we have no keyboard accelerator text already provided by the app, + // then we'll see if we can ruct it ourselves based on keyboard accelerators + // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" + // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". + if (pValue == null) + { + pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); + + // If we were able to get a string representation from keyboard accelerators, + // then we should now set that as the value of KeyboardAcceleratorText. + if (pValue != null) + { + KeyboardAcceleratorTextOverrideImpl = pValue; + } + } + + return pValue; + } + set + { + KeyboardAcceleratorTextOverride = value; + } + } + + internal Size GetKeyboardAcceleratorTextDesiredSize() + { + var desiredSize = new Size(0, 0); + + if (!m_isTemplateApplied) + { + bool templateApplied = ApplyTemplate(); + m_isTemplateApplied = templateApplied; + } + + if (m_tpKeyboardAcceleratorTextBlock != null) + { + Thickness margin; + + m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; + margin = m_tpKeyboardAcceleratorTextBlock.Margin; + + desiredSize.Width -= (float)(margin.Left + margin.Right); + desiredSize.Height -= (float)(margin.Top + margin.Bottom); + } + + return desiredSize; + } + + internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) + { + if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) + { + m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; + + MenuFlyoutItemTemplateSettings templateSettings; + templateSettings = TemplateSettings; + + if (templateSettings == null) + { + MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); + TemplateSettings = templateSettingsImplementation; + templateSettings = templateSettingsImplementation; + } + + templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; + } + } + + internal virtual bool HasToggle() + { + return false; + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs new file mode 100644 index 000000000000..9c3e701fd3a5 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs @@ -0,0 +1,828 @@ +using System; +using Uno.Client; +using Uno.Disposables; +using Windows.Foundation; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using ICommand = System.Windows.Input.ICommand; +using Microsoft.UI.Xaml.Markup; + +#if HAS_UNO_WINUI +using Microsoft.UI.Input; +#else +using Windows.Devices.Input; +using Windows.UI.Input; +#endif + +namespace Microsoft.UI.Xaml.Controls +{ + [ContentProperty(Name = nameof(Text))] + public partial class MenuFlyoutItem : MenuFlyoutItemBase + { + // Whether the pointer is currently over the + bool m_bIsPointerOver = true; + + // Whether the pointer is currently pressed over the + internal bool m_bIsPressed = true; + + // Whether the pointer's left button is currently down. + internal bool m_bIsPointerLeftButtonDown = true; + + // True if the SPACE or ENTER key is currently pressed, false otherwise. + internal bool m_bIsSpaceOrEnterKeyDown = true; + + // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. + internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; + + // On pointer released we perform some actions depending on control. We decide to whether to perform them + // depending on some parameters including but not limited to whether released is followed by a pressed, which + // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. + bool m_shouldPerformActions = true; + + // Event pointer for the ICommand.CanExecuteChanged event. + IDisposable m_epCanExecuteChangedHandler; + + // UNO TODO + // Event pointer for the Loaded event. + // IDisposable m_epLoadedHandler; + + // UNO TODO + // IDisposable m_epMenuFlyoutItemClickEventCallback; + + double m_maxKeyboardAcceleratorTextWidth; + TextBlock m_tpKeyboardAcceleratorTextBlock; + + bool m_isTemplateApplied; + + #region CommandParameter + + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public static DependencyProperty CommandParameterProperty { get; } = + DependencyProperty.Register( + "CommandParameter", typeof(object), + typeof(Controls.MenuFlyoutItem), + new FrameworkPropertyMetadata(default(object))); + + #endregion + + #region Command + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public static DependencyProperty CommandProperty { get; } = + DependencyProperty.Register( + name: nameof(Command), + propertyType: typeof(ICommand), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); + + #endregion + + #region Text + + public string Text + { + get { return (string)GetValue(TextProperty) ?? ""; } + set { SetValue(TextProperty, value); } + } + + public static DependencyProperty TextProperty { get; } = + DependencyProperty.Register( + name: nameof(Text), + propertyType: typeof(string), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(string))); + + #endregion + + public IconElement Icon + { + get => (IconElement)this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); + } + + public static DependencyProperty IconProperty { get; } = + DependencyProperty.Register( + name: nameof(Icon), + propertyType: typeof(IconElement), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); + + public string KeyboardAcceleratorTextOverride + { + get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; + set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); + } + + public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = + DependencyProperty.Register( + name: nameof(KeyboardAcceleratorTextOverride), + propertyType: typeof(string), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(string))); + + public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } + +#pragma warning disable CS0108 + public event RoutedEventHandler Click; +#pragma warning restore CS0108 + + internal void InvokeClick() + { + Click?.Invoke(this, new RoutedEventArgs(this)); + Command.ExecuteIfPossible(this.CommandParameter); + } + + public MenuFlyoutItem() + { + m_bIsPointerOver = false; + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + m_shouldPerformActions = false; + + DefaultStyleKey = typeof(MenuFlyoutItem); + + Initialize(); + } + + // Prepares object's state + void Initialize() + { + Loaded += (s, e) => ClearStateFlags(); + + this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); + } + + // Apply a template to the + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; + m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; + + SuppressIsEnabled(false); + UpdateCanExecute(); + + // Sync the logical and visual states of the control + UpdateVisualState(); + } + + // PointerPressed event handler. + protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) + { + base.OnPointerPressed(pArgs); + var handled = pArgs.Handled; + if (!handled) + { + PointerPoint spPointerPoint; + PointerPointProperties spPointerProperties; + spPointerPoint = pArgs.GetCurrentPoint(this); + spPointerProperties = spPointerPoint.Properties; + var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; + if (bIsLeftButtonPressed) + { + m_bIsPointerLeftButtonDown = true; + m_bIsPressed = true; + + pArgs.Handled = true; + UpdateVisualState(); + } + } + } + + // PointerReleased event handler. + protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) + { + base.OnPointerReleased(pArgs); + + bool handled = false; + + handled = pArgs.Handled; + if (!handled) + { + m_bIsPointerLeftButtonDown = false; + + m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; + +#if false + // UNO TODO + if (m_shouldPerformActions) + { + GestureModes gestureFollowing = GestureModes.None; + + m_bIsPressed = false; + + gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); + + // Note that we are intentionally NOT handling the args + // if we do not fall through here because basically we are no_opting in that case. + if (gestureFollowing != GestureModes.RightTapped) + { + pArgs.Handled = true; + PerformPointerUpAction(); + } + } +#else + PerformPointerUpAction(); +#endif + } + } + + private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + PerformPointerUpAction(); + pArgs.Handled = true; + } + } + + // Contains the logic to be employed if we decide to handle pointer released. + void PerformPointerUpAction() + { + if (m_shouldPerformActions) + { + Focus(FocusState.Pointer); + Invoke(); + } + } + + // Performs appropriate actions upon a mouse/keyboard invocation of a + internal virtual void Invoke() + { + RoutedEventArgs spArgs; + MenuFlyoutPresenter spParentMenuFlyoutPresenter; + + // Create the args + spArgs = new RoutedEventArgs(); + spArgs.OriginalSource = this; + + // Raise the event + Click?.Invoke(this, spArgs); + + // UNO TODO + //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); + + // Execute command associated with the button + ExecuteCommand(); + + bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; + if (!shouldPreventDismissOnPointer) + { + // Close the MenuFlyout. + spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + if (spParentMenuFlyoutPresenter != null) + { + IMenu owningMenu; + + owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; + if (owningMenu != null) + { + // We need to make close all menu flyout sub items before we hide the parent menu flyout, + // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a + // significant amount of time before closing the sub menu. + (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); + owningMenu.Close(); + } + } + } + + ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); + } + + // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) + //{ + // DependencyObject peer = pMenuFlyoutItem; + + // if (peer == null) + // { + // return; + // } + + // MenuFlyoutItem peerAsMenuFlyoutItem; + // (peer.As(&peerAsMenuFlyoutItem)); + // IFCPTR_RETURN(peerAsMenuFlyoutItem); + + // (peerAsAddProofingItemHandler(eventHandler)); + + // return S_OK; + //} + + // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) + //{ + // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) + // { + // DependencyObject spSender; + // EventArgs spArgs; + + // IFCPTR_RETURN(pSender); + // IFCPTR_RETURN(pArgs); + + // (ctl.do_query_interface(spSender, pSender)); + // (ctl.do_query_interface(spArgs, pArgs)); + + // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); + + // return S_OK; + // })); + + // return S_OK; + //} + + // Executes Command if CanExecute() returns true. + void ExecuteCommand() + { + ICommand spCommand = Command; + + if (spCommand != null) + { + object spCommandParameter; + bool canExecute; + + spCommandParameter = CommandParameter; + canExecute = spCommand.CanExecute(spCommandParameter); + if (canExecute) + { + spCommand.Execute(spCommandParameter); + } + } + } + + // PointerEnter event handler. + protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) + { + base.OnPointerEntered(pArgs); + + MenuFlyoutPresenter spParentPresenter; + + m_bIsPointerOver = true; + + spParentPresenter = GetParentMenuFlyoutPresenter(); + if (spParentPresenter != null) + { + IMenuPresenter subPresenter; + + subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; + if (subPresenter != null) + { + ISubMenuOwner subPresenterOwner; + + subPresenterOwner = subPresenter.Owner; + + if (subPresenterOwner != null) + { + subPresenterOwner.DelayCloseSubMenu(); + } + } + } + + UpdateVisualState(); + } + + // PointerExited event handler. + protected override void OnPointerExited(PointerRoutedEventArgs pArgs) + { + base.OnPointerExited(pArgs); + + // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + UpdateVisualState(); + } + + // PointerCaptureLost event handler. + protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) + { + base.OnPointerCaptureLost(pArgs); + + // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + UpdateVisualState(); + } + + // Called when the IsEnabled property changes. + private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) + { + if (!e.NewValue) + { + ClearStateFlags(); + } + else + { + UpdateVisualState(); + } + + base.OnIsEnabledChanged(e); + } + + // Called when the control got focus. + protected override void OnGotFocus(RoutedEventArgs pArgs) + { + UpdateVisualState(); + } + + // LostFocus event handler. + protected override void OnLostFocus(RoutedEventArgs pArgs) + { + if (!m_bIsPointerLeftButtonDown) + { + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + m_bIsPressed = false; + } + + UpdateVisualState(); + } + + // KeyDown event handler. + protected override void OnKeyDown(KeyRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + var key = pArgs.Key; + handled = KeyPressMenuFlyout.KeyDown(key, this); + pArgs.Handled = handled; + } + } + + // KeyUp event handler. + protected override void OnKeyUp(KeyRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + if (!handled) + { + var key = (pArgs.Key); + KeyPressMenuFlyout.KeyUp(key, this); + pArgs.Handled = true; + } + } + + // Handle the custom property changed event and call the OnPropertyChanged2 methods. + internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) + { + if (args.Property == UIElement.VisibilityProperty) + { + OnVisibilityChanged(); + } + else if (args.Property == MenuFlyoutItem.CommandProperty) + { + OnCommandChanged(args.OldValue, args.NewValue); + } + else if (args.Property == MenuFlyoutItem.CommandParameterProperty) + { + UpdateCanExecute(); + } + } + + // Update the visual states when the Visibility property is changed. + void OnVisibilityChanged() + { + Visibility visibility = Visibility; + + if (Visibility.Visible != visibility) + { + ClearStateFlags(); + } + } + + private protected override void OnLoaded() + { + base.OnLoaded(); + + if (m_epCanExecuteChangedHandler == null) + { + ICommand spCommand = Command; + + if (spCommand != null) + { + void OnCanExecuteChanged(object sender, object args) + { + UpdateCanExecute(); + } + + spCommand.CanExecuteChanged += OnCanExecuteChanged; + + m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); + } + } + + // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, + // we need to update our value now. + UpdateCanExecute(); + } + + private protected override void OnUnloaded() + { + base.OnUnloaded(); + + if (m_epCanExecuteChangedHandler != null) + { + ICommand spCommand = Command; + + if (spCommand != null) + { + m_epCanExecuteChangedHandler.Dispose(); + } + } + } + + // Called when the Command property changes. + void + OnCommandChanged( + object pOldValue, + object pNewValue) + { + // Remove handler for CanExecuteChanged from the old value + m_epCanExecuteChangedHandler?.Dispose(); + + if (pOldValue != null) + { + XamlUICommand oldCommand = pOldValue as XamlUICommand; + + if (oldCommand != null) + { + CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); + CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); + } + } + + // Subscribe to the CanExecuteChanged event on the new value + if (pNewValue != null) + { + var spNewCommand = pNewValue as ICommand; + + void OnCanExecuteUpdated(object sender, object args) + { + UpdateCanExecute(); + } + + spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; + m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); + + var newCommandAsUICommand = spNewCommand as XamlUICommand; + + if (newCommandAsUICommand != null) + { + CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); + CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); + CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); + CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); + CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); + } + } + + // Coerce the button enabled state with the CanExecute state of the command. + UpdateCanExecute(); + } + + // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. + void UpdateCanExecute() + { + ICommand spCommand; + object spCommandParameter; + bool canExecute = true; + + spCommand = Command; + if (spCommand != null) + { + spCommandParameter = CommandParameter; + canExecute = spCommand.CanExecute(spCommandParameter); + } + + SuppressIsEnabled(!canExecute); + } + + // Change to the correct visual state for the + private protected override void ChangeVisualState( + // true to use transitions when updating the visual state, false + // to snap directly to the new visual state. + bool bUseTransitions) + { + bool hasToggleMenuItem = false; + bool hasIconMenuItem = false; + bool hasMenuItemWithKeyboardAcceleratorText = false; + bool isKeyboardPresent = false; + + MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); + if (spPresenter != null) + { + hasToggleMenuItem = spPresenter.GetContainsToggleItems(); + hasIconMenuItem = spPresenter.GetContainsIconItems(); + hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); + } + + var bIsEnabled = IsEnabled; + var focusState = FocusState; + var shouldBeNarrow = GetShouldBeNarrow(); + + // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, + // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. + if (hasMenuItemWithKeyboardAcceleratorText) + { + // UNO TODO + // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); + isKeyboardPresent = true; + } + + // CommonStates + if (!bIsEnabled) + { + VisualStateManager.GoToState(this, "Disabled", bUseTransitions); + } + else if (m_bIsPressed) + { + VisualStateManager.GoToState(this, "Pressed", bUseTransitions); + } + else if (m_bIsPointerOver) + { + VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Normal", bUseTransitions); + } + + // FocusStates + if (FocusState.Unfocused != focusState && bIsEnabled) + { + if (FocusState.Pointer == focusState) + { + VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Focused", bUseTransitions); + } + } + else + { + VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); + } + + // CheckPlaceholderStates + if (hasToggleMenuItem && hasIconMenuItem) + { + VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); + } + else if (hasToggleMenuItem) + { + VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); + } + else if (hasIconMenuItem) + { + VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); + } + + // PaddingSizeStates + if (shouldBeNarrow) + { + VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); + } + + // We'll make the accelerator text visible if any item has accelerator text, + // as this causes the margin to be applied which reserves space, ensuring that accelerator text + // in one item won't be at the same horizontal position as label text in another item. + if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); + } + } + + // Clear flags relating to the visual state. Called when IsEnabled is set to false + // or when Visibility is set to Hidden or Collapsed. + void ClearStateFlags() + { + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + + UpdateVisualState(); + } + + // Create MenuFlyoutItemAutomationPeer to represent the + protected override AutomationPeer OnCreateAutomationPeer() + { + return new MenuFlyoutItemAutomationPeer(this); + } + + private protected override string GetPlainText() => Text; + + internal string KeyboardAcceleratorTextOverrideImpl + { + get + { + var pValue = KeyboardAcceleratorTextOverride; + + // If we have no keyboard accelerator text already provided by the app, + // then we'll see if we can ruct it ourselves based on keyboard accelerators + // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" + // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". + if (pValue == null) + { + pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); + + // If we were able to get a string representation from keyboard accelerators, + // then we should now set that as the value of KeyboardAcceleratorText. + if (pValue != null) + { + KeyboardAcceleratorTextOverrideImpl = pValue; + } + } + + return pValue; + } + set + { + KeyboardAcceleratorTextOverride = value; + } + } + + internal Size GetKeyboardAcceleratorTextDesiredSize() + { + var desiredSize = new Size(0, 0); + + if (!m_isTemplateApplied) + { + bool templateApplied = ApplyTemplate(); + m_isTemplateApplied = templateApplied; + } + + if (m_tpKeyboardAcceleratorTextBlock != null) + { + Thickness margin; + + m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; + margin = m_tpKeyboardAcceleratorTextBlock.Margin; + + desiredSize.Width -= (float)(margin.Left + margin.Right); + desiredSize.Height -= (float)(margin.Top + margin.Bottom); + } + + return desiredSize; + } + + internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) + { + if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) + { + m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; + + MenuFlyoutItemTemplateSettings templateSettings; + templateSettings = TemplateSettings; + + if (templateSettings == null) + { + MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); + TemplateSettings = templateSettingsImplementation; + templateSettings = templateSettingsImplementation; + } + + templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; + } + } + + internal virtual bool HasToggle() + { + return false; + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs new file mode 100644 index 000000000000..9cbe9fd49f74 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Uno.Disposables; +using Uno.UI.Extensions; +using Uno.UI.Xaml.Core; +using Windows.Foundation; +using Windows.System; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Markup; + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyoutSubItem +{ + public IconElement Icon + { + get => (IconElement)this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); + } + + + public string Text + { + get => (string)this.GetValue(TextProperty) ?? ""; + set => SetValue(TextProperty, value); + } + + public static Microsoft.UI.Xaml.DependencyProperty TextProperty { get; } = + Microsoft.UI.Xaml.DependencyProperty.Register( + "Text", + typeof(string), + typeof(MenuFlyoutSubItem), + new FrameworkPropertyMetadata(default(string))); + + public static Microsoft.UI.Xaml.DependencyProperty IconProperty { get; } = + Microsoft.UI.Xaml.DependencyProperty.Register( + "Icon", + typeof(IconElement), + typeof(MenuFlyoutSubItem), + new FrameworkPropertyMetadata(default(IconElement))); +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs index 29f1a3ab6933..656587aaf52a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs @@ -15,870 +15,4 @@ namespace Microsoft.UI.Xaml.Controls; [ContentProperty(Name = nameof(Items))] public partial class MenuFlyoutSubItem : MenuFlyoutItemBase, ISubMenuOwner { - // Popup for the MenuFlyoutSubItem - Popup m_tpPopup; - - // Presenter for the MenuFlyoutSubItem - Control m_tpPresenter; - - // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. - // UNO TODO Transition m_tpMenuPopupThemeTransition = null; - - // Event pointer for the Loaded event - // IDisposable m_epLoadedHandler; - - // Event pointer for the size changed on the MenuFlyoutSubItem's presenter - IDisposable m_epPresenterSizeChangedHandler; - - // Helper to which to delegate cascading menu functionality. - CascadingMenuHelper m_menuHelper; - - // Weak reference the parent that owns the menu that this item belongs to. - private WeakReference m_wrParentOwner; - - DependencyObjectCollection m_tpItems; - - public string Text - { - get => (string)this.GetValue(TextProperty) ?? ""; - set => SetValue(TextProperty, value); - } - - public IList Items => m_tpItems; - - public IconElement Icon - { - get => (IconElement)this.GetValue(IconProperty); - set => this.SetValue(IconProperty, value); - } - - public static Microsoft.UI.Xaml.DependencyProperty TextProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - "Text", - typeof(string), - typeof(MenuFlyoutSubItem), - new FrameworkPropertyMetadata(default(string))); - - public static Microsoft.UI.Xaml.DependencyProperty IconProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - "Icon", - typeof(IconElement), - typeof(MenuFlyoutSubItem), - new FrameworkPropertyMetadata(default(IconElement))); - - public MenuFlyoutSubItem() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ", this)); -#endif // MFSI_DEBUG - - PrepareState(); - - DefaultStyleKey = typeof(MenuFlyoutSubItem); - } - - void PrepareState() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareState.", this)); -#endif // MFSI_DEBUG - - // Create the sub menu items collection and set the owner - m_tpItems = new DependencyObjectCollection(this); - - m_menuHelper = new CascadingMenuHelper(); - m_menuHelper.Initialize(this); - } - -#if false - void DisconnectFrameworkPeerCore() - { - // Ensure the clean up the items whenever MenuFlyoutSubItem is disconnected - //if (m_tpItems.GetAsCoreDO() != null) - //{ - // (Collection_Clear((CCollection*)(m_tpItems.GetAsCoreDO()))); - // (Collection_SetOwner((CCollection*)(m_tpItems.GetAsCoreDO()), null)); - //} - - // (MenuFlyoutSubItemGenerated.DisconnectFrameworkPeerCore()); - } -#endif - - protected override void OnApplyTemplate() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnApplyTemplate.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnApplyTemplate(); - } - - // PointerEntered event handler that shows the MenuFlyoutSubItem - // whenever the pointer is over to the - // In case of touch, the MenuFlyoutSubItem will be shown by - // PointerReleased event. - - protected override void OnPointerEntered(PointerRoutedEventArgs args) - { - base.OnPointerEntered(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerEntered.", this)); -#endif // MFSI_DEBUG - - UpdateParentOwner(null /*parentMenuFlyoutPresenter*/); - m_menuHelper.OnPointerEntered(args); - } - - // PointerExited event handler that ensures the close MenuFlyoutSubItem - // whenever the pointer over is out of the current MenuFlyoutSubItem or - // out of the main presenter. If the exited point is on MenuFlyoutSubItem - // or sub presenter position, we want to keep the opened - - protected override void OnPointerExited(PointerRoutedEventArgs args) - { - base.OnPointerExited(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerExited.", this)); -#endif // MFSI_DEBUG - - bool parentIsSubMenu = false; - MenuFlyoutPresenter parentPresenter = GetParentMenuFlyoutPresenter(); - - if (parentPresenter != null) - { - parentIsSubMenu = parentPresenter.IsSubPresenter; - } - - m_menuHelper.OnPointerExited(args, parentIsSubMenu); - } - - // PointerPressed event handler that ensures the pressed state. - - protected override void OnPointerPressed(PointerRoutedEventArgs args) - { - base.OnPointerPressed(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerPressed.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnPointerPressed(args); - } - - // PointerReleased event handler that shows MenuFlyoutSubItem in - // case of touch input. - - protected override void OnPointerReleased(PointerRoutedEventArgs args) - { - base.OnPointerReleased(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerReleased.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnPointerReleased(args); - - } - - - protected override void OnGotFocus(RoutedEventArgs args) - { - base.OnGotFocus(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnGotFocus.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnGotFocus(args); - } - - - protected override void OnLostFocus(RoutedEventArgs args) - { - base.OnLostFocus(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnLostFocus.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnLostFocus(args); - } - - // KeyDown event handler that handles the keyboard navigation between - // the menu items and shows the MenuFlyoutSubItem in case of hitting - // the enter or right arrow key. - - protected override void OnKeyDown(KeyRoutedEventArgs args) - { - base.OnKeyDown(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyDown.", this)); -#endif // MFSI_DEBUG - - bool handled = args.Handled; - bool shouldHandleEvent = false; - - if (!handled) - { - MenuFlyoutPresenter spParentPresenter = GetParentMenuFlyoutPresenter(); - - if (spParentPresenter != null) - { - var key = args.Key; - - // Navigate each item with the arrow down or up key - if (key == VirtualKey.Down || key == VirtualKey.Up) - { - spParentPresenter.HandleUpOrDownKey(key == VirtualKey.Down); - UpdateVisualState(); - - // If we handle the event here, it won't get handled in m_menuHelper.OnKeyDown, - // so we'll do that afterwards. - shouldHandleEvent = true; - } - } - } - - m_menuHelper.OnKeyDown(args); - args.Handled = shouldHandleEvent; - } - - - protected override void OnKeyUp(KeyRoutedEventArgs args) - { - base.OnKeyUp(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyUp.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnKeyUp(args); - } - - // Ensure the creating the popup and menu presenter to show the - void EnsurePopupAndPresenter() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsurePopupAndPresenter.", this)); -#endif // MFSI_DEBUG - - if (m_tpPopup == null) - { - MenuFlyoutPresenter spParentMenuFlyoutPresenter = null; - Control spPresenter; - UIElement spPresenterAsUI; - FrameworkElement spPresenterAsFE; - Popup spPopup; - - spPopup = new Popup(); - spPopup.IsSubMenu = true; - spPopup.IsLightDismissEnabled = false; - - spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); -#if false // UNO TODO Windowed Popup is not available - if (spParentMenuFlyoutPresenter != null) - { - spParentMenuFlyout = spParentMenuFlyoutPresenter.GetParentMenuFlyout(); - // Set the windowed Popup if the MenuFlyout is set the windowed Popup - if (spParentMenuFlyout && spParentMenuFlyout.IsWindowedPopup()) - { - ASSERT((CPopup*)(spPopup as Popup.GetHandle()).DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle())); - - ((CPopup*)(spPopup as Popup.GetHandle()).SetIsWindowed()); - - // Ensure the sub menu is the windowed Popup - ASSERT((CPopup*)(spPopup as Popup.GetHandle()).IsWindowed()); - - xaml.IXamlRoot xamlRoot = XamlRoot.GetForElementStatic(spParentMenuFlyoutPresenter); - if (xamlRoot) - { - (spPopup as Popup.XamlRoot = xamlRoot); - } - } - } -#endif - - spPresenter = CreateSubPresenter(); - spPresenterAsUI = spPresenter; - - if (spParentMenuFlyoutPresenter != null) - { - int parentDepth = spParentMenuFlyoutPresenter.GetDepth(); - (spPresenter as MenuFlyoutPresenter).SetDepth(parentDepth + 1); - } - - spPopup.Child = spPresenterAsUI as FrameworkElement; - - m_tpPresenter = spPresenter; - m_tpPopup = spPopup; - - ((ItemsControl)m_tpPresenter).ItemsSource = m_tpItems; - - spPresenterAsFE = spPresenter; - - spPresenterAsFE.SizeChanged += OnPresenterSizeChanged; - m_epPresenterSizeChangedHandler = Disposable.Create(() => spPresenterAsFE.SizeChanged -= OnPresenterSizeChanged); - - m_menuHelper.SetSubMenuPresenter(spPresenter as Control); - } - } - - void ForwardPresenterProperties( - MenuFlyout pOwnerMenuFlyout, - MenuFlyoutPresenter pParentMenuFlyoutPresenter, - MenuFlyoutPresenter pSubMenuFlyoutPresenter) - { - Style spStyle; - ElementTheme parentPresenterTheme; - object spDataContext; - FrameworkElement spPopupAsFE; - MenuFlyoutPresenter spSubMenuFlyoutPresenter = pSubMenuFlyoutPresenter; - Control spSubMenuFlyoutPresenterAsControl; - DependencyObject spThisAsDO = this; - - global::System.Diagnostics.Debug.Assert(pOwnerMenuFlyout != null && pParentMenuFlyoutPresenter != null && pSubMenuFlyoutPresenter != null); - - spSubMenuFlyoutPresenterAsControl = spSubMenuFlyoutPresenter; - - // Set the sub presenter style from the MenuFlyout's presenter style - spStyle = pOwnerMenuFlyout.MenuFlyoutPresenterStyle; - - if (spStyle != null) - { - ((Control)pSubMenuFlyoutPresenter).Style = spStyle; - } - else - { - ((Control)pSubMenuFlyoutPresenter).ClearValue(FrameworkElement.StyleProperty); - } - - // Set the sub presenter's RequestTheme from the parent presenter's RequestTheme - parentPresenterTheme = pParentMenuFlyoutPresenter.RequestedTheme; - pSubMenuFlyoutPresenter.RequestedTheme = parentPresenterTheme; - - // Set the sub presenter's DataContext from the parent presenter's DataContext - spDataContext = pParentMenuFlyoutPresenter.DataContext; - pSubMenuFlyoutPresenter.DataContext = spDataContext; - - // Set the sub presenter's FlowDirection from the current sub menu item's FlowDirection - var flowDirection = FlowDirection; - pSubMenuFlyoutPresenter.FlowDirection = flowDirection; - - // Set the popup's FlowDirection from the current FlowDirection - spPopupAsFE = m_tpPopup; - spPopupAsFE.FlowDirection = flowDirection; - - // Set the sub presenter's Language from the parent presenter's Language - pSubMenuFlyoutPresenter.Language = pParentMenuFlyoutPresenter.Language; - - // Set the sub presenter's IsTextScaleFactorEnabledInternal from the parent presenter's IsTextScaleFactorEnabledInternal - var isTextScaleFactorEnabled = pParentMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal; - pSubMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal = isTextScaleFactorEnabled; - - ElementSoundMode soundMode = ElementSoundPlayerService.Instance.GetEffectiveSoundMode(spThisAsDO as DependencyObject); - - (spSubMenuFlyoutPresenterAsControl as Control).ElementSoundMode = soundMode; - } - - // Ensure that any currently open MenuFlyoutSubItems are closed - void EnsureCloseExistingSubItems() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsureCloseExistingSubItems.", this)); -#endif // MFSI_DEBUG - - MenuFlyoutPresenter spParentPresenter; - - spParentPresenter = GetParentMenuFlyoutPresenter(); - if (spParentPresenter != null) - { - IMenuPresenter openedSubPresenter; - - openedSubPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; - if (openedSubPresenter != null) - { - ISubMenuOwner subMenuOwner; - - subMenuOwner = openedSubPresenter.Owner; - if (subMenuOwner != null && subMenuOwner != this) - { - openedSubPresenter.CloseSubMenu(); - } - } - } - - - } - - bool IsOpen => m_tpPopup?.IsOpen ?? false; - - Control - CreateSubPresenter() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CreateSubPresenter.", this)); -#endif // MFSI_DEBUG - - MenuFlyoutPresenter spPresenter = new MenuFlyoutPresenter(); - - // Specify the sub MenuFlyoutPresenter - (spPresenter as MenuFlyoutPresenter).IsSubPresenter = true; - (spPresenter as IMenuPresenter).Owner = this; - - return spPresenter; - } - - void UpdateParentOwner(MenuFlyoutPresenter parentMenuFlyoutPresenter) - { - MenuFlyoutPresenter parentPresenter = parentMenuFlyoutPresenter; - if (parentPresenter == null) - { - parentPresenter = GetParentMenuFlyoutPresenter(); - } -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentPresenter=0x%p.", this, parentPresenter)); -#endif // MFSI_DEBUG - - if (parentPresenter != null) - { - ISubMenuOwner parentSubMenuOwner; - parentSubMenuOwner = (parentPresenter as IMenuPresenter).Owner; - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentSubMenuOwner=0x%p.", this, parentSubMenuOwner)); -#endif // MFSI_DEBUG - - if (parentSubMenuOwner != null) - { - ((ISubMenuOwner)this).ParentOwner = parentSubMenuOwner; - } - } - } - - // Set the popup open or close status for MenuFlyoutSubItem and ensure the - // focus to the current presenter. - void SetIsOpen(bool isOpen) - { - bool isOpened = false; - - isOpened = m_tpPopup.IsOpen; - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: SetIsOpen isOpen=%d, isOpened=%d.", this, isOpen, isOpened)); -#endif // MFSI_DEBUG - - if (isOpen != isOpened) - { - (m_tpPresenter as IMenuPresenter).Owner = isOpen ? this : null; - - MenuFlyoutPresenter parentPresenter; - parentPresenter = GetParentMenuFlyoutPresenter(); - - if (parentPresenter != null) - { - (parentPresenter as IMenuPresenter).SubPresenter = isOpen ? m_tpPresenter as MenuFlyoutPresenter : null; - - IMenu owningMenu; - owningMenu = (parentPresenter as IMenuPresenter).OwningMenu; - - if (owningMenu != null) - { - (m_tpPresenter as IMenuPresenter).OwningMenu = isOpen ? owningMenu : null; - } - - UpdateParentOwner(parentPresenter); - } - - // UNO TODO - VisualTree visualTree = VisualTree.GetForElement(this); - if (visualTree is not null) - { - // Put the popup on the same VisualTree as this flyout sub item to make sure it shows up in the right place - m_tpPopup.SetVisualTree(visualTree); - } - - // Set the popup open or close state - m_tpPopup.IsOpen = isOpen; - - // Set the focus to the displayed sub menu presenter when MenuFlyoutSubItem is opened and - // set the focus back to the original sub item when the displayed sub menu presenter is closed. - if (isOpen) - { - // Set the focus to the displayed sub menu presenter to navigate the each sub items - m_tpPresenter.Focus(FocusState.Programmatic); - - // UNO TODO - // (DependencyObject.SetFocusedElement( - // spPresenterAsDO as DependencyObject, - // xaml.FocusState_Programmatic, - // false /*animateIfBringIntoView*/, - // &focusUpdated)); - } - else - { - // Set the focus to the sub menu item - this.Focus(FocusState.Programmatic); - - // UNO TODO - //(DependencyObject.SetFocusedElement( - // spThisAsDO as DependencyObject, - // xaml.FocusState_Programmatic, - // false /*animateIfBringIntoView*/, - // &focusUpdated)); - } - - UpdateVisualState(); - } - - - } - - internal void Open() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Open.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OpenSubMenu(); - } - - internal void Close() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Close.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.CloseSubMenu(); - - } - - private protected override void ChangeVisualState(bool bUseTransitions) - { - bool hasToggleMenuItem = false; - bool hasIconMenuItem = false; - bool bIsPopupOpened = false; - MenuFlyoutPresenter spPresenter; - - var bIsEnabled = IsEnabled; - var focusState = FocusState; - var shouldBeNarrow = GetShouldBeNarrow(); - - spPresenter = GetParentMenuFlyoutPresenter(); - if (spPresenter != null) - { - hasToggleMenuItem = spPresenter.GetContainsToggleItems(); - hasIconMenuItem = spPresenter.GetContainsIconItems(); - } - - if (m_tpPopup != null) - { - bIsPopupOpened = m_tpPopup.IsOpen; - } - - // CommonStates - if (!bIsEnabled) - { - VisualStateManager.GoToState(this, "Disabled", bUseTransitions); - } - else if (bIsPopupOpened) - { - VisualStateManager.GoToState(this, "SubMenuOpened", bUseTransitions); - } - else if (m_menuHelper.IsPressed) - { - VisualStateManager.GoToState(this, "Pressed", bUseTransitions); - } - else if (m_menuHelper.IsPointerOver) - { - VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Normal", bUseTransitions); - } - - // FocusStates - if (FocusState.Unfocused != focusState && bIsEnabled) - { - if (FocusState.Pointer == focusState) - { - VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Focused", bUseTransitions); - } - } - else - { - VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); - } - - // CheckPlaceholderStates - if (hasToggleMenuItem && hasIconMenuItem) - { - VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); - } - else if (hasToggleMenuItem) - { - VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); - } - else if (hasIconMenuItem) - { - VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); - } - - // PaddingSizeStates - if (shouldBeNarrow) - { - VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); - } - - - } - - // MenuFlyoutSubItem's presenter size changed event handler that - // adjust the sub presenter position to the proper space area - // on the available window rect. - void OnPresenterSizeChanged( - object pSender, - SizeChangedEventArgs args) - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPresenterSizeChanged.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnPresenterSizeChanged(pSender, args, m_tpPopup as Popup); - -#if false // UNO TODO - if (m_tpMenuPopupThemeTransition == null) - { - - MenuFlyoutPresenter parentMenuFlyoutPresenter; - parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); - - // Get how many sub menus deep we are. We need this number to know what kind of Z - // offset to use for displaying elevation. The menus aren't parented in the visual - // hierarchy so that has to be applied with an additional transform. - int depth = 1; - if (parentMenuFlyoutPresenter != null) - { - depth = parentMenuFlyoutPresenter.GetDepth() + 1; - } - - Transition spMenuPopupChildTransition; - (MenuFlyout.PreparePopupThemeTransitionsAndShadows((Popup*)(m_tpPopup), 0.67 /* closedRatioConstant */, depth, &spMenuPopupChildTransition)); - (spMenuPopupChildTransition as MenuPopupThemeTransition.Direction = xaml_primitives.AnimationDirection_Top); - m_tpMenuPopupThemeTransition = spMenuPopupChildTransition; - } - - // Update the OpenedLength property of the ThemeTransition. - double openedLength = (m_tpPresenter as Control).ActualHeight; - - (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; -#endif - } - -#if false - void ClearStateFlags() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClearStateFlags.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.ClearStateFlags(); - - } - - void OnIsEnabledChanged(/*IsEnabledChangedEventArgs* args*/) - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnIsEnabledChanged.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnIsEnabledChanged(); - } - - void OnVisibilityChanged() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnVisibilityChanged.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnVisibilityChanged(); - - } -#endif - - protected override AutomationPeer OnCreateAutomationPeer() - { - //*ppAutomationPeer = null; - //MenuFlyoutSubItemAutomationPeer spAutomationPeer; - //(ActivationAPI.ActivateAutomationInstance(KnownTypeIndex.MenuFlyoutSubItemAutomationPeer, GetHandle(), spAutomationPeer.GetAddressOf())); - //(spAutomationPeer.Owner = this); - //*ppAutomationPeer = spAutomationPeer.Detach(); - - return null; - } - - private protected override string GetPlainText() => Text; - - bool ISubMenuOwner.IsSubMenuOpen => IsOpen; - - ISubMenuOwner ISubMenuOwner.ParentOwner - { - get => m_wrParentOwner?.Target as ISubMenuOwner; - set => m_wrParentOwner = new WeakReference(value); - } - - bool ISubMenuOwner.IsSubMenuPositionedAbsolutely => true; - - void ISubMenuOwner.PrepareSubMenu() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareSubMenu.", this)); -#endif // MFSI_DEBUG - - EnsurePopupAndPresenter(); - - global::System.Diagnostics.Debug.Assert(m_tpPopup != null); - global::System.Diagnostics.Debug.Assert(m_tpPresenter != null); - } - - void ISubMenuOwner.OpenSubMenu(Point position) - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OpenSubMenu.", this)); -#endif // MFSI_DEBUG - - EnsurePopupAndPresenter(); - EnsureCloseExistingSubItems(); - - MenuFlyoutPresenter parentMenuFlyoutPresenter; - parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); - - if (parentMenuFlyoutPresenter != null) - { - IMenu owningMenu; - owningMenu = (parentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; - (m_tpPresenter as IMenuPresenter).OwningMenu = owningMenu; - - MenuFlyout parentMenuFlyout; - parentMenuFlyout = parentMenuFlyoutPresenter.GetParentMenuFlyout(); - - if (parentMenuFlyout != null) - { - // Update the TemplateSettings before it is opened. - (m_tpPresenter as MenuFlyoutPresenter).SetParentMenuFlyout(parentMenuFlyout); - (m_tpPresenter as MenuFlyoutPresenter).UpdateTemplateSettings(); - - // Forward the parent presenter's properties to the sub presenter - ForwardPresenterProperties( - parentMenuFlyout, - parentMenuFlyoutPresenter, - m_tpPresenter as MenuFlyoutPresenter); - } - } - - m_tpPopup.HorizontalOffset = position.X; - m_tpPopup.VerticalOffset = position.Y; - SetIsOpen(true); - - - } - - void ISubMenuOwner.PositionSubMenu(Point position) - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PositionSubMenu - (%f, %f).", this, position.X, position.Y)); -#endif // MFSI_DEBUG - - if (position.X != float.NegativeInfinity) - { - m_tpPopup.HorizontalOffset = position.X; - } - - if (position.Y != float.NegativeInfinity) - { - m_tpPopup.VerticalOffset = position.Y; - } - - - } - - void ISubMenuOwner.ClosePeerSubMenus() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClosePeerSubMenus.", this)); -#endif // MFSI_DEBUG - - EnsureCloseExistingSubItems(); - - } - - void ISubMenuOwner.CloseSubMenu() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CloseSubMenu.", this)); -#endif // MFSI_DEBUG - - SetIsOpen(false); - - } - - void ISubMenuOwner.CloseSubMenuTree() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CloseSubMenuTree.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.CloseChildSubMenus(); - - } - - void ISubMenuOwner.DelayCloseSubMenu() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: DelayCloseSubMenu.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.DelayCloseSubMenu(); - } - - void ISubMenuOwner.CancelCloseSubMenu() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CancelCloseSubMenu.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.CancelCloseSubMenu(); - } - - void ISubMenuOwner.RaiseAutomationPeerExpandCollapse(bool isOpen) - { - // UNO TODO - //AutomationPeer spAutomationPeer; - //bool isListener = false; - - //AutomationPeer.ListenerExistsHelper(xaml_automation_peers.AutomationEvents_PropertyChanged, &isListener); - //if (isListener) - //{ - // (GetOrCreateAutomationPeer(&spAutomationPeer)); - // if (spAutomationPeer) - // { - // (spAutomationPeer as MenuFlyoutSubItemAutomationPeer.RaiseExpandCollapseAutomationEvent(isOpen)); - // } - //} - } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs new file mode 100644 index 000000000000..52ddedcd2e2b --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Uno.Disposables; +using Uno.UI.Extensions; +using Uno.UI.Xaml.Core; +using Windows.Foundation; +using Windows.System; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Markup; + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyoutSubItem +{ + + // ISubMenuOwner implementation + bool ISubMenuOwner.IsSubMenuOpen => IsOpen; + + bool ISubMenuOwner.IsSubMenuPositionedAbsolutely => true; +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs new file mode 100644 index 000000000000..34ba442b0b67 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs @@ -0,0 +1,882 @@ +using System; +using System.Collections.Generic; +using Uno.Disposables; +using Uno.UI.Extensions; +using Uno.UI.Xaml.Core; +using Windows.Foundation; +using Windows.System; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Markup; + +namespace Microsoft.UI.Xaml.Controls; + +[ContentProperty(Name = nameof(Items))] +public partial class MenuFlyoutSubItem : MenuFlyoutItemBase, ISubMenuOwner +{ + // Popup for the MenuFlyoutSubItem + Popup m_tpPopup; + + // Presenter for the MenuFlyoutSubItem + Control m_tpPresenter; + + // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. + // UNO TODO Transition m_tpMenuPopupThemeTransition = null; + + // Event pointer for the Loaded event + // IDisposable m_epLoadedHandler; + + // Event pointer for the size changed on the MenuFlyoutSubItem's presenter + IDisposable m_epPresenterSizeChangedHandler; + + // Helper to which to delegate cascading menu functionality. + CascadingMenuHelper m_menuHelper; + + // Weak reference the parent that owns the menu that this item belongs to. + private WeakReference m_wrParentOwner; + + DependencyObjectCollection m_tpItems; + + public string Text + { + get => (string)this.GetValue(TextProperty) ?? ""; + set => SetValue(TextProperty, value); + } + + public IList Items => m_tpItems; + + public IconElement Icon + { + get => (IconElement)this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); + } + + public static Microsoft.UI.Xaml.DependencyProperty TextProperty { get; } = + Microsoft.UI.Xaml.DependencyProperty.Register( + "Text", + typeof(string), + typeof(MenuFlyoutSubItem), + new FrameworkPropertyMetadata(default(string))); + + public static Microsoft.UI.Xaml.DependencyProperty IconProperty { get; } = + Microsoft.UI.Xaml.DependencyProperty.Register( + "Icon", + typeof(IconElement), + typeof(MenuFlyoutSubItem), + new FrameworkPropertyMetadata(default(IconElement))); + + public MenuFlyoutSubItem() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ", this)); +#endif // MFSI_DEBUG + + PrepareState(); + + DefaultStyleKey = typeof(MenuFlyoutSubItem); + } + + void PrepareState() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareState.", this)); +#endif // MFSI_DEBUG + + // Create the sub menu items collection and set the owner + m_tpItems = new DependencyObjectCollection(this); + + m_menuHelper = new CascadingMenuHelper(); + m_menuHelper.Initialize(this); + } + +#if false + void DisconnectFrameworkPeerCore() + { + // Ensure the clean up the items whenever MenuFlyoutSubItem is disconnected + //if (m_tpItems.GetAsCoreDO() != null) + //{ + // (Collection_Clear((CCollection*)(m_tpItems.GetAsCoreDO()))); + // (Collection_SetOwner((CCollection*)(m_tpItems.GetAsCoreDO()), null)); + //} + + // (MenuFlyoutSubItemGenerated.DisconnectFrameworkPeerCore()); + } +#endif + + protected override void OnApplyTemplate() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnApplyTemplate.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnApplyTemplate(); + } + + // PointerEntered event handler that shows the MenuFlyoutSubItem + // whenever the pointer is over to the + // In case of touch, the MenuFlyoutSubItem will be shown by + // PointerReleased event. + + protected override void OnPointerEntered(PointerRoutedEventArgs args) + { + base.OnPointerEntered(args); + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerEntered.", this)); +#endif // MFSI_DEBUG + + UpdateParentOwner(null /*parentMenuFlyoutPresenter*/); + m_menuHelper.OnPointerEntered(args); + } + + // PointerExited event handler that ensures the close MenuFlyoutSubItem + // whenever the pointer over is out of the current MenuFlyoutSubItem or + // out of the main presenter. If the exited point is on MenuFlyoutSubItem + // or sub presenter position, we want to keep the opened + + protected override void OnPointerExited(PointerRoutedEventArgs args) + { + base.OnPointerExited(args); + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerExited.", this)); +#endif // MFSI_DEBUG + + bool parentIsSubMenu = false; + MenuFlyoutPresenter parentPresenter = GetParentMenuFlyoutPresenter(); + + if (parentPresenter != null) + { + parentIsSubMenu = parentPresenter.IsSubPresenter; + } + + m_menuHelper.OnPointerExited(args, parentIsSubMenu); + } + + // PointerPressed event handler that ensures the pressed state. + + protected override void OnPointerPressed(PointerRoutedEventArgs args) + { + base.OnPointerPressed(args); + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerPressed.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnPointerPressed(args); + } + + // PointerReleased event handler that shows MenuFlyoutSubItem in + // case of touch input. + + protected override void OnPointerReleased(PointerRoutedEventArgs args) + { + base.OnPointerReleased(args); + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerReleased.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnPointerReleased(args); + + } + + + protected override void OnGotFocus(RoutedEventArgs args) + { + base.OnGotFocus(args); + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnGotFocus.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnGotFocus(args); + } + + + protected override void OnLostFocus(RoutedEventArgs args) + { + base.OnLostFocus(args); + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnLostFocus.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnLostFocus(args); + } + + // KeyDown event handler that handles the keyboard navigation between + // the menu items and shows the MenuFlyoutSubItem in case of hitting + // the enter or right arrow key. + + protected override void OnKeyDown(KeyRoutedEventArgs args) + { + base.OnKeyDown(args); + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyDown.", this)); +#endif // MFSI_DEBUG + + bool handled = args.Handled; + bool shouldHandleEvent = false; + + if (!handled) + { + MenuFlyoutPresenter spParentPresenter = GetParentMenuFlyoutPresenter(); + + if (spParentPresenter != null) + { + var key = args.Key; + + // Navigate each item with the arrow down or up key + if (key == VirtualKey.Down || key == VirtualKey.Up) + { + spParentPresenter.HandleUpOrDownKey(key == VirtualKey.Down); + UpdateVisualState(); + + // If we handle the event here, it won't get handled in m_menuHelper.OnKeyDown, + // so we'll do that afterwards. + shouldHandleEvent = true; + } + } + } + + m_menuHelper.OnKeyDown(args); + args.Handled = shouldHandleEvent; + } + + + protected override void OnKeyUp(KeyRoutedEventArgs args) + { + base.OnKeyUp(args); + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyUp.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnKeyUp(args); + } + + // Ensure the creating the popup and menu presenter to show the + void EnsurePopupAndPresenter() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsurePopupAndPresenter.", this)); +#endif // MFSI_DEBUG + + if (m_tpPopup == null) + { + MenuFlyoutPresenter spParentMenuFlyoutPresenter = null; + Control spPresenter; + UIElement spPresenterAsUI; + FrameworkElement spPresenterAsFE; + Popup spPopup; + + spPopup = new Popup(); + spPopup.IsSubMenu = true; + spPopup.IsLightDismissEnabled = false; + + spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); +#if false // UNO TODO Windowed Popup is not available + if (spParentMenuFlyoutPresenter != null) + { + spParentMenuFlyout = spParentMenuFlyoutPresenter.GetParentMenuFlyout(); + // Set the windowed Popup if the MenuFlyout is set the windowed Popup + if (spParentMenuFlyout && spParentMenuFlyout.IsWindowedPopup()) + { + ASSERT((CPopup*)(spPopup as Popup.GetHandle()).DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle())); + + ((CPopup*)(spPopup as Popup.GetHandle()).SetIsWindowed()); + + // Ensure the sub menu is the windowed Popup + ASSERT((CPopup*)(spPopup as Popup.GetHandle()).IsWindowed()); + + xaml.IXamlRoot xamlRoot = XamlRoot.GetForElementStatic(spParentMenuFlyoutPresenter); + if (xamlRoot) + { + (spPopup as Popup.XamlRoot = xamlRoot); + } + } + } +#endif + + spPresenter = CreateSubPresenter(); + spPresenterAsUI = spPresenter; + + if (spParentMenuFlyoutPresenter != null) + { + int parentDepth = spParentMenuFlyoutPresenter.GetDepth(); + (spPresenter as MenuFlyoutPresenter).SetDepth(parentDepth + 1); + } + + spPopup.Child = spPresenterAsUI as FrameworkElement; + + m_tpPresenter = spPresenter; + m_tpPopup = spPopup; + + ((ItemsControl)m_tpPresenter).ItemsSource = m_tpItems; + + spPresenterAsFE = spPresenter; + + spPresenterAsFE.SizeChanged += OnPresenterSizeChanged; + m_epPresenterSizeChangedHandler = Disposable.Create(() => spPresenterAsFE.SizeChanged -= OnPresenterSizeChanged); + + m_menuHelper.SetSubMenuPresenter(spPresenter as Control); + } + } + + void ForwardPresenterProperties( + MenuFlyout pOwnerMenuFlyout, + MenuFlyoutPresenter pParentMenuFlyoutPresenter, + MenuFlyoutPresenter pSubMenuFlyoutPresenter) + { + Style spStyle; + ElementTheme parentPresenterTheme; + object spDataContext; + FrameworkElement spPopupAsFE; + MenuFlyoutPresenter spSubMenuFlyoutPresenter = pSubMenuFlyoutPresenter; + Control spSubMenuFlyoutPresenterAsControl; + DependencyObject spThisAsDO = this; + + global::System.Diagnostics.Debug.Assert(pOwnerMenuFlyout != null && pParentMenuFlyoutPresenter != null && pSubMenuFlyoutPresenter != null); + + spSubMenuFlyoutPresenterAsControl = spSubMenuFlyoutPresenter; + + // Set the sub presenter style from the MenuFlyout's presenter style + spStyle = pOwnerMenuFlyout.MenuFlyoutPresenterStyle; + + if (spStyle != null) + { + ((Control)pSubMenuFlyoutPresenter).Style = spStyle; + } + else + { + ((Control)pSubMenuFlyoutPresenter).ClearValue(FrameworkElement.StyleProperty); + } + + // Set the sub presenter's RequestTheme from the parent presenter's RequestTheme + parentPresenterTheme = pParentMenuFlyoutPresenter.RequestedTheme; + pSubMenuFlyoutPresenter.RequestedTheme = parentPresenterTheme; + + // Set the sub presenter's DataContext from the parent presenter's DataContext + spDataContext = pParentMenuFlyoutPresenter.DataContext; + pSubMenuFlyoutPresenter.DataContext = spDataContext; + + // Set the sub presenter's FlowDirection from the current sub menu item's FlowDirection + var flowDirection = FlowDirection; + pSubMenuFlyoutPresenter.FlowDirection = flowDirection; + + // Set the popup's FlowDirection from the current FlowDirection + spPopupAsFE = m_tpPopup; + spPopupAsFE.FlowDirection = flowDirection; + + // Set the sub presenter's Language from the parent presenter's Language + pSubMenuFlyoutPresenter.Language = pParentMenuFlyoutPresenter.Language; + + // Set the sub presenter's IsTextScaleFactorEnabledInternal from the parent presenter's IsTextScaleFactorEnabledInternal + var isTextScaleFactorEnabled = pParentMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal; + pSubMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal = isTextScaleFactorEnabled; + + ElementSoundMode soundMode = ElementSoundPlayerService.Instance.GetEffectiveSoundMode(spThisAsDO as DependencyObject); + + (spSubMenuFlyoutPresenterAsControl as Control).ElementSoundMode = soundMode; + } + + // Ensure that any currently open MenuFlyoutSubItems are closed + void EnsureCloseExistingSubItems() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsureCloseExistingSubItems.", this)); +#endif // MFSI_DEBUG + + MenuFlyoutPresenter spParentPresenter; + + spParentPresenter = GetParentMenuFlyoutPresenter(); + if (spParentPresenter != null) + { + IMenuPresenter openedSubPresenter; + + openedSubPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; + if (openedSubPresenter != null) + { + ISubMenuOwner subMenuOwner; + + subMenuOwner = openedSubPresenter.Owner; + if (subMenuOwner != null && subMenuOwner != this) + { + openedSubPresenter.CloseSubMenu(); + } + } + } + + + } + + bool IsOpen => m_tpPopup?.IsOpen ?? false; + + Control + CreateSubPresenter() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CreateSubPresenter.", this)); +#endif // MFSI_DEBUG + + MenuFlyoutPresenter spPresenter = new MenuFlyoutPresenter(); + + // Specify the sub MenuFlyoutPresenter + (spPresenter as MenuFlyoutPresenter).IsSubPresenter = true; + (spPresenter as IMenuPresenter).Owner = this; + + return spPresenter; + } + + void UpdateParentOwner(MenuFlyoutPresenter parentMenuFlyoutPresenter) + { + MenuFlyoutPresenter parentPresenter = parentMenuFlyoutPresenter; + if (parentPresenter == null) + { + parentPresenter = GetParentMenuFlyoutPresenter(); + } +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentPresenter=0x%p.", this, parentPresenter)); +#endif // MFSI_DEBUG + + if (parentPresenter != null) + { + ISubMenuOwner parentSubMenuOwner; + parentSubMenuOwner = (parentPresenter as IMenuPresenter).Owner; + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentSubMenuOwner=0x%p.", this, parentSubMenuOwner)); +#endif // MFSI_DEBUG + + if (parentSubMenuOwner != null) + { + ((ISubMenuOwner)this).ParentOwner = parentSubMenuOwner; + } + } + } + + // Set the popup open or close status for MenuFlyoutSubItem and ensure the + // focus to the current presenter. + void SetIsOpen(bool isOpen) + { + bool isOpened = false; + + isOpened = m_tpPopup.IsOpen; + +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: SetIsOpen isOpen=%d, isOpened=%d.", this, isOpen, isOpened)); +#endif // MFSI_DEBUG + + if (isOpen != isOpened) + { + (m_tpPresenter as IMenuPresenter).Owner = isOpen ? this : null; + + MenuFlyoutPresenter parentPresenter; + parentPresenter = GetParentMenuFlyoutPresenter(); + + if (parentPresenter != null) + { + (parentPresenter as IMenuPresenter).SubPresenter = isOpen ? m_tpPresenter as MenuFlyoutPresenter : null; + + IMenu owningMenu; + owningMenu = (parentPresenter as IMenuPresenter).OwningMenu; + + if (owningMenu != null) + { + (m_tpPresenter as IMenuPresenter).OwningMenu = isOpen ? owningMenu : null; + } + + UpdateParentOwner(parentPresenter); + } + + // UNO TODO + VisualTree visualTree = VisualTree.GetForElement(this); + if (visualTree is not null) + { + // Put the popup on the same VisualTree as this flyout sub item to make sure it shows up in the right place + m_tpPopup.SetVisualTree(visualTree); + } + + // Set the popup open or close state + m_tpPopup.IsOpen = isOpen; + + // Set the focus to the displayed sub menu presenter when MenuFlyoutSubItem is opened and + // set the focus back to the original sub item when the displayed sub menu presenter is closed. + if (isOpen) + { + // Set the focus to the displayed sub menu presenter to navigate the each sub items + m_tpPresenter.Focus(FocusState.Programmatic); + + // UNO TODO + // (DependencyObject.SetFocusedElement( + // spPresenterAsDO as DependencyObject, + // xaml.FocusState_Programmatic, + // false /*animateIfBringIntoView*/, + // &focusUpdated)); + } + else + { + // Set the focus to the sub menu item + this.Focus(FocusState.Programmatic); + + // UNO TODO + //(DependencyObject.SetFocusedElement( + // spThisAsDO as DependencyObject, + // xaml.FocusState_Programmatic, + // false /*animateIfBringIntoView*/, + // &focusUpdated)); + } + + UpdateVisualState(); + } + + + } + + internal void Open() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Open.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OpenSubMenu(); + } + + internal void Close() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Close.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.CloseSubMenu(); + + } + + private protected override void ChangeVisualState(bool bUseTransitions) + { + bool hasToggleMenuItem = false; + bool hasIconMenuItem = false; + bool bIsPopupOpened = false; + MenuFlyoutPresenter spPresenter; + + var bIsEnabled = IsEnabled; + var focusState = FocusState; + var shouldBeNarrow = GetShouldBeNarrow(); + + spPresenter = GetParentMenuFlyoutPresenter(); + if (spPresenter != null) + { + hasToggleMenuItem = spPresenter.GetContainsToggleItems(); + hasIconMenuItem = spPresenter.GetContainsIconItems(); + } + + if (m_tpPopup != null) + { + bIsPopupOpened = m_tpPopup.IsOpen; + } + + // CommonStates + if (!bIsEnabled) + { + VisualStateManager.GoToState(this, "Disabled", bUseTransitions); + } + else if (bIsPopupOpened) + { + VisualStateManager.GoToState(this, "SubMenuOpened", bUseTransitions); + } + else if (m_menuHelper.IsPressed) + { + VisualStateManager.GoToState(this, "Pressed", bUseTransitions); + } + else if (m_menuHelper.IsPointerOver) + { + VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Normal", bUseTransitions); + } + + // FocusStates + if (FocusState.Unfocused != focusState && bIsEnabled) + { + if (FocusState.Pointer == focusState) + { + VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Focused", bUseTransitions); + } + } + else + { + VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); + } + + // CheckPlaceholderStates + if (hasToggleMenuItem && hasIconMenuItem) + { + VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); + } + else if (hasToggleMenuItem) + { + VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); + } + else if (hasIconMenuItem) + { + VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); + } + + // PaddingSizeStates + if (shouldBeNarrow) + { + VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); + } + + + } + + // MenuFlyoutSubItem's presenter size changed event handler that + // adjust the sub presenter position to the proper space area + // on the available window rect. + void OnPresenterSizeChanged( + object pSender, + SizeChangedEventArgs args) + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPresenterSizeChanged.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnPresenterSizeChanged(pSender, args, m_tpPopup as Popup); + +#if false // UNO TODO + if (m_tpMenuPopupThemeTransition == null) + { + + MenuFlyoutPresenter parentMenuFlyoutPresenter; + parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + + // Get how many sub menus deep we are. We need this number to know what kind of Z + // offset to use for displaying elevation. The menus aren't parented in the visual + // hierarchy so that has to be applied with an additional transform. + int depth = 1; + if (parentMenuFlyoutPresenter != null) + { + depth = parentMenuFlyoutPresenter.GetDepth() + 1; + } + + Transition spMenuPopupChildTransition; + (MenuFlyout.PreparePopupThemeTransitionsAndShadows((Popup*)(m_tpPopup), 0.67 /* closedRatioConstant */, depth, &spMenuPopupChildTransition)); + (spMenuPopupChildTransition as MenuPopupThemeTransition.Direction = xaml_primitives.AnimationDirection_Top); + m_tpMenuPopupThemeTransition = spMenuPopupChildTransition; + } + + // Update the OpenedLength property of the ThemeTransition. + double openedLength = (m_tpPresenter as Control).ActualHeight; + + (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; +#endif + } + +#if false + void ClearStateFlags() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClearStateFlags.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.ClearStateFlags(); + + } + + void OnIsEnabledChanged(/*IsEnabledChangedEventArgs* args*/) + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnIsEnabledChanged.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnIsEnabledChanged(); + } + + void OnVisibilityChanged() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnVisibilityChanged.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.OnVisibilityChanged(); + + } +#endif + + protected override AutomationPeer OnCreateAutomationPeer() + { + //*ppAutomationPeer = null; + //MenuFlyoutSubItemAutomationPeer spAutomationPeer; + //(ActivationAPI.ActivateAutomationInstance(KnownTypeIndex.MenuFlyoutSubItemAutomationPeer, GetHandle(), spAutomationPeer.GetAddressOf())); + //(spAutomationPeer.Owner = this); + //*ppAutomationPeer = spAutomationPeer.Detach(); + + return null; + } + + private protected override string GetPlainText() => Text; + + bool ISubMenuOwner.IsSubMenuOpen => IsOpen; + + ISubMenuOwner ISubMenuOwner.ParentOwner + { + get => m_wrParentOwner?.Target as ISubMenuOwner; + set => m_wrParentOwner = new WeakReference(value); + } + + void ISubMenuOwner.PrepareSubMenu() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareSubMenu.", this)); +#endif // MFSI_DEBUG + + EnsurePopupAndPresenter(); + + global::System.Diagnostics.Debug.Assert(m_tpPopup != null); + global::System.Diagnostics.Debug.Assert(m_tpPresenter != null); + } + + void ISubMenuOwner.OpenSubMenu(Point position) + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OpenSubMenu.", this)); +#endif // MFSI_DEBUG + + EnsurePopupAndPresenter(); + EnsureCloseExistingSubItems(); + + MenuFlyoutPresenter parentMenuFlyoutPresenter; + parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + + if (parentMenuFlyoutPresenter != null) + { + IMenu owningMenu; + owningMenu = (parentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; + (m_tpPresenter as IMenuPresenter).OwningMenu = owningMenu; + + MenuFlyout parentMenuFlyout; + parentMenuFlyout = parentMenuFlyoutPresenter.GetParentMenuFlyout(); + + if (parentMenuFlyout != null) + { + // Update the TemplateSettings before it is opened. + (m_tpPresenter as MenuFlyoutPresenter).SetParentMenuFlyout(parentMenuFlyout); + (m_tpPresenter as MenuFlyoutPresenter).UpdateTemplateSettings(); + + // Forward the parent presenter's properties to the sub presenter + ForwardPresenterProperties( + parentMenuFlyout, + parentMenuFlyoutPresenter, + m_tpPresenter as MenuFlyoutPresenter); + } + } + + m_tpPopup.HorizontalOffset = position.X; + m_tpPopup.VerticalOffset = position.Y; + SetIsOpen(true); + + + } + + void ISubMenuOwner.PositionSubMenu(Point position) + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PositionSubMenu - (%f, %f).", this, position.X, position.Y)); +#endif // MFSI_DEBUG + + if (position.X != float.NegativeInfinity) + { + m_tpPopup.HorizontalOffset = position.X; + } + + if (position.Y != float.NegativeInfinity) + { + m_tpPopup.VerticalOffset = position.Y; + } + + + } + + void ISubMenuOwner.ClosePeerSubMenus() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClosePeerSubMenus.", this)); +#endif // MFSI_DEBUG + + EnsureCloseExistingSubItems(); + + } + + void ISubMenuOwner.CloseSubMenu() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CloseSubMenu.", this)); +#endif // MFSI_DEBUG + + SetIsOpen(false); + + } + + void ISubMenuOwner.CloseSubMenuTree() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CloseSubMenuTree.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.CloseChildSubMenus(); + + } + + void ISubMenuOwner.DelayCloseSubMenu() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: DelayCloseSubMenu.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.DelayCloseSubMenu(); + } + + void ISubMenuOwner.CancelCloseSubMenu() + { +#if MFSI_DEBUG + IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CancelCloseSubMenu.", this)); +#endif // MFSI_DEBUG + + m_menuHelper.CancelCloseSubMenu(); + } + + void ISubMenuOwner.RaiseAutomationPeerExpandCollapse(bool isOpen) + { + // UNO TODO + //AutomationPeer spAutomationPeer; + //bool isListener = false; + + //AutomationPeer.ListenerExistsHelper(xaml_automation_peers.AutomationEvents_PropertyChanged, &isListener); + //if (isListener) + //{ + // (GetOrCreateAutomationPeer(&spAutomationPeer)); + // if (spAutomationPeer) + // { + // (spAutomationPeer as MenuFlyoutSubItemAutomationPeer.RaiseExpandCollapseAutomationEvent(isOpen)); + // } + //} + } +} From 2ffa7c3a6efec867200a3603b904f83b41376b41 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Wed, 19 Jun 2024 15:43:23 +0200 Subject: [PATCH 03/28] chore: Properties and docs --- .../MenuFlyout/MenuFlyout.Properties.cs | 22 +- .../MenuFlyout/MenuFlyoutItem.Properties.cs | 888 ++---------------- .../MenuFlyoutItemTemplateSettings.cs | 16 +- .../MenuFlyoutPresenterTemplateSettings.cs | 22 +- .../MenuFlyoutSubItem.Properties.cs | 42 +- .../ToggleMenuFlyoutItem.Properties.cs | 32 + 6 files changed, 185 insertions(+), 837 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Properties.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Properties.cs index e1d2f681cace..8d3071d96001 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Properties.cs @@ -1,32 +1,46 @@ -namespace Microsoft.UI.Xaml.Controls; +using System.Collections.Generic; + +namespace Microsoft.UI.Xaml.Controls; partial class MenuFlyout { - public IList Items + /// + /// Gets the collection used to generate the content of the menu. + /// + public IList Items { get => (IList)this.GetValue(ItemsProperty); private set => this.SetValue(ItemsProperty, value); } - public static DependencyProperty ItemsProperty { get; } = + /// + /// Identifies the Items dependency property. + /// + internal static DependencyProperty ItemsProperty { get; } = DependencyProperty.Register( nameof(Items), typeof(IList), typeof(MenuFlyout), new FrameworkPropertyMetadata(defaultValue: null)); + /// + /// Gets or sets the style that is used when rendering the MenuFlyout. + /// public Style MenuFlyoutPresenterStyle { get => (Style)this.GetValue(MenuFlyoutPresenterStyleProperty); set => SetValue(MenuFlyoutPresenterStyleProperty, value); } + /// + /// Identifies the MenuFlyoutPresenterStyle dependency property. + /// public static DependencyProperty MenuFlyoutPresenterStyleProperty { get; } = DependencyProperty.Register( nameof(MenuFlyoutPresenterStyle), typeof(Style), typeof(MenuFlyout), new FrameworkPropertyMetadata( - null, + null, FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext)); } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs index 9c3e701fd3a5..9768dacedaa6 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs @@ -1,828 +1,110 @@ -using System; -using Uno.Client; -using Uno.Disposables; -using Windows.Foundation; -using Microsoft.UI.Xaml.Automation; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using ICommand = System.Windows.Input.ICommand; -using Microsoft.UI.Xaml.Markup; +using System.Windows.Input; -#if HAS_UNO_WINUI -using Microsoft.UI.Input; -#else -using Windows.Devices.Input; -using Windows.UI.Input; -#endif +namespace Microsoft.UI.Xaml.Controls; -namespace Microsoft.UI.Xaml.Controls +partial class MenuFlyoutItem : MenuFlyoutItemBase { - [ContentProperty(Name = nameof(Text))] - public partial class MenuFlyoutItem : MenuFlyoutItemBase + /// + /// Gets or sets the command to invoke when the item is pressed. + /// + public ICommand Command { - // Whether the pointer is currently over the - bool m_bIsPointerOver = true; - - // Whether the pointer is currently pressed over the - internal bool m_bIsPressed = true; - - // Whether the pointer's left button is currently down. - internal bool m_bIsPointerLeftButtonDown = true; - - // True if the SPACE or ENTER key is currently pressed, false otherwise. - internal bool m_bIsSpaceOrEnterKeyDown = true; - - // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. - internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; - - // On pointer released we perform some actions depending on control. We decide to whether to perform them - // depending on some parameters including but not limited to whether released is followed by a pressed, which - // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. - bool m_shouldPerformActions = true; - - // Event pointer for the ICommand.CanExecuteChanged event. - IDisposable m_epCanExecuteChangedHandler; - - // UNO TODO - // Event pointer for the Loaded event. - // IDisposable m_epLoadedHandler; - - // UNO TODO - // IDisposable m_epMenuFlyoutItemClickEventCallback; - - double m_maxKeyboardAcceleratorTextWidth; - TextBlock m_tpKeyboardAcceleratorTextBlock; - - bool m_isTemplateApplied; - - #region CommandParameter - - public object CommandParameter - { - get { return (object)GetValue(CommandParameterProperty); } - set { SetValue(CommandParameterProperty, value); } - } - - public static DependencyProperty CommandParameterProperty { get; } = - DependencyProperty.Register( - "CommandParameter", typeof(object), - typeof(Controls.MenuFlyoutItem), - new FrameworkPropertyMetadata(default(object))); - - #endregion - - #region Command - - public ICommand Command - { - get { return (ICommand)GetValue(CommandProperty); } - set { SetValue(CommandProperty, value); } - } - - public static DependencyProperty CommandProperty { get; } = - DependencyProperty.Register( - name: nameof(Command), - propertyType: typeof(ICommand), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); - - #endregion - - #region Text - - public string Text - { - get { return (string)GetValue(TextProperty) ?? ""; } - set { SetValue(TextProperty, value); } - } + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } - public static DependencyProperty TextProperty { get; } = - DependencyProperty.Register( - name: nameof(Text), - propertyType: typeof(string), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(string))); + /// + /// Identifies the Command dependency property. + /// + public static DependencyProperty CommandProperty { get; } = + DependencyProperty.Register( + name: nameof(Command), + propertyType: typeof(ICommand), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); - #endregion + /// + /// Gets or sets the parameter to pass to the Command property. + /// + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } - public IconElement Icon - { - get => (IconElement)this.GetValue(IconProperty); - set => this.SetValue(IconProperty, value); - } + /// + /// Identifies the CommandParameter dependency property. + /// + public static DependencyProperty CommandParameterProperty { get; } = + DependencyProperty.Register( + "CommandParameter", typeof(object), + typeof(Controls.MenuFlyoutItem), + new FrameworkPropertyMetadata(default(object))); + + /// + /// Gets or sets the graphic content of the menu flyout item. + /// + public IconElement Icon + { + get => (IconElement)this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); + } - public static DependencyProperty IconProperty { get; } = + /// + /// Identifies the Icon dependency property. + /// + public static DependencyProperty IconProperty { get; } = DependencyProperty.Register( name: nameof(Icon), propertyType: typeof(IconElement), ownerType: typeof(MenuFlyoutItem), typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); - public string KeyboardAcceleratorTextOverride - { - get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; - set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); - } + /// + /// Gets or sets a string that overrides the default key combination string associated with a keyboard accelerator. + /// + public string KeyboardAcceleratorTextOverride + { + get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; + set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); + } - public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = + /// + /// Identifies the MenuFlyoutItem.KeyboardAcceleratorTextOverride dependency property. + /// + public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = DependencyProperty.Register( name: nameof(KeyboardAcceleratorTextOverride), propertyType: typeof(string), ownerType: typeof(MenuFlyoutItem), typeMetadata: new FrameworkPropertyMetadata(default(string))); - public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } - -#pragma warning disable CS0108 - public event RoutedEventHandler Click; -#pragma warning restore CS0108 - - internal void InvokeClick() - { - Click?.Invoke(this, new RoutedEventArgs(this)); - Command.ExecuteIfPossible(this.CommandParameter); - } - - public MenuFlyoutItem() - { - m_bIsPointerOver = false; - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - m_shouldPerformActions = false; - - DefaultStyleKey = typeof(MenuFlyoutItem); - - Initialize(); - } - - // Prepares object's state - void Initialize() - { - Loaded += (s, e) => ClearStateFlags(); - - this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); - } - - // Apply a template to the - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; - m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; - - SuppressIsEnabled(false); - UpdateCanExecute(); - - // Sync the logical and visual states of the control - UpdateVisualState(); - } - - // PointerPressed event handler. - protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) - { - base.OnPointerPressed(pArgs); - var handled = pArgs.Handled; - if (!handled) - { - PointerPoint spPointerPoint; - PointerPointProperties spPointerProperties; - spPointerPoint = pArgs.GetCurrentPoint(this); - spPointerProperties = spPointerPoint.Properties; - var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; - if (bIsLeftButtonPressed) - { - m_bIsPointerLeftButtonDown = true; - m_bIsPressed = true; - - pArgs.Handled = true; - UpdateVisualState(); - } - } - } - - // PointerReleased event handler. - protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) - { - base.OnPointerReleased(pArgs); - - bool handled = false; - - handled = pArgs.Handled; - if (!handled) - { - m_bIsPointerLeftButtonDown = false; - - m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; - -#if false - // UNO TODO - if (m_shouldPerformActions) - { - GestureModes gestureFollowing = GestureModes.None; - - m_bIsPressed = false; - - gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); - - // Note that we are intentionally NOT handling the args - // if we do not fall through here because basically we are no_opting in that case. - if (gestureFollowing != GestureModes.RightTapped) - { - pArgs.Handled = true; - PerformPointerUpAction(); - } - } -#else - PerformPointerUpAction(); -#endif - } - } - - private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - PerformPointerUpAction(); - pArgs.Handled = true; - } - } - - // Contains the logic to be employed if we decide to handle pointer released. - void PerformPointerUpAction() - { - if (m_shouldPerformActions) - { - Focus(FocusState.Pointer); - Invoke(); - } - } - - // Performs appropriate actions upon a mouse/keyboard invocation of a - internal virtual void Invoke() - { - RoutedEventArgs spArgs; - MenuFlyoutPresenter spParentMenuFlyoutPresenter; - - // Create the args - spArgs = new RoutedEventArgs(); - spArgs.OriginalSource = this; - - // Raise the event - Click?.Invoke(this, spArgs); - - // UNO TODO - //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); - - // Execute command associated with the button - ExecuteCommand(); - - bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; - if (!shouldPreventDismissOnPointer) - { - // Close the MenuFlyout. - spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); - if (spParentMenuFlyoutPresenter != null) - { - IMenu owningMenu; - - owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; - if (owningMenu != null) - { - // We need to make close all menu flyout sub items before we hide the parent menu flyout, - // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a - // significant amount of time before closing the sub menu. - (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); - owningMenu.Close(); - } - } - } - - ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); - } - - // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) - //{ - // DependencyObject peer = pMenuFlyoutItem; - - // if (peer == null) - // { - // return; - // } - - // MenuFlyoutItem peerAsMenuFlyoutItem; - // (peer.As(&peerAsMenuFlyoutItem)); - // IFCPTR_RETURN(peerAsMenuFlyoutItem); - - // (peerAsAddProofingItemHandler(eventHandler)); - - // return S_OK; - //} - - // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) - //{ - // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) - // { - // DependencyObject spSender; - // EventArgs spArgs; - - // IFCPTR_RETURN(pSender); - // IFCPTR_RETURN(pArgs); - - // (ctl.do_query_interface(spSender, pSender)); - // (ctl.do_query_interface(spArgs, pArgs)); - - // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); - - // return S_OK; - // })); - - // return S_OK; - //} - - // Executes Command if CanExecute() returns true. - void ExecuteCommand() - { - ICommand spCommand = Command; - - if (spCommand != null) - { - object spCommandParameter; - bool canExecute; - - spCommandParameter = CommandParameter; - canExecute = spCommand.CanExecute(spCommandParameter); - if (canExecute) - { - spCommand.Execute(spCommandParameter); - } - } - } + /// + /// Gets an object that provides calculated values that can be referenced as {TemplateBinding} markup extension sources when defining templates for a MenuFlyoutItem control. + /// + public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } - // PointerEnter event handler. - protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) - { - base.OnPointerEntered(pArgs); - - MenuFlyoutPresenter spParentPresenter; - - m_bIsPointerOver = true; - - spParentPresenter = GetParentMenuFlyoutPresenter(); - if (spParentPresenter != null) - { - IMenuPresenter subPresenter; - - subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; - if (subPresenter != null) - { - ISubMenuOwner subPresenterOwner; - - subPresenterOwner = subPresenter.Owner; - - if (subPresenterOwner != null) - { - subPresenterOwner.DelayCloseSubMenu(); - } - } - } - - UpdateVisualState(); - } - - // PointerExited event handler. - protected override void OnPointerExited(PointerRoutedEventArgs pArgs) - { - base.OnPointerExited(pArgs); - - // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - UpdateVisualState(); - } - - // PointerCaptureLost event handler. - protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) - { - base.OnPointerCaptureLost(pArgs); - - // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - UpdateVisualState(); - } - - // Called when the IsEnabled property changes. - private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) - { - if (!e.NewValue) - { - ClearStateFlags(); - } - else - { - UpdateVisualState(); - } - - base.OnIsEnabledChanged(e); - } - - // Called when the control got focus. - protected override void OnGotFocus(RoutedEventArgs pArgs) - { - UpdateVisualState(); - } - - // LostFocus event handler. - protected override void OnLostFocus(RoutedEventArgs pArgs) - { - if (!m_bIsPointerLeftButtonDown) - { - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - m_bIsPressed = false; - } - - UpdateVisualState(); - } - - // KeyDown event handler. - protected override void OnKeyDown(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - var key = pArgs.Key; - handled = KeyPressMenuFlyout.KeyDown(key, this); - pArgs.Handled = handled; - } - } - - // KeyUp event handler. - protected override void OnKeyUp(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - var key = (pArgs.Key); - KeyPressMenuFlyout.KeyUp(key, this); - pArgs.Handled = true; - } - } - - // Handle the custom property changed event and call the OnPropertyChanged2 methods. - internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) - { - if (args.Property == UIElement.VisibilityProperty) - { - OnVisibilityChanged(); - } - else if (args.Property == MenuFlyoutItem.CommandProperty) - { - OnCommandChanged(args.OldValue, args.NewValue); - } - else if (args.Property == MenuFlyoutItem.CommandParameterProperty) - { - UpdateCanExecute(); - } - } - - // Update the visual states when the Visibility property is changed. - void OnVisibilityChanged() - { - Visibility visibility = Visibility; - - if (Visibility.Visible != visibility) - { - ClearStateFlags(); - } - } - - private protected override void OnLoaded() - { - base.OnLoaded(); - - if (m_epCanExecuteChangedHandler == null) - { - ICommand spCommand = Command; - - if (spCommand != null) - { - void OnCanExecuteChanged(object sender, object args) - { - UpdateCanExecute(); - } - - spCommand.CanExecuteChanged += OnCanExecuteChanged; - - m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); - } - } - - // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, - // we need to update our value now. - UpdateCanExecute(); - } - - private protected override void OnUnloaded() - { - base.OnUnloaded(); - - if (m_epCanExecuteChangedHandler != null) - { - ICommand spCommand = Command; - - if (spCommand != null) - { - m_epCanExecuteChangedHandler.Dispose(); - } - } - } - - // Called when the Command property changes. - void - OnCommandChanged( - object pOldValue, - object pNewValue) - { - // Remove handler for CanExecuteChanged from the old value - m_epCanExecuteChangedHandler?.Dispose(); - - if (pOldValue != null) - { - XamlUICommand oldCommand = pOldValue as XamlUICommand; - - if (oldCommand != null) - { - CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); - } - } - - // Subscribe to the CanExecuteChanged event on the new value - if (pNewValue != null) - { - var spNewCommand = pNewValue as ICommand; - - void OnCanExecuteUpdated(object sender, object args) - { - UpdateCanExecute(); - } - - spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; - m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); - - var newCommandAsUICommand = spNewCommand as XamlUICommand; - - if (newCommandAsUICommand != null) - { - CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); - CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); - CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); - CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); - CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); - } - } - - // Coerce the button enabled state with the CanExecute state of the command. - UpdateCanExecute(); - } - - // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. - void UpdateCanExecute() - { - ICommand spCommand; - object spCommandParameter; - bool canExecute = true; - - spCommand = Command; - if (spCommand != null) - { - spCommandParameter = CommandParameter; - canExecute = spCommand.CanExecute(spCommandParameter); - } - - SuppressIsEnabled(!canExecute); - } - - // Change to the correct visual state for the - private protected override void ChangeVisualState( - // true to use transitions when updating the visual state, false - // to snap directly to the new visual state. - bool bUseTransitions) - { - bool hasToggleMenuItem = false; - bool hasIconMenuItem = false; - bool hasMenuItemWithKeyboardAcceleratorText = false; - bool isKeyboardPresent = false; - - MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); - if (spPresenter != null) - { - hasToggleMenuItem = spPresenter.GetContainsToggleItems(); - hasIconMenuItem = spPresenter.GetContainsIconItems(); - hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); - } - - var bIsEnabled = IsEnabled; - var focusState = FocusState; - var shouldBeNarrow = GetShouldBeNarrow(); - - // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, - // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. - if (hasMenuItemWithKeyboardAcceleratorText) - { - // UNO TODO - // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); - isKeyboardPresent = true; - } - - // CommonStates - if (!bIsEnabled) - { - VisualStateManager.GoToState(this, "Disabled", bUseTransitions); - } - else if (m_bIsPressed) - { - VisualStateManager.GoToState(this, "Pressed", bUseTransitions); - } - else if (m_bIsPointerOver) - { - VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Normal", bUseTransitions); - } - - // FocusStates - if (FocusState.Unfocused != focusState && bIsEnabled) - { - if (FocusState.Pointer == focusState) - { - VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Focused", bUseTransitions); - } - } - else - { - VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); - } - - // CheckPlaceholderStates - if (hasToggleMenuItem && hasIconMenuItem) - { - VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); - } - else if (hasToggleMenuItem) - { - VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); - } - else if (hasIconMenuItem) - { - VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); - } - - // PaddingSizeStates - if (shouldBeNarrow) - { - VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); - } - - // We'll make the accelerator text visible if any item has accelerator text, - // as this causes the margin to be applied which reserves space, ensuring that accelerator text - // in one item won't be at the same horizontal position as label text in another item. - if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); - } - } - - // Clear flags relating to the visual state. Called when IsEnabled is set to false - // or when Visibility is set to Hidden or Collapsed. - void ClearStateFlags() - { - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - - UpdateVisualState(); - } - - // Create MenuFlyoutItemAutomationPeer to represent the - protected override AutomationPeer OnCreateAutomationPeer() - { - return new MenuFlyoutItemAutomationPeer(this); - } - - private protected override string GetPlainText() => Text; - - internal string KeyboardAcceleratorTextOverrideImpl - { - get - { - var pValue = KeyboardAcceleratorTextOverride; - - // If we have no keyboard accelerator text already provided by the app, - // then we'll see if we can ruct it ourselves based on keyboard accelerators - // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" - // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". - if (pValue == null) - { - pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); - - // If we were able to get a string representation from keyboard accelerators, - // then we should now set that as the value of KeyboardAcceleratorText. - if (pValue != null) - { - KeyboardAcceleratorTextOverrideImpl = pValue; - } - } - - return pValue; - } - set - { - KeyboardAcceleratorTextOverride = value; - } - } - - internal Size GetKeyboardAcceleratorTextDesiredSize() - { - var desiredSize = new Size(0, 0); - - if (!m_isTemplateApplied) - { - bool templateApplied = ApplyTemplate(); - m_isTemplateApplied = templateApplied; - } - - if (m_tpKeyboardAcceleratorTextBlock != null) - { - Thickness margin; - - m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; - margin = m_tpKeyboardAcceleratorTextBlock.Margin; - - desiredSize.Width -= (float)(margin.Left + margin.Right); - desiredSize.Height -= (float)(margin.Top + margin.Bottom); - } - - return desiredSize; - } - - internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) - { - if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) - { - m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; - - MenuFlyoutItemTemplateSettings templateSettings; - templateSettings = TemplateSettings; - - if (templateSettings == null) - { - MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); - TemplateSettings = templateSettingsImplementation; - templateSettings = templateSettingsImplementation; - } + /// + /// Gets or sets the text content of a MenuFlyoutItem. + /// + public string Text + { + get { return (string)GetValue(TextProperty) ?? ""; } + set { SetValue(TextProperty, value); } + } - templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; - } - } + /// + /// Identifies the Text dependency property. + /// + public static DependencyProperty TextProperty { get; } = + DependencyProperty.Register( + name: nameof(Text), + propertyType: typeof(string), + ownerType: typeof(MenuFlyoutItem), + typeMetadata: new FrameworkPropertyMetadata(default(string))); - internal virtual bool HasToggle() - { - return false; - } - } + /// + /// Occurs when a menu item is clicked. + /// + public event RoutedEventHandler Click; } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemTemplateSettings.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemTemplateSettings.cs index 0113608c3c35..d62e8aaf2178 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemTemplateSettings.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemTemplateSettings.cs @@ -1,7 +1,13 @@ -namespace Microsoft.UI.Xaml.Controls.Primitives +namespace Microsoft.UI.Xaml.Controls.Primitives; + +/// +/// Provides calculated values that can be referenced as TemplatedParent sources when defining templates for a MenuFlyoutPresenter control. +/// Not intended for general use. +/// +public partial class MenuFlyoutItemTemplateSettings : DependencyObject { - public partial class MenuFlyoutItemTemplateSettings : global::Microsoft.UI.Xaml.DependencyObject - { - public double KeyboardAcceleratorTextMinWidth { get; internal set; } - } + /// + /// Gets the minimum width allocated for the accelerator key tip of an MenuFlyout. + /// + public double KeyboardAcceleratorTextMinWidth { get; internal set; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenterTemplateSettings.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenterTemplateSettings.cs index a66f0712a663..c0d90949e4ca 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenterTemplateSettings.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenterTemplateSettings.cs @@ -1,15 +1,13 @@ -#pragma warning disable 108 // new keyword hiding -#pragma warning disable 114 // new keyword hiding +namespace Microsoft.UI.Xaml.Controls.Primitives; -using Uno; - -namespace Microsoft.UI.Xaml.Controls.Primitives +/// +/// Provides calculated values that can be referenced as TemplatedParent sources when defining templates for a MenuFlyoutPresenter control. +/// Not intended for general use. +/// +public partial class MenuFlyoutPresenterTemplateSettings : DependencyObject { - public partial class MenuFlyoutPresenterTemplateSettings : global::Microsoft.UI.Xaml.DependencyObject - { - public double FlyoutContentMinWidth - { - get; internal set; - } - } + /// + /// Gets the minimum width of flyout content. + /// + public double FlyoutContentMinWidth { get; internal set; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs index 9cbe9fd49f74..6f1ffba658d0 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs @@ -14,30 +14,46 @@ namespace Microsoft.UI.Xaml.Controls; partial class MenuFlyoutSubItem { + /// + /// Gets or sets the graphic content of the menu flyout subitem. + /// public IconElement Icon { get => (IconElement)this.GetValue(IconProperty); set => this.SetValue(IconProperty, value); } + /// + /// Identifies the Icon dependency property. + /// + public static DependencyProperty IconProperty { get; } = + DependencyProperty.Register( + nameof(Icon), + typeof(IconElement), + typeof(MenuFlyoutSubItem), + new FrameworkPropertyMetadata(default(IconElement))); + /// + /// Gets the collection used to generate the content of the sub-menu. + /// + public IList Items => m_tpItems; + + /// + /// Gets or sets the text content of a MenuFlyoutSubItem. + /// public string Text { get => (string)this.GetValue(TextProperty) ?? ""; set => SetValue(TextProperty, value); } - public static Microsoft.UI.Xaml.DependencyProperty TextProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - "Text", - typeof(string), - typeof(MenuFlyoutSubItem), - new FrameworkPropertyMetadata(default(string))); - - public static Microsoft.UI.Xaml.DependencyProperty IconProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - "Icon", - typeof(IconElement), - typeof(MenuFlyoutSubItem), - new FrameworkPropertyMetadata(default(IconElement))); + /// + /// Identifies the Text dependency property. + /// + public static DependencyProperty TextProperty { get; } = + DependencyProperty.Register( + nameof(Text), + typeof(string), + typeof(MenuFlyoutSubItem), + new FrameworkPropertyMetadata(default(string))); } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Properties.cs new file mode 100644 index 000000000000..2f83b0ec8250 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Properties.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.UI.Xaml; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ToggleMenuFlyoutItem +{ + /// + /// Gets or sets whether the ToggleMenuFlyoutItem is checked. + /// + public bool IsChecked + { + get => (bool)GetValue(IsCheckedProperty); + set => SetValue(IsCheckedProperty, value); + } + + /// + /// Identifies the IsChecked dependency property. + /// + public static DependencyProperty IsCheckedProperty { get; } = + DependencyProperty.Register( + nameof(IsChecked), + typeof(bool), + typeof(ToggleMenuFlyoutItem), + new FrameworkPropertyMetadata( + defaultValue: false, + propertyChangedCallback: (s, e) => (s as ToggleMenuFlyoutItem)?.OnIsCheckedChanged((bool)e.OldValue, (bool)e.NewValue))); +} From 3399595a82b7f60fab87efe78f3f33c9607fbe06 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Wed, 19 Jun 2024 16:06:24 +0200 Subject: [PATCH 04/28] feat: ToggleMenuFlyoutItemAutomationPeer --- .../Automation/Peers/AutomationPeer.mux.cs | 27 +++++ .../ToggleMenuFlyoutItemAutomationPeer.cs | 98 ++++++++++--------- 2 files changed, 79 insertions(+), 46 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Automation/Peers/AutomationPeer.mux.cs b/src/Uno.UI/UI/Xaml/Automation/Peers/AutomationPeer.mux.cs index 10a1ad4b9ea1..e859e58f2acd 100644 --- a/src/Uno.UI/UI/Xaml/Automation/Peers/AutomationPeer.mux.cs +++ b/src/Uno.UI/UI/Xaml/Automation/Peers/AutomationPeer.mux.cs @@ -6,5 +6,32 @@ public partial class AutomationPeer { [NotImplemented] internal static bool ListenerExistsHelper(AutomationEvents eventId) => false; + + /// + /// Removes the leading and trailing spaces in the provided string and returns the trimmed version + /// or an empty string when no characters are left. + /// Because it is recommended to set an AppBarButton, AppBarToggleButton, MenuFlyoutItem or ToggleMenuFlyoutItem's + /// KeyboardAcceleratorTextOverride to a single space to hide their keyboard accelerator UI, this trimming method + /// prevents automation tools like Narrator from emitting a space when navigating to such an element. + /// + private protected static string GetTrimmedKeyboardAcceleratorTextOverride(string keyboardAcceleratorTextOverride) + { + // Return an empty string when the provided keyboardAcceleratorTextOverride is already empty. + if (!string.IsNullOrEmpty(keyboardAcceleratorTextOverride)) + { + var trimmedKeyboardAcceleratorTextOverride = keyboardAcceleratorTextOverride.TrimStart(' '); + + // Return an empty string when the remaining string is empty. + if (!string.IsNullOrEmpty(trimmedKeyboardAcceleratorTextOverride)) + { + // Trim the trailing spaces as well. + trimmedKeyboardAcceleratorTextOverride = trimmedKeyboardAcceleratorTextOverride.TrimEnd(' '); + return trimmedKeyboardAcceleratorTextOverride; + } + } + + // Return an empty string + return ""; + } } } diff --git a/src/Uno.UI/UI/Xaml/Automation/Peers/ToggleMenuFlyoutItemAutomationPeer.cs b/src/Uno.UI/UI/Xaml/Automation/Peers/ToggleMenuFlyoutItemAutomationPeer.cs index f804032ac112..b88ac030f4e8 100644 --- a/src/Uno.UI/UI/Xaml/Automation/Peers/ToggleMenuFlyoutItemAutomationPeer.cs +++ b/src/Uno.UI/UI/Xaml/Automation/Peers/ToggleMenuFlyoutItemAutomationPeer.cs @@ -1,16 +1,20 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. -// MUX Reference ToggleMenuFlyoutItemAutomationPeer_Partial.cpp, tag winui3/release/1.4.2 +using System; +using Microsoft.UI.Xaml.Automation.Provider; +using Microsoft.UI.Xaml.Controls; + namespace Microsoft.UI.Xaml.Automation.Peers; /// /// Exposes ToggleMenuFlyoutItem types to Microsoft UI Automation. /// -public partial class ToggleMenuFlyoutItemAutomationPeer : FrameworkElementAutomationPeer, Provider.IToggleProvider +public partial class ToggleMenuFlyoutItemAutomationPeer : FrameworkElementAutomationPeer, IToggleProvider { - public ToggleMenuFlyoutItemAutomationPeer(Controls.ToggleMenuFlyoutItem owner) : base(owner) + /// + /// Initializes a new instance of the ToggleMenuFlyoutItemAutomationPeer class. + /// + /// The owner element to create for. + public ToggleMenuFlyoutItemAutomationPeer(ToggleMenuFlyoutItem owner) : base(owner) { - } protected override object GetPatternCore(PatternInterface patternInterface) @@ -19,36 +23,38 @@ protected override object GetPatternCore(PatternInterface patternInterface) { return this; } - else - { - return base.GetPatternCore(patternInterface); - } + + return base.GetPatternCore(patternInterface); } - protected override string GetClassNameCore() => nameof(Controls.ToggleMenuFlyoutItem); + protected override string GetClassNameCore() => nameof(ToggleMenuFlyoutItem); - protected override AutomationControlType GetAutomationControlTypeCore() - => AutomationControlType.MenuItem; + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.MenuItem; protected override string GetAcceleratorKeyCore() { - var acceleratorKey = base.GetAcceleratorKeyCore(); + var returnValue = base.GetAcceleratorKeyCore(); - if (string.IsNullOrEmpty(acceleratorKey)) + if (returnValue is null) { - return (Owner as Controls.ToggleMenuFlyoutItem).KeyboardAcceleratorTextOverride; + // If AutomationProperties.AcceleratorKey hasn't been set, then return the value of our KeyboardAcceleratorTextOverride property. + var ownerAsToggleMenuFlyoutItem = (ToggleMenuFlyoutItem)Owner; + var keyboardAcceleratorTextOverride = ownerAsToggleMenuFlyoutItem.KeyboardAcceleratorTextOverride; + returnValue = GetTrimmedKeyboardAcceleratorTextOverride(keyboardAcceleratorTextOverride); } - return acceleratorKey; + return returnValue; } protected override int GetPositionInSetCore() { + // First retrieve any valid value being directly set on the container, that value will get precedence. var returnValue = base.GetPositionInSetCore(); + // if it still is default value, calculate it ourselves. if (returnValue == -1) { - returnValue = GetPositionInSet(); + returnValue = MenuFlyoutPresenter.GetPositionInSetHelper((MenuFlyoutItemBase)Owner); } return returnValue; @@ -56,11 +62,13 @@ protected override int GetPositionInSetCore() protected override int GetSizeOfSetCore() { + // First retrieve any valid value being directly set on the container, that value will get precedence. var returnValue = base.GetSizeOfSetCore(); + // if it still is default value, calculate it ourselves. if (returnValue == -1) { - returnValue = GetPositionInSet(); + returnValue = MenuFlyoutPresenter.GetSizeOfSetHelper((MenuFlyoutItemBase)Owner); } return returnValue; @@ -69,15 +77,15 @@ protected override int GetSizeOfSetCore() /// /// Cycles through the toggle states of a control. /// - /// public void Toggle() { - if (!IsEnabled()) + var isEnabled = IsEnabled(); + if (!isEnabled) { - throw new ElementNotEnabledException(); + throw new InvalidOperationException("Element is not enabled"); } - (Owner as Controls.ToggleMenuFlyoutItem).Invoke(); + ((ToggleMenuFlyoutItem)Owner).Invoke(); } /// @@ -87,39 +95,37 @@ public ToggleState ToggleState { get { - var isChecked = (Owner as Controls.ToggleMenuFlyoutItem).IsChecked; - - if (isChecked) - { - return ToggleState.On; - } - else - { - return ToggleState.Off; - } + var isChecked = ((ToggleMenuFlyoutItem)Owner).IsChecked; + return isChecked ? Automation.ToggleState.On : Automation.ToggleState.Off; } } - internal void RaisePropertyChangedEvent(object oldValue, object newValue) + internal void RaiseToggleStatePropertyChangedEvent(object oldValue, object newValue) { - var oldToggleState = ConvertToToggleState(oldValue); - var newToggleState = ConvertToToggleState(newValue); - - if (oldToggleState != newToggleState) + var oldState = ConvertToToggleState(oldValue); + var newState = ConvertToToggleState(newValue); + if (oldState != newState) { - RaisePropertyChangedEvent(TogglePatternIdentifiers.ToggleStateProperty, oldToggleState, newToggleState); + RaisePropertyChangedEvent(TogglePatternIdentifiers.ToggleStateProperty, oldState, newState); } } - /// - /// Convert the Boolean in Inspectable to the ToggleState Enum, if the Inspectable is NULL that corresponds to Indeterminate state. - /// - private ToggleState ConvertToToggleState(object value) + private static ToggleState ConvertToToggleState(object value) { - if (value is bool v) + var state = Automation.ToggleState.Indeterminate; + + if (value is bool boolValue) { - return v ? ToggleState.On : ToggleState.Off; + if (boolValue) + { + state = Automation.ToggleState.On; + } + else + { + state = Automation.ToggleState.Off; + } } - return ToggleState.Indeterminate; + + return state; } } From 64a5c2d9ef3735d99df34f78dcad569b3dd5df7e Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Wed, 19 Jun 2024 16:18:51 +0200 Subject: [PATCH 05/28] feat: Update ToggleMenuFlyoutItem to winui3/release/1.5.4 --- .../ToggleMenuFlyoutItemAutomationPeer.cs | 6 +- .../MenuFlyout/ToggleMenuFlyoutItem.Uno.cs | 15 ++ .../MenuFlyout/ToggleMenuFlyoutItem.cs | 179 +----------------- .../MenuFlyout/ToggleMenuFlyoutItem.mux.cs | 159 ++++++++++++++++ 4 files changed, 189 insertions(+), 170 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Uno.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.mux.cs diff --git a/src/Uno.UI/UI/Xaml/Automation/Peers/ToggleMenuFlyoutItemAutomationPeer.cs b/src/Uno.UI/UI/Xaml/Automation/Peers/ToggleMenuFlyoutItemAutomationPeer.cs index b88ac030f4e8..ec60aff5ace4 100644 --- a/src/Uno.UI/UI/Xaml/Automation/Peers/ToggleMenuFlyoutItemAutomationPeer.cs +++ b/src/Uno.UI/UI/Xaml/Automation/Peers/ToggleMenuFlyoutItemAutomationPeer.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\ToggleMenuFlyoutItemAutomationPeer_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System; using Microsoft.UI.Xaml.Automation.Provider; using Microsoft.UI.Xaml.Controls; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Uno.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Uno.cs new file mode 100644 index 000000000000..31b7e197b6a0 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Uno.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ToggleMenuFlyoutItem +{ + /// + /// Test hook used to determine if the item is a toggle. + /// + internal override bool HasToggle() => true; +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.cs index f2f082c15d89..af3edc53cdb0 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.cs @@ -1,174 +1,15 @@ -#nullable enable +namespace Microsoft.UI.Xaml.Controls; -using System; -using System.Windows.Input; -using Windows.Devices.Input; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls; - -namespace Microsoft.UI.Xaml.Controls +/// +/// Represents an item in a MenuFlyout that a user can change between two states, checked or unchecked. +/// +public partial class ToggleMenuFlyoutItem : MenuFlyoutItem { - public partial class ToggleMenuFlyoutItem : MenuFlyoutItem + /// + /// Initializes a new instance of the ToggleMenuFlyoutItem class. + /// + public ToggleMenuFlyoutItem() { - public ToggleMenuFlyoutItem() - { - DefaultStyleKey = typeof(ToggleMenuFlyoutItem); - } - - #region IsChecked - - public bool IsChecked - { - get => (bool)GetValue(IsCheckedProperty); - set => SetValue(IsCheckedProperty, value); - } - - public static DependencyProperty IsCheckedProperty { get; } = - DependencyProperty.Register( - name: nameof(IsChecked), - propertyType: typeof(bool), - ownerType: typeof(ToggleMenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata( - defaultValue: false, - propertyChangedCallback: (s, e) => (s as ToggleMenuFlyoutItem)?.OnIsCheckedChanged((bool)e.OldValue, (bool)e.NewValue))); - - #endregion - - // Change to the correct visual state for the MenuFlyoutItem. - private protected override void ChangeVisualState( - // true to use transitions when updating the visual state, false - // to snap directly to the new visual state. - bool bUseTransitions) - { - var bIsPressed = IsPointerPressed; - var bIsPointerOver = IsPointerOver; - var bIsEnabled = IsEnabled; - var bIsChecked = IsChecked; - var hasIconMenuItem = false; - var hasMenuItemWithKeyboardAcceleratorText = false; - var shouldBeNarrow = GetShouldBeNarrow(); - var focusState = FocusState; - var spPresenter = GetParentMenuFlyoutPresenter(); - var isKeyboardPresent = false; - - if (spPresenter != null) - { - hasIconMenuItem = spPresenter.GetContainsIconItems(); - hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); - } - - // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, - // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. - if (hasMenuItemWithKeyboardAcceleratorText) - { - isKeyboardPresent = new KeyboardCapabilities().KeyboardPresent != 0; - } - - // CommonStates - if (!bIsEnabled) - { - VisualStateManager.GoToState(this, "Disabled", bUseTransitions); - } - else if (bIsPressed) - { - VisualStateManager.GoToState(this, "Pressed", bUseTransitions); - } - else if (bIsPointerOver) - { - VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Normal", bUseTransitions); - } - - // FocusStates - if (FocusState.Unfocused != focusState && bIsEnabled) - { - if (FocusState.Pointer == focusState) - { - VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Focused", bUseTransitions); - } - } - else - { - VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); - } - - // CheckStates - if (bIsChecked && hasIconMenuItem) - { - VisualStateManager.GoToState(this, "CheckedWithIcon", bUseTransitions); - } - else if (hasIconMenuItem) - { - VisualStateManager.GoToState(this, "UncheckedWithIcon", bUseTransitions); - } - else if (bIsChecked) - { - VisualStateManager.GoToState(this, "Checked", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Unchecked", bUseTransitions); - } - - // PaddingSizeStates - if (shouldBeNarrow) - { - VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); - } - - // We'll make the accelerator text visible if any item has accelerator text, - // as this causes the margin to be applied which reserves space, ensuring that accelerator text - // in one item won't be at the same horizontal position as label text in another item. - if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); - } - } - - // Performs appropriate actions upon a mouse/keyboard invocation of a MenuFlyoutItem. - internal override void Invoke() - { - IsChecked = !IsChecked; - - base.Invoke(); - } - - private void OnIsCheckedChanged(bool oldValue, bool newValue) - { - UpdateVisualState(); - - var bAutomationListener = AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged); - if (bAutomationListener) - { - var spAutomationPeer = GetAutomationPeer(); - - if (spAutomationPeer is ToggleMenuFlyoutItemAutomationPeer spToggleButtonAutomationPeer) - { - spToggleButtonAutomationPeer.Toggle(); - } - } - } - - // Create ToggleMenuFlyoutItemAutomationPeer to represent the - protected override AutomationPeer OnCreateAutomationPeer() - => new ToggleMenuFlyoutItemAutomationPeer(this); - - internal override bool HasToggle() => true; + DefaultStyleKey = typeof(ToggleMenuFlyoutItem); } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.mux.cs new file mode 100644 index 000000000000..94219bd9045a --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.mux.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\ToggleMenuFlyoutItem_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DirectUI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation.Peers; +using Windows.Devices.Input; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ToggleMenuFlyoutItem +{ + // Change to the correct visual state for the MenuFlyoutItem. + private protected override void ChangeVisualState( + // true to use transitions when updating the visual state, false + // to snap directly to the new visual state. + bool bUseTransitions) + { + bool hasIconMenuItem = false; + bool hasMenuItemWithKeyboardAcceleratorText = false; + bool isKeyboardPresent = false; + + var isPressed = IsPointerPressed; + var isPointerOver = IsPointerOver; + var isEnabled = IsEnabled; + var isChecked = IsChecked; + var focusState = FocusState; + var shouldBeNarrow = GetShouldBeNarrow(); + var spPresenter = GetParentMenuFlyoutPresenter(); + + if (spPresenter is not null) + { + hasIconMenuItem = spPresenter.GetContainsIconItems(); + hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); + } + + // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, + // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. + if (hasMenuItemWithKeyboardAcceleratorText) + { + isKeyboardPresent = DXamlCore.Current.IsKeyboardPresent; // TODO MZ new KeyboardCapabilities().KeyboardPresent != 0; + } + + // CommonStates + if (!isEnabled) + { + VisualStateManager.GoToState(this, "Disabled", bUseTransitions); + } + else if (isPressed) + { + VisualStateManager.GoToState(this, "Pressed", bUseTransitions); + } + else if (isPointerOver) + { + VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Normal", bUseTransitions); + } + + // FocusStates + if (FocusState.Unfocused != focusState && isEnabled) + { + if (FocusState.Pointer == focusState) + { + VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Focused", bUseTransitions); + } + } + else + { + VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); + } + + // CheckStates + if (isChecked && hasIconMenuItem) + { + VisualStateManager.GoToState(this, "CheckedWithIcon", bUseTransitions); + } + else if (hasIconMenuItem) + { + VisualStateManager.GoToState(this, "UncheckedWithIcon", bUseTransitions); + } + else if (isChecked) + { + VisualStateManager.GoToState(this, "Checked", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Unchecked", bUseTransitions); + } + + // PaddingSizeStates + if (shouldBeNarrow) + { + VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); + } + + // We'll make the accelerator text visible if any item has accelerator text, + // as this causes the margin to be applied which reserves space, ensuring that accelerator text + // in one item won't be at the same horizontal position as label text in another item. + if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); + } + } + + // Performs appropriate actions upon a mouse/keyboard invocation of a MenuFlyoutItem. + internal override void Invoke() + { + IsChecked = !IsChecked; + + base.Invoke(); + } + + internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) + { + base.OnPropertyChanged2(args); + + if (args.Property == IsCheckedProperty) + { + UpdateVisualState(); + + var automationListener = AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged); + if (automationListener) + { + var spAutomationPeer = GetOrCreateAutomationPeer(); + + if (spAutomationPeer is ToggleMenuFlyoutItemAutomationPeer spToggleButtonAutomationPeer) + { + spToggleButtonAutomationPeer.RaiseToggleStatePropertyChangedEvent(args.OldValue, args.NewValue); + } + } + } + } + + // Create ToggleMenuFlyoutItemAutomationPeer to represent the ToggleMenuFlyoutItem. + protected override AutomationPeer OnCreateAutomationPeer() => new ToggleMenuFlyoutItemAutomationPeer(this); +} From accab48d4bee6eb4ac41012ba481ddc8f1fef5fc Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Wed, 19 Jun 2024 17:38:55 +0200 Subject: [PATCH 06/28] chore: Add helper MenuFlyout classes --- .../MenuFlyoutItemBaseCollection.cs | 37 +++++++++++++++++++ .../MenuFlyout/MenuFlyoutSeparator.cs | 17 +++++---- .../MenuFlyout/MenuPopupThemeTransition.cs | 37 +++++++++++++++++++ .../ToggleMenuFlyoutItem.Properties.cs | 13 ++----- 4 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuPopupThemeTransition.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs new file mode 100644 index 000000000000..d8e19b138809 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.UI.Xaml.Controls; + +internal class MenuFlyoutItemBaseCollection : DependencyObjectCollection +{ +#if HAS_UNO // Our API is a bit different from WinUI + private protected override void OnCollectionChanged() => NotifyMenuFlyoutOfCollectionChange(); +#endif + + private void NotifyMenuFlyoutOfCollectionChange() + { + ctl::ComPtr ownerAsDO; + IFC_RETURN(DXamlCore::GetCurrent()->GetPeer(static_cast(GetHandle())->GetOwner(), &ownerAsDO)); + + auto ownerAsMenuFlyout = ownerAsDO.AsOrNull(); + + if (ownerAsMenuFlyout) + { + IFC_RETURN(ownerAsMenuFlyout.Cast()->QueueRefreshItemsSource()); + } + else + { + auto ownerAsMenuFlyoutSubItem = ownerAsDO.AsOrNull(); + + // MenuFlyoutItemBaseCollection is only used by MenuFlyout and MenuFlyoutSubItem. + // If another type is added, this will need to change. + IFCEXPECT_RETURN(ownerAsMenuFlyoutSubItem != nullptr); + + IFC_RETURN(ownerAsMenuFlyoutSubItem.Cast()->QueueRefreshItemsSource()); + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSeparator.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSeparator.cs index 22567e476f72..700a51f195f0 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSeparator.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSeparator.cs @@ -1,11 +1,14 @@ -#pragma warning disable 108 // new keyword hiding -#pragma warning disable 114 // new keyword hiding -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +/// +/// Represents a horizontal line that separates items in an MenuFlyout. +/// +public partial class MenuFlyoutSeparator : MenuFlyoutItemBase { - public partial class MenuFlyoutSeparator : MenuFlyoutItemBase + /// + /// Initializes a new instance of the MenuFlyoutSeparator class. + /// + public MenuFlyoutSeparator() { - public MenuFlyoutSeparator() - { - } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuPopupThemeTransition.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuPopupThemeTransition.cs new file mode 100644 index 000000000000..9743885e49fe --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuPopupThemeTransition.cs @@ -0,0 +1,37 @@ +using Microsoft.UI.Xaml.Controls.Primitives; +using Uno; + +namespace Microsoft.UI.Xaml.Media.Animation; + +[NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] +internal class MenuPopupThemeTransition : PopupThemeTransition +{ + public double OpenedLength + { + get => (double)GetValue(OpenedLengthProperty); + set => SetValue(OpenedLengthProperty, value); + } + + public static DependencyProperty OpenedLengthProperty = + DependencyProperty.Register(nameof(OpenedLength), typeof(double), typeof(MenuPopupThemeTransition), new FrameworkPropertyMetadata(0.0)); + + public double ClosedRatio + { + get => (double)GetValue(ClosedRatioProperty); + set => SetValue(ClosedRatioProperty, value); + } + + public static DependencyProperty ClosedRatioProperty { get; } = + DependencyProperty.Register(nameof(ClosedRatio), typeof(double), typeof(MenuPopupThemeTransition), new FrameworkPropertyMetadata(0.0)); + + public AnimationDirection Direction + { + get => (AnimationDirection)GetValue(DirectionProperty); + set => SetValue(DirectionProperty, value); + } + + public static readonly DependencyProperty DirectionProperty = + DependencyProperty.Register(nameof(Direction), typeof(AnimationDirection), typeof(MenuPopupThemeTransition), new FrameworkPropertyMetadata(AnimationDirection.Left)); + + +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Properties.cs index 2f83b0ec8250..fd7bdb962ad4 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Properties.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.UI.Xaml; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\ToggleMenuFlyoutItem_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 namespace Microsoft.UI.Xaml.Controls; @@ -26,7 +23,5 @@ public bool IsChecked nameof(IsChecked), typeof(bool), typeof(ToggleMenuFlyoutItem), - new FrameworkPropertyMetadata( - defaultValue: false, - propertyChangedCallback: (s, e) => (s as ToggleMenuFlyoutItem)?.OnIsCheckedChanged((bool)e.OldValue, (bool)e.NewValue))); + new FrameworkPropertyMetadata(false)); } From 6acd57bab22417b72d6fee9ca212cd5c2a6d2b92 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Wed, 19 Jun 2024 17:39:30 +0200 Subject: [PATCH 07/28] feat: Update MenuFlyoutSubItem to winui3/release/1.5.4 --- .../MenuFlyoutSubItem.Properties.cs | 30 +- .../Controls/MenuFlyout/MenuFlyoutSubItem.cs | 24 +- .../MenuFlyout/MenuFlyoutSubItem.mux.cs | 16 +- .../MenuFlyoutSubItem.partial.h.mux.cs | 49 +- .../MenuFlyoutSubItem.partial.mux.cs | 456 +++++++----------- 5 files changed, 241 insertions(+), 334 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs index 6f1ffba658d0..7be7f8c39301 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs @@ -1,14 +1,8 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutSubItem_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + using System.Collections.Generic; -using Uno.Disposables; -using Uno.UI.Extensions; -using Uno.UI.Xaml.Core; -using Windows.Foundation; -using Windows.System; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Markup; namespace Microsoft.UI.Xaml.Controls; @@ -36,7 +30,21 @@ public IconElement Icon /// /// Gets the collection used to generate the content of the sub-menu. /// - public IList Items => m_tpItems; + public IList Items + { + get => (IList)this.GetValue(ItemsProperty); + private set => this.SetValue(ItemsProperty, value); + } + + /// + /// Identifies the Items dependency property. + /// + internal static DependencyProperty ItemsProperty { get; } = + DependencyProperty.Register( + nameof(Items), + typeof(IList), + typeof(MenuFlyout), + new FrameworkPropertyMetadata(defaultValue: null)); /// /// Gets or sets the text content of a MenuFlyoutSubItem. diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs index 656587aaf52a..bac1862061fe 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs @@ -1,18 +1,20 @@ -using System; -using System.Collections.Generic; -using Uno.Disposables; -using Uno.UI.Extensions; -using Uno.UI.Xaml.Core; -using Windows.Foundation; -using Windows.System; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Markup; +using Microsoft.UI.Xaml.Markup; namespace Microsoft.UI.Xaml.Controls; +/// +/// Represents a menu item that displays a sub-menu in a MenuFlyout control. +/// [ContentProperty(Name = nameof(Items))] public partial class MenuFlyoutSubItem : MenuFlyoutItemBase, ISubMenuOwner { + /// + /// Initializes a new instance of the MenuFlyoutSubItem class. + /// + public MenuFlyoutSubItem() + { + DefaultStyleKey = typeof(MenuFlyoutSubItem); + + PrepareState(); + } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs index db5800e8790f..c9bbd44e0093 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs @@ -1,8 +1,13 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\core\core\elements\MenuFlyoutSubItem.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Uno.UI.Xaml; namespace Microsoft.UI.Xaml.Controls; @@ -10,18 +15,13 @@ partial class MenuFlyoutSubItem { private void EnterImpl(DependencyObject pNamescopeOwner, EnterParams parameters) { - - base.EnterImpl(pNamescopeOwner, parameters); + //base.EnterImpl(pNamescopeOwner, parameters); MenuFlyout.KeyboardAcceleratorFlyoutItemEnter(this, pNamescopeOwner, MenuFlyoutSubItem.ItemsProperty, parameters); - - return S_OK; } private void LeaveImpl(DependencyObject pNamescopeOwner, LeaveParams parameters) { - base.LeaveImpl(pNamescopeOwner, parameters); + //base.LeaveImpl(pNamescopeOwner, parameters); MenuFlyout.KeyboardAcceleratorFlyoutItemLeave(this, pNamescopeOwner, MenuFlyoutSubItem.ItemsProperty, parameters); - - return S_OK; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs index 52ddedcd2e2b..3bfb347da51a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs @@ -1,22 +1,47 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutSubItem_Partial.h, tag winui3/release/1.5.4, commit 98a60c8 + using System.Collections.Generic; -using Uno.Disposables; -using Uno.UI.Extensions; -using Uno.UI.Xaml.Core; -using Windows.Foundation; -using Windows.System; -using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Markup; +using Microsoft.UI.Xaml.Media.Animation; +using Uno.Disposables; +using Uno.UI.DataBinding; namespace Microsoft.UI.Xaml.Controls; partial class MenuFlyoutSubItem { - - // ISubMenuOwner implementation - bool ISubMenuOwner.IsSubMenuOpen => IsOpen; + internal Popup GetPopup() => m_tpPopup; + + internal Control GetMenuFlyoutPresenter() => m_tpPresenter; + // ISubMenuOwner implementation bool ISubMenuOwner.IsSubMenuPositionedAbsolutely => true; + + // Collection of the sub menu item + private IList m_tpItems; + + // Popup for the MenuFlyoutSubItem + private Popup m_tpPopup; + + // Presenter for the MenuFlyoutSubItem + private Control m_tpPresenter; + + // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. + private Transition m_tpMenuPopupThemeTransition; + + // Event pointer for the Loaded event + private readonly SerialDisposable m_epLoadedHandler = new(); + + // Event pointer for the size changed on the MenuFlyoutSubItem's presenter + private readonly SerialDisposable m_epPresenterSizeChangedHandler; + + // Helper to which to delegate cascading menu functionality. + private CascadingMenuHelper m_menuHelper; + + // Weak reference the parent that owns the menu that this item belongs to. + private ManagedWeakReference m_wrParentOwner; + + private bool _itemsSourceRefreshPending; } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs index 34ba442b0b67..f35bf3df54aa 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs @@ -1,90 +1,36 @@ -using System; -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutSubItem_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System.Linq; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media.Animation; using Uno.Disposables; +using Uno.UI.DataBinding; using Uno.UI.Extensions; using Uno.UI.Xaml.Core; using Windows.Foundation; +using Windows.Foundation.Metadata; using Windows.System; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Markup; namespace Microsoft.UI.Xaml.Controls; -[ContentProperty(Name = nameof(Items))] public partial class MenuFlyoutSubItem : MenuFlyoutItemBase, ISubMenuOwner { - // Popup for the MenuFlyoutSubItem - Popup m_tpPopup; - - // Presenter for the MenuFlyoutSubItem - Control m_tpPresenter; - - // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. - // UNO TODO Transition m_tpMenuPopupThemeTransition = null; - - // Event pointer for the Loaded event - // IDisposable m_epLoadedHandler; - - // Event pointer for the size changed on the MenuFlyoutSubItem's presenter - IDisposable m_epPresenterSizeChangedHandler; - - // Helper to which to delegate cascading menu functionality. - CascadingMenuHelper m_menuHelper; - - // Weak reference the parent that owns the menu that this item belongs to. - private WeakReference m_wrParentOwner; - - DependencyObjectCollection m_tpItems; - - public string Text - { - get => (string)this.GetValue(TextProperty) ?? ""; - set => SetValue(TextProperty, value); - } - - public IList Items => m_tpItems; - - public IconElement Icon - { - get => (IconElement)this.GetValue(IconProperty); - set => this.SetValue(IconProperty, value); - } - - public static Microsoft.UI.Xaml.DependencyProperty TextProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - "Text", - typeof(string), - typeof(MenuFlyoutSubItem), - new FrameworkPropertyMetadata(default(string))); - - public static Microsoft.UI.Xaml.DependencyProperty IconProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - "Icon", - typeof(IconElement), - typeof(MenuFlyoutSubItem), - new FrameworkPropertyMetadata(default(IconElement))); - - public MenuFlyoutSubItem() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ", this)); -#endif // MFSI_DEBUG - - PrepareState(); - - DefaultStyleKey = typeof(MenuFlyoutSubItem); - } - void PrepareState() { #if MFSI_DEBUG IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PrepareState.", this)); #endif // MFSI_DEBUG + //base.PrepareState(); + // Create the sub menu items collection and set the owner - m_tpItems = new DependencyObjectCollection(this); + var spItems = new DependencyObjectCollection(this); + m_tpItems = spItems; + Items = spItems; m_menuHelper = new CascadingMenuHelper(); m_menuHelper.Initialize(this); @@ -122,10 +68,6 @@ protected override void OnPointerEntered(PointerRoutedEventArgs args) { base.OnPointerEntered(args); -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerEntered.", this)); -#endif // MFSI_DEBUG - UpdateParentOwner(null /*parentMenuFlyoutPresenter*/); m_menuHelper.OnPointerEntered(args); } @@ -155,42 +97,23 @@ protected override void OnPointerExited(PointerRoutedEventArgs args) } // PointerPressed event handler that ensures the pressed state. - protected override void OnPointerPressed(PointerRoutedEventArgs args) { base.OnPointerPressed(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerPressed.", this)); -#endif // MFSI_DEBUG - m_menuHelper.OnPointerPressed(args); } // PointerReleased event handler that shows MenuFlyoutSubItem in // case of touch input. - protected override void OnPointerReleased(PointerRoutedEventArgs args) { base.OnPointerReleased(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPointerReleased.", this)); -#endif // MFSI_DEBUG - m_menuHelper.OnPointerReleased(args); - } - protected override void OnGotFocus(RoutedEventArgs args) { base.OnGotFocus(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnGotFocus.", this)); -#endif // MFSI_DEBUG - m_menuHelper.OnGotFocus(args); } @@ -198,11 +121,6 @@ protected override void OnGotFocus(RoutedEventArgs args) protected override void OnLostFocus(RoutedEventArgs args) { base.OnLostFocus(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnLostFocus.", this)); -#endif // MFSI_DEBUG - m_menuHelper.OnLostFocus(args); } @@ -214,10 +132,6 @@ protected override void OnKeyDown(KeyRoutedEventArgs args) { base.OnKeyDown(args); -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyDown.", this)); -#endif // MFSI_DEBUG - bool handled = args.Handled; bool shouldHandleEvent = false; @@ -250,58 +164,41 @@ protected override void OnKeyDown(KeyRoutedEventArgs args) protected override void OnKeyUp(KeyRoutedEventArgs args) { base.OnKeyUp(args); - -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnKeyUp.", this)); -#endif // MFSI_DEBUG - m_menuHelper.OnKeyUp(args); } // Ensure the creating the popup and menu presenter to show the - void EnsurePopupAndPresenter() + private void EnsurePopupAndPresenter() { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsurePopupAndPresenter.", this)); -#endif // MFSI_DEBUG - - if (m_tpPopup == null) + if (m_tpPopup is null) { - MenuFlyoutPresenter spParentMenuFlyoutPresenter = null; - Control spPresenter; UIElement spPresenterAsUI; FrameworkElement spPresenterAsFE; - Popup spPopup; - - spPopup = new Popup(); + Popup spPopup = new(); spPopup.IsSubMenu = true; spPopup.IsLightDismissEnabled = false; - spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); -#if false // UNO TODO Windowed Popup is not available + var spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); if (spParentMenuFlyoutPresenter != null) { - spParentMenuFlyout = spParentMenuFlyoutPresenter.GetParentMenuFlyout(); + var spParentMenuFlyout = spParentMenuFlyoutPresenter.GetParentMenuFlyout(); // Set the windowed Popup if the MenuFlyout is set the windowed Popup - if (spParentMenuFlyout && spParentMenuFlyout.IsWindowedPopup()) + if (spParentMenuFlyout is not null && spParentMenuFlyout.IsWindowedPopup) { - ASSERT((CPopup*)(spPopup as Popup.GetHandle()).DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle())); - - ((CPopup*)(spPopup as Popup.GetHandle()).SetIsWindowed()); + spPopup.SetIsWindowed(); // Ensure the sub menu is the windowed Popup - ASSERT((CPopup*)(spPopup as Popup.GetHandle()).IsWindowed()); + global::System.Diagnostics.Debug.Assert((spPopup.IsWindowed()) - xaml.IXamlRoot xamlRoot = XamlRoot.GetForElementStatic(spParentMenuFlyoutPresenter); - if (xamlRoot) + XamlRoot xamlRoot = XamlRoot.GetForElement(spParentMenuFlyoutPresenter); + if (xamlRoot is not null) { - (spPopup as Popup.XamlRoot = xamlRoot); + spPopup.XamlRoot = xamlRoot; } } } -#endif - spPresenter = CreateSubPresenter(); + var spPresenter = CreateSubPresenter(); spPresenterAsUI = spPresenter; if (spParentMenuFlyoutPresenter != null) @@ -320,47 +217,42 @@ void EnsurePopupAndPresenter() spPresenterAsFE = spPresenter; spPresenterAsFE.SizeChanged += OnPresenterSizeChanged; - m_epPresenterSizeChangedHandler = Disposable.Create(() => spPresenterAsFE.SizeChanged -= OnPresenterSizeChanged); + m_epPresenterSizeChangedHandler.Disposable = Disposable.Create(() => spPresenterAsFE.SizeChanged -= OnPresenterSizeChanged); - m_menuHelper.SetSubMenuPresenter(spPresenter as Control); + m_menuHelper.SetSubMenuPresenter(spPresenter); } } - void ForwardPresenterProperties( + private void ForwardPresenterProperties( MenuFlyout pOwnerMenuFlyout, MenuFlyoutPresenter pParentMenuFlyoutPresenter, MenuFlyoutPresenter pSubMenuFlyoutPresenter) { - Style spStyle; - ElementTheme parentPresenterTheme; - object spDataContext; - FrameworkElement spPopupAsFE; - MenuFlyoutPresenter spSubMenuFlyoutPresenter = pSubMenuFlyoutPresenter; - Control spSubMenuFlyoutPresenterAsControl; + var spSubMenuFlyoutPresenter = pSubMenuFlyoutPresenter; DependencyObject spThisAsDO = this; global::System.Diagnostics.Debug.Assert(pOwnerMenuFlyout != null && pParentMenuFlyoutPresenter != null && pSubMenuFlyoutPresenter != null); - spSubMenuFlyoutPresenterAsControl = spSubMenuFlyoutPresenter; + Control spSubMenuFlyoutPresenterAsControl = spSubMenuFlyoutPresenter; // Set the sub presenter style from the MenuFlyout's presenter style - spStyle = pOwnerMenuFlyout.MenuFlyoutPresenterStyle; + Style spStyle = pOwnerMenuFlyout.MenuFlyoutPresenterStyle; if (spStyle != null) { - ((Control)pSubMenuFlyoutPresenter).Style = spStyle; + pSubMenuFlyoutPresenter.Style = spStyle; } else { - ((Control)pSubMenuFlyoutPresenter).ClearValue(FrameworkElement.StyleProperty); + pSubMenuFlyoutPresenter.ClearValue(FrameworkElement.StyleProperty); } // Set the sub presenter's RequestTheme from the parent presenter's RequestTheme - parentPresenterTheme = pParentMenuFlyoutPresenter.RequestedTheme; + ElementTheme parentPresenterTheme = pParentMenuFlyoutPresenter.RequestedTheme; pSubMenuFlyoutPresenter.RequestedTheme = parentPresenterTheme; // Set the sub presenter's DataContext from the parent presenter's DataContext - spDataContext = pParentMenuFlyoutPresenter.DataContext; + object spDataContext = pParentMenuFlyoutPresenter.DataContext; pSubMenuFlyoutPresenter.DataContext = spDataContext; // Set the sub presenter's FlowDirection from the current sub menu item's FlowDirection @@ -368,8 +260,11 @@ void ForwardPresenterProperties( pSubMenuFlyoutPresenter.FlowDirection = flowDirection; // Set the popup's FlowDirection from the current FlowDirection - spPopupAsFE = m_tpPopup; + FrameworkElement spPopupAsFE = m_tpPopup; spPopupAsFE.FlowDirection = flowDirection; + // Also set the popup's theme. If there is a SystemBackdrop on the menu, it'll be watching the theme on the popup + // itself rather than the presenter set as the popup's child. + spPopupAsFE.RequestedTheme = parentPresenterTheme; // Set the sub presenter's Language from the parent presenter's Language pSubMenuFlyoutPresenter.Language = pParentMenuFlyoutPresenter.Language; @@ -378,13 +273,27 @@ void ForwardPresenterProperties( var isTextScaleFactorEnabled = pParentMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal; pSubMenuFlyoutPresenter.IsTextScaleFactorEnabledInternal = isTextScaleFactorEnabled; - ElementSoundMode soundMode = ElementSoundPlayerService.Instance.GetEffectiveSoundMode(spThisAsDO as DependencyObject); + ElementSoundMode soundMode = ElementSoundPlayerService.Instance.GetEffectiveSoundMode(spThisAsDO); - (spSubMenuFlyoutPresenterAsControl as Control).ElementSoundMode = soundMode; + spSubMenuFlyoutPresenterAsControl.ElementSoundMode = soundMode; + } + + private void ForwardSystemBackdropToPopup(MenuFlyout ownerMenuFlyout) + { + // Set the popup's SystemBackdrop from the parent flyout's SystemBackdrop. Note that the top-level menu is a + // MenuFlyout with a SystemBackdrop property, but submenus are MenyFlyoutSubItems with no SystemBackdrop property, + // so we just use the one set on the top-level menu. SystemBackdrop can handle having multiple parents, and will + // keep a separate controller/configuration object per place it's used. + var flyoutSystemBackdrop = ownerMenuFlyout.SystemBackdrop; + var popupSystemBackdrop = m_tpPopup.SystemBackdrop; + if (flyoutSystemBackdrop != popupSystemBackdrop) + { + m_tpPopup.SystemBackdrop = flyoutSystemBackdrop; + } } // Ensure that any currently open MenuFlyoutSubItems are closed - void EnsureCloseExistingSubItems() + private void EnsureCloseExistingSubItems() { #if MFSI_DEBUG IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: EnsureCloseExistingSubItems.", this)); @@ -413,44 +322,32 @@ void EnsureCloseExistingSubItems() } - bool IsOpen => m_tpPopup?.IsOpen ?? false; + internal bool IsOpen => m_tpPopup?.IsOpen ?? false; - Control - CreateSubPresenter() + private Control CreateSubPresenter() { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: CreateSubPresenter.", this)); -#endif // MFSI_DEBUG - - MenuFlyoutPresenter spPresenter = new MenuFlyoutPresenter(); + MenuFlyoutPresenter spPresenter = new(); // Specify the sub MenuFlyoutPresenter - (spPresenter as MenuFlyoutPresenter).IsSubPresenter = true; + spPresenter.IsSubPresenter = true; (spPresenter as IMenuPresenter).Owner = this; return spPresenter; } - void UpdateParentOwner(MenuFlyoutPresenter parentMenuFlyoutPresenter) + private void UpdateParentOwner(MenuFlyoutPresenter parentMenuFlyoutPresenter) { - MenuFlyoutPresenter parentPresenter = parentMenuFlyoutPresenter; - if (parentPresenter == null) + var parentPresenter = parentMenuFlyoutPresenter; + if (parentPresenter is null) { parentPresenter = GetParentMenuFlyoutPresenter(); } -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentPresenter=0x%p.", this, parentPresenter)); -#endif // MFSI_DEBUG if (parentPresenter != null) { ISubMenuOwner parentSubMenuOwner; parentSubMenuOwner = (parentPresenter as IMenuPresenter).Owner; -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: UpdateParentOwner - parentSubMenuOwner=0x%p.", this, parentSubMenuOwner)); -#endif // MFSI_DEBUG - if (parentSubMenuOwner != null) { ((ISubMenuOwner)this).ParentOwner = parentSubMenuOwner; @@ -460,16 +357,12 @@ void UpdateParentOwner(MenuFlyoutPresenter parentMenuFlyoutPresenter) // Set the popup open or close status for MenuFlyoutSubItem and ensure the // focus to the current presenter. - void SetIsOpen(bool isOpen) + private void SetIsOpen(bool isOpen) { bool isOpened = false; isOpened = m_tpPopup.IsOpen; -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: SetIsOpen isOpen=%d, isOpened=%d.", this, isOpen, isOpened)); -#endif // MFSI_DEBUG - if (isOpen != isOpened) { (m_tpPresenter as IMenuPresenter).Owner = isOpen ? this : null; @@ -492,13 +385,12 @@ void SetIsOpen(bool isOpen) UpdateParentOwner(parentPresenter); } - // UNO TODO - VisualTree visualTree = VisualTree.GetForElement(this); - if (visualTree is not null) - { - // Put the popup on the same VisualTree as this flyout sub item to make sure it shows up in the right place - m_tpPopup.SetVisualTree(visualTree); - } + VisualTree visualTree = VisualTree.GetForElement(this); + if (visualTree is not null) + { + // Put the popup on the same VisualTree as this flyout sub item to make sure it shows up in the right place + m_tpPopup.SetVisualTree(visualTree); + } // Set the popup open or close state m_tpPopup.IsOpen = isOpen; @@ -508,58 +400,28 @@ void SetIsOpen(bool isOpen) if (isOpen) { // Set the focus to the displayed sub menu presenter to navigate the each sub items - m_tpPresenter.Focus(FocusState.Programmatic); - - // UNO TODO - // (DependencyObject.SetFocusedElement( - // spPresenterAsDO as DependencyObject, - // xaml.FocusState_Programmatic, - // false /*animateIfBringIntoView*/, - // &focusUpdated)); + this.SetFocusedElement(m_tpPresenter, FocusState.Programmatic, false); } else { // Set the focus to the sub menu item - this.Focus(FocusState.Programmatic); - - // UNO TODO - //(DependencyObject.SetFocusedElement( - // spThisAsDO as DependencyObject, - // xaml.FocusState_Programmatic, - // false /*animateIfBringIntoView*/, - // &focusUpdated)); + this.SetFocusedElement(this, FocusState.Programmatic, false); } UpdateVisualState(); } - - } - internal void Open() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Open.", this)); -#endif // MFSI_DEBUG + internal void Open() => m_menuHelper.OpenSubMenu(); - m_menuHelper.OpenSubMenu(); - } - - internal void Close() - { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: Close.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.CloseSubMenu(); - - } + internal void Close() => m_menuHelper.CloseSubMenu(); private protected override void ChangeVisualState(bool bUseTransitions) { bool hasToggleMenuItem = false; bool hasIconMenuItem = false; bool bIsPopupOpened = false; + bool showSubMenuOpenedState = false; MenuFlyoutPresenter spPresenter; var bIsEnabled = IsEnabled; @@ -578,12 +440,18 @@ private protected override void ChangeVisualState(bool bUseTransitions) bIsPopupOpened = m_tpPopup.IsOpen; } + var isDelayCloseTimerRunning = m_menuHelper.IsDelayCloseTimerRunning(); + if (bIsPopupOpened && !isDelayCloseTimerRunning) + { + showSubMenuOpenedState = true; + } + // CommonStates if (!bIsEnabled) { VisualStateManager.GoToState(this, "Disabled", bUseTransitions); } - else if (bIsPopupOpened) + else if (showSubMenuOpenedState) { VisualStateManager.GoToState(this, "SubMenuOpened", bUseTransitions); } @@ -651,22 +519,11 @@ private protected override void ChangeVisualState(bool bUseTransitions) // MenuFlyoutSubItem's presenter size changed event handler that // adjust the sub presenter position to the proper space area // on the available window rect. - void OnPresenterSizeChanged( - object pSender, - SizeChangedEventArgs args) + private void OnPresenterSizeChanged(object pSender, SizeChangedEventArgs args) { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnPresenterSizeChanged.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnPresenterSizeChanged(pSender, args, m_tpPopup as Popup); - -#if false // UNO TODO - if (m_tpMenuPopupThemeTransition == null) + if (m_tpMenuPopupThemeTransition == null && ApiInformation.IsTypePresent("Microsoft.UI.Xaml.Media.Animation.PopupThemeTransition")) { - - MenuFlyoutPresenter parentMenuFlyoutPresenter; - parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + MenuFlyoutPresenter parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); // Get how many sub menus deep we are. We need this number to know what kind of Z // offset to use for displaying elevation. The menus aren't parented in the visual @@ -677,60 +534,35 @@ void OnPresenterSizeChanged( depth = parentMenuFlyoutPresenter.GetDepth() + 1; } - Transition spMenuPopupChildTransition; - (MenuFlyout.PreparePopupThemeTransitionsAndShadows((Popup*)(m_tpPopup), 0.67 /* closedRatioConstant */, depth, &spMenuPopupChildTransition)); - (spMenuPopupChildTransition as MenuPopupThemeTransition.Direction = xaml_primitives.AnimationDirection_Top); + Transition spMenuPopupChildTransition = MenuFlyout.PreparePopupThemeTransitionsAndShadows(m_tpPopup, 0.67 /* closedRatioConstant */, depth); + ((MenuPopupThemeTransition)spMenuPopupChildTransition).Direction = AnimationDirection.Top; m_tpMenuPopupThemeTransition = spMenuPopupChildTransition; } + m_menuHelper.OnPresenterSizeChanged(pSender, args, m_tpPopup as Popup); + // Update the OpenedLength property of the ThemeTransition. double openedLength = (m_tpPresenter as Control).ActualHeight; (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; -#endif } -#if false - void ClearStateFlags() + private void ClearStateFlags() { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: ClearStateFlags.", this)); -#endif // MFSI_DEBUG - m_menuHelper.ClearStateFlags(); - } - void OnIsEnabledChanged(/*IsEnabledChangedEventArgs* args*/) + private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs args) { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnIsEnabledChanged.", this)); -#endif // MFSI_DEBUG - - m_menuHelper.OnIsEnabledChanged(); + m_menuHelper.OnIsEnabledChanged(args); } - void OnVisibilityChanged() + protected override void OnVisibilityChanged(Visibility oldValue, Visibility newValue) { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OnVisibilityChanged.", this)); -#endif // MFSI_DEBUG - m_menuHelper.OnVisibilityChanged(); - } -#endif - protected override AutomationPeer OnCreateAutomationPeer() - { - //*ppAutomationPeer = null; - //MenuFlyoutSubItemAutomationPeer spAutomationPeer; - //(ActivationAPI.ActivateAutomationInstance(KnownTypeIndex.MenuFlyoutSubItemAutomationPeer, GetHandle(), spAutomationPeer.GetAddressOf())); - //(spAutomationPeer.Owner = this); - //*ppAutomationPeer = spAutomationPeer.Detach(); - - return null; - } + protected override AutomationPeer OnCreateAutomationPeer() => new MenuFlyoutSubItemAutomationPeer(this); private protected override string GetPlainText() => Text; @@ -739,7 +571,16 @@ protected override AutomationPeer OnCreateAutomationPeer() ISubMenuOwner ISubMenuOwner.ParentOwner { get => m_wrParentOwner?.Target as ISubMenuOwner; - set => m_wrParentOwner = new WeakReference(value); + set => m_wrParentOwner = WeakReferencePool.RentWeakReference(this, value); + } + + private void SetSubMenuDirection(bool isSubMenuDirectionUp) + { + if (m_tpMenuPopupThemeTransition is not null) + { + ((MenuPopupThemeTransition)m_tpMenuPopupThemeTransition).Direction = isSubMenuDirectionUp ? + AnimationDirection.Bottom : AnimationDirection.Top; + } } void ISubMenuOwner.PrepareSubMenu() @@ -756,13 +597,10 @@ void ISubMenuOwner.PrepareSubMenu() void ISubMenuOwner.OpenSubMenu(Point position) { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: OpenSubMenu.", this)); -#endif // MFSI_DEBUG - EnsurePopupAndPresenter(); EnsureCloseExistingSubItems(); + MenuFlyout parentMenuFlyout = null; MenuFlyoutPresenter parentMenuFlyoutPresenter; parentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); @@ -772,7 +610,6 @@ void ISubMenuOwner.OpenSubMenu(Point position) owningMenu = (parentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; (m_tpPresenter as IMenuPresenter).OwningMenu = owningMenu; - MenuFlyout parentMenuFlyout; parentMenuFlyout = parentMenuFlyoutPresenter.GetParentMenuFlyout(); if (parentMenuFlyout != null) @@ -793,26 +630,29 @@ void ISubMenuOwner.OpenSubMenu(Point position) m_tpPopup.VerticalOffset = position.Y; SetIsOpen(true); - + if (parentMenuFlyout is not null) + { + // Note: This is the call that propagates the SystemBackdrop object set on either this FlyoutBase or its + // MenuFlyoutPresenter to the Popup. A convenient place to do this is in ForwardTargetPropertiesToPresenter, but we + // see cases where the MenuFlyoutPresenter's SystemBackdrop property is null until it enters the tree via the + // Popup::Open call above. So trying to propagate it before opening the popup actually finds no SystemBackdrop, and + // the popup is left with a transparent background. Do the propagation after the popup opens instead. Windowed + // popups support having a backdrop set after the popup is open. + ForwardSystemBackdropToPopup(parentMenuFlyout); + } } void ISubMenuOwner.PositionSubMenu(Point position) { -#if MFSI_DEBUG - IGNOREHR(DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "MFSI[0x%p]: PositionSubMenu - (%f, %f).", this, position.X, position.Y)); -#endif // MFSI_DEBUG - - if (position.X != float.NegativeInfinity) + if (position.X != double.NegativeInfinity) { m_tpPopup.HorizontalOffset = position.X; } - if (position.Y != float.NegativeInfinity) + if (position.Y != double.NegativeInfinity) { m_tpPopup.VerticalOffset = position.Y; } - - } void ISubMenuOwner.ClosePeerSubMenus() @@ -865,18 +705,50 @@ void ISubMenuOwner.CancelCloseSubMenu() void ISubMenuOwner.RaiseAutomationPeerExpandCollapse(bool isOpen) { - // UNO TODO - //AutomationPeer spAutomationPeer; - //bool isListener = false; + var isListener = AutomationPeer.ListenerExistsHelper(AutomationEvents.PropertyChanged); + if (isListener) + { + var spAutomationPeer = GetOrCreateAutomationPeer(); + if (spAutomationPeer is not null) + { + (spAutomationPeer as MenuFlyoutSubItemAutomationPeer).RaiseExpandCollapseAutomationEvent(isOpen); + } + } + } - //AutomationPeer.ListenerExistsHelper(xaml_automation_peers.AutomationEvents_PropertyChanged, &isListener); - //if (isListener) - //{ - // (GetOrCreateAutomationPeer(&spAutomationPeer)); - // if (spAutomationPeer) - // { - // (spAutomationPeer as MenuFlyoutSubItemAutomationPeer.RaiseExpandCollapseAutomationEvent(isOpen)); - // } - //} + internal void QueueRefreshItemsSource() + { + // The items source might change multiple times in a single tick, so we'll coalesce the refresh + // into a single event once all of the changes have completed. + if (m_tpPresenter is not null && !_itemsSourceRefreshPending) + { + var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + + var wrThis = WeakReferencePool.RentSelfWeakReference(this); + + dispatcherQueue.TryEnqueue( + () => + { + var thisMenuFlyoutSubItem = wrThis.Target as MenuFlyoutSubItem; + + if (thisMenuFlyoutSubItem is not null) + { + thisMenuFlyoutSubItem.RefreshItemsSource(); + } + }); + + _itemsSourceRefreshPending = true; + } + } + + private void RefreshItemsSource() + { + _itemsSourceRefreshPending = false; + + global::System.Diagnostics.Debug.Assert(m_tpPresenter is not null); + + // Setting the items source to null and then back to Items causes the presenter to pick up any changes. + ((ItemsControl)m_tpPresenter).ItemsSource = null; + ((ItemsControl)m_tpPresenter).ItemsSource = m_tpItems; } } From 4551ec5910fbf3b46fa26b64d51fecba7554ec26 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Wed, 19 Jun 2024 17:51:56 +0200 Subject: [PATCH 08/28] feat: MenuFlyoutSubItemAutomationPeer --- .../Peers/MenuFlyoutSubItemAutomationPeer.cs | 77 +++++++++++++++++++ .../UI/Xaml/Controls/MenuFlyout/IMenu.cs | 17 ++-- .../Controls/MenuFlyout/IMenuPresenter.cs | 37 +++++---- .../Xaml/Controls/MenuFlyout/ISubMenuOwner.cs | 49 ++++++------ 4 files changed, 127 insertions(+), 53 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutSubItemAutomationPeer.cs diff --git a/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutSubItemAutomationPeer.cs b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutSubItemAutomationPeer.cs new file mode 100644 index 000000000000..364578441263 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutSubItemAutomationPeer.cs @@ -0,0 +1,77 @@ +using Microsoft.UI.Xaml.Automation.Provider; +using Microsoft.UI.Xaml.Controls; + +namespace Microsoft.UI.Xaml.Automation.Peers; + +internal class MenuFlyoutSubItemAutomationPeer : FrameworkElementAutomationPeer, IExpandCollapseProvider +{ + public MenuFlyoutSubItemAutomationPeer(FrameworkElement owner) : base(owner) + { + } + + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (patternInterface == PatternInterface.ExpandCollapse) + { + return this; + } + + return base.GetPatternCore(patternInterface); + } + + protected override string GetClassNameCore() => nameof(MenuFlyoutSubItem); + + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.MenuItem; + + protected override int GetPositionInSetCore() + { + // First retrieve any valid value being directly set on the container, that value will get precedence. + var returnValue = base.GetPositionInSetCore(); + + // if it still is default value, calculate it ourselves. + if (returnValue == -1) + { + returnValue = MenuFlyoutPresenter.GetPositionInSetHelper((MenuFlyoutItemBase)Owner); + } + + return returnValue; + } + + protected override int GetSizeOfSetCore() + { + // First retrieve any valid value being directly set on the container, that value will get precedence. + var returnValue = base.GetSizeOfSetCore(); + + // if it still is default value, calculate it ourselves. + if (returnValue == -1) + { + returnValue = MenuFlyoutPresenter.GetSizeOfSetHelper((MenuFlyoutItemBase)Owner); + } + + return returnValue; + } + + void IExpandCollapseProvider.Expand() => ((MenuFlyoutSubItem)Owner).Open(); + + void IExpandCollapseProvider.Collapse() => ((MenuFlyoutSubItem)Owner).Close(); + + ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState => ((MenuFlyoutSubItem)Owner).IsOpen ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed; + + internal void RaiseExpandCollapseAutomationEvent(bool isOpen) + { + ExpandCollapseState oldValue; + ExpandCollapseState newValue; + if (isOpen) + { + oldValue = ExpandCollapseState.Collapsed; + newValue = ExpandCollapseState.Expanded; + } + else + { + oldValue = ExpandCollapseState.Expanded; + newValue = ExpandCollapseState.Collapsed; + } + + RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, oldValue, newValue); + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/IMenu.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/IMenu.cs index a5b2e55cc9f3..e060c34a3564 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/IMenu.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/IMenu.cs @@ -1,14 +1,13 @@ -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial interface IMenu { - public partial interface IMenu - { - void Close(); + void Close(); - IMenu ParentMenu - { - get; - set; - } + IMenu ParentMenu + { + get; + set; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/IMenuPresenter.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/IMenuPresenter.cs index 4016fd7323ac..d6638684ccd8 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/IMenuPresenter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/IMenuPresenter.cs @@ -1,26 +1,25 @@ -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial interface IMenuPresenter { - public partial interface IMenuPresenter - { - void CloseSubMenu(); + void CloseSubMenu(); - IMenu OwningMenu - { - get; - set; - } + IMenu OwningMenu + { + get; + set; + } - ISubMenuOwner Owner - { - get; - set; - } + ISubMenuOwner Owner + { + get; + set; + } - IMenuPresenter SubPresenter - { - get; - set; - } + IMenuPresenter SubPresenter + { + get; + set; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ISubMenuOwner.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ISubMenuOwner.cs index 2e5dc1c45974..4ad590d3e062 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ISubMenuOwner.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ISubMenuOwner.cs @@ -1,42 +1,41 @@ using Windows.Foundation; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial interface ISubMenuOwner { - public partial interface ISubMenuOwner - { - void PrepareSubMenu(); + void PrepareSubMenu(); - void OpenSubMenu(Point position); + void OpenSubMenu(Point position); - void PositionSubMenu(Point position); + void PositionSubMenu(Point position); - void ClosePeerSubMenus(); + void ClosePeerSubMenus(); - void CloseSubMenu(); + void CloseSubMenu(); - void CloseSubMenuTree(); + void CloseSubMenuTree(); - void DelayCloseSubMenu(); + void DelayCloseSubMenu(); - void CancelCloseSubMenu(); + void CancelCloseSubMenu(); - void RaiseAutomationPeerExpandCollapse(bool isOpen); + void RaiseAutomationPeerExpandCollapse(bool isOpen); - bool IsSubMenuOpen - { - get; - } + bool IsSubMenuOpen + { + get; + } - bool IsSubMenuPositionedAbsolutely - { - get; - } + bool IsSubMenuPositionedAbsolutely + { + get; + } - ISubMenuOwner ParentOwner - { - get; - set; - } + ISubMenuOwner ParentOwner + { + get; + set; } } From 130e42390840ed2be9a7c9c38b5cd042fed9ea4f Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Wed, 19 Jun 2024 18:26:21 +0200 Subject: [PATCH 09/28] chore: In progress adjustments of MenuFlyout --- .../Peers/MenuFlyoutItemAutomationPeer.cs | 28 + .../MenuFlyoutPresenterAutomationPeer.cs | 20 + .../MenuFlyout/CascadingMenuHelper.cs | 1229 ++++++------ .../Controls/MenuFlyout/CommandingHelpers.cs | 309 ++- .../Controls/MenuFlyout/MenuFlyout.Android.cs | 127 +- .../Controls/MenuFlyout/MenuFlyout.iOS.cs | 63 +- .../MenuFlyout/MenuFlyout.iOS7.iOS.cs | 105 +- .../MenuFlyout/MenuFlyout.iOS8.iOS.cs | 165 +- .../Controls/MenuFlyout/MenuFlyoutItem.cs | 1655 ++++++++-------- .../MenuFlyout/MenuFlyoutItem.h.mux.cs | 1656 ++++++++--------- .../Controls/MenuFlyout/MenuFlyoutItem.mux.cs | 1656 ++++++++--------- .../MenuFlyout/MenuFlyoutPresenter.cs | 1395 +++++++------- .../MenuFlyout/MenuPopupThemeTransition.cs | 2 - 13 files changed, 4225 insertions(+), 4185 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutItemAutomationPeer.cs create mode 100644 src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutPresenterAutomationPeer.cs diff --git a/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutItemAutomationPeer.cs b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutItemAutomationPeer.cs new file mode 100644 index 000000000000..3911cc4e3bd4 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutItemAutomationPeer.cs @@ -0,0 +1,28 @@ +// +#pragma warning disable 108 // new keyword hiding +#pragma warning disable 114 // new keyword hiding +namespace Microsoft.UI.Xaml.Automation.Peers +{ +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented] +#endif + public partial class MenuFlyoutItemAutomationPeer : global::Microsoft.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer, global::Microsoft.UI.Xaml.Automation.Provider.IInvokeProvider + { +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] + public MenuFlyoutItemAutomationPeer(global::Microsoft.UI.Xaml.Controls.MenuFlyoutItem owner) : base(owner) + { + global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutItemAutomationPeer", "MenuFlyoutItemAutomationPeer.MenuFlyoutItemAutomationPeer(MenuFlyoutItem owner)"); + } +#endif + // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutItemAutomationPeer.MenuFlyoutItemAutomationPeer(Microsoft.UI.Xaml.Controls.MenuFlyoutItem) +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] + public void Invoke() + { + global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutItemAutomationPeer", "void MenuFlyoutItemAutomationPeer.Invoke()"); + } +#endif + // Processing: Microsoft.UI.Xaml.Automation.Provider.IInvokeProvider + } +} diff --git a/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutPresenterAutomationPeer.cs b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutPresenterAutomationPeer.cs new file mode 100644 index 000000000000..62ac9f7716a1 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutPresenterAutomationPeer.cs @@ -0,0 +1,20 @@ +// +#pragma warning disable 108 // new keyword hiding +#pragma warning disable 114 // new keyword hiding +namespace Microsoft.UI.Xaml.Automation.Peers +{ +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented] +#endif + public partial class MenuFlyoutPresenterAutomationPeer : global::Microsoft.UI.Xaml.Automation.Peers.ItemsControlAutomationPeer + { +#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] + public MenuFlyoutPresenterAutomationPeer(global::Microsoft.UI.Xaml.Controls.MenuFlyoutPresenter owner) : base(owner) + { + global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutPresenterAutomationPeer", "MenuFlyoutPresenterAutomationPeer.MenuFlyoutPresenterAutomationPeer(MenuFlyoutPresenter owner)"); + } +#endif + // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutPresenterAutomationPeer.MenuFlyoutPresenterAutomationPeer(Microsoft.UI.Xaml.Controls.MenuFlyoutPresenter) + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs index f5673a7fd02d..4655428ccf8a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs @@ -19,266 +19,279 @@ using Windows.UI.Input; #endif -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +internal class CascadingMenuHelper { - internal class CascadingMenuHelper - { - // The overlapped menu pixels between the main main menu presenter and the sub presenter - const int m_subMenuOverlapPixels = 4; + // The overlapped menu pixels between the main main menu presenter and the sub presenter + const int m_subMenuOverlapPixels = 4; - int m_subMenuShowDelay; + int m_subMenuShowDelay; - // Owner of the cascading menu - WeakReference m_wpOwner; + // Owner of the cascading menu + WeakReference m_wpOwner; - // Presenter of the sub-menu - WeakReference m_wpSubMenuPresenter; + // Presenter of the sub-menu + WeakReference m_wpSubMenuPresenter; - // Event pointer for the Loaded event - IDisposable m_loadedHandler; + // Event pointer for the Loaded event + IDisposable m_loadedHandler; - // Dispatcher timer to delay showing the sub menu flyout - DispatcherTimer m_delayOpenMenuTimer; + // Dispatcher timer to delay showing the sub menu flyout + DispatcherTimer m_delayOpenMenuTimer; - // Dispatcher timer to delay hiding the sub menu flyout - DispatcherTimer m_delayCloseMenuTimer; + // Dispatcher timer to delay hiding the sub menu flyout + DispatcherTimer m_delayCloseMenuTimer; - // Indicate the pointer is over the cascading menu owner - bool m_isPointerOver = true; + // Indicate the pointer is over the cascading menu owner + bool m_isPointerOver = true; - // Indicate the pointer is pressed on the cascading menu owner - bool m_isPressed = true; + // Indicate the pointer is pressed on the cascading menu owner + bool m_isPressed = true; - internal bool IsPressed => m_isPressed; + internal bool IsPressed => m_isPressed; - internal bool IsPointerOver => m_isPointerOver; + internal bool IsPointerOver => m_isPointerOver; - // This fallback is used if we fail to retrieve a value from the MenuShowDelay RegKey - const int DefaultMenuShowDelay = 400; // in milliseconds + // This fallback is used if we fail to retrieve a value from the MenuShowDelay RegKey + const int DefaultMenuShowDelay = 400; // in milliseconds - // Uno-specific workaround (see comment below) - private Point? _lastTargetPoint; + // Uno-specific workaround (see comment below) + private Point? _lastTargetPoint; - public CascadingMenuHelper() - { - m_isPointerOver = false; - m_isPressed = false; - m_subMenuShowDelay = -1; + public CascadingMenuHelper() + { + m_isPointerOver = false; + m_isPressed = false; + m_subMenuShowDelay = -1; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ", this)); #endif // CMH_DEBUG - } + } - ~CascadingMenuHelper() - { + ~CascadingMenuHelper() + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~", this)); #endif // CMH_DEBUG - var delayOpenMenuTimer = m_delayOpenMenuTimer; - if (delayOpenMenuTimer != null) - { + var delayOpenMenuTimer = m_delayOpenMenuTimer; + if (delayOpenMenuTimer != null) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~CascadingMenuHelper - Stopping m_delayOpenMenuTimer.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~CascadingMenuHelper - Stopping m_delayOpenMenuTimer.", this)); #endif // CMH_DEBUG - delayOpenMenuTimer.Stop(); - } + delayOpenMenuTimer.Stop(); + } - var delayCloseMenuTimer = m_delayCloseMenuTimer; - if (delayCloseMenuTimer != null) - { + var delayCloseMenuTimer = m_delayCloseMenuTimer; + if (delayCloseMenuTimer != null) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~CascadingMenuHelper - Stopping m_delayCloseMenuTimer.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~CascadingMenuHelper - Stopping m_delayCloseMenuTimer.", this)); #endif // CMH_DEBUG - delayCloseMenuTimer.Stop(); - } + delayCloseMenuTimer.Stop(); } + } - internal void Initialize(FrameworkElement owner) - { + internal void Initialize(FrameworkElement owner) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: Initialize.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: Initialize.", this)); #endif // CMH_DEBUG - FrameworkElement ownerLocal = owner; - m_wpOwner = new WeakReference(ownerLocal); + FrameworkElement ownerLocal = owner; + m_wpOwner = new WeakReference(ownerLocal); - void OnLoadedClearFlags(object pSender, RoutedEventArgs args) - { - ClearStateFlags(); - } + void OnLoadedClearFlags(object pSender, RoutedEventArgs args) + { + ClearStateFlags(); + } - owner.Loaded += OnLoadedClearFlags; - m_loadedHandler = Disposable.Create(() => owner.Loaded -= OnLoadedClearFlags); + owner.Loaded += OnLoadedClearFlags; + m_loadedHandler = Disposable.Create(() => owner.Loaded -= OnLoadedClearFlags); #if false - // Try and read from Reg Key - HKEY key = null; + // Try and read from Reg Key + HKEY key = null; - if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, "Control Panel\\Desktop\\", 0, KEY_QUERY_VALUE, &key)) - { - char Buffer[32] = { 0 }; - DWORD BufferSize = sizeof(Buffer); - DWORD dwOutType; + if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, "Control Panel\\Desktop\\", 0, KEY_QUERY_VALUE, &key)) + { + char Buffer[32] = { 0 }; + DWORD BufferSize = sizeof(Buffer); + DWORD dwOutType; - if (RegQueryValueEx(key, "MenuShowDelay", NULL, &dwOutType/*REG_SZ*/, (LPBYTE)Buffer, &BufferSize) == ERROR_SUCCESS) - { - m_subMenuShowDelay = _wtoi(Buffer); - } - RegCloseKey(key); + if (RegQueryValueEx(key, "MenuShowDelay", NULL, &dwOutType/*REG_SZ*/, (LPBYTE)Buffer, &BufferSize) == ERROR_SUCCESS) + { + m_subMenuShowDelay = _wtoi(Buffer); } + RegCloseKey(key); + } #endif - // If the field wasn't successfully populated from the reg key - // Or if the reg key contained a negative number - // Then use a default value - if (m_subMenuShowDelay < 0) - { - m_subMenuShowDelay = DefaultMenuShowDelay; - } + // If the field wasn't successfully populated from the reg key + // Or if the reg key contained a negative number + // Then use a default value + if (m_subMenuShowDelay < 0) + { + m_subMenuShowDelay = DefaultMenuShowDelay; + } #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: Initialize - m_subMenuShowDelay=%d.", this, m_subMenuShowDelay)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: Initialize - m_subMenuShowDelay=%d.", this, m_subMenuShowDelay)); #endif // CMH_DEBUG - // Cascading menu owners should be access key scopes by default. - ownerLocal.IsAccessKeyScope = true; - } + // Cascading menu owners should be access key scopes by default. + ownerLocal.IsAccessKeyScope = true; + } - internal void OnApplyTemplate() - { + internal void OnApplyTemplate() + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnApplyTemplate.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnApplyTemplate.", this)); #endif // CMH_DEBUG - UpdateOwnerVisualState(); + UpdateOwnerVisualState(); - } + } - // PointerEntered event handler that shows the sub menu - // whenever the pointer is over the sub menu owner. - // In case of touch, the sub menu item will be shown by - // PointerReleased event. - internal void OnPointerEntered(PointerRoutedEventArgs args) - { - bool handled = false; + // PointerEntered event handler that shows the sub menu + // whenever the pointer is over the sub menu owner. + // In case of touch, the sub menu item will be shown by + // PointerReleased event. + internal void OnPointerEntered(PointerRoutedEventArgs args) + { + bool handled = false; - m_isPointerOver = true; + m_isPointerOver = true; - handled = args.Handled; + handled = args.Handled; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - handled=%d.", this, handled)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - handled=%d.", this, handled)); #endif // CMH_DEBUG - if (!handled) - { - var owner = m_wpOwner?.Target as ISubMenuOwner; + if (!handled) + { + var owner = m_wpOwner?.Target as ISubMenuOwner; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - owner=0x%p.", this, owner)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - owner=0x%p.", this, owner)); #endif // CMH_DEBUG - if (owner != null) - { - ISubMenuOwner parentOwner; - parentOwner = owner.ParentOwner; + if (owner != null) + { + ISubMenuOwner parentOwner; + parentOwner = owner.ParentOwner; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - parentOwner=0x%p.", this, parentOwner)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - parentOwner=0x%p.", this, parentOwner)); #endif // CMH_DEBUG - if (parentOwner != null) - { - parentOwner.CancelCloseSubMenu(); - } + if (parentOwner != null) + { + parentOwner.CancelCloseSubMenu(); } + } - Pointer pointer = args.Pointer; - var pointerDeviceType = pointer.PointerDeviceType; + Pointer pointer = args.Pointer; + var pointerDeviceType = pointer.PointerDeviceType; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - pointerDeviceType=%d.", this, pointerDeviceType)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - pointerDeviceType=%d.", this, pointerDeviceType)); #endif // CMH_DEBUG - if (pointerDeviceType != PointerDeviceType.Touch) - { - CancelCloseSubMenu(); + if (pointerDeviceType != PointerDeviceType.Touch) + { + CancelCloseSubMenu(); - EnsureDelayOpenMenuTimer(); + EnsureDelayOpenMenuTimer(); #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - Starting m_delayOpenMenuTimer.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - Starting m_delayOpenMenuTimer.", this)); #endif // CMH_DEBUG - m_delayOpenMenuTimer.Start(); - - UpdateOwnerVisualState(); - } + m_delayOpenMenuTimer.Start(); - args.Handled = true; + UpdateOwnerVisualState(); } - + args.Handled = true; } - // PointerExited event handler that ensures we close the sub menu - // whenever the pointer leaves the current sub menu or - // the main presenter. If the exited point is on the sub menu owner - // or the sub menu, we want to keep the sub menu open. - internal void OnPointerExited( - PointerRoutedEventArgs args, - bool parentIsSubMenu) - { - bool handled = false; - m_isPointerOver = false; - m_isPressed = false; + } - handled = args.Handled; + // PointerExited event handler that ensures we close the sub menu + // whenever the pointer leaves the current sub menu or + // the main presenter. If the exited point is on the sub menu owner + // or the sub menu, we want to keep the sub menu open. + internal void OnPointerExited( + PointerRoutedEventArgs args, + bool parentIsSubMenu) + { + bool handled = false; + + m_isPointerOver = false; + m_isPressed = false; + + handled = args.Handled; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - handled=%d, parentIsSubMenu=%d.", this, handled, parentIsSubMenu)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - handled=%d, parentIsSubMenu=%d.", this, handled, parentIsSubMenu)); #endif // CMH_DEBUG - if (m_delayOpenMenuTimer != null) - { + if (m_delayOpenMenuTimer != null) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - Stopping m_delayOpenMenuTimer.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - Stopping m_delayOpenMenuTimer.", this)); #endif // CMH_DEBUG - m_delayOpenMenuTimer.Stop(); - } + m_delayOpenMenuTimer.Stop(); + } - var ownerAsUIE = m_wpOwner?.Target as FrameworkElement; + var ownerAsUIE = m_wpOwner?.Target as FrameworkElement; - if (!handled && ownerAsUIE != null && ownerAsUIE.IsLoaded) - { - Pointer pointer; + if (!handled && ownerAsUIE != null && ownerAsUIE.IsLoaded) + { + Pointer pointer; - pointer = args.Pointer; - var pointerDeviceType = pointer.PointerDeviceType; + pointer = args.Pointer; + var pointerDeviceType = pointer.PointerDeviceType; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - pointerDeviceType=%d.", this, pointerDeviceType)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - pointerDeviceType=%d.", this, pointerDeviceType)); #endif // CMH_DEBUG - if (PointerDeviceType.Mouse == pointerDeviceType && !parentIsSubMenu) + if (PointerDeviceType.Mouse == pointerDeviceType && !parentIsSubMenu) + { + UIElement subMenuPresenterAsUIE = m_wpSubMenuPresenter?.Target as UIElement; + + if (subMenuPresenterAsUIE != null) { - UIElement subMenuPresenterAsUIE = m_wpSubMenuPresenter?.Target as UIElement; + bool isOwnerOrSubMenuHit = false; + var pointerPoint = args.GetCurrentPoint(null); + var point = pointerPoint.Position; + + var elements = VisualTreeHelper.FindElementsInHostCoordinates(point, ownerAsUIE, true /* includeAllElements */); - if (subMenuPresenterAsUIE != null) + foreach (var element in elements) { - bool isOwnerOrSubMenuHit = false; - var pointerPoint = args.GetCurrentPoint(null); - var point = pointerPoint.Position; + if (ownerAsUIE == element || subMenuPresenterAsUIE == element) + { + isOwnerOrSubMenuHit = true; + break; + } + } - var elements = VisualTreeHelper.FindElementsInHostCoordinates(point, ownerAsUIE, true /* includeAllElements */); + if (!isOwnerOrSubMenuHit) + { + elements = VisualTreeHelper.FindElementsInHostCoordinates(point, subMenuPresenterAsUIE, true /* includeAllElements */); foreach (var element in elements) { @@ -288,682 +301,668 @@ internal void OnPointerExited( break; } } + } - if (!isOwnerOrSubMenuHit) - { - elements = VisualTreeHelper.FindElementsInHostCoordinates(point, subMenuPresenterAsUIE, true /* includeAllElements */); - - foreach (var element in elements) - { - if (ownerAsUIE == element || subMenuPresenterAsUIE == element) - { - isOwnerOrSubMenuHit = true; - break; - } - } - } - - // To close the sub menu, the pointer must be outside of the opened chain of sub-menus. - if (!isOwnerOrSubMenuHit) - { - DelayCloseSubMenu(); - args.Handled = true; - } + // To close the sub menu, the pointer must be outside of the opened chain of sub-menus. + if (!isOwnerOrSubMenuHit) + { + DelayCloseSubMenu(); + args.Handled = true; } } - - UpdateOwnerVisualState(); } + + UpdateOwnerVisualState(); } + } - // PointerPressed event handler ensures that we're in the pressed state. - internal void OnPointerPressed(PointerRoutedEventArgs args) - { + // PointerPressed event handler ensures that we're in the pressed state. + internal void OnPointerPressed(PointerRoutedEventArgs args) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerPressed.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerPressed.", this)); #endif // CMH_DEBUG - m_isPressed = true; + m_isPressed = true; - args.Handled = true; - } + args.Handled = true; + } - // PointerReleased event handler shows the sub menu in the - // case of touch input. - internal void OnPointerReleased(PointerRoutedEventArgs args) - { - Pointer pointer; - PointerDeviceType pointerDeviceType; + // PointerReleased event handler shows the sub menu in the + // case of touch input. + internal void OnPointerReleased(PointerRoutedEventArgs args) + { + Pointer pointer; + PointerDeviceType pointerDeviceType; - m_isPressed = false; + m_isPressed = false; - pointer = args.Pointer; - pointerDeviceType = pointer.PointerDeviceType; + pointer = args.Pointer; + pointerDeviceType = pointer.PointerDeviceType; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerReleased - pointerDeviceType=%d.", this, pointerDeviceType)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerReleased - pointerDeviceType=%d.", this, pointerDeviceType)); #endif // CMH_DEBUG - // Show the sub menu in the case of touch input. - // In case of the mouse device or pen, the sub menu will be shown whenever the pointer is over - // the sub menu owner. - if (PointerDeviceType.Touch == pointerDeviceType) - { - OpenSubMenu(); - } - - args.Handled = true; + // Show the sub menu in the case of touch input. + // In case of the mouse device or pen, the sub menu will be shown whenever the pointer is over + // the sub menu owner. + if (PointerDeviceType.Touch == pointerDeviceType) + { + OpenSubMenu(); } - internal void OnGotFocus(RoutedEventArgs args) - { + args.Handled = true; + } + + internal void OnGotFocus(RoutedEventArgs args) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnGotFocus.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnGotFocus.", this)); #endif // CMH_DEBUG - UpdateOwnerVisualState(); - } + UpdateOwnerVisualState(); + } - internal void OnLostFocus(RoutedEventArgs args) - { + internal void OnLostFocus(RoutedEventArgs args) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnLostFocus.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnLostFocus.", this)); #endif // CMH_DEBUG - m_isPressed = false; - UpdateOwnerVisualState(); - } + m_isPressed = false; + UpdateOwnerVisualState(); + } - // KeyDown event handler that handles the keyboard navigation between - // the menu items and shows the sub menu in the case where we hit - // the enter, space, or right arrow keys. - internal void OnKeyDown(KeyRoutedEventArgs args) - { - bool handled = false; + // KeyDown event handler that handles the keyboard navigation between + // the menu items and shows the sub menu in the case where we hit + // the enter, space, or right arrow keys. + internal void OnKeyDown(KeyRoutedEventArgs args) + { + bool handled = false; - handled = args.Handled; + handled = args.Handled; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnKeyDown - handled=%d.", this, handled)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnKeyDown - handled=%d.", this, handled)); #endif // CMH_DEBUG - if (!handled) - { - var key = args.Key; + if (!handled) + { + var key = args.Key; - // Show the sub menu with the enter, space, or right arrow keys - if (key == VirtualKey.Enter || - key == VirtualKey.Right || - key == VirtualKey.Space) - { - OpenSubMenu(); - args.Handled = true; - } + // Show the sub menu with the enter, space, or right arrow keys + if (key == VirtualKey.Enter || + key == VirtualKey.Right || + key == VirtualKey.Space) + { + OpenSubMenu(); + args.Handled = true; } } + } - internal void OnKeyUp(KeyRoutedEventArgs args) - { + internal void OnKeyUp(KeyRoutedEventArgs args) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnKeyUp.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnKeyUp.", this)); #endif // CMH_DEBUG - UpdateOwnerVisualState(); - args.Handled = true; - } + UpdateOwnerVisualState(); + args.Handled = true; + } - // Creates a DispatcherTimer for delaying showing the sub menu flyout - void EnsureDelayOpenMenuTimer() + // Creates a DispatcherTimer for delaying showing the sub menu flyout + void EnsureDelayOpenMenuTimer() + { + if (m_delayOpenMenuTimer == null) { - if (m_delayOpenMenuTimer == null) - { - m_delayOpenMenuTimer = new DispatcherTimer(); - m_delayOpenMenuTimer.Tick += (s, e) => DelayOpenMenuTimerTickHandler(); + m_delayOpenMenuTimer = new DispatcherTimer(); + m_delayOpenMenuTimer.Tick += (s, e) => DelayOpenMenuTimerTickHandler(); - TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(m_subMenuShowDelay); - m_delayOpenMenuTimer.Interval = delayTimeSpan; - } + TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(m_subMenuShowDelay); + m_delayOpenMenuTimer.Interval = delayTimeSpan; } + } - // Handler for the Tick event on the delay open menu timer. - void DelayOpenMenuTimerTickHandler() - { + // Handler for the Tick event on the delay open menu timer. + void DelayOpenMenuTimerTickHandler() + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayOpenMenuTimerTickHandler.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayOpenMenuTimerTickHandler.", this)); #endif // CMH_DEBUG - EnsureCloseExistingSubItems(); + EnsureCloseExistingSubItems(); - // Open the current sub menu - OpenSubMenu(); + // Open the current sub menu + OpenSubMenu(); - if (m_delayOpenMenuTimer != null) - { + if (m_delayOpenMenuTimer != null) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayOpenMenuTimerTickHandler - Stopping m_delayOpenMenuTimer.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayOpenMenuTimerTickHandler - Stopping m_delayOpenMenuTimer.", this)); #endif // CMH_DEBUG - m_delayOpenMenuTimer.Stop(); - } + m_delayOpenMenuTimer.Stop(); } + } - // Creates a DispatcherTimer for delaying hiding the sub menu flyout - void EnsureDelayCloseMenuTimer() + // Creates a DispatcherTimer for delaying hiding the sub menu flyout + void EnsureDelayCloseMenuTimer() + { + if (m_delayCloseMenuTimer == null) { - if (m_delayCloseMenuTimer == null) - { - m_delayCloseMenuTimer = new DispatcherTimer(); - m_delayCloseMenuTimer.Tick += (s, e) => DelayCloseMenuTimerTickHandler(); + m_delayCloseMenuTimer = new DispatcherTimer(); + m_delayCloseMenuTimer.Tick += (s, e) => DelayCloseMenuTimerTickHandler(); - TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(m_subMenuShowDelay); - m_delayCloseMenuTimer.Interval = delayTimeSpan; - } + TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(m_subMenuShowDelay); + m_delayCloseMenuTimer.Interval = delayTimeSpan; } + } - // Handler for the Tick event on the delay close menu timer. - void DelayCloseMenuTimerTickHandler() - { + // Handler for the Tick event on the delay close menu timer. + void DelayCloseMenuTimerTickHandler() + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseMenuTimerTickHandler.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseMenuTimerTickHandler.", this)); #endif // CMH_DEBUG - CloseSubMenu(); + CloseSubMenu(); - if (m_delayCloseMenuTimer != null) - { + if (m_delayCloseMenuTimer != null) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseMenuTimerTickHandler - Stopping m_delayCloseMenuTimer.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseMenuTimerTickHandler - Stopping m_delayCloseMenuTimer.", this)); #endif // CMH_DEBUG - m_delayCloseMenuTimer.Stop(); - } + m_delayCloseMenuTimer.Stop(); } + } - // Ensure that any currently open sub menus are closed - void EnsureCloseExistingSubItems() - { + // Ensure that any currently open sub menus are closed + void EnsureCloseExistingSubItems() + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: EnsureCloseExistingSubItems.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: EnsureCloseExistingSubItems.", this)); #endif // CMH_DEBUG - ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; + ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; - if (ownerAsSubMenuOwner != null) - { - ownerAsSubMenuOwner.ClosePeerSubMenus(); - } + if (ownerAsSubMenuOwner != null) + { + ownerAsSubMenuOwner.ClosePeerSubMenus(); } + } - internal void SetSubMenuPresenter(FrameworkElement subMenuPresenter) - { + internal void SetSubMenuPresenter(FrameworkElement subMenuPresenter) + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter.", this)); #endif // CMH_DEBUG - m_wpSubMenuPresenter = new WeakReference(subMenuPresenter); + m_wpSubMenuPresenter = new WeakReference(subMenuPresenter); - ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; + ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter - ownerAsSubMenuOwner=0x%p.", this, ownerAsSubMenuOwner)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter - ownerAsSubMenuOwner=0x%p.", this, ownerAsSubMenuOwner)); #endif // CMH_DEBUG - if (ownerAsSubMenuOwner != null) - { - IMenuPresenter menuPresenter = subMenuPresenter as IMenuPresenter; + if (ownerAsSubMenuOwner != null) + { + IMenuPresenter menuPresenter = subMenuPresenter as IMenuPresenter; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter - menuPresenter=0x%p.", this, menuPresenter)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter - menuPresenter=0x%p.", this, menuPresenter)); #endif // CMH_DEBUG - if (menuPresenter != null) - { - menuPresenter.Owner = ownerAsSubMenuOwner; - } + if (menuPresenter != null) + { + menuPresenter.Owner = ownerAsSubMenuOwner; } } + } - // Shows the sub menu at the appropriate position. - // The sub menu will be adjusted if the sub presenter size changes. - internal void OpenSubMenu() - { - ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; + // Shows the sub menu at the appropriate position. + // The sub menu will be adjusted if the sub presenter size changes. + internal void OpenSubMenu() + { + ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OpenSubMenu - ownerAsSubMenuOwner=0x%p.", this, ownerAsSubMenuOwner)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OpenSubMenu - ownerAsSubMenuOwner=0x%p.", this, ownerAsSubMenuOwner)); #endif // CMH_DEBUG - if (ownerAsSubMenuOwner != null) - { - ownerAsSubMenuOwner.PrepareSubMenu(); + if (ownerAsSubMenuOwner != null) + { + ownerAsSubMenuOwner.PrepareSubMenu(); - bool isSubMenuOpen = false; - isSubMenuOpen = ownerAsSubMenuOwner.IsSubMenuOpen; + bool isSubMenuOpen = false; + isSubMenuOpen = ownerAsSubMenuOwner.IsSubMenuOpen; - if (!isSubMenuOpen) - { - Control ownerAsControl = m_wpOwner?.Target as Control; + if (!isSubMenuOpen) + { + Control ownerAsControl = m_wpOwner?.Target as Control; - if (ownerAsControl != null) - { - EnsureCloseExistingSubItems(); + if (ownerAsControl != null) + { + EnsureCloseExistingSubItems(); - double subItemWidth = 0; - subItemWidth = ownerAsControl.ActualWidth; + double subItemWidth = 0; + subItemWidth = ownerAsControl.ActualWidth; - FlowDirection flowDirection = FlowDirection.LeftToRight; - flowDirection = ownerAsControl.FlowDirection; + FlowDirection flowDirection = FlowDirection.LeftToRight; + flowDirection = ownerAsControl.FlowDirection; - Point targetPoint = new Point(0, 0); + Point targetPoint = new Point(0, 0); - bool isPositionedAbsolutely = false; - isPositionedAbsolutely = ownerAsSubMenuOwner.IsSubMenuPositionedAbsolutely; + bool isPositionedAbsolutely = false; + isPositionedAbsolutely = ownerAsSubMenuOwner.IsSubMenuPositionedAbsolutely; - if (isPositionedAbsolutely) - { - GeneralTransform transformToRoot; - transformToRoot = ownerAsControl.TransformToVisual(null); - targetPoint = transformToRoot.TransformPoint(targetPoint); - } + if (isPositionedAbsolutely) + { + GeneralTransform transformToRoot; + transformToRoot = ownerAsControl.TransformToVisual(null); + targetPoint = transformToRoot.TransformPoint(targetPoint); + } - if (flowDirection == FlowDirection.RightToLeft) - { - targetPoint.X += (float)(m_subMenuOverlapPixels - subItemWidth); - } - else - { - targetPoint.X += (float)(subItemWidth - m_subMenuOverlapPixels); - } + if (flowDirection == FlowDirection.RightToLeft) + { + targetPoint.X += (float)(m_subMenuOverlapPixels - subItemWidth); + } + else + { + targetPoint.X += (float)(subItemWidth - m_subMenuOverlapPixels); + } - ownerAsSubMenuOwner.OpenSubMenu(targetPoint); - if (_lastTargetPoint is { } lastTargetPoint) - { - // Uno-specific workaround: reapply the location calculated in OnPresenterSizeChanged(), since that one properly - // adjusts to keep submenu within screen bounds. (WinUI seemingly relies upon presenter.SizeChanged being raised - // every time submenu opens? On Uno it isn't.) - ownerAsSubMenuOwner.PositionSubMenu(lastTargetPoint); - } - ownerAsSubMenuOwner.RaiseAutomationPeerExpandCollapse(true /* isOpen */); - ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, ownerAsControl); + ownerAsSubMenuOwner.OpenSubMenu(targetPoint); + if (_lastTargetPoint is { } lastTargetPoint) + { + // Uno-specific workaround: reapply the location calculated in OnPresenterSizeChanged(), since that one properly + // adjusts to keep submenu within screen bounds. (WinUI seemingly relies upon presenter.SizeChanged being raised + // every time submenu opens? On Uno it isn't.) + ownerAsSubMenuOwner.PositionSubMenu(lastTargetPoint); } + ownerAsSubMenuOwner.RaiseAutomationPeerExpandCollapse(true /* isOpen */); + ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, ownerAsControl); } } } + } - internal void CloseSubMenu() - { + internal void CloseSubMenu() + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseSubMenu.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseSubMenu.", this)); #endif // CMH_DEBUG - CloseChildSubMenus(); + CloseChildSubMenus(); - ISubMenuOwner owner = m_wpOwner?.Target as ISubMenuOwner; + ISubMenuOwner owner = m_wpOwner?.Target as ISubMenuOwner; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseSubMenu - owner=0x%p.", this, owner)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseSubMenu - owner=0x%p.", this, owner)); #endif // CMH_DEBUG - if (owner != null) - { - owner.CloseSubMenu(); - owner.RaiseAutomationPeerExpandCollapse(false /* isOpen */); + if (owner != null) + { + owner.CloseSubMenu(); + owner.RaiseAutomationPeerExpandCollapse(false /* isOpen */); - DependencyObject ownerAsDO = owner as DependencyObject; + DependencyObject ownerAsDO = owner as DependencyObject; - if (ownerAsDO != null) - { - ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Hide, ownerAsDO as DependencyObject); - } + if (ownerAsDO != null) + { + ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Hide, ownerAsDO as DependencyObject); } } + } - internal void CloseChildSubMenus() - { - // WeakRefPtr fails an assert if we attempt to AsOrNull() to a type - // that we aren't sure if the contents of the weak reference - // implement that type. To avoid that assert, we first As() the - // weak reference contents to a known type that it's guaranteed - // to be (if it isn't null), and we then AsOrNull() the ComPtr, - // once it's safe to ask about a type that we're not sure of. - FrameworkElement subMenuPresenterAsFE = m_wpSubMenuPresenter?.Target as Frame; + internal void CloseChildSubMenus() + { + // WeakRefPtr fails an assert if we attempt to AsOrNull() to a type + // that we aren't sure if the contents of the weak reference + // implement that type. To avoid that assert, we first As() the + // weak reference contents to a known type that it's guaranteed + // to be (if it isn't null), and we then AsOrNull() the ComPtr, + // once it's safe to ask about a type that we're not sure of. + FrameworkElement subMenuPresenterAsFE = m_wpSubMenuPresenter?.Target as Frame; - IMenuPresenter subMenuPresenter = null; + IMenuPresenter subMenuPresenter = null; - if (subMenuPresenterAsFE != null) - { - subMenuPresenter = subMenuPresenterAsFE as IMenuPresenter; - } + if (subMenuPresenterAsFE != null) + { + subMenuPresenter = subMenuPresenterAsFE as IMenuPresenter; + } #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseChildSubMenus - subMenuPresenter=0x%p.", this, subMenuPresenter)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseChildSubMenus - subMenuPresenter=0x%p.", this, subMenuPresenter)); #endif // CMH_DEBUG - if (subMenuPresenter != null) - { - subMenuPresenter.CloseSubMenu(); - } + if (subMenuPresenter != null) + { + subMenuPresenter.CloseSubMenu(); } + } - internal void DelayCloseSubMenu() + internal void DelayCloseSubMenu() + { + EnsureDelayCloseMenuTimer(); + if (m_delayCloseMenuTimer != null) { - EnsureDelayCloseMenuTimer(); - if (m_delayCloseMenuTimer != null) - { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseSubMenu - Starting m_delayCloseMenuTimer.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseSubMenu - Starting m_delayCloseMenuTimer.", this)); #endif // CMH_DEBUG - m_delayCloseMenuTimer.Start(); - } + m_delayCloseMenuTimer.Start(); } + } - internal void CancelCloseSubMenu() + internal void CancelCloseSubMenu() + { + if (m_delayCloseMenuTimer != null) { - if (m_delayCloseMenuTimer != null) - { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CancelCloseSubMenu - Stopping m_delayCloseMenuTimer.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CancelCloseSubMenu - Stopping m_delayCloseMenuTimer.", this)); #endif // CMH_DEBUG - m_delayCloseMenuTimer.Stop(); - } + m_delayCloseMenuTimer.Stop(); } + } - internal void ClearStateFlags() - { + internal void ClearStateFlags() + { #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ClearStateFlags.", this)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ClearStateFlags.", this)); #endif // CMH_DEBUG - m_isPressed = false; - m_isPointerOver = false; - UpdateOwnerVisualState(); - } + m_isPressed = false; + m_isPointerOver = false; + UpdateOwnerVisualState(); + } - internal void OnIsEnabledChanged() - { - Control ownerAsControl = m_wpOwner?.Target as Control; + internal void OnIsEnabledChanged() + { + Control ownerAsControl = m_wpOwner?.Target as Control; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnIsEnabledChanged - ownerAsControl=0x%p.", this, ownerAsControl)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnIsEnabledChanged - ownerAsControl=0x%p.", this, ownerAsControl)); #endif // CMH_DEBUG - if (ownerAsControl != null) - { - bool bIsEnabled = false; - bIsEnabled = ownerAsControl.IsEnabled; + if (ownerAsControl != null) + { + bool bIsEnabled = false; + bIsEnabled = ownerAsControl.IsEnabled; - if (!bIsEnabled) - { - ClearStateFlags(); - } - else - { - ownerAsControl.UpdateVisualState(true /* useTransitions */); - } + if (!bIsEnabled) + { + ClearStateFlags(); + } + else + { + ownerAsControl.UpdateVisualState(true /* useTransitions */); } } + } - public void OnVisibilityChanged() - { - UIElement ownerAsUIE = m_wpOwner?.Target as UIElement; + public void OnVisibilityChanged() + { + UIElement ownerAsUIE = m_wpOwner?.Target as UIElement; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnVisibilityChanged - ownerAsUIE=0x%p.", this, ownerAsUIE)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnVisibilityChanged - ownerAsUIE=0x%p.", this, ownerAsUIE)); #endif // CMH_DEBUG - if (ownerAsUIE != null) - { - Visibility visibility = ownerAsUIE.Visibility; + if (ownerAsUIE != null) + { + Visibility visibility = ownerAsUIE.Visibility; - if (Visibility.Visible != visibility) - { - ClearStateFlags(); - } + if (Visibility.Visible != visibility) + { + ClearStateFlags(); } } + } - internal void OnPresenterSizeChanged( - object pSender, - SizeChangedEventArgs args, - Popup popup) - { - Control ownerAsControl = m_wpOwner?.Target as Control; + internal void OnPresenterSizeChanged( + object pSender, + SizeChangedEventArgs args, + Popup popup) + { + Control ownerAsControl = m_wpOwner?.Target as Control; - ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; + ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; - Control presenterAsControl = m_wpSubMenuPresenter?.Target as Control; + Control presenterAsControl = m_wpSubMenuPresenter?.Target as Control; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPresenterSizeChanged - ownerAsControl=0x%p, ownerAsSubMenuOwner=0x%p.", this, ownerAsControl, ownerAsSubMenuOwner)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPresenterSizeChanged - ownerAsControl=0x%p, ownerAsSubMenuOwner=0x%p.", this, ownerAsControl, ownerAsSubMenuOwner)); #endif // CMH_DEBUG - if (ownerAsControl != null && ownerAsSubMenuOwner != null && presenterAsControl != null) - { - Size newPresenterSize = args.NewSize; + if (ownerAsControl != null && ownerAsSubMenuOwner != null && presenterAsControl != null) + { + Size newPresenterSize = args.NewSize; - FlowDirection flowDirection = ownerAsControl.FlowDirection; + FlowDirection flowDirection = ownerAsControl.FlowDirection; - // We sometimes will only want to change one of the two XY-positions of the menu, - // but some menus (e.g. AppBarButton.Flyout) don't allow you to individually change - // one axis of the position - you need to close and reopen the menu in a different location. - // This necessitates a single function call that takes a Point parameter rather than - // two function calls that individually change the X and then the Y position of the menu, - // since otherwise we'd be closing and reopening the menu twice if we needed to change - // both positions, which would be visually disruptive. - // As such, we need a way to tell PositionSubMenu to leave one of the positions as it was. - // We'll use negative infinity as a sentinel value that means "don't change this coordinate value". - Point targetPoint = new Point(float.NegativeInfinity, float.NegativeInfinity); + // We sometimes will only want to change one of the two XY-positions of the menu, + // but some menus (e.g. AppBarButton.Flyout) don't allow you to individually change + // one axis of the position - you need to close and reopen the menu in a different location. + // This necessitates a single function call that takes a Point parameter rather than + // two function calls that individually change the X and then the Y position of the menu, + // since otherwise we'd be closing and reopening the menu twice if we needed to change + // both positions, which would be visually disruptive. + // As such, we need a way to tell PositionSubMenu to leave one of the positions as it was. + // We'll use negative infinity as a sentinel value that means "don't change this coordinate value". + Point targetPoint = new Point(float.NegativeInfinity, float.NegativeInfinity); - bool isPositionedAbsolutely = false; - isPositionedAbsolutely = ownerAsSubMenuOwner.IsSubMenuPositionedAbsolutely; + bool isPositionedAbsolutely = false; + isPositionedAbsolutely = ownerAsSubMenuOwner.IsSubMenuPositionedAbsolutely; - Point positionPoint = new Point(0, 0); + Point positionPoint = new Point(0, 0); - if (isPositionedAbsolutely) - { - // Get the current sub menu item target position as the client point - GeneralTransform transformToRoot; - transformToRoot = ownerAsControl.TransformToVisual(null); - positionPoint = transformToRoot.TransformPoint(positionPoint); - } + if (isPositionedAbsolutely) + { + // Get the current sub menu item target position as the client point + GeneralTransform transformToRoot; + transformToRoot = ownerAsControl.TransformToVisual(null); + positionPoint = transformToRoot.TransformPoint(positionPoint); + } - // Get the current sub menu item width and height - double width = 0; - double height = 0; - width = ownerAsControl.ActualWidth; - height = ownerAsControl.ActualHeight; + // Get the current sub menu item width and height + double width = 0; + double height = 0; + width = ownerAsControl.ActualWidth; + height = ownerAsControl.ActualHeight; - // Get the current presenter max width/height - var maxWidth = presenterAsControl.MaxWidth; - var maxHeight = presenterAsControl.MaxHeight; + // Get the current presenter max width/height + var maxWidth = presenterAsControl.MaxWidth; + var maxHeight = presenterAsControl.MaxHeight; #if false // UNO TODO Windowed menus are not supported - // If the current menu is a windowed popup, the position setting will be within the current nearest - // monitor boundary. Otherwise, the position will ensure within the Xaml window boundary. - if ((CPopup*)(popup.GetHandle()).IsWindowed()) + // If the current menu is a windowed popup, the position setting will be within the current nearest + // monitor boundary. Otherwise, the position will ensure within the Xaml window boundary. + if ((CPopup*)(popup.GetHandle()).IsWindowed()) + { + Rect targetBounds = { + positionPoint.X, + positionPoint.Y, + (FLOAT)(width), + (FLOAT)(height)}; + + // Get the available monitor bounds from the current nearest monitor. + // IHM bounds will be excluded from the monitor bounds. + Rect availableMonitorRect = default; + (DXamlCore.GetCurrent().CalculateAvailableMonitorRect(popup, positionPoint, &availableMonitorRect)); + + // Set the max width and height with the available monitor bounds + (presenterAsControl.put_MaxWidth( + DoubleUtil.IsNaN(maxWidth) ? availableMonitorRect.Width : DoubleUtil.Min(maxWidth, availableMonitorRect.Width))); + (presenterAsControl.put_MaxHeight( + DoubleUtil.IsNaN(maxHeight) ? availableMonitorRect.Height : DoubleUtil.Min(maxHeight, availableMonitorRect.Height))); + + // Get the available bottom space from the current nearest monitor bounds + double bottomSpace = (availableMonitorRect.Y + availableMonitorRect.Height) - (targetBounds.Y); + + if (flowDirection == FlowDirection_LeftToRight) { - Rect targetBounds = { - positionPoint.X, - positionPoint.Y, - (FLOAT)(width), - (FLOAT)(height)}; - - // Get the available monitor bounds from the current nearest monitor. - // IHM bounds will be excluded from the monitor bounds. - Rect availableMonitorRect = default; - (DXamlCore.GetCurrent().CalculateAvailableMonitorRect(popup, positionPoint, &availableMonitorRect)); - - // Set the max width and height with the available monitor bounds - (presenterAsControl.put_MaxWidth( - DoubleUtil.IsNaN(maxWidth) ? availableMonitorRect.Width : DoubleUtil.Min(maxWidth, availableMonitorRect.Width))); - (presenterAsControl.put_MaxHeight( - DoubleUtil.IsNaN(maxHeight) ? availableMonitorRect.Height : DoubleUtil.Min(maxHeight, availableMonitorRect.Height))); - - // Get the available bottom space from the current nearest monitor bounds - double bottomSpace = (availableMonitorRect.Y + availableMonitorRect.Height) - (targetBounds.Y); - - if (flowDirection == FlowDirection_LeftToRight) + // Get the available right space from the current nearest monitor bounds + double rightSpace = (availableMonitorRect.X + availableMonitorRect.Width) - (targetBounds.X + targetBounds.Width); + // If the current sub presenter width isn't enough in the default right space, + // the MenuFlyoutSubItem will be positioned on the left side if the current presenter + // width is less than the sub item left(X) position. Otherwise, it will be aligned to + // the right side of the available monitor rect. + if (newPresenterSize.Width > rightSpace) { - // Get the available right space from the current nearest monitor bounds - double rightSpace = (availableMonitorRect.X + availableMonitorRect.Width) - (targetBounds.X + targetBounds.Width); - // If the current sub presenter width isn't enough in the default right space, - // the MenuFlyoutSubItem will be positioned on the left side if the current presenter - // width is less than the sub item left(X) position. Otherwise, it will be aligned to - // the right side of the available monitor rect. - if (newPresenterSize.Width > rightSpace) + if (newPresenterSize.Width < availableMonitorRect.Width - rightSpace - targetBounds.Width) { - if (newPresenterSize.Width < availableMonitorRect.Width - rightSpace - targetBounds.Width) - { - targetPoint.X = (float)(positionPoint.X - newPresenterSize.Width + m_subMenuOverlapPixels); - } - else - { - targetPoint.X = (float)(positionPoint.X + width + rightSpace - newPresenterSize.Width); - } - } - } - else - { - // Get the available left space from the current nearest monitor bounds - double leftSpace = targetBounds.X - availableMonitorRect.X - targetBounds.Width; - // If the current sub presenter width isn't enough in the default left space, - // the MenuFlyoutSubItem will be positioned on the right side if the current presenter - // width is less than the sub item right(X) position. Otherwise, it will be aligned to - // the left side of the available monitor rect. - if (newPresenterSize.Width > leftSpace) - { - if (newPresenterSize.Width < (availableMonitorRect.Width - leftSpace - targetBounds.Width)) - { - targetPoint.X = (float)(positionPoint.X + newPresenterSize.Width - m_subMenuOverlapPixels); - } - else - { - targetPoint.X = (float)(positionPoint.X - width - leftSpace + newPresenterSize.Width); - } + targetPoint.X = (float)(positionPoint.X - newPresenterSize.Width + m_subMenuOverlapPixels); } else { - targetPoint.X = (float)(positionPoint.X - targetBounds.Width + m_subMenuOverlapPixels); + targetPoint.X = (float)(positionPoint.X + width + rightSpace - newPresenterSize.Width); } } - - // If the current sub presenter doesn't have space to fit in the default bottom position, - // then the MenuFlyoutSubItem will be aligned with the bottom of the target bounds. - // If the MenuFlyoutSubItem is too tall to fit when bottom aligned with the target bounds - // then it will be bottom aligned with the edge of the monitor. - if (newPresenterSize.Height > bottomSpace) + } + else + { + // Get the available left space from the current nearest monitor bounds + double leftSpace = targetBounds.X - availableMonitorRect.X - targetBounds.Width; + // If the current sub presenter width isn't enough in the default left space, + // the MenuFlyoutSubItem will be positioned on the right side if the current presenter + // width is less than the sub item right(X) position. Otherwise, it will be aligned to + // the left side of the available monitor rect. + if (newPresenterSize.Width > leftSpace) { - if ((targetBounds.Y + targetBounds.Height) > newPresenterSize.Height) + if (newPresenterSize.Width < (availableMonitorRect.Width - leftSpace - targetBounds.Width)) { - targetPoint.Y = (float)(positionPoint.Y - newPresenterSize.Height + targetBounds.Height); + targetPoint.X = (float)(positionPoint.X + newPresenterSize.Width - m_subMenuOverlapPixels); } else { - targetPoint.Y = (float)(positionPoint.Y - DoubleUtil.Min(newPresenterSize.Height - bottomSpace, availableMonitorRect.Height - bottomSpace)); + targetPoint.X = (float)(positionPoint.X - width - leftSpace + newPresenterSize.Width); } } + else + { + targetPoint.X = (float)(positionPoint.X - targetBounds.Width + m_subMenuOverlapPixels); + } } - else -#endif // UNO TODO Windowed menus are not supported + + // If the current sub presenter doesn't have space to fit in the default bottom position, + // then the MenuFlyoutSubItem will be aligned with the bottom of the target bounds. + // If the MenuFlyoutSubItem is too tall to fit when bottom aligned with the target bounds + // then it will be bottom aligned with the edge of the monitor. + if (newPresenterSize.Height > bottomSpace) { - // Get the available window rect - Rect availableWindowRect = FlyoutBase.CalculateAvailableWindowRect( - true /* isMenuFlyout */, - popup, - null /* placementTarget */, - true /* hasTargetPosition */, - positionPoint, - false /* isFull */); - - // Set the max width and height with the available windows bounds - presenterAsControl.MaxWidth = - double.IsNaN(maxWidth) ? availableWindowRect.Width : Math.Min(maxWidth, availableWindowRect.Width); - presenterAsControl.MaxHeight = - double.IsNaN(maxHeight) ? availableWindowRect.Height : Math.Min(maxHeight, availableWindowRect.Height); - - // Get the available bottom space to set the MenuFlyoutSubItem - double bottomSpace = availableWindowRect.Height - positionPoint.Y; - - if (flowDirection == FlowDirection.LeftToRight) + if ((targetBounds.Y + targetBounds.Height) > newPresenterSize.Height) { - // Get the available right space to set the MenuFlyoutSubItem - double rightSpace = availableWindowRect.Width - (positionPoint.X + width); - // If the current sub presenter width isn't enough in the default right space, - // the MenuFlyoutSubItem will be positioned on the left side if the current presenter - // width is less than the sub item left(X) position. Otherwise, it will be aligned - // right side of the available window rect. - if (newPresenterSize.Width > rightSpace) - { - if (newPresenterSize.Width < positionPoint.X) - { - targetPoint.X = (float)(positionPoint.X - newPresenterSize.Width + m_subMenuOverlapPixels); - } - else - { - targetPoint.X = (float)(availableWindowRect.Width - newPresenterSize.Width); - } - } + targetPoint.Y = (float)(positionPoint.Y - newPresenterSize.Height + targetBounds.Height); } else { - // Get the available left space to set the MenuFlyoutSubItem - double leftSpace = positionPoint.X - availableWindowRect.X - width; - // If the current sub presenter width isn't enough in the default left space, - // the MenuFlyoutSubItem will be positioned on the right side if the current presenter - // width is less than the sub item right(X) position. Otherwise, it will be aligned - // left side of the available window rect. - if (newPresenterSize.Width > leftSpace) + targetPoint.Y = (float)(positionPoint.Y - DoubleUtil.Min(newPresenterSize.Height - bottomSpace, availableMonitorRect.Height - bottomSpace)); + } + } + } + else +#endif // UNO TODO Windowed menus are not supported + { + // Get the available window rect + Rect availableWindowRect = FlyoutBase.CalculateAvailableWindowRect( + true /* isMenuFlyout */, + popup, + null /* placementTarget */, + true /* hasTargetPosition */, + positionPoint, + false /* isFull */); + + // Set the max width and height with the available windows bounds + presenterAsControl.MaxWidth = + double.IsNaN(maxWidth) ? availableWindowRect.Width : Math.Min(maxWidth, availableWindowRect.Width); + presenterAsControl.MaxHeight = + double.IsNaN(maxHeight) ? availableWindowRect.Height : Math.Min(maxHeight, availableWindowRect.Height); + + // Get the available bottom space to set the MenuFlyoutSubItem + double bottomSpace = availableWindowRect.Height - positionPoint.Y; + + if (flowDirection == FlowDirection.LeftToRight) + { + // Get the available right space to set the MenuFlyoutSubItem + double rightSpace = availableWindowRect.Width - (positionPoint.X + width); + // If the current sub presenter width isn't enough in the default right space, + // the MenuFlyoutSubItem will be positioned on the left side if the current presenter + // width is less than the sub item left(X) position. Otherwise, it will be aligned + // right side of the available window rect. + if (newPresenterSize.Width > rightSpace) + { + if (newPresenterSize.Width < positionPoint.X) { - if (newPresenterSize.Width < (availableWindowRect.Width + availableWindowRect.X - positionPoint.X)) - { - targetPoint.X = (float)(positionPoint.X + width - m_subMenuOverlapPixels); - } - else - { - targetPoint.X = (float)width; - } + targetPoint.X = (float)(positionPoint.X - newPresenterSize.Width + m_subMenuOverlapPixels); } else { - targetPoint.X = (float)(positionPoint.X - width + m_subMenuOverlapPixels); + targetPoint.X = (float)(availableWindowRect.Width - newPresenterSize.Width); } } - - // If the current sub presenter doesn't have space to fit in the default bottom position, - // then the MenuFlyoutSubItem will be aligned with the bottom of the target bounds. - // If the MenuFlyoutSubItem is too tall to fit when bottom aligned with the target bounds - // then it will be bottom aligned with the edge of the monitor. - if (newPresenterSize.Height > bottomSpace) + } + else + { + // Get the available left space to set the MenuFlyoutSubItem + double leftSpace = positionPoint.X - availableWindowRect.X - width; + // If the current sub presenter width isn't enough in the default left space, + // the MenuFlyoutSubItem will be positioned on the right side if the current presenter + // width is less than the sub item right(X) position. Otherwise, it will be aligned + // left side of the available window rect. + if (newPresenterSize.Width > leftSpace) { - if ((positionPoint.Y + height) > newPresenterSize.Height) + if (newPresenterSize.Width < (availableWindowRect.Width + availableWindowRect.X - positionPoint.X)) { - targetPoint.Y = (float)(positionPoint.Y + height - newPresenterSize.Height); + targetPoint.X = (float)(positionPoint.X + width - m_subMenuOverlapPixels); } else { - targetPoint.Y = (float)(positionPoint.Y - Math.Min(newPresenterSize.Height - bottomSpace, availableWindowRect.Height - bottomSpace)); + targetPoint.X = (float)width; } } + else + { + targetPoint.X = (float)(positionPoint.X - width + m_subMenuOverlapPixels); + } } - _lastTargetPoint = targetPoint; - ownerAsSubMenuOwner.PositionSubMenu(targetPoint); + // If the current sub presenter doesn't have space to fit in the default bottom position, + // then the MenuFlyoutSubItem will be aligned with the bottom of the target bounds. + // If the MenuFlyoutSubItem is too tall to fit when bottom aligned with the target bounds + // then it will be bottom aligned with the edge of the monitor. + if (newPresenterSize.Height > bottomSpace) + { + if ((positionPoint.Y + height) > newPresenterSize.Height) + { + targetPoint.Y = (float)(positionPoint.Y + height - newPresenterSize.Height); + } + else + { + targetPoint.Y = (float)(positionPoint.Y - Math.Min(newPresenterSize.Height - bottomSpace, availableWindowRect.Height - bottomSpace)); + } + } } + + _lastTargetPoint = targetPoint; + ownerAsSubMenuOwner.PositionSubMenu(targetPoint); } + } - void UpdateOwnerVisualState() - { - Control ownerAsControl = m_wpOwner?.Target as Control; + void UpdateOwnerVisualState() + { + Control ownerAsControl = m_wpOwner?.Target as Control; #if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: UpdateOwnerVisualState - ownerAsControl=0x%p.", this, ownerAsControl)); + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: UpdateOwnerVisualState - ownerAsControl=0x%p.", this, ownerAsControl)); #endif // CMH_DEBUG - if (ownerAsControl != null) - { - ownerAsControl.UpdateVisualState(true /* useTransitions */); - } + if (ownerAsControl != null) + { + ownerAsControl.UpdateVisualState(true /* useTransitions */); } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CommandingHelpers.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CommandingHelpers.cs index 54367409214c..51f597c002fa 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CommandingHelpers.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CommandingHelpers.cs @@ -9,223 +9,222 @@ using ICommand = System.Windows.Input.ICommand; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public class CommandingHelpers { - public class CommandingHelpers + class IconSourceToIconSourceElementConverter : IValueConverter { - class IconSourceToIconSourceElementConverter : IValueConverter + public object Convert(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) + if (value != null) { - if (value != null) - { - var valueAsI = value; + var valueAsI = value; - IconSource valueAsIconSource; - IconSourceElement returnValueAsIconElement = new IconSourceElement(); + IconSource valueAsIconSource; + IconSourceElement returnValueAsIconElement = new IconSourceElement(); - valueAsIconSource = valueAsI as IconSource; + valueAsIconSource = valueAsI as IconSource; - returnValueAsIconElement.IconSource = valueAsIconSource; + returnValueAsIconElement.IconSource = valueAsIconSource; - return returnValueAsIconElement; - } - else - { - return null; - } + return returnValueAsIconElement; } - - public object ConvertBack(object value, Type targetType, object parameter, string language) + else { - throw new NotImplementedException(); + return null; } } - class KeyboardAcceleratorCopyConverter : IValueConverter + public object ConvertBack(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) - { - if (value != null) - { - object valueAsI = value; - - IList valueAsKeyboardAccelerators; - var returnValueAsKeyboardAcceleratorCollection = new DependencyObjectCollection(); + throw new NotImplementedException(); + } + } - valueAsKeyboardAccelerators = valueAsI as IList; - int keyboardAcceleratorCount; + class KeyboardAcceleratorCopyConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value != null) + { + object valueAsI = value; - keyboardAcceleratorCount = valueAsKeyboardAccelerators.Count; + IList valueAsKeyboardAccelerators; + var returnValueAsKeyboardAcceleratorCollection = new DependencyObjectCollection(); - // Keyboard accelerators can't have two parents, - // so we'll need to copy them and bind to the original properties - // instead of assigning them. - // We set up bindings so that modifications to the app-defined accelerators - // will propagate to the accelerators that are used by the framework. - for (int i = 0; i < keyboardAcceleratorCount; i++) - { - KeyboardAccelerator keyboardAccelerator = valueAsKeyboardAccelerators[i]; - KeyboardAccelerator keyboardAcceleratorCopy = new KeyboardAccelerator(); + valueAsKeyboardAccelerators = valueAsI as IList; + int keyboardAcceleratorCount; - keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.IsEnabledProperty, new Binding { Path = "IsEnabled", Source = keyboardAccelerator }); - keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.KeyProperty, new Binding { Path = "Key", Source = keyboardAccelerator }); - keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.ModifiersProperty, new Binding { Path = "Modifiers", Source = keyboardAccelerator }); - keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.ScopeOwnerProperty, new Binding { Path = "ScopeOwner", Source = keyboardAccelerator }); - } + keyboardAcceleratorCount = valueAsKeyboardAccelerators.Count; - return returnValueAsKeyboardAcceleratorCollection; - } - else + // Keyboard accelerators can't have two parents, + // so we'll need to copy them and bind to the original properties + // instead of assigning them. + // We set up bindings so that modifications to the app-defined accelerators + // will propagate to the accelerators that are used by the framework. + for (int i = 0; i < keyboardAcceleratorCount; i++) { - return null; + KeyboardAccelerator keyboardAccelerator = valueAsKeyboardAccelerators[i]; + KeyboardAccelerator keyboardAcceleratorCopy = new KeyboardAccelerator(); + + keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.IsEnabledProperty, new Binding { Path = "IsEnabled", Source = keyboardAccelerator }); + keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.KeyProperty, new Binding { Path = "Key", Source = keyboardAccelerator }); + keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.ModifiersProperty, new Binding { Path = "Modifiers", Source = keyboardAccelerator }); + keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.ScopeOwnerProperty, new Binding { Path = "ScopeOwner", Source = keyboardAccelerator }); } - } - public object ConvertBack(object value, Type targetType, object parameter, string language) + return returnValueAsKeyboardAcceleratorCollection; + } + else { - throw new NotImplementedException(); + return null; } } - internal static void BindToLabelPropertyIfUnset( - ICommand uiCommand, - DependencyObject target, - DependencyProperty labelProperty) + public object ConvertBack(object value, Type targetType, object parameter, string language) { - string localLabel = null; - var localLabelAsI = target.ReadLocalValue(labelProperty); + throw new NotImplementedException(); + } + } - if (localLabelAsI != null) - { - localLabel = localLabelAsI.ToString(); - } + internal static void BindToLabelPropertyIfUnset( + ICommand uiCommand, + DependencyObject target, + DependencyProperty labelProperty) + { + string localLabel = null; + var localLabelAsI = target.ReadLocalValue(labelProperty); - if (localLabelAsI == DependencyProperty.UnsetValue || string.IsNullOrEmpty(localLabel)) - { - if (target is IDependencyObjectStoreProvider dosp) - { - dosp.Store.SetBinding(labelProperty, new Binding { Path = "Label", Source = uiCommand }); - } - } + if (localLabelAsI != null) + { + localLabel = localLabelAsI.ToString(); } - internal static void BindToIconPropertyIfUnset( - XamlUICommand uiCommand, - DependencyObject target, - DependencyProperty iconProperty) + if (localLabelAsI == DependencyProperty.UnsetValue || string.IsNullOrEmpty(localLabel)) { - IconElement localIcon; - object localIconAsI = target.ReadLocalValue(iconProperty); - - localIcon = localIconAsI as IconElement; - - if (localIconAsI == DependencyProperty.UnsetValue || localIcon == null) + if (target is IDependencyObjectStoreProvider dosp) { - if (target is IDependencyObjectStoreProvider dosp) - { - IconSourceToIconSourceElementConverter converter = new IconSourceToIconSourceElementConverter(); - dosp.Store.SetBinding(iconProperty, new Binding { Path = "IconSource", Source = uiCommand, Converter = converter }); - } + dosp.Store.SetBinding(labelProperty, new Binding { Path = "Label", Source = uiCommand }); } } + } - internal static void BindToIconSourcePropertyIfUnset( - XamlUICommand uiCommand, - DependencyObject target, - DependencyProperty iconSourceProperty) - { - object localIconSourceAsI; - IconSource localIconSource; - localIconSourceAsI = target.ReadLocalValue(iconSourceProperty); + internal static void BindToIconPropertyIfUnset( + XamlUICommand uiCommand, + DependencyObject target, + DependencyProperty iconProperty) + { + IconElement localIcon; + object localIconAsI = target.ReadLocalValue(iconProperty); - localIconSource = localIconSourceAsI as IconSource; + localIcon = localIconAsI as IconElement; - if (localIconSourceAsI == DependencyProperty.UnsetValue || localIconSource == null) + if (localIconAsI == DependencyProperty.UnsetValue || localIcon == null) + { + if (target is IDependencyObjectStoreProvider dosp) { - if (target is IDependencyObjectStoreProvider dosp) - { - dosp.Store.SetBinding(iconSourceProperty, new Binding { Path = "IconSource", Source = uiCommand }); - } + IconSourceToIconSourceElementConverter converter = new IconSourceToIconSourceElementConverter(); + dosp.Store.SetBinding(iconProperty, new Binding { Path = "IconSource", Source = uiCommand, Converter = converter }); } } + } - internal static void BindToKeyboardAcceleratorsIfUnset( - XamlUICommand uiCommand, - UIElement target) - { - IList targetKeyboardAccelerators; - int targetKeyboardAcceleratorCount; + internal static void BindToIconSourcePropertyIfUnset( + XamlUICommand uiCommand, + DependencyObject target, + DependencyProperty iconSourceProperty) + { + object localIconSourceAsI; + IconSource localIconSource; + localIconSourceAsI = target.ReadLocalValue(iconSourceProperty); - targetKeyboardAccelerators = target.KeyboardAccelerators; - targetKeyboardAcceleratorCount = targetKeyboardAccelerators.Count; + localIconSource = localIconSourceAsI as IconSource; - if (targetKeyboardAcceleratorCount == 0) + if (localIconSourceAsI == DependencyProperty.UnsetValue || localIconSource == null) + { + if (target is IDependencyObjectStoreProvider dosp) { - var converter = new KeyboardAcceleratorCopyConverter(); - target.SetBinding(UIElement.KeyboardAcceleratorsProperty, new Binding { Path = "KeyboardAccelerators", Source = uiCommand, Converter = converter }); + dosp.Store.SetBinding(iconSourceProperty, new Binding { Path = "IconSource", Source = uiCommand }); } } + } - internal static void BindToAccessKeyIfUnset( - XamlUICommand uiCommand, - UIElement target) - { - string localAccessKey; - localAccessKey = target.AccessKey; + internal static void BindToKeyboardAcceleratorsIfUnset( + XamlUICommand uiCommand, + UIElement target) + { + IList targetKeyboardAccelerators; + int targetKeyboardAcceleratorCount; - if (localAccessKey == null || string.IsNullOrEmpty(localAccessKey)) - { - target.SetBinding(UIElement.AccessKeyProperty, new Binding { Path = "AccessKey", Source = uiCommand }); - } + targetKeyboardAccelerators = target.KeyboardAccelerators; + targetKeyboardAcceleratorCount = targetKeyboardAccelerators.Count; + + if (targetKeyboardAcceleratorCount == 0) + { + var converter = new KeyboardAcceleratorCopyConverter(); + target.SetBinding(UIElement.KeyboardAcceleratorsProperty, new Binding { Path = "KeyboardAccelerators", Source = uiCommand, Converter = converter }); } + } - internal static void BindToDescriptionPropertiesIfUnset( - XamlUICommand uiCommand, - FrameworkElement target) + internal static void BindToAccessKeyIfUnset( + XamlUICommand uiCommand, + UIElement target) + { + string localAccessKey; + localAccessKey = target.AccessKey; + + if (localAccessKey == null || string.IsNullOrEmpty(localAccessKey)) { - string localHelpText = AutomationProperties.GetHelpText(target); + target.SetBinding(UIElement.AccessKeyProperty, new Binding { Path = "AccessKey", Source = uiCommand }); + } + } - if (localHelpText == null || string.IsNullOrEmpty(localHelpText)) - { - target.SetBinding(AutomationProperties.HelpTextProperty, new Binding { Path = "Description", Source = uiCommand }); - } + internal static void BindToDescriptionPropertiesIfUnset( + XamlUICommand uiCommand, + FrameworkElement target) + { + string localHelpText = AutomationProperties.GetHelpText(target); - object localToolTipAsI; - localToolTipAsI = ToolTipService.GetToolTip(target); + if (localHelpText == null || string.IsNullOrEmpty(localHelpText)) + { + target.SetBinding(AutomationProperties.HelpTextProperty, new Binding { Path = "Description", Source = uiCommand }); + } - string localToolTipAsString = null; - ToolTip localToolTip = null; + object localToolTipAsI; + localToolTipAsI = ToolTipService.GetToolTip(target); - if (localToolTipAsI != null) - { - localToolTipAsString = localToolTipAsI.ToString(); - localToolTip = localToolTipAsI as ToolTip; - } + string localToolTipAsString = null; + ToolTip localToolTip = null; - if ((localToolTipAsString == null || string.IsNullOrEmpty(localToolTipAsString)) && localToolTip == null) - { - target.SetBinding(ToolTipService.ToolTipProperty, new Binding { Path = "Description", Source = uiCommand }); - } + if (localToolTipAsI != null) + { + localToolTipAsString = localToolTipAsI.ToString(); + localToolTip = localToolTipAsI as ToolTip; } - internal static void ClearBindingIfSet( - ICommand uiCommand, - FrameworkElement target, - DependencyProperty targetProperty) + if ((localToolTipAsString == null || string.IsNullOrEmpty(localToolTipAsString)) && localToolTip == null) { - BindingExpression bindingExpression; - bindingExpression = target.GetBindingExpression(targetProperty); + target.SetBinding(ToolTipService.ToolTipProperty, new Binding { Path = "Description", Source = uiCommand }); + } + } - if (bindingExpression != null) - { - object bindingSource; - bindingSource = bindingExpression.ParentBinding.Source; + internal static void ClearBindingIfSet( + ICommand uiCommand, + FrameworkElement target, + DependencyProperty targetProperty) + { + BindingExpression bindingExpression; + bindingExpression = target.GetBindingExpression(targetProperty); - if (bindingSource != null && bindingSource == uiCommand) - { - target.ClearValue(targetProperty); - } + if (bindingExpression != null) + { + object bindingSource; + bindingSource = bindingExpression.ParentBinding.Source; + + if (bindingSource != null && bindingSource == uiCommand) + { + target.ClearValue(targetProperty); } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs index 2dee9795e198..e46749ddff26 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs @@ -9,94 +9,93 @@ using Uno.UI; using Uno.UI.Controls; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial class MenuFlyout { - public partial class MenuFlyout - { - private PopupMenu _menu; + private PopupMenu _menu; - internal override View NativeTarget + internal override View NativeTarget + { + get { - get + if (GetActualTarget() is { } actualTarget && actualTarget != Target) { - if (GetActualTarget() is { } actualTarget && actualTarget != Target) - { - return actualTarget; - } - - return null; + return actualTarget; } + + return null; } + } - internal protected override void Open() + internal protected override void Open() + { + if (UseNativePopup) { - if (UseNativePopup) - { - _menu = new PopupMenu(ContextHelper.Current, GetActualTarget()); + _menu = new PopupMenu(ContextHelper.Current, GetActualTarget()); - _menu.MenuItemClick += OnMenuItemClick; - _menu.DismissEvent += OnDismiss; + _menu.MenuItemClick += OnMenuItemClick; + _menu.DismissEvent += OnDismiss; - Items - .OfType() - .Where(item => item.Visibility == Visibility.Visible) - .ForEach((index, item) => - { - // Using index as ID - _menu.Menu.Add(0, index, index, item.Text); - }); + Items + .OfType() + .Where(item => item.Visibility == Visibility.Visible) + .ForEach((index, item) => + { + // Using index as ID + _menu.Menu.Add(0, index, index, item.Text); + }); - _menu.Show(); - } - else - { - base.Open(); - } + _menu.Show(); } + else + { + base.Open(); + } + } - internal protected override void Close() + internal protected override void Close() + { + if (UseNativePopup) { - if (UseNativePopup) - { - _menu?.Dismiss(); - if (_menu != null) - { - _menu.MenuItemClick -= OnMenuItemClick; - _menu.DismissEvent -= OnDismiss; - } - } - else + _menu?.Dismiss(); + if (_menu != null) { - base.Close(); + _menu.MenuItemClick -= OnMenuItemClick; + _menu.DismissEvent -= OnDismiss; } } - - private void OnDismiss(object sender, PopupMenu.DismissEventArgs e) + else { - Hide(canCancel: false); + base.Close(); } + } - private void OnMenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) - { - var items = Items.OfType().Where(i => i.Visibility == Visibility.Visible).ToArray(); - var item = items[e.Item.ItemId]; + private void OnDismiss(object sender, PopupMenu.DismissEventArgs e) + { + Hide(canCancel: false); + } - item.InvokeClick(); - } + private void OnMenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) + { + var items = Items.OfType().Where(i => i.Visibility == Visibility.Visible).ToArray(); + var item = items[e.Item.ItemId]; - private View GetActualTarget() - { - if (Target is AppBarButton appBarButton - && ((Android.Views.View)Target).Parent == null // View.Parent (IViewParent) is the visual parent (hidden using `new` modifier). - && Target.Parent is CommandBar commandBar // FrameworkElement.Parent (DependencyObject) is the logical parent. - && commandBar.FindViewById(appBarButton.GetHashCode()) is View actionMenuItemView) - { - // This AppBarButton doesn't exist in the visual tree and is likely rendered natively as an ActionMenuItemView. - return actionMenuItemView; - } + item.InvokeClick(); + } - return Target; + private View GetActualTarget() + { + if (Target is AppBarButton appBarButton + && ((Android.Views.View)Target).Parent == null // View.Parent (IViewParent) is the visual parent (hidden using `new` modifier). + && Target.Parent is CommandBar commandBar // FrameworkElement.Parent (DependencyObject) is the logical parent. + && commandBar.FindViewById(appBarButton.GetHashCode()) is View actionMenuItemView) + { + // This AppBarButton doesn't exist in the visual tree and is likely rendered natively as an ActionMenuItemView. + return actionMenuItemView; } + + return Target; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS.cs index 14f3db62e485..ed0ed8bc5918 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS.cs @@ -12,53 +12,52 @@ using UIKit; using Uno.UI; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial class MenuFlyout { - public partial class MenuFlyout - { - private static DependencyProperty CancelTextIosOverrideProperty = ToolkitHelper.GetProperty("Uno.UI.Toolkit.MenuFlyoutExtensions", "CancelTextIosOverride"); + private static DependencyProperty CancelTextIosOverrideProperty = ToolkitHelper.GetProperty("Uno.UI.Toolkit.MenuFlyoutExtensions", "CancelTextIosOverride"); - private string LocalizedCancelString => NSBundle.FromIdentifier("com.apple.UIKit") - .GetLocalizedString("Cancel", null); + private string LocalizedCancelString => NSBundle.FromIdentifier("com.apple.UIKit") + .GetLocalizedString("Cancel", null); - internal protected override void Open() + internal protected override void Open() + { + if (UseNativePopup) { - if (UseNativePopup) - { - if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) - { - ShowAlert(Target); - } - else if (UIDevice.CurrentDevice.CheckSystemVersion(7, 0)) - { - ShowActionSheet(Target); - } + if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) + { + ShowAlert(Target); } - else + else if (UIDevice.CurrentDevice.CheckSystemVersion(7, 0)) { - base.Open(); + ShowActionSheet(Target); } } + else + { + base.Open(); + } + } - internal protected override void Close() + internal protected override void Close() + { + if (UseNativePopup) { - if (UseNativePopup) - { - if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) - { - HideAlert(); - } - else if (UIDevice.CurrentDevice.CheckSystemVersion(7, 0)) - { - HideActionSheet(); - } + if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) + { + HideAlert(); } - else + else if (UIDevice.CurrentDevice.CheckSystemVersion(7, 0)) { - base.Close(); + HideActionSheet(); } } + else + { + base.Close(); + } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs index 7bbbc425f949..97a12c2726cf 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs @@ -8,73 +8,72 @@ using Uno.Disposables; using Windows.UI.Core; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial class MenuFlyout { - public partial class MenuFlyout - { - private UIActionSheet _actionSheet; - private SerialDisposable _subscriptions; + private UIActionSheet _actionSheet; + private SerialDisposable _subscriptions; - private void ShowActionSheet(UIView placementTarget) - { - _subscriptions = new SerialDisposable(); + private void ShowActionSheet(UIView placementTarget) + { + _subscriptions = new SerialDisposable(); - var availableItems = Items.Where(item => item.Visibility == Visibility.Visible); + var availableItems = Items.Where(item => item.Visibility == Visibility.Visible); #pragma warning disable CS0618 // Type or member is obsolete - _actionSheet = new UIActionSheet( - title: null, - del: null, - cancelTitle: GetValue(CancelTextIosOverrideProperty) as string ?? LocalizedCancelString, - destroy: null, - other: availableItems.OfType().Select(item => item.Text).ToArray() - ); + _actionSheet = new UIActionSheet( + title: null, + del: null, + cancelTitle: GetValue(CancelTextIosOverrideProperty) as string ?? LocalizedCancelString, + destroy: null, + other: availableItems.OfType().Select(item => item.Text).ToArray() + ); #pragma warning restore CS0618 // Type or member is obsolete - _actionSheet.Dismissed += OnDismissed; + _actionSheet.Dismissed += OnDismissed; - EventHandler handler = - (_, args) => - { - var item = availableItems.OfType().ElementAtOrDefault((int)args.ButtonIndex); + EventHandler handler = + (_, args) => + { + var item = availableItems.OfType().ElementAtOrDefault((int)args.ButtonIndex); - if (item != null) - { - item.InvokeClick(); - Hide(); - } - }; + if (item != null) + { + item.InvokeClick(); + Hide(); + } + }; - _actionSheet.Clicked += handler; - _subscriptions.Disposable = Disposable.Create(() => _actionSheet.Clicked -= handler); + _actionSheet.Clicked += handler; + _subscriptions.Disposable = Disposable.Create(() => _actionSheet.Clicked -= handler); - _ = CoreDispatcher.Main.RunAsync( - CoreDispatcherPriority.Normal, - () => + _ = CoreDispatcher.Main.RunAsync( + CoreDispatcherPriority.Normal, + () => + { + switch (UIDevice.CurrentDevice.UserInterfaceIdiom) { - switch (UIDevice.CurrentDevice.UserInterfaceIdiom) - { - case UIUserInterfaceIdiom.Pad: - _actionSheet.ShowFrom(placementTarget.Bounds, placementTarget, animated: true); - break; - case UIUserInterfaceIdiom.Phone: - _actionSheet.ShowInView(UIApplication.SharedApplication.KeyWindow); - break; - case UIUserInterfaceIdiom.Unspecified: - break; - } + case UIUserInterfaceIdiom.Pad: + _actionSheet.ShowFrom(placementTarget.Bounds, placementTarget, animated: true); + break; + case UIUserInterfaceIdiom.Phone: + _actionSheet.ShowInView(UIApplication.SharedApplication.KeyWindow); + break; + case UIUserInterfaceIdiom.Unspecified: + break; } - ); - } + } + ); + } - private void OnDismissed(object sender, UIButtonEventArgs e) - { - _subscriptions.Dispose(); - } + private void OnDismissed(object sender, UIButtonEventArgs e) + { + _subscriptions.Dispose(); + } - private void HideActionSheet() - { - _actionSheet?.DismissWithClickedButtonIndex(_actionSheet.CancelButtonIndex, animated: true); - } + private void HideActionSheet() + { + _actionSheet?.DismissWithClickedButtonIndex(_actionSheet.CancelButtonIndex, animated: true); } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs index c9a7978ec1d1..6e2338968d36 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs @@ -8,104 +8,103 @@ using Uno.UI.Controls; using Uno.UI; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial class MenuFlyout { - public partial class MenuFlyout - { - private static DependencyProperty IsDestructiveProperty = ToolkitHelper.GetProperty("Uno.UI.Toolkit.MenuFlyoutItemExtensions", "IsDestructive"); + private static DependencyProperty IsDestructiveProperty = ToolkitHelper.GetProperty("Uno.UI.Toolkit.MenuFlyoutItemExtensions", "IsDestructive"); - private UIAlertController _alertController; + private UIAlertController _alertController; - internal override UIView NativeTarget + internal override UIView NativeTarget + { + get { - get + if (Target is AppBarButton appBarButton + && appBarButton.Superview == null + && appBarButton.Parent is CommandBar commandBar + && GetNativeAppBarButton(appBarButton) is { } nativeAppBarButton + && commandBar.GetRenderer(() => new CommandBarRenderer(commandBar)).Native is { } navigationBar) { - if (Target is AppBarButton appBarButton - && appBarButton.Superview == null - && appBarButton.Parent is CommandBar commandBar - && GetNativeAppBarButton(appBarButton) is { } nativeAppBarButton - && commandBar.GetRenderer(() => new CommandBarRenderer(commandBar)).Native is { } navigationBar) - { - // If the AppBarButton is a proxy for a native navigation bar button, find the corresponding native view. + // If the AppBarButton is a proxy for a native navigation bar button, find the corresponding native view. - if (nativeAppBarButton.Image is { } image) // AppBarButton.Icon is set - { - return navigationBar.FindSubviewsOfType().FirstOrDefault(v => v.Image == image); - } - else if (!nativeAppBarButton.Title.IsNullOrEmpty()) // AppBarButton.Content is set to a string - { - return navigationBar.FindSubviewsOfType() - .SelectMany(b => b.FindSubviewsOfType()) - .FirstOrDefault(l => l.Text == nativeAppBarButton.Title); - } - // Don't worry about the CustomView case (AppBarButton.Content is a FrameworkElement) - if it's set, it means the - // AppBarButton is attached to the visual tree and can be used as Target in the standard way + if (nativeAppBarButton.Image is { } image) // AppBarButton.Icon is set + { + return navigationBar.FindSubviewsOfType().FirstOrDefault(v => v.Image == image); } - - return null; + else if (!nativeAppBarButton.Title.IsNullOrEmpty()) // AppBarButton.Content is set to a string + { + return navigationBar.FindSubviewsOfType() + .SelectMany(b => b.FindSubviewsOfType()) + .FirstOrDefault(l => l.Text == nativeAppBarButton.Title); + } + // Don't worry about the CustomView case (AppBarButton.Content is a FrameworkElement) - if it's set, it means the + // AppBarButton is attached to the visual tree and can be used as Target in the standard way } - } - private void ShowAlert(UIView placementTarget) - { - _alertController = new UIAlertController(); + return null; + } + } - Items - .OfType() - .Trim() - .Where(item => item.Visibility == Visibility.Visible) - .Select(item => UIAlertAction.Create( - item.Text, - true == (item.GetValue(IsDestructiveProperty) as bool?) ? UIAlertActionStyle.Destructive : UIAlertActionStyle.Default, - _ => - { - item.InvokeClick(); - Hide(); - } - )) - .Concat(UIAlertAction.Create( - GetValue(CancelTextIosOverrideProperty) as string ?? LocalizedCancelString, - UIAlertActionStyle.Cancel, - _ => this.Hide() - )) - .ForEach(_alertController.AddAction); + private void ShowAlert(UIView placementTarget) + { + _alertController = new UIAlertController(); - _ = Dispatcher.RunAsync(global::Windows.UI.Core.CoreDispatcherPriority.Normal, () => - { - switch (UIDevice.CurrentDevice.UserInterfaceIdiom) + Items + .OfType() + .Trim() + .Where(item => item.Visibility == Visibility.Visible) + .Select(item => UIAlertAction.Create( + item.Text, + true == (item.GetValue(IsDestructiveProperty) as bool?) ? UIAlertActionStyle.Destructive : UIAlertActionStyle.Default, + _ => { - case UIUserInterfaceIdiom.Pad: - if (placementTarget.Superview != null) - { - // This UIView exists in the visual tree. - _alertController.PopoverPresentationController.SourceView = placementTarget; - _alertController.PopoverPresentationController.SourceRect = placementTarget.Bounds; - } - else if (placementTarget is AppBarButton appBarButton) - { - // This AppBarButton doesn't exist in the visual tree and is likely rendered natively as a UIBarButton. - var barButtonItem = GetNativeAppBarButton(appBarButton); - _alertController.PopoverPresentationController.BarButtonItem = barButtonItem; - } - _alertController.PopoverPresentationController.PermittedArrowDirections = UIPopoverArrowDirection.Any; - placementTarget.FindViewController().PresentViewController(_alertController, true, null); - break; - case UIUserInterfaceIdiom.Phone: - placementTarget.FindViewController().PresentViewController(_alertController, true, null); - break; - case UIUserInterfaceIdiom.Unspecified: - break; + item.InvokeClick(); + Hide(); } - }); + )) + .Concat(UIAlertAction.Create( + GetValue(CancelTextIosOverrideProperty) as string ?? LocalizedCancelString, + UIAlertActionStyle.Cancel, + _ => this.Hide() + )) + .ForEach(_alertController.AddAction); - } + _ = Dispatcher.RunAsync(global::Windows.UI.Core.CoreDispatcherPriority.Normal, () => + { + switch (UIDevice.CurrentDevice.UserInterfaceIdiom) + { + case UIUserInterfaceIdiom.Pad: + if (placementTarget.Superview != null) + { + // This UIView exists in the visual tree. + _alertController.PopoverPresentationController.SourceView = placementTarget; + _alertController.PopoverPresentationController.SourceRect = placementTarget.Bounds; + } + else if (placementTarget is AppBarButton appBarButton) + { + // This AppBarButton doesn't exist in the visual tree and is likely rendered natively as a UIBarButton. + var barButtonItem = GetNativeAppBarButton(appBarButton); + _alertController.PopoverPresentationController.BarButtonItem = barButtonItem; + } + _alertController.PopoverPresentationController.PermittedArrowDirections = UIPopoverArrowDirection.Any; + placementTarget.FindViewController().PresentViewController(_alertController, true, null); + break; + case UIUserInterfaceIdiom.Phone: + placementTarget.FindViewController().PresentViewController(_alertController, true, null); + break; + case UIUserInterfaceIdiom.Unspecified: + break; + } + }); + + } - private static UIBarButtonItem GetNativeAppBarButton(AppBarButton appBarButton) => appBarButton.GetRenderer(() => new AppBarButtonRenderer(appBarButton)).Native; + private static UIBarButtonItem GetNativeAppBarButton(AppBarButton appBarButton) => appBarButton.GetRenderer(() => new AppBarButtonRenderer(appBarButton)).Native; - private void HideAlert() - { - _alertController?.DismissViewController(true, null); - _alertController = null; - } + private void HideAlert() + { + _alertController?.DismissViewController(true, null); + _alertController = null; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs index be2a22a98f95..42826f7a255e 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs @@ -1,827 +1,828 @@ -using System; -using Uno.Client; -using Uno.Disposables; -using Windows.Foundation; -using Microsoft.UI.Xaml.Automation; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using ICommand = System.Windows.Input.ICommand; -using Microsoft.UI.Xaml.Markup; - -#if HAS_UNO_WINUI -using Microsoft.UI.Input; -#else -using Windows.Devices.Input; -using Windows.UI.Input; -#endif - -namespace Microsoft.UI.Xaml.Controls -{ - [ContentProperty(Name = nameof(Text))] - public partial class MenuFlyoutItem : MenuFlyoutItemBase - { - // Whether the pointer is currently over the MenuFlyoutItem - bool m_bIsPointerOver = true; - - // Whether the pointer is currently pressed over the MenuFlyoutItem - internal bool m_bIsPressed = true; - - // Whether the pointer's left button is currently down. - internal bool m_bIsPointerLeftButtonDown = true; - - // True if the SPACE or ENTER key is currently pressed, false otherwise. - internal bool m_bIsSpaceOrEnterKeyDown = true; - - // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. - internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; - - // On pointer released we perform some actions depending on control. We decide to whether to perform them - // depending on some parameters including but not limited to whether released is followed by a pressed, which - // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. - bool m_shouldPerformActions = true; - - // Event pointer for the ICommand.CanExecuteChanged event. - IDisposable m_epCanExecuteChangedHandler; - - // UNO TODO - // Event pointer for the Loaded event. - // IDisposable m_epLoadedHandler; - - // UNO TODO - // IDisposable m_epMenuFlyoutItemClickEventCallback; - - double m_maxKeyboardAcceleratorTextWidth; - TextBlock m_tpKeyboardAcceleratorTextBlock; - - bool m_isTemplateApplied; - - #region CommandParameter - - public object CommandParameter - { - get { return (object)GetValue(CommandParameterProperty); } - set { SetValue(CommandParameterProperty, value); } - } - - public static DependencyProperty CommandParameterProperty { get; } = - DependencyProperty.Register( - "CommandParameter", typeof(object), - typeof(Controls.MenuFlyoutItem), - new FrameworkPropertyMetadata(default(object))); - - #endregion - - #region Command - - public ICommand Command - { - get { return (ICommand)GetValue(CommandProperty); } - set { SetValue(CommandProperty, value); } - } - - public static DependencyProperty CommandProperty { get; } = - DependencyProperty.Register( - name: nameof(Command), - propertyType: typeof(ICommand), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); - - #endregion - - #region Text - - public string Text - { - get { return (string)GetValue(TextProperty) ?? ""; } - set { SetValue(TextProperty, value); } - } - - public static DependencyProperty TextProperty { get; } = - DependencyProperty.Register( - name: nameof(Text), - propertyType: typeof(string), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(string))); - - #endregion - - public IconElement Icon - { - get => (IconElement)this.GetValue(IconProperty); - set => this.SetValue(IconProperty, value); - } - - public static DependencyProperty IconProperty { get; } = - DependencyProperty.Register( - name: nameof(Icon), - propertyType: typeof(IconElement), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); - - public string KeyboardAcceleratorTextOverride - { - get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; - set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); - } - - public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = - DependencyProperty.Register( - name: nameof(KeyboardAcceleratorTextOverride), - propertyType: typeof(string), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(string))); - - public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } - -#pragma warning disable CS0108 - public event RoutedEventHandler Click; -#pragma warning restore CS0108 - - internal void InvokeClick() - { - Click?.Invoke(this, new RoutedEventArgs(this)); - Command.ExecuteIfPossible(this.CommandParameter); - } - - public MenuFlyoutItem() - { - m_bIsPointerOver = false; - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - m_shouldPerformActions = false; - - DefaultStyleKey = typeof(MenuFlyoutItem); - - Initialize(); - } - - // Prepares object's state - void Initialize() - { - Loaded += (s, e) => ClearStateFlags(); - } - - // Apply a template to the - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; - m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; - - SuppressIsEnabled(false); - UpdateCanExecute(); - - // Sync the logical and visual states of the control - UpdateVisualState(); - } - - // PointerPressed event handler. - protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) - { - base.OnPointerPressed(pArgs); - var handled = pArgs.Handled; - if (!handled) - { - PointerPoint spPointerPoint; - PointerPointProperties spPointerProperties; - spPointerPoint = pArgs.GetCurrentPoint(this); - spPointerProperties = spPointerPoint.Properties; - var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; - if (bIsLeftButtonPressed) - { - m_bIsPointerLeftButtonDown = true; - m_bIsPressed = true; - - pArgs.Handled = true; - UpdateVisualState(); - } - } - } - - // PointerReleased event handler. - protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) - { - base.OnPointerReleased(pArgs); - - bool handled = false; - - handled = pArgs.Handled; - if (!handled) - { - m_bIsPointerLeftButtonDown = false; - - m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; - -#if false - // UNO TODO - if (m_shouldPerformActions) - { - GestureModes gestureFollowing = GestureModes.None; - - m_bIsPressed = false; - - gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); - - // Note that we are intentionally NOT handling the args - // if we do not fall through here because basically we are no_opting in that case. - if (gestureFollowing != GestureModes.RightTapped) - { - pArgs.Handled = true; - PerformPointerUpAction(); - } - } -#else - PerformPointerUpAction(); -#endif - } - } - - private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - PerformPointerUpAction(); - pArgs.Handled = true; - } - } - - // Contains the logic to be employed if we decide to handle pointer released. - void PerformPointerUpAction() - { - if (m_shouldPerformActions) - { - Focus(FocusState.Pointer); - Invoke(); - } - } - - // Performs appropriate actions upon a mouse/keyboard invocation of a - internal virtual void Invoke() - { - RoutedEventArgs spArgs; - MenuFlyoutPresenter spParentMenuFlyoutPresenter; - - // Create the args - spArgs = new RoutedEventArgs(); - spArgs.OriginalSource = this; - - // Raise the event - Click?.Invoke(this, spArgs); - - // UNO TODO - //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); - - // Execute command associated with the button - ExecuteCommand(); - - bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; - if (!shouldPreventDismissOnPointer) - { - // Close the MenuFlyout. - spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); - if (spParentMenuFlyoutPresenter != null) - { - IMenu owningMenu; - - owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; - if (owningMenu != null) - { - // We need to make close all menu flyout sub items before we hide the parent menu flyout, - // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a - // significant amount of time before closing the sub menu. - (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); - owningMenu.Close(); - } - } - } - - ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); - } - - // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) - //{ - // DependencyObject peer = pMenuFlyoutItem; - - // if (peer == null) - // { - // return; - // } - - // MenuFlyoutItem peerAsMenuFlyoutItem; - // (peer.As(&peerAsMenuFlyoutItem)); - // IFCPTR_RETURN(peerAsMenuFlyoutItem); - - // (peerAsAddProofingItemHandler(eventHandler)); - - // return S_OK; - //} - - // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) - //{ - // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) - // { - // DependencyObject spSender; - // EventArgs spArgs; - - // IFCPTR_RETURN(pSender); - // IFCPTR_RETURN(pArgs); - - // (ctl.do_query_interface(spSender, pSender)); - // (ctl.do_query_interface(spArgs, pArgs)); - - // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); - - // return S_OK; - // })); - - // return S_OK; - //} - - // Executes Command if CanExecute() returns true. - void ExecuteCommand() - { - ICommand spCommand = Command; - - if (spCommand != null) - { - object spCommandParameter; - bool canExecute; - - spCommandParameter = CommandParameter; - canExecute = spCommand.CanExecute(spCommandParameter); - if (canExecute) - { - spCommand.Execute(spCommandParameter); - } - } - } - - // PointerEnter event handler. - protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) - { - base.OnPointerEntered(pArgs); - - MenuFlyoutPresenter spParentPresenter; - - m_bIsPointerOver = true; - - spParentPresenter = GetParentMenuFlyoutPresenter(); - if (spParentPresenter != null) - { - IMenuPresenter subPresenter; - - subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; - if (subPresenter != null) - { - ISubMenuOwner subPresenterOwner; - - subPresenterOwner = subPresenter.Owner; - - if (subPresenterOwner != null) - { - subPresenterOwner.DelayCloseSubMenu(); - } - } - } - - UpdateVisualState(); - } - - // PointerExited event handler. - protected override void OnPointerExited(PointerRoutedEventArgs pArgs) - { - base.OnPointerExited(pArgs); - - // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - UpdateVisualState(); - } - - // PointerCaptureLost event handler. - protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) - { - base.OnPointerCaptureLost(pArgs); - - // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - UpdateVisualState(); - } - - // Called when the IsEnabled property changes. - private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) - { - if (!e.NewValue) - { - ClearStateFlags(); - } - else - { - UpdateVisualState(); - } - - base.OnIsEnabledChanged(e); - } - - // Called when the control got focus. - protected override void OnGotFocus(RoutedEventArgs pArgs) - { - UpdateVisualState(); - } - - // LostFocus event handler. - protected override void OnLostFocus(RoutedEventArgs pArgs) - { - if (!m_bIsPointerLeftButtonDown) - { - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - m_bIsPressed = false; - } - - UpdateVisualState(); - } - - // KeyDown event handler. - protected override void OnKeyDown(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - var key = pArgs.Key; - handled = KeyPressMenuFlyout.KeyDown(key, this); - pArgs.Handled = handled; - } - } - - // KeyUp event handler. - protected override void OnKeyUp(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - var key = (pArgs.Key); - KeyPressMenuFlyout.KeyUp(key, this); - pArgs.Handled = true; - } - } - - // Handle the custom property changed event and call the OnPropertyChanged2 methods. - internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) - { - base.OnPropertyChanged2(args); - if (args.Property == UIElement.VisibilityProperty) - { - OnVisibilityChanged(); - } - else if (args.Property == MenuFlyoutItem.CommandProperty) - { - OnCommandChanged(args.OldValue, args.NewValue); - } - else if (args.Property == MenuFlyoutItem.CommandParameterProperty) - { - UpdateCanExecute(); - } - } - - // Update the visual states when the Visibility property is changed. - void OnVisibilityChanged() - { - Visibility visibility = Visibility; - - if (Visibility.Visible != visibility) - { - ClearStateFlags(); - } - } - - private protected override void OnLoaded() - { - base.OnLoaded(); - - if (m_epCanExecuteChangedHandler == null) - { - ICommand spCommand = Command; - - if (spCommand != null) - { - void OnCanExecuteChanged(object sender, object args) - { - UpdateCanExecute(); - } - - spCommand.CanExecuteChanged += OnCanExecuteChanged; - - m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); - } - } - - // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, - // we need to update our value now. - UpdateCanExecute(); - } - - private protected override void OnUnloaded() - { - base.OnUnloaded(); - - if (m_epCanExecuteChangedHandler != null) - { - ICommand spCommand = Command; - - if (spCommand != null) - { - m_epCanExecuteChangedHandler.Dispose(); - } - } - } - - // Called when the Command property changes. - void - OnCommandChanged( - object pOldValue, - object pNewValue) - { - // Remove handler for CanExecuteChanged from the old value - m_epCanExecuteChangedHandler?.Dispose(); - - if (pOldValue != null) - { - XamlUICommand oldCommand = pOldValue as XamlUICommand; - - if (oldCommand != null) - { - CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); - } - } - - // Subscribe to the CanExecuteChanged event on the new value - if (pNewValue != null) - { - var spNewCommand = pNewValue as ICommand; - - void OnCanExecuteUpdated(object sender, object args) - { - UpdateCanExecute(); - } - - spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; - m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); - - var newCommandAsUICommand = spNewCommand as XamlUICommand; - - if (newCommandAsUICommand != null) - { - CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); - CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); - CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); - CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); - CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); - } - } - - // Coerce the button enabled state with the CanExecute state of the command. - UpdateCanExecute(); - } - - // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. - void UpdateCanExecute() - { - ICommand spCommand; - object spCommandParameter; - bool canExecute = true; - - spCommand = Command; - if (spCommand != null) - { - spCommandParameter = CommandParameter; - canExecute = spCommand.CanExecute(spCommandParameter); - } - - SuppressIsEnabled(!canExecute); - } - - // Change to the correct visual state for the - private protected override void ChangeVisualState( - // true to use transitions when updating the visual state, false - // to snap directly to the new visual state. - bool bUseTransitions) - { - bool hasToggleMenuItem = false; - bool hasIconMenuItem = false; - bool hasMenuItemWithKeyboardAcceleratorText = false; - bool isKeyboardPresent = false; - - MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); - if (spPresenter != null) - { - hasToggleMenuItem = spPresenter.GetContainsToggleItems(); - hasIconMenuItem = spPresenter.GetContainsIconItems(); - hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); - } - - var bIsEnabled = IsEnabled; - var focusState = FocusState; - var shouldBeNarrow = GetShouldBeNarrow(); - - // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, - // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. - if (hasMenuItemWithKeyboardAcceleratorText) - { - // UNO TODO - // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); - isKeyboardPresent = true; - } - - // CommonStates - if (!bIsEnabled) - { - VisualStateManager.GoToState(this, "Disabled", bUseTransitions); - } - else if (m_bIsPressed) - { - VisualStateManager.GoToState(this, "Pressed", bUseTransitions); - } - else if (m_bIsPointerOver) - { - VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Normal", bUseTransitions); - } - - // FocusStates - if (FocusState.Unfocused != focusState && bIsEnabled) - { - if (FocusState.Pointer == focusState) - { - VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Focused", bUseTransitions); - } - } - else - { - VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); - } - - // CheckPlaceholderStates - if (hasToggleMenuItem && hasIconMenuItem) - { - VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); - } - else if (hasToggleMenuItem) - { - VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); - } - else if (hasIconMenuItem) - { - VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); - } - - // PaddingSizeStates - if (shouldBeNarrow) - { - VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); - } - - // We'll make the accelerator text visible if any item has accelerator text, - // as this causes the margin to be applied which reserves space, ensuring that accelerator text - // in one item won't be at the same horizontal position as label text in another item. - if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); - } - } - - // Clear flags relating to the visual state. Called when IsEnabled is set to false - // or when Visibility is set to Hidden or Collapsed. - void ClearStateFlags() - { - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - - UpdateVisualState(); - } - - // Create MenuFlyoutItemAutomationPeer to represent the - protected override AutomationPeer OnCreateAutomationPeer() - { - return new MenuFlyoutItemAutomationPeer(this); - } - - private protected override string GetPlainText() => Text; - - internal string KeyboardAcceleratorTextOverrideImpl - { - get - { - var pValue = KeyboardAcceleratorTextOverride; - - // If we have no keyboard accelerator text already provided by the app, - // then we'll see if we can ruct it ourselves based on keyboard accelerators - // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" - // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". - if (pValue == null) - { - pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); - - // If we were able to get a string representation from keyboard accelerators, - // then we should now set that as the value of KeyboardAcceleratorText. - if (pValue != null) - { - KeyboardAcceleratorTextOverrideImpl = pValue; - } - } - - return pValue; - } - set - { - KeyboardAcceleratorTextOverride = value; - } - } - - internal Size GetKeyboardAcceleratorTextDesiredSize() - { - var desiredSize = new Size(0, 0); - - if (!m_isTemplateApplied) - { - bool templateApplied = ApplyTemplate(); - m_isTemplateApplied = templateApplied; - } - - if (m_tpKeyboardAcceleratorTextBlock != null) - { - Thickness margin; - - m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; - margin = m_tpKeyboardAcceleratorTextBlock.Margin; - - desiredSize.Width -= (float)(margin.Left + margin.Right); - desiredSize.Height -= (float)(margin.Top + margin.Bottom); - } - - return desiredSize; - } - - internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) - { - if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) - { - m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; - - MenuFlyoutItemTemplateSettings templateSettings; - templateSettings = TemplateSettings; - - if (templateSettings == null) - { - MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); - TemplateSettings = templateSettingsImplementation; - templateSettings = templateSettingsImplementation; - } - - templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; - } - } - - internal virtual bool HasToggle() - { - return false; - } - } -} +//using System; +//using Uno.Client; +//using Uno.Disposables; +//using Windows.Foundation; +//using Microsoft.UI.Xaml.Automation; +//using Microsoft.UI.Xaml.Automation.Peers; +//using Microsoft.UI.Xaml.Controls.Primitives; +//using Microsoft.UI.Xaml.Input; +//using ICommand = System.Windows.Input.ICommand; +//using Microsoft.UI.Xaml.Markup; + +//#if HAS_UNO_WINUI +//using Microsoft.UI.Input; +//#else +//using Windows.Devices.Input; +//using Windows.UI.Input; +//#endif + +//namespace Microsoft.UI.Xaml.Controls +//{ +// [ContentProperty(Name = nameof(Text))] +// public partial class MenuFlyoutItem : MenuFlyoutItemBase +// { +// // Whether the pointer is currently over the +// bool m_bIsPointerOver = true; + +// // Whether the pointer is currently pressed over the +// internal bool m_bIsPressed = true; + +// // Whether the pointer's left button is currently down. +// internal bool m_bIsPointerLeftButtonDown = true; + +// // True if the SPACE or ENTER key is currently pressed, false otherwise. +// internal bool m_bIsSpaceOrEnterKeyDown = true; + +// // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. +// internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; + +// // On pointer released we perform some actions depending on control. We decide to whether to perform them +// // depending on some parameters including but not limited to whether released is followed by a pressed, which +// // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. +// bool m_shouldPerformActions = true; + +// // Event pointer for the ICommand.CanExecuteChanged event. +// IDisposable m_epCanExecuteChangedHandler; + +// // UNO TODO +// // Event pointer for the Loaded event. +// // IDisposable m_epLoadedHandler; + +// // UNO TODO +// // IDisposable m_epMenuFlyoutItemClickEventCallback; + +// double m_maxKeyboardAcceleratorTextWidth; +// TextBlock m_tpKeyboardAcceleratorTextBlock; + +// bool m_isTemplateApplied; + +// #region CommandParameter + +// public object CommandParameter +// { +// get { return (object)GetValue(CommandParameterProperty); } +// set { SetValue(CommandParameterProperty, value); } +// } + +// public static DependencyProperty CommandParameterProperty { get; } = +// DependencyProperty.Register( +// "CommandParameter", typeof(object), +// typeof(Controls.MenuFlyoutItem), +// new FrameworkPropertyMetadata(default(object))); + +// #endregion + +// #region Command + +// public ICommand Command +// { +// get { return (ICommand)GetValue(CommandProperty); } +// set { SetValue(CommandProperty, value); } +// } + +// public static DependencyProperty CommandProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Command), +// propertyType: typeof(ICommand), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); + +// #endregion + +// #region Text + +// public string Text +// { +// get { return (string)GetValue(TextProperty) ?? ""; } +// set { SetValue(TextProperty, value); } +// } + +// public static DependencyProperty TextProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Text), +// propertyType: typeof(string), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(string))); + +// #endregion + +// public IconElement Icon +// { +// get => (IconElement)this.GetValue(IconProperty); +// set => this.SetValue(IconProperty, value); +// } + +// public static DependencyProperty IconProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Icon), +// propertyType: typeof(IconElement), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); + +// public string KeyboardAcceleratorTextOverride +// { +// get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; +// set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); +// } + +// public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = +// DependencyProperty.Register( +// name: nameof(KeyboardAcceleratorTextOverride), +// propertyType: typeof(string), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(string))); + +// public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } + +//#pragma warning disable CS0108 +// public event RoutedEventHandler Click; +//#pragma warning restore CS0108 + +// internal void InvokeClick() +// { +// Click?.Invoke(this, new RoutedEventArgs(this)); +// Command.ExecuteIfPossible(this.CommandParameter); +// } + +// public MenuFlyoutItem() +// { +// m_bIsPointerOver = false; +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; +// m_shouldPerformActions = false; + +// DefaultStyleKey = typeof(MenuFlyoutItem); + +// Initialize(); +// } + +// // Prepares object's state +// void Initialize() +// { +// Loaded += (s, e) => ClearStateFlags(); + +// this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); +// } + +// // Apply a template to the + +// protected override void OnApplyTemplate() +// { +// base.OnApplyTemplate(); + +// TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; +// m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; + +// SuppressIsEnabled(false); +// UpdateCanExecute(); + +// // Sync the logical and visual states of the control +// UpdateVisualState(); +// } + +// // PointerPressed event handler. +// protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerPressed(pArgs); +// var handled = pArgs.Handled; +// if (!handled) +// { +// PointerPoint spPointerPoint; +// PointerPointProperties spPointerProperties; +// spPointerPoint = pArgs.GetCurrentPoint(this); +// spPointerProperties = spPointerPoint.Properties; +// var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; +// if (bIsLeftButtonPressed) +// { +// m_bIsPointerLeftButtonDown = true; +// m_bIsPressed = true; + +// pArgs.Handled = true; +// UpdateVisualState(); +// } +// } +// } + +// // PointerReleased event handler. +// protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerReleased(pArgs); + +// bool handled = false; + +// handled = pArgs.Handled; +// if (!handled) +// { +// m_bIsPointerLeftButtonDown = false; + +// m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; + +//#if false +// // UNO TODO +// if (m_shouldPerformActions) +// { +// GestureModes gestureFollowing = GestureModes.None; + +// m_bIsPressed = false; + +// gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); + +// // Note that we are intentionally NOT handling the args +// // if we do not fall through here because basically we are no_opting in that case. +// if (gestureFollowing != GestureModes.RightTapped) +// { +// pArgs.Handled = true; +// PerformPointerUpAction(); +// } +// } +//#else +// PerformPointerUpAction(); +//#endif +// } +// } + +// private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// PerformPointerUpAction(); +// pArgs.Handled = true; +// } +// } + +// // Contains the logic to be employed if we decide to handle pointer released. +// void PerformPointerUpAction() +// { +// if (m_shouldPerformActions) +// { +// Focus(FocusState.Pointer); +// Invoke(); +// } +// } + +// // Performs appropriate actions upon a mouse/keyboard invocation of a +// internal virtual void Invoke() +// { +// RoutedEventArgs spArgs; +// MenuFlyoutPresenter spParentMenuFlyoutPresenter; + +// // Create the args +// spArgs = new RoutedEventArgs(); +// spArgs.OriginalSource = this; + +// // Raise the event +// Click?.Invoke(this, spArgs); + +// // UNO TODO +// //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); + +// // Execute command associated with the button +// ExecuteCommand(); + +// bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; +// if (!shouldPreventDismissOnPointer) +// { +// // Close the MenuFlyout. +// spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); +// if (spParentMenuFlyoutPresenter != null) +// { +// IMenu owningMenu; + +// owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; +// if (owningMenu != null) +// { +// // We need to make close all menu flyout sub items before we hide the parent menu flyout, +// // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a +// // significant amount of time before closing the sub menu. +// (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); +// owningMenu.Close(); +// } +// } +// } + +// ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); +// } + +// // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) +// //{ +// // DependencyObject peer = pMenuFlyoutItem; + +// // if (peer == null) +// // { +// // return; +// // } + +// // MenuFlyoutItem peerAsMenuFlyoutItem; +// // (peer.As(&peerAsMenuFlyoutItem)); +// // IFCPTR_RETURN(peerAsMenuFlyoutItem); + +// // (peerAsAddProofingItemHandler(eventHandler)); + +// // return S_OK; +// //} + +// // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) +// //{ +// // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) +// // { +// // DependencyObject spSender; +// // EventArgs spArgs; + +// // IFCPTR_RETURN(pSender); +// // IFCPTR_RETURN(pArgs); + +// // (ctl.do_query_interface(spSender, pSender)); +// // (ctl.do_query_interface(spArgs, pArgs)); + +// // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); + +// // return S_OK; +// // })); + +// // return S_OK; +// //} + +// // Executes Command if CanExecute() returns true. +// void ExecuteCommand() +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// object spCommandParameter; +// bool canExecute; + +// spCommandParameter = CommandParameter; +// canExecute = spCommand.CanExecute(spCommandParameter); +// if (canExecute) +// { +// spCommand.Execute(spCommandParameter); +// } +// } +// } + +// // PointerEnter event handler. +// protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerEntered(pArgs); + +// MenuFlyoutPresenter spParentPresenter; + +// m_bIsPointerOver = true; + +// spParentPresenter = GetParentMenuFlyoutPresenter(); +// if (spParentPresenter != null) +// { +// IMenuPresenter subPresenter; + +// subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; +// if (subPresenter != null) +// { +// ISubMenuOwner subPresenterOwner; + +// subPresenterOwner = subPresenter.Owner; + +// if (subPresenterOwner != null) +// { +// subPresenterOwner.DelayCloseSubMenu(); +// } +// } +// } + +// UpdateVisualState(); +// } + +// // PointerExited event handler. +// protected override void OnPointerExited(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerExited(pArgs); + +// // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// UpdateVisualState(); +// } + +// // PointerCaptureLost event handler. +// protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerCaptureLost(pArgs); + +// // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// UpdateVisualState(); +// } + +// // Called when the IsEnabled property changes. +// private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) +// { +// if (!e.NewValue) +// { +// ClearStateFlags(); +// } +// else +// { +// UpdateVisualState(); +// } + +// base.OnIsEnabledChanged(e); +// } + +// // Called when the control got focus. +// protected override void OnGotFocus(RoutedEventArgs pArgs) +// { +// UpdateVisualState(); +// } + +// // LostFocus event handler. +// protected override void OnLostFocus(RoutedEventArgs pArgs) +// { +// if (!m_bIsPointerLeftButtonDown) +// { +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; +// m_bIsPressed = false; +// } + +// UpdateVisualState(); +// } + +// // KeyDown event handler. +// protected override void OnKeyDown(KeyRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// var key = pArgs.Key; +// handled = KeyPressMenuFlyout.KeyDown(key, this); +// pArgs.Handled = handled; +// } +// } + +// // KeyUp event handler. +// protected override void OnKeyUp(KeyRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// var key = (pArgs.Key); +// KeyPressMenuFlyout.KeyUp(key, this); +// pArgs.Handled = true; +// } +// } + +// // Handle the custom property changed event and call the OnPropertyChanged2 methods. +// internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) +// { +// if (args.Property == UIElement.VisibilityProperty) +// { +// OnVisibilityChanged(); +// } +// else if (args.Property == MenuFlyoutItem.CommandProperty) +// { +// OnCommandChanged(args.OldValue, args.NewValue); +// } +// else if (args.Property == MenuFlyoutItem.CommandParameterProperty) +// { +// UpdateCanExecute(); +// } +// } + +// // Update the visual states when the Visibility property is changed. +// void OnVisibilityChanged() +// { +// Visibility visibility = Visibility; + +// if (Visibility.Visible != visibility) +// { +// ClearStateFlags(); +// } +// } + +// private protected override void OnLoaded() +// { +// base.OnLoaded(); + +// if (m_epCanExecuteChangedHandler == null) +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// void OnCanExecuteChanged(object sender, object args) +// { +// UpdateCanExecute(); +// } + +// spCommand.CanExecuteChanged += OnCanExecuteChanged; + +// m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); +// } +// } + +// // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, +// // we need to update our value now. +// UpdateCanExecute(); +// } + +// private protected override void OnUnloaded() +// { +// base.OnUnloaded(); + +// if (m_epCanExecuteChangedHandler != null) +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// m_epCanExecuteChangedHandler.Dispose(); +// } +// } +// } + +// // Called when the Command property changes. +// void +// OnCommandChanged( +// object pOldValue, +// object pNewValue) +// { +// // Remove handler for CanExecuteChanged from the old value +// m_epCanExecuteChangedHandler?.Dispose(); + +// if (pOldValue != null) +// { +// XamlUICommand oldCommand = pOldValue as XamlUICommand; + +// if (oldCommand != null) +// { +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); +// } +// } + +// // Subscribe to the CanExecuteChanged event on the new value +// if (pNewValue != null) +// { +// var spNewCommand = pNewValue as ICommand; + +// void OnCanExecuteUpdated(object sender, object args) +// { +// UpdateCanExecute(); +// } + +// spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; +// m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); + +// var newCommandAsUICommand = spNewCommand as XamlUICommand; + +// if (newCommandAsUICommand != null) +// { +// CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); +// CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); +// CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); +// CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); +// CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); +// } +// } + +// // Coerce the button enabled state with the CanExecute state of the command. +// UpdateCanExecute(); +// } + +// // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. +// void UpdateCanExecute() +// { +// ICommand spCommand; +// object spCommandParameter; +// bool canExecute = true; + +// spCommand = Command; +// if (spCommand != null) +// { +// spCommandParameter = CommandParameter; +// canExecute = spCommand.CanExecute(spCommandParameter); +// } + +// SuppressIsEnabled(!canExecute); +// } + +// // Change to the correct visual state for the +// private protected override void ChangeVisualState( +// // true to use transitions when updating the visual state, false +// // to snap directly to the new visual state. +// bool bUseTransitions) +// { +// bool hasToggleMenuItem = false; +// bool hasIconMenuItem = false; +// bool hasMenuItemWithKeyboardAcceleratorText = false; +// bool isKeyboardPresent = false; + +// MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); +// if (spPresenter != null) +// { +// hasToggleMenuItem = spPresenter.GetContainsToggleItems(); +// hasIconMenuItem = spPresenter.GetContainsIconItems(); +// hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); +// } + +// var bIsEnabled = IsEnabled; +// var focusState = FocusState; +// var shouldBeNarrow = GetShouldBeNarrow(); + +// // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, +// // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. +// if (hasMenuItemWithKeyboardAcceleratorText) +// { +// // UNO TODO +// // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); +// isKeyboardPresent = true; +// } + +// // CommonStates +// if (!bIsEnabled) +// { +// VisualStateManager.GoToState(this, "Disabled", bUseTransitions); +// } +// else if (m_bIsPressed) +// { +// VisualStateManager.GoToState(this, "Pressed", bUseTransitions); +// } +// else if (m_bIsPointerOver) +// { +// VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "Normal", bUseTransitions); +// } + +// // FocusStates +// if (FocusState.Unfocused != focusState && bIsEnabled) +// { +// if (FocusState.Pointer == focusState) +// { +// VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "Focused", bUseTransitions); +// } +// } +// else +// { +// VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); +// } + +// // CheckPlaceholderStates +// if (hasToggleMenuItem && hasIconMenuItem) +// { +// VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); +// } +// else if (hasToggleMenuItem) +// { +// VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); +// } +// else if (hasIconMenuItem) +// { +// VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); +// } + +// // PaddingSizeStates +// if (shouldBeNarrow) +// { +// VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); +// } + +// // We'll make the accelerator text visible if any item has accelerator text, +// // as this causes the margin to be applied which reserves space, ensuring that accelerator text +// // in one item won't be at the same horizontal position as label text in another item. +// if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) +// { +// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); +// } +// } + +// // Clear flags relating to the visual state. Called when IsEnabled is set to false +// // or when Visibility is set to Hidden or Collapsed. +// void ClearStateFlags() +// { +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; + +// UpdateVisualState(); +// } + +// // Create MenuFlyoutItemAutomationPeer to represent the +// protected override AutomationPeer OnCreateAutomationPeer() +// { +// return new MenuFlyoutItemAutomationPeer(this); +// } + +// private protected override string GetPlainText() => Text; + +// internal string KeyboardAcceleratorTextOverrideImpl +// { +// get +// { +// var pValue = KeyboardAcceleratorTextOverride; + +// // If we have no keyboard accelerator text already provided by the app, +// // then we'll see if we can ruct it ourselves based on keyboard accelerators +// // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" +// // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". +// if (pValue == null) +// { +// pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); + +// // If we were able to get a string representation from keyboard accelerators, +// // then we should now set that as the value of KeyboardAcceleratorText. +// if (pValue != null) +// { +// KeyboardAcceleratorTextOverrideImpl = pValue; +// } +// } + +// return pValue; +// } +// set +// { +// KeyboardAcceleratorTextOverride = value; +// } +// } + +// internal Size GetKeyboardAcceleratorTextDesiredSize() +// { +// var desiredSize = new Size(0, 0); + +// if (!m_isTemplateApplied) +// { +// bool templateApplied = ApplyTemplate(); +// m_isTemplateApplied = templateApplied; +// } + +// if (m_tpKeyboardAcceleratorTextBlock != null) +// { +// Thickness margin; + +// m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); +// desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; +// margin = m_tpKeyboardAcceleratorTextBlock.Margin; + +// desiredSize.Width -= (float)(margin.Left + margin.Right); +// desiredSize.Height -= (float)(margin.Top + margin.Bottom); +// } + +// return desiredSize; +// } + +// internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) +// { +// if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) +// { +// m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; + +// MenuFlyoutItemTemplateSettings templateSettings; +// templateSettings = TemplateSettings; + +// if (templateSettings == null) +// { +// MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); +// TemplateSettings = templateSettingsImplementation; +// templateSettings = templateSettingsImplementation; +// } + +// templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; +// } +// } + +// internal virtual bool HasToggle() +// { +// return false; +// } +// } +//} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs index 9c3e701fd3a5..42826f7a255e 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs @@ -1,828 +1,828 @@ -using System; -using Uno.Client; -using Uno.Disposables; -using Windows.Foundation; -using Microsoft.UI.Xaml.Automation; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using ICommand = System.Windows.Input.ICommand; -using Microsoft.UI.Xaml.Markup; - -#if HAS_UNO_WINUI -using Microsoft.UI.Input; -#else -using Windows.Devices.Input; -using Windows.UI.Input; -#endif - -namespace Microsoft.UI.Xaml.Controls -{ - [ContentProperty(Name = nameof(Text))] - public partial class MenuFlyoutItem : MenuFlyoutItemBase - { - // Whether the pointer is currently over the - bool m_bIsPointerOver = true; - - // Whether the pointer is currently pressed over the - internal bool m_bIsPressed = true; - - // Whether the pointer's left button is currently down. - internal bool m_bIsPointerLeftButtonDown = true; - - // True if the SPACE or ENTER key is currently pressed, false otherwise. - internal bool m_bIsSpaceOrEnterKeyDown = true; - - // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. - internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; - - // On pointer released we perform some actions depending on control. We decide to whether to perform them - // depending on some parameters including but not limited to whether released is followed by a pressed, which - // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. - bool m_shouldPerformActions = true; - - // Event pointer for the ICommand.CanExecuteChanged event. - IDisposable m_epCanExecuteChangedHandler; - - // UNO TODO - // Event pointer for the Loaded event. - // IDisposable m_epLoadedHandler; - - // UNO TODO - // IDisposable m_epMenuFlyoutItemClickEventCallback; - - double m_maxKeyboardAcceleratorTextWidth; - TextBlock m_tpKeyboardAcceleratorTextBlock; - - bool m_isTemplateApplied; - - #region CommandParameter - - public object CommandParameter - { - get { return (object)GetValue(CommandParameterProperty); } - set { SetValue(CommandParameterProperty, value); } - } - - public static DependencyProperty CommandParameterProperty { get; } = - DependencyProperty.Register( - "CommandParameter", typeof(object), - typeof(Controls.MenuFlyoutItem), - new FrameworkPropertyMetadata(default(object))); - - #endregion - - #region Command - - public ICommand Command - { - get { return (ICommand)GetValue(CommandProperty); } - set { SetValue(CommandProperty, value); } - } - - public static DependencyProperty CommandProperty { get; } = - DependencyProperty.Register( - name: nameof(Command), - propertyType: typeof(ICommand), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); - - #endregion - - #region Text - - public string Text - { - get { return (string)GetValue(TextProperty) ?? ""; } - set { SetValue(TextProperty, value); } - } - - public static DependencyProperty TextProperty { get; } = - DependencyProperty.Register( - name: nameof(Text), - propertyType: typeof(string), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(string))); - - #endregion - - public IconElement Icon - { - get => (IconElement)this.GetValue(IconProperty); - set => this.SetValue(IconProperty, value); - } - - public static DependencyProperty IconProperty { get; } = - DependencyProperty.Register( - name: nameof(Icon), - propertyType: typeof(IconElement), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); - - public string KeyboardAcceleratorTextOverride - { - get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; - set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); - } - - public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = - DependencyProperty.Register( - name: nameof(KeyboardAcceleratorTextOverride), - propertyType: typeof(string), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(string))); - - public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } - -#pragma warning disable CS0108 - public event RoutedEventHandler Click; -#pragma warning restore CS0108 - - internal void InvokeClick() - { - Click?.Invoke(this, new RoutedEventArgs(this)); - Command.ExecuteIfPossible(this.CommandParameter); - } - - public MenuFlyoutItem() - { - m_bIsPointerOver = false; - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - m_shouldPerformActions = false; - - DefaultStyleKey = typeof(MenuFlyoutItem); - - Initialize(); - } - - // Prepares object's state - void Initialize() - { - Loaded += (s, e) => ClearStateFlags(); - - this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); - } - - // Apply a template to the - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; - m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; - - SuppressIsEnabled(false); - UpdateCanExecute(); - - // Sync the logical and visual states of the control - UpdateVisualState(); - } - - // PointerPressed event handler. - protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) - { - base.OnPointerPressed(pArgs); - var handled = pArgs.Handled; - if (!handled) - { - PointerPoint spPointerPoint; - PointerPointProperties spPointerProperties; - spPointerPoint = pArgs.GetCurrentPoint(this); - spPointerProperties = spPointerPoint.Properties; - var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; - if (bIsLeftButtonPressed) - { - m_bIsPointerLeftButtonDown = true; - m_bIsPressed = true; - - pArgs.Handled = true; - UpdateVisualState(); - } - } - } - - // PointerReleased event handler. - protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) - { - base.OnPointerReleased(pArgs); - - bool handled = false; - - handled = pArgs.Handled; - if (!handled) - { - m_bIsPointerLeftButtonDown = false; - - m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; - -#if false - // UNO TODO - if (m_shouldPerformActions) - { - GestureModes gestureFollowing = GestureModes.None; - - m_bIsPressed = false; - - gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); - - // Note that we are intentionally NOT handling the args - // if we do not fall through here because basically we are no_opting in that case. - if (gestureFollowing != GestureModes.RightTapped) - { - pArgs.Handled = true; - PerformPointerUpAction(); - } - } -#else - PerformPointerUpAction(); -#endif - } - } - - private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - PerformPointerUpAction(); - pArgs.Handled = true; - } - } - - // Contains the logic to be employed if we decide to handle pointer released. - void PerformPointerUpAction() - { - if (m_shouldPerformActions) - { - Focus(FocusState.Pointer); - Invoke(); - } - } - - // Performs appropriate actions upon a mouse/keyboard invocation of a - internal virtual void Invoke() - { - RoutedEventArgs spArgs; - MenuFlyoutPresenter spParentMenuFlyoutPresenter; - - // Create the args - spArgs = new RoutedEventArgs(); - spArgs.OriginalSource = this; - - // Raise the event - Click?.Invoke(this, spArgs); - - // UNO TODO - //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); - - // Execute command associated with the button - ExecuteCommand(); - - bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; - if (!shouldPreventDismissOnPointer) - { - // Close the MenuFlyout. - spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); - if (spParentMenuFlyoutPresenter != null) - { - IMenu owningMenu; - - owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; - if (owningMenu != null) - { - // We need to make close all menu flyout sub items before we hide the parent menu flyout, - // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a - // significant amount of time before closing the sub menu. - (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); - owningMenu.Close(); - } - } - } - - ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); - } - - // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) - //{ - // DependencyObject peer = pMenuFlyoutItem; - - // if (peer == null) - // { - // return; - // } - - // MenuFlyoutItem peerAsMenuFlyoutItem; - // (peer.As(&peerAsMenuFlyoutItem)); - // IFCPTR_RETURN(peerAsMenuFlyoutItem); - - // (peerAsAddProofingItemHandler(eventHandler)); - - // return S_OK; - //} - - // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) - //{ - // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) - // { - // DependencyObject spSender; - // EventArgs spArgs; - - // IFCPTR_RETURN(pSender); - // IFCPTR_RETURN(pArgs); - - // (ctl.do_query_interface(spSender, pSender)); - // (ctl.do_query_interface(spArgs, pArgs)); - - // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); - - // return S_OK; - // })); - - // return S_OK; - //} - - // Executes Command if CanExecute() returns true. - void ExecuteCommand() - { - ICommand spCommand = Command; - - if (spCommand != null) - { - object spCommandParameter; - bool canExecute; - - spCommandParameter = CommandParameter; - canExecute = spCommand.CanExecute(spCommandParameter); - if (canExecute) - { - spCommand.Execute(spCommandParameter); - } - } - } - - // PointerEnter event handler. - protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) - { - base.OnPointerEntered(pArgs); - - MenuFlyoutPresenter spParentPresenter; - - m_bIsPointerOver = true; - - spParentPresenter = GetParentMenuFlyoutPresenter(); - if (spParentPresenter != null) - { - IMenuPresenter subPresenter; - - subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; - if (subPresenter != null) - { - ISubMenuOwner subPresenterOwner; - - subPresenterOwner = subPresenter.Owner; - - if (subPresenterOwner != null) - { - subPresenterOwner.DelayCloseSubMenu(); - } - } - } - - UpdateVisualState(); - } - - // PointerExited event handler. - protected override void OnPointerExited(PointerRoutedEventArgs pArgs) - { - base.OnPointerExited(pArgs); - - // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - UpdateVisualState(); - } - - // PointerCaptureLost event handler. - protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) - { - base.OnPointerCaptureLost(pArgs); - - // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - UpdateVisualState(); - } - - // Called when the IsEnabled property changes. - private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) - { - if (!e.NewValue) - { - ClearStateFlags(); - } - else - { - UpdateVisualState(); - } - - base.OnIsEnabledChanged(e); - } - - // Called when the control got focus. - protected override void OnGotFocus(RoutedEventArgs pArgs) - { - UpdateVisualState(); - } - - // LostFocus event handler. - protected override void OnLostFocus(RoutedEventArgs pArgs) - { - if (!m_bIsPointerLeftButtonDown) - { - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - m_bIsPressed = false; - } - - UpdateVisualState(); - } - - // KeyDown event handler. - protected override void OnKeyDown(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - var key = pArgs.Key; - handled = KeyPressMenuFlyout.KeyDown(key, this); - pArgs.Handled = handled; - } - } - - // KeyUp event handler. - protected override void OnKeyUp(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - var key = (pArgs.Key); - KeyPressMenuFlyout.KeyUp(key, this); - pArgs.Handled = true; - } - } - - // Handle the custom property changed event and call the OnPropertyChanged2 methods. - internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) - { - if (args.Property == UIElement.VisibilityProperty) - { - OnVisibilityChanged(); - } - else if (args.Property == MenuFlyoutItem.CommandProperty) - { - OnCommandChanged(args.OldValue, args.NewValue); - } - else if (args.Property == MenuFlyoutItem.CommandParameterProperty) - { - UpdateCanExecute(); - } - } - - // Update the visual states when the Visibility property is changed. - void OnVisibilityChanged() - { - Visibility visibility = Visibility; - - if (Visibility.Visible != visibility) - { - ClearStateFlags(); - } - } - - private protected override void OnLoaded() - { - base.OnLoaded(); - - if (m_epCanExecuteChangedHandler == null) - { - ICommand spCommand = Command; - - if (spCommand != null) - { - void OnCanExecuteChanged(object sender, object args) - { - UpdateCanExecute(); - } - - spCommand.CanExecuteChanged += OnCanExecuteChanged; - - m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); - } - } - - // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, - // we need to update our value now. - UpdateCanExecute(); - } - - private protected override void OnUnloaded() - { - base.OnUnloaded(); - - if (m_epCanExecuteChangedHandler != null) - { - ICommand spCommand = Command; - - if (spCommand != null) - { - m_epCanExecuteChangedHandler.Dispose(); - } - } - } - - // Called when the Command property changes. - void - OnCommandChanged( - object pOldValue, - object pNewValue) - { - // Remove handler for CanExecuteChanged from the old value - m_epCanExecuteChangedHandler?.Dispose(); - - if (pOldValue != null) - { - XamlUICommand oldCommand = pOldValue as XamlUICommand; - - if (oldCommand != null) - { - CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); - } - } - - // Subscribe to the CanExecuteChanged event on the new value - if (pNewValue != null) - { - var spNewCommand = pNewValue as ICommand; - - void OnCanExecuteUpdated(object sender, object args) - { - UpdateCanExecute(); - } - - spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; - m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); - - var newCommandAsUICommand = spNewCommand as XamlUICommand; - - if (newCommandAsUICommand != null) - { - CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); - CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); - CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); - CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); - CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); - } - } - - // Coerce the button enabled state with the CanExecute state of the command. - UpdateCanExecute(); - } - - // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. - void UpdateCanExecute() - { - ICommand spCommand; - object spCommandParameter; - bool canExecute = true; - - spCommand = Command; - if (spCommand != null) - { - spCommandParameter = CommandParameter; - canExecute = spCommand.CanExecute(spCommandParameter); - } - - SuppressIsEnabled(!canExecute); - } - - // Change to the correct visual state for the - private protected override void ChangeVisualState( - // true to use transitions when updating the visual state, false - // to snap directly to the new visual state. - bool bUseTransitions) - { - bool hasToggleMenuItem = false; - bool hasIconMenuItem = false; - bool hasMenuItemWithKeyboardAcceleratorText = false; - bool isKeyboardPresent = false; - - MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); - if (spPresenter != null) - { - hasToggleMenuItem = spPresenter.GetContainsToggleItems(); - hasIconMenuItem = spPresenter.GetContainsIconItems(); - hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); - } - - var bIsEnabled = IsEnabled; - var focusState = FocusState; - var shouldBeNarrow = GetShouldBeNarrow(); - - // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, - // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. - if (hasMenuItemWithKeyboardAcceleratorText) - { - // UNO TODO - // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); - isKeyboardPresent = true; - } - - // CommonStates - if (!bIsEnabled) - { - VisualStateManager.GoToState(this, "Disabled", bUseTransitions); - } - else if (m_bIsPressed) - { - VisualStateManager.GoToState(this, "Pressed", bUseTransitions); - } - else if (m_bIsPointerOver) - { - VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Normal", bUseTransitions); - } - - // FocusStates - if (FocusState.Unfocused != focusState && bIsEnabled) - { - if (FocusState.Pointer == focusState) - { - VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Focused", bUseTransitions); - } - } - else - { - VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); - } - - // CheckPlaceholderStates - if (hasToggleMenuItem && hasIconMenuItem) - { - VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); - } - else if (hasToggleMenuItem) - { - VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); - } - else if (hasIconMenuItem) - { - VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); - } - - // PaddingSizeStates - if (shouldBeNarrow) - { - VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); - } - - // We'll make the accelerator text visible if any item has accelerator text, - // as this causes the margin to be applied which reserves space, ensuring that accelerator text - // in one item won't be at the same horizontal position as label text in another item. - if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); - } - } - - // Clear flags relating to the visual state. Called when IsEnabled is set to false - // or when Visibility is set to Hidden or Collapsed. - void ClearStateFlags() - { - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - - UpdateVisualState(); - } - - // Create MenuFlyoutItemAutomationPeer to represent the - protected override AutomationPeer OnCreateAutomationPeer() - { - return new MenuFlyoutItemAutomationPeer(this); - } - - private protected override string GetPlainText() => Text; - - internal string KeyboardAcceleratorTextOverrideImpl - { - get - { - var pValue = KeyboardAcceleratorTextOverride; - - // If we have no keyboard accelerator text already provided by the app, - // then we'll see if we can ruct it ourselves based on keyboard accelerators - // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" - // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". - if (pValue == null) - { - pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); - - // If we were able to get a string representation from keyboard accelerators, - // then we should now set that as the value of KeyboardAcceleratorText. - if (pValue != null) - { - KeyboardAcceleratorTextOverrideImpl = pValue; - } - } - - return pValue; - } - set - { - KeyboardAcceleratorTextOverride = value; - } - } - - internal Size GetKeyboardAcceleratorTextDesiredSize() - { - var desiredSize = new Size(0, 0); - - if (!m_isTemplateApplied) - { - bool templateApplied = ApplyTemplate(); - m_isTemplateApplied = templateApplied; - } - - if (m_tpKeyboardAcceleratorTextBlock != null) - { - Thickness margin; - - m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; - margin = m_tpKeyboardAcceleratorTextBlock.Margin; - - desiredSize.Width -= (float)(margin.Left + margin.Right); - desiredSize.Height -= (float)(margin.Top + margin.Bottom); - } - - return desiredSize; - } - - internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) - { - if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) - { - m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; - - MenuFlyoutItemTemplateSettings templateSettings; - templateSettings = TemplateSettings; - - if (templateSettings == null) - { - MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); - TemplateSettings = templateSettingsImplementation; - templateSettings = templateSettingsImplementation; - } - - templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; - } - } - - internal virtual bool HasToggle() - { - return false; - } - } -} +//using System; +//using Uno.Client; +//using Uno.Disposables; +//using Windows.Foundation; +//using Microsoft.UI.Xaml.Automation; +//using Microsoft.UI.Xaml.Automation.Peers; +//using Microsoft.UI.Xaml.Controls.Primitives; +//using Microsoft.UI.Xaml.Input; +//using ICommand = System.Windows.Input.ICommand; +//using Microsoft.UI.Xaml.Markup; + +//#if HAS_UNO_WINUI +//using Microsoft.UI.Input; +//#else +//using Windows.Devices.Input; +//using Windows.UI.Input; +//#endif + +//namespace Microsoft.UI.Xaml.Controls +//{ +// [ContentProperty(Name = nameof(Text))] +// public partial class MenuFlyoutItem : MenuFlyoutItemBase +// { +// // Whether the pointer is currently over the +// bool m_bIsPointerOver = true; + +// // Whether the pointer is currently pressed over the +// internal bool m_bIsPressed = true; + +// // Whether the pointer's left button is currently down. +// internal bool m_bIsPointerLeftButtonDown = true; + +// // True if the SPACE or ENTER key is currently pressed, false otherwise. +// internal bool m_bIsSpaceOrEnterKeyDown = true; + +// // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. +// internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; + +// // On pointer released we perform some actions depending on control. We decide to whether to perform them +// // depending on some parameters including but not limited to whether released is followed by a pressed, which +// // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. +// bool m_shouldPerformActions = true; + +// // Event pointer for the ICommand.CanExecuteChanged event. +// IDisposable m_epCanExecuteChangedHandler; + +// // UNO TODO +// // Event pointer for the Loaded event. +// // IDisposable m_epLoadedHandler; + +// // UNO TODO +// // IDisposable m_epMenuFlyoutItemClickEventCallback; + +// double m_maxKeyboardAcceleratorTextWidth; +// TextBlock m_tpKeyboardAcceleratorTextBlock; + +// bool m_isTemplateApplied; + +// #region CommandParameter + +// public object CommandParameter +// { +// get { return (object)GetValue(CommandParameterProperty); } +// set { SetValue(CommandParameterProperty, value); } +// } + +// public static DependencyProperty CommandParameterProperty { get; } = +// DependencyProperty.Register( +// "CommandParameter", typeof(object), +// typeof(Controls.MenuFlyoutItem), +// new FrameworkPropertyMetadata(default(object))); + +// #endregion + +// #region Command + +// public ICommand Command +// { +// get { return (ICommand)GetValue(CommandProperty); } +// set { SetValue(CommandProperty, value); } +// } + +// public static DependencyProperty CommandProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Command), +// propertyType: typeof(ICommand), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); + +// #endregion + +// #region Text + +// public string Text +// { +// get { return (string)GetValue(TextProperty) ?? ""; } +// set { SetValue(TextProperty, value); } +// } + +// public static DependencyProperty TextProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Text), +// propertyType: typeof(string), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(string))); + +// #endregion + +// public IconElement Icon +// { +// get => (IconElement)this.GetValue(IconProperty); +// set => this.SetValue(IconProperty, value); +// } + +// public static DependencyProperty IconProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Icon), +// propertyType: typeof(IconElement), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); + +// public string KeyboardAcceleratorTextOverride +// { +// get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; +// set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); +// } + +// public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = +// DependencyProperty.Register( +// name: nameof(KeyboardAcceleratorTextOverride), +// propertyType: typeof(string), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(string))); + +// public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } + +//#pragma warning disable CS0108 +// public event RoutedEventHandler Click; +//#pragma warning restore CS0108 + +// internal void InvokeClick() +// { +// Click?.Invoke(this, new RoutedEventArgs(this)); +// Command.ExecuteIfPossible(this.CommandParameter); +// } + +// public MenuFlyoutItem() +// { +// m_bIsPointerOver = false; +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; +// m_shouldPerformActions = false; + +// DefaultStyleKey = typeof(MenuFlyoutItem); + +// Initialize(); +// } + +// // Prepares object's state +// void Initialize() +// { +// Loaded += (s, e) => ClearStateFlags(); + +// this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); +// } + +// // Apply a template to the + +// protected override void OnApplyTemplate() +// { +// base.OnApplyTemplate(); + +// TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; +// m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; + +// SuppressIsEnabled(false); +// UpdateCanExecute(); + +// // Sync the logical and visual states of the control +// UpdateVisualState(); +// } + +// // PointerPressed event handler. +// protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerPressed(pArgs); +// var handled = pArgs.Handled; +// if (!handled) +// { +// PointerPoint spPointerPoint; +// PointerPointProperties spPointerProperties; +// spPointerPoint = pArgs.GetCurrentPoint(this); +// spPointerProperties = spPointerPoint.Properties; +// var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; +// if (bIsLeftButtonPressed) +// { +// m_bIsPointerLeftButtonDown = true; +// m_bIsPressed = true; + +// pArgs.Handled = true; +// UpdateVisualState(); +// } +// } +// } + +// // PointerReleased event handler. +// protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerReleased(pArgs); + +// bool handled = false; + +// handled = pArgs.Handled; +// if (!handled) +// { +// m_bIsPointerLeftButtonDown = false; + +// m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; + +//#if false +// // UNO TODO +// if (m_shouldPerformActions) +// { +// GestureModes gestureFollowing = GestureModes.None; + +// m_bIsPressed = false; + +// gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); + +// // Note that we are intentionally NOT handling the args +// // if we do not fall through here because basically we are no_opting in that case. +// if (gestureFollowing != GestureModes.RightTapped) +// { +// pArgs.Handled = true; +// PerformPointerUpAction(); +// } +// } +//#else +// PerformPointerUpAction(); +//#endif +// } +// } + +// private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// PerformPointerUpAction(); +// pArgs.Handled = true; +// } +// } + +// // Contains the logic to be employed if we decide to handle pointer released. +// void PerformPointerUpAction() +// { +// if (m_shouldPerformActions) +// { +// Focus(FocusState.Pointer); +// Invoke(); +// } +// } + +// // Performs appropriate actions upon a mouse/keyboard invocation of a +// internal virtual void Invoke() +// { +// RoutedEventArgs spArgs; +// MenuFlyoutPresenter spParentMenuFlyoutPresenter; + +// // Create the args +// spArgs = new RoutedEventArgs(); +// spArgs.OriginalSource = this; + +// // Raise the event +// Click?.Invoke(this, spArgs); + +// // UNO TODO +// //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); + +// // Execute command associated with the button +// ExecuteCommand(); + +// bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; +// if (!shouldPreventDismissOnPointer) +// { +// // Close the MenuFlyout. +// spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); +// if (spParentMenuFlyoutPresenter != null) +// { +// IMenu owningMenu; + +// owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; +// if (owningMenu != null) +// { +// // We need to make close all menu flyout sub items before we hide the parent menu flyout, +// // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a +// // significant amount of time before closing the sub menu. +// (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); +// owningMenu.Close(); +// } +// } +// } + +// ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); +// } + +// // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) +// //{ +// // DependencyObject peer = pMenuFlyoutItem; + +// // if (peer == null) +// // { +// // return; +// // } + +// // MenuFlyoutItem peerAsMenuFlyoutItem; +// // (peer.As(&peerAsMenuFlyoutItem)); +// // IFCPTR_RETURN(peerAsMenuFlyoutItem); + +// // (peerAsAddProofingItemHandler(eventHandler)); + +// // return S_OK; +// //} + +// // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) +// //{ +// // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) +// // { +// // DependencyObject spSender; +// // EventArgs spArgs; + +// // IFCPTR_RETURN(pSender); +// // IFCPTR_RETURN(pArgs); + +// // (ctl.do_query_interface(spSender, pSender)); +// // (ctl.do_query_interface(spArgs, pArgs)); + +// // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); + +// // return S_OK; +// // })); + +// // return S_OK; +// //} + +// // Executes Command if CanExecute() returns true. +// void ExecuteCommand() +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// object spCommandParameter; +// bool canExecute; + +// spCommandParameter = CommandParameter; +// canExecute = spCommand.CanExecute(spCommandParameter); +// if (canExecute) +// { +// spCommand.Execute(spCommandParameter); +// } +// } +// } + +// // PointerEnter event handler. +// protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerEntered(pArgs); + +// MenuFlyoutPresenter spParentPresenter; + +// m_bIsPointerOver = true; + +// spParentPresenter = GetParentMenuFlyoutPresenter(); +// if (spParentPresenter != null) +// { +// IMenuPresenter subPresenter; + +// subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; +// if (subPresenter != null) +// { +// ISubMenuOwner subPresenterOwner; + +// subPresenterOwner = subPresenter.Owner; + +// if (subPresenterOwner != null) +// { +// subPresenterOwner.DelayCloseSubMenu(); +// } +// } +// } + +// UpdateVisualState(); +// } + +// // PointerExited event handler. +// protected override void OnPointerExited(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerExited(pArgs); + +// // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// UpdateVisualState(); +// } + +// // PointerCaptureLost event handler. +// protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerCaptureLost(pArgs); + +// // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// UpdateVisualState(); +// } + +// // Called when the IsEnabled property changes. +// private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) +// { +// if (!e.NewValue) +// { +// ClearStateFlags(); +// } +// else +// { +// UpdateVisualState(); +// } + +// base.OnIsEnabledChanged(e); +// } + +// // Called when the control got focus. +// protected override void OnGotFocus(RoutedEventArgs pArgs) +// { +// UpdateVisualState(); +// } + +// // LostFocus event handler. +// protected override void OnLostFocus(RoutedEventArgs pArgs) +// { +// if (!m_bIsPointerLeftButtonDown) +// { +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; +// m_bIsPressed = false; +// } + +// UpdateVisualState(); +// } + +// // KeyDown event handler. +// protected override void OnKeyDown(KeyRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// var key = pArgs.Key; +// handled = KeyPressMenuFlyout.KeyDown(key, this); +// pArgs.Handled = handled; +// } +// } + +// // KeyUp event handler. +// protected override void OnKeyUp(KeyRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// var key = (pArgs.Key); +// KeyPressMenuFlyout.KeyUp(key, this); +// pArgs.Handled = true; +// } +// } + +// // Handle the custom property changed event and call the OnPropertyChanged2 methods. +// internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) +// { +// if (args.Property == UIElement.VisibilityProperty) +// { +// OnVisibilityChanged(); +// } +// else if (args.Property == MenuFlyoutItem.CommandProperty) +// { +// OnCommandChanged(args.OldValue, args.NewValue); +// } +// else if (args.Property == MenuFlyoutItem.CommandParameterProperty) +// { +// UpdateCanExecute(); +// } +// } + +// // Update the visual states when the Visibility property is changed. +// void OnVisibilityChanged() +// { +// Visibility visibility = Visibility; + +// if (Visibility.Visible != visibility) +// { +// ClearStateFlags(); +// } +// } + +// private protected override void OnLoaded() +// { +// base.OnLoaded(); + +// if (m_epCanExecuteChangedHandler == null) +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// void OnCanExecuteChanged(object sender, object args) +// { +// UpdateCanExecute(); +// } + +// spCommand.CanExecuteChanged += OnCanExecuteChanged; + +// m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); +// } +// } + +// // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, +// // we need to update our value now. +// UpdateCanExecute(); +// } + +// private protected override void OnUnloaded() +// { +// base.OnUnloaded(); + +// if (m_epCanExecuteChangedHandler != null) +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// m_epCanExecuteChangedHandler.Dispose(); +// } +// } +// } + +// // Called when the Command property changes. +// void +// OnCommandChanged( +// object pOldValue, +// object pNewValue) +// { +// // Remove handler for CanExecuteChanged from the old value +// m_epCanExecuteChangedHandler?.Dispose(); + +// if (pOldValue != null) +// { +// XamlUICommand oldCommand = pOldValue as XamlUICommand; + +// if (oldCommand != null) +// { +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); +// } +// } + +// // Subscribe to the CanExecuteChanged event on the new value +// if (pNewValue != null) +// { +// var spNewCommand = pNewValue as ICommand; + +// void OnCanExecuteUpdated(object sender, object args) +// { +// UpdateCanExecute(); +// } + +// spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; +// m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); + +// var newCommandAsUICommand = spNewCommand as XamlUICommand; + +// if (newCommandAsUICommand != null) +// { +// CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); +// CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); +// CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); +// CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); +// CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); +// } +// } + +// // Coerce the button enabled state with the CanExecute state of the command. +// UpdateCanExecute(); +// } + +// // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. +// void UpdateCanExecute() +// { +// ICommand spCommand; +// object spCommandParameter; +// bool canExecute = true; + +// spCommand = Command; +// if (spCommand != null) +// { +// spCommandParameter = CommandParameter; +// canExecute = spCommand.CanExecute(spCommandParameter); +// } + +// SuppressIsEnabled(!canExecute); +// } + +// // Change to the correct visual state for the +// private protected override void ChangeVisualState( +// // true to use transitions when updating the visual state, false +// // to snap directly to the new visual state. +// bool bUseTransitions) +// { +// bool hasToggleMenuItem = false; +// bool hasIconMenuItem = false; +// bool hasMenuItemWithKeyboardAcceleratorText = false; +// bool isKeyboardPresent = false; + +// MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); +// if (spPresenter != null) +// { +// hasToggleMenuItem = spPresenter.GetContainsToggleItems(); +// hasIconMenuItem = spPresenter.GetContainsIconItems(); +// hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); +// } + +// var bIsEnabled = IsEnabled; +// var focusState = FocusState; +// var shouldBeNarrow = GetShouldBeNarrow(); + +// // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, +// // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. +// if (hasMenuItemWithKeyboardAcceleratorText) +// { +// // UNO TODO +// // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); +// isKeyboardPresent = true; +// } + +// // CommonStates +// if (!bIsEnabled) +// { +// VisualStateManager.GoToState(this, "Disabled", bUseTransitions); +// } +// else if (m_bIsPressed) +// { +// VisualStateManager.GoToState(this, "Pressed", bUseTransitions); +// } +// else if (m_bIsPointerOver) +// { +// VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "Normal", bUseTransitions); +// } + +// // FocusStates +// if (FocusState.Unfocused != focusState && bIsEnabled) +// { +// if (FocusState.Pointer == focusState) +// { +// VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "Focused", bUseTransitions); +// } +// } +// else +// { +// VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); +// } + +// // CheckPlaceholderStates +// if (hasToggleMenuItem && hasIconMenuItem) +// { +// VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); +// } +// else if (hasToggleMenuItem) +// { +// VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); +// } +// else if (hasIconMenuItem) +// { +// VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); +// } + +// // PaddingSizeStates +// if (shouldBeNarrow) +// { +// VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); +// } + +// // We'll make the accelerator text visible if any item has accelerator text, +// // as this causes the margin to be applied which reserves space, ensuring that accelerator text +// // in one item won't be at the same horizontal position as label text in another item. +// if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) +// { +// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); +// } +// } + +// // Clear flags relating to the visual state. Called when IsEnabled is set to false +// // or when Visibility is set to Hidden or Collapsed. +// void ClearStateFlags() +// { +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; + +// UpdateVisualState(); +// } + +// // Create MenuFlyoutItemAutomationPeer to represent the +// protected override AutomationPeer OnCreateAutomationPeer() +// { +// return new MenuFlyoutItemAutomationPeer(this); +// } + +// private protected override string GetPlainText() => Text; + +// internal string KeyboardAcceleratorTextOverrideImpl +// { +// get +// { +// var pValue = KeyboardAcceleratorTextOverride; + +// // If we have no keyboard accelerator text already provided by the app, +// // then we'll see if we can ruct it ourselves based on keyboard accelerators +// // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" +// // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". +// if (pValue == null) +// { +// pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); + +// // If we were able to get a string representation from keyboard accelerators, +// // then we should now set that as the value of KeyboardAcceleratorText. +// if (pValue != null) +// { +// KeyboardAcceleratorTextOverrideImpl = pValue; +// } +// } + +// return pValue; +// } +// set +// { +// KeyboardAcceleratorTextOverride = value; +// } +// } + +// internal Size GetKeyboardAcceleratorTextDesiredSize() +// { +// var desiredSize = new Size(0, 0); + +// if (!m_isTemplateApplied) +// { +// bool templateApplied = ApplyTemplate(); +// m_isTemplateApplied = templateApplied; +// } + +// if (m_tpKeyboardAcceleratorTextBlock != null) +// { +// Thickness margin; + +// m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); +// desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; +// margin = m_tpKeyboardAcceleratorTextBlock.Margin; + +// desiredSize.Width -= (float)(margin.Left + margin.Right); +// desiredSize.Height -= (float)(margin.Top + margin.Bottom); +// } + +// return desiredSize; +// } + +// internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) +// { +// if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) +// { +// m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; + +// MenuFlyoutItemTemplateSettings templateSettings; +// templateSettings = TemplateSettings; + +// if (templateSettings == null) +// { +// MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); +// TemplateSettings = templateSettingsImplementation; +// templateSettings = templateSettingsImplementation; +// } + +// templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; +// } +// } + +// internal virtual bool HasToggle() +// { +// return false; +// } +// } +//} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs index 9c3e701fd3a5..42826f7a255e 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs @@ -1,828 +1,828 @@ -using System; -using Uno.Client; -using Uno.Disposables; -using Windows.Foundation; -using Microsoft.UI.Xaml.Automation; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using ICommand = System.Windows.Input.ICommand; -using Microsoft.UI.Xaml.Markup; - -#if HAS_UNO_WINUI -using Microsoft.UI.Input; -#else -using Windows.Devices.Input; -using Windows.UI.Input; -#endif - -namespace Microsoft.UI.Xaml.Controls -{ - [ContentProperty(Name = nameof(Text))] - public partial class MenuFlyoutItem : MenuFlyoutItemBase - { - // Whether the pointer is currently over the - bool m_bIsPointerOver = true; - - // Whether the pointer is currently pressed over the - internal bool m_bIsPressed = true; - - // Whether the pointer's left button is currently down. - internal bool m_bIsPointerLeftButtonDown = true; - - // True if the SPACE or ENTER key is currently pressed, false otherwise. - internal bool m_bIsSpaceOrEnterKeyDown = true; - - // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. - internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; - - // On pointer released we perform some actions depending on control. We decide to whether to perform them - // depending on some parameters including but not limited to whether released is followed by a pressed, which - // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. - bool m_shouldPerformActions = true; - - // Event pointer for the ICommand.CanExecuteChanged event. - IDisposable m_epCanExecuteChangedHandler; - - // UNO TODO - // Event pointer for the Loaded event. - // IDisposable m_epLoadedHandler; - - // UNO TODO - // IDisposable m_epMenuFlyoutItemClickEventCallback; - - double m_maxKeyboardAcceleratorTextWidth; - TextBlock m_tpKeyboardAcceleratorTextBlock; - - bool m_isTemplateApplied; - - #region CommandParameter - - public object CommandParameter - { - get { return (object)GetValue(CommandParameterProperty); } - set { SetValue(CommandParameterProperty, value); } - } - - public static DependencyProperty CommandParameterProperty { get; } = - DependencyProperty.Register( - "CommandParameter", typeof(object), - typeof(Controls.MenuFlyoutItem), - new FrameworkPropertyMetadata(default(object))); - - #endregion - - #region Command - - public ICommand Command - { - get { return (ICommand)GetValue(CommandProperty); } - set { SetValue(CommandProperty, value); } - } - - public static DependencyProperty CommandProperty { get; } = - DependencyProperty.Register( - name: nameof(Command), - propertyType: typeof(ICommand), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); - - #endregion - - #region Text - - public string Text - { - get { return (string)GetValue(TextProperty) ?? ""; } - set { SetValue(TextProperty, value); } - } - - public static DependencyProperty TextProperty { get; } = - DependencyProperty.Register( - name: nameof(Text), - propertyType: typeof(string), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(string))); - - #endregion - - public IconElement Icon - { - get => (IconElement)this.GetValue(IconProperty); - set => this.SetValue(IconProperty, value); - } - - public static DependencyProperty IconProperty { get; } = - DependencyProperty.Register( - name: nameof(Icon), - propertyType: typeof(IconElement), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); - - public string KeyboardAcceleratorTextOverride - { - get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; - set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); - } - - public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = - DependencyProperty.Register( - name: nameof(KeyboardAcceleratorTextOverride), - propertyType: typeof(string), - ownerType: typeof(MenuFlyoutItem), - typeMetadata: new FrameworkPropertyMetadata(default(string))); - - public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } - -#pragma warning disable CS0108 - public event RoutedEventHandler Click; -#pragma warning restore CS0108 - - internal void InvokeClick() - { - Click?.Invoke(this, new RoutedEventArgs(this)); - Command.ExecuteIfPossible(this.CommandParameter); - } - - public MenuFlyoutItem() - { - m_bIsPointerOver = false; - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - m_shouldPerformActions = false; - - DefaultStyleKey = typeof(MenuFlyoutItem); - - Initialize(); - } - - // Prepares object's state - void Initialize() - { - Loaded += (s, e) => ClearStateFlags(); - - this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); - } - - // Apply a template to the - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; - m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; - - SuppressIsEnabled(false); - UpdateCanExecute(); - - // Sync the logical and visual states of the control - UpdateVisualState(); - } - - // PointerPressed event handler. - protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) - { - base.OnPointerPressed(pArgs); - var handled = pArgs.Handled; - if (!handled) - { - PointerPoint spPointerPoint; - PointerPointProperties spPointerProperties; - spPointerPoint = pArgs.GetCurrentPoint(this); - spPointerProperties = spPointerPoint.Properties; - var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; - if (bIsLeftButtonPressed) - { - m_bIsPointerLeftButtonDown = true; - m_bIsPressed = true; - - pArgs.Handled = true; - UpdateVisualState(); - } - } - } - - // PointerReleased event handler. - protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) - { - base.OnPointerReleased(pArgs); - - bool handled = false; - - handled = pArgs.Handled; - if (!handled) - { - m_bIsPointerLeftButtonDown = false; - - m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; - -#if false - // UNO TODO - if (m_shouldPerformActions) - { - GestureModes gestureFollowing = GestureModes.None; - - m_bIsPressed = false; - - gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); - - // Note that we are intentionally NOT handling the args - // if we do not fall through here because basically we are no_opting in that case. - if (gestureFollowing != GestureModes.RightTapped) - { - pArgs.Handled = true; - PerformPointerUpAction(); - } - } -#else - PerformPointerUpAction(); -#endif - } - } - - private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - PerformPointerUpAction(); - pArgs.Handled = true; - } - } - - // Contains the logic to be employed if we decide to handle pointer released. - void PerformPointerUpAction() - { - if (m_shouldPerformActions) - { - Focus(FocusState.Pointer); - Invoke(); - } - } - - // Performs appropriate actions upon a mouse/keyboard invocation of a - internal virtual void Invoke() - { - RoutedEventArgs spArgs; - MenuFlyoutPresenter spParentMenuFlyoutPresenter; - - // Create the args - spArgs = new RoutedEventArgs(); - spArgs.OriginalSource = this; - - // Raise the event - Click?.Invoke(this, spArgs); - - // UNO TODO - //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); - - // Execute command associated with the button - ExecuteCommand(); - - bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; - if (!shouldPreventDismissOnPointer) - { - // Close the MenuFlyout. - spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); - if (spParentMenuFlyoutPresenter != null) - { - IMenu owningMenu; - - owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; - if (owningMenu != null) - { - // We need to make close all menu flyout sub items before we hide the parent menu flyout, - // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a - // significant amount of time before closing the sub menu. - (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); - owningMenu.Close(); - } - } - } - - ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); - } - - // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) - //{ - // DependencyObject peer = pMenuFlyoutItem; - - // if (peer == null) - // { - // return; - // } - - // MenuFlyoutItem peerAsMenuFlyoutItem; - // (peer.As(&peerAsMenuFlyoutItem)); - // IFCPTR_RETURN(peerAsMenuFlyoutItem); - - // (peerAsAddProofingItemHandler(eventHandler)); - - // return S_OK; - //} - - // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) - //{ - // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) - // { - // DependencyObject spSender; - // EventArgs spArgs; - - // IFCPTR_RETURN(pSender); - // IFCPTR_RETURN(pArgs); - - // (ctl.do_query_interface(spSender, pSender)); - // (ctl.do_query_interface(spArgs, pArgs)); - - // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); - - // return S_OK; - // })); - - // return S_OK; - //} - - // Executes Command if CanExecute() returns true. - void ExecuteCommand() - { - ICommand spCommand = Command; - - if (spCommand != null) - { - object spCommandParameter; - bool canExecute; - - spCommandParameter = CommandParameter; - canExecute = spCommand.CanExecute(spCommandParameter); - if (canExecute) - { - spCommand.Execute(spCommandParameter); - } - } - } - - // PointerEnter event handler. - protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) - { - base.OnPointerEntered(pArgs); - - MenuFlyoutPresenter spParentPresenter; - - m_bIsPointerOver = true; - - spParentPresenter = GetParentMenuFlyoutPresenter(); - if (spParentPresenter != null) - { - IMenuPresenter subPresenter; - - subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; - if (subPresenter != null) - { - ISubMenuOwner subPresenterOwner; - - subPresenterOwner = subPresenter.Owner; - - if (subPresenterOwner != null) - { - subPresenterOwner.DelayCloseSubMenu(); - } - } - } - - UpdateVisualState(); - } - - // PointerExited event handler. - protected override void OnPointerExited(PointerRoutedEventArgs pArgs) - { - base.OnPointerExited(pArgs); - - // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - UpdateVisualState(); - } - - // PointerCaptureLost event handler. - protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) - { - base.OnPointerCaptureLost(pArgs); - - // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - UpdateVisualState(); - } - - // Called when the IsEnabled property changes. - private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) - { - if (!e.NewValue) - { - ClearStateFlags(); - } - else - { - UpdateVisualState(); - } - - base.OnIsEnabledChanged(e); - } - - // Called when the control got focus. - protected override void OnGotFocus(RoutedEventArgs pArgs) - { - UpdateVisualState(); - } - - // LostFocus event handler. - protected override void OnLostFocus(RoutedEventArgs pArgs) - { - if (!m_bIsPointerLeftButtonDown) - { - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - m_bIsPressed = false; - } - - UpdateVisualState(); - } - - // KeyDown event handler. - protected override void OnKeyDown(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - var key = pArgs.Key; - handled = KeyPressMenuFlyout.KeyDown(key, this); - pArgs.Handled = handled; - } - } - - // KeyUp event handler. - protected override void OnKeyUp(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - if (!handled) - { - var key = (pArgs.Key); - KeyPressMenuFlyout.KeyUp(key, this); - pArgs.Handled = true; - } - } - - // Handle the custom property changed event and call the OnPropertyChanged2 methods. - internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) - { - if (args.Property == UIElement.VisibilityProperty) - { - OnVisibilityChanged(); - } - else if (args.Property == MenuFlyoutItem.CommandProperty) - { - OnCommandChanged(args.OldValue, args.NewValue); - } - else if (args.Property == MenuFlyoutItem.CommandParameterProperty) - { - UpdateCanExecute(); - } - } - - // Update the visual states when the Visibility property is changed. - void OnVisibilityChanged() - { - Visibility visibility = Visibility; - - if (Visibility.Visible != visibility) - { - ClearStateFlags(); - } - } - - private protected override void OnLoaded() - { - base.OnLoaded(); - - if (m_epCanExecuteChangedHandler == null) - { - ICommand spCommand = Command; - - if (spCommand != null) - { - void OnCanExecuteChanged(object sender, object args) - { - UpdateCanExecute(); - } - - spCommand.CanExecuteChanged += OnCanExecuteChanged; - - m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); - } - } - - // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, - // we need to update our value now. - UpdateCanExecute(); - } - - private protected override void OnUnloaded() - { - base.OnUnloaded(); - - if (m_epCanExecuteChangedHandler != null) - { - ICommand spCommand = Command; - - if (spCommand != null) - { - m_epCanExecuteChangedHandler.Dispose(); - } - } - } - - // Called when the Command property changes. - void - OnCommandChanged( - object pOldValue, - object pNewValue) - { - // Remove handler for CanExecuteChanged from the old value - m_epCanExecuteChangedHandler?.Dispose(); - - if (pOldValue != null) - { - XamlUICommand oldCommand = pOldValue as XamlUICommand; - - if (oldCommand != null) - { - CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); - CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); - } - } - - // Subscribe to the CanExecuteChanged event on the new value - if (pNewValue != null) - { - var spNewCommand = pNewValue as ICommand; - - void OnCanExecuteUpdated(object sender, object args) - { - UpdateCanExecute(); - } - - spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; - m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); - - var newCommandAsUICommand = spNewCommand as XamlUICommand; - - if (newCommandAsUICommand != null) - { - CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); - CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); - CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); - CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); - CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); - } - } - - // Coerce the button enabled state with the CanExecute state of the command. - UpdateCanExecute(); - } - - // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. - void UpdateCanExecute() - { - ICommand spCommand; - object spCommandParameter; - bool canExecute = true; - - spCommand = Command; - if (spCommand != null) - { - spCommandParameter = CommandParameter; - canExecute = spCommand.CanExecute(spCommandParameter); - } - - SuppressIsEnabled(!canExecute); - } - - // Change to the correct visual state for the - private protected override void ChangeVisualState( - // true to use transitions when updating the visual state, false - // to snap directly to the new visual state. - bool bUseTransitions) - { - bool hasToggleMenuItem = false; - bool hasIconMenuItem = false; - bool hasMenuItemWithKeyboardAcceleratorText = false; - bool isKeyboardPresent = false; - - MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); - if (spPresenter != null) - { - hasToggleMenuItem = spPresenter.GetContainsToggleItems(); - hasIconMenuItem = spPresenter.GetContainsIconItems(); - hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); - } - - var bIsEnabled = IsEnabled; - var focusState = FocusState; - var shouldBeNarrow = GetShouldBeNarrow(); - - // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, - // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. - if (hasMenuItemWithKeyboardAcceleratorText) - { - // UNO TODO - // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); - isKeyboardPresent = true; - } - - // CommonStates - if (!bIsEnabled) - { - VisualStateManager.GoToState(this, "Disabled", bUseTransitions); - } - else if (m_bIsPressed) - { - VisualStateManager.GoToState(this, "Pressed", bUseTransitions); - } - else if (m_bIsPointerOver) - { - VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Normal", bUseTransitions); - } - - // FocusStates - if (FocusState.Unfocused != focusState && bIsEnabled) - { - if (FocusState.Pointer == focusState) - { - VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "Focused", bUseTransitions); - } - } - else - { - VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); - } - - // CheckPlaceholderStates - if (hasToggleMenuItem && hasIconMenuItem) - { - VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); - } - else if (hasToggleMenuItem) - { - VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); - } - else if (hasIconMenuItem) - { - VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); - } - - // PaddingSizeStates - if (shouldBeNarrow) - { - VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); - } - - // We'll make the accelerator text visible if any item has accelerator text, - // as this causes the margin to be applied which reserves space, ensuring that accelerator text - // in one item won't be at the same horizontal position as label text in another item. - if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); - } - else - { - VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); - } - } - - // Clear flags relating to the visual state. Called when IsEnabled is set to false - // or when Visibility is set to Hidden or Collapsed. - void ClearStateFlags() - { - m_bIsPressed = false; - m_bIsPointerLeftButtonDown = false; - m_bIsPointerOver = false; - m_bIsSpaceOrEnterKeyDown = false; - m_bIsNavigationAcceptOrGamepadAKeyDown = false; - - UpdateVisualState(); - } - - // Create MenuFlyoutItemAutomationPeer to represent the - protected override AutomationPeer OnCreateAutomationPeer() - { - return new MenuFlyoutItemAutomationPeer(this); - } - - private protected override string GetPlainText() => Text; - - internal string KeyboardAcceleratorTextOverrideImpl - { - get - { - var pValue = KeyboardAcceleratorTextOverride; - - // If we have no keyboard accelerator text already provided by the app, - // then we'll see if we can ruct it ourselves based on keyboard accelerators - // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" - // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". - if (pValue == null) - { - pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); - - // If we were able to get a string representation from keyboard accelerators, - // then we should now set that as the value of KeyboardAcceleratorText. - if (pValue != null) - { - KeyboardAcceleratorTextOverrideImpl = pValue; - } - } - - return pValue; - } - set - { - KeyboardAcceleratorTextOverride = value; - } - } - - internal Size GetKeyboardAcceleratorTextDesiredSize() - { - var desiredSize = new Size(0, 0); - - if (!m_isTemplateApplied) - { - bool templateApplied = ApplyTemplate(); - m_isTemplateApplied = templateApplied; - } - - if (m_tpKeyboardAcceleratorTextBlock != null) - { - Thickness margin; - - m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; - margin = m_tpKeyboardAcceleratorTextBlock.Margin; - - desiredSize.Width -= (float)(margin.Left + margin.Right); - desiredSize.Height -= (float)(margin.Top + margin.Bottom); - } - - return desiredSize; - } - - internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) - { - if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) - { - m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; - - MenuFlyoutItemTemplateSettings templateSettings; - templateSettings = TemplateSettings; - - if (templateSettings == null) - { - MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); - TemplateSettings = templateSettingsImplementation; - templateSettings = templateSettingsImplementation; - } - - templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; - } - } - - internal virtual bool HasToggle() - { - return false; - } - } -} +//using System; +//using Uno.Client; +//using Uno.Disposables; +//using Windows.Foundation; +//using Microsoft.UI.Xaml.Automation; +//using Microsoft.UI.Xaml.Automation.Peers; +//using Microsoft.UI.Xaml.Controls.Primitives; +//using Microsoft.UI.Xaml.Input; +//using ICommand = System.Windows.Input.ICommand; +//using Microsoft.UI.Xaml.Markup; + +//#if HAS_UNO_WINUI +//using Microsoft.UI.Input; +//#else +//using Windows.Devices.Input; +//using Windows.UI.Input; +//#endif + +//namespace Microsoft.UI.Xaml.Controls +//{ +// [ContentProperty(Name = nameof(Text))] +// public partial class MenuFlyoutItem : MenuFlyoutItemBase +// { +// // Whether the pointer is currently over the +// bool m_bIsPointerOver = true; + +// // Whether the pointer is currently pressed over the +// internal bool m_bIsPressed = true; + +// // Whether the pointer's left button is currently down. +// internal bool m_bIsPointerLeftButtonDown = true; + +// // True if the SPACE or ENTER key is currently pressed, false otherwise. +// internal bool m_bIsSpaceOrEnterKeyDown = true; + +// // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. +// internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; + +// // On pointer released we perform some actions depending on control. We decide to whether to perform them +// // depending on some parameters including but not limited to whether released is followed by a pressed, which +// // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. +// bool m_shouldPerformActions = true; + +// // Event pointer for the ICommand.CanExecuteChanged event. +// IDisposable m_epCanExecuteChangedHandler; + +// // UNO TODO +// // Event pointer for the Loaded event. +// // IDisposable m_epLoadedHandler; + +// // UNO TODO +// // IDisposable m_epMenuFlyoutItemClickEventCallback; + +// double m_maxKeyboardAcceleratorTextWidth; +// TextBlock m_tpKeyboardAcceleratorTextBlock; + +// bool m_isTemplateApplied; + +// #region CommandParameter + +// public object CommandParameter +// { +// get { return (object)GetValue(CommandParameterProperty); } +// set { SetValue(CommandParameterProperty, value); } +// } + +// public static DependencyProperty CommandParameterProperty { get; } = +// DependencyProperty.Register( +// "CommandParameter", typeof(object), +// typeof(Controls.MenuFlyoutItem), +// new FrameworkPropertyMetadata(default(object))); + +// #endregion + +// #region Command + +// public ICommand Command +// { +// get { return (ICommand)GetValue(CommandProperty); } +// set { SetValue(CommandProperty, value); } +// } + +// public static DependencyProperty CommandProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Command), +// propertyType: typeof(ICommand), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); + +// #endregion + +// #region Text + +// public string Text +// { +// get { return (string)GetValue(TextProperty) ?? ""; } +// set { SetValue(TextProperty, value); } +// } + +// public static DependencyProperty TextProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Text), +// propertyType: typeof(string), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(string))); + +// #endregion + +// public IconElement Icon +// { +// get => (IconElement)this.GetValue(IconProperty); +// set => this.SetValue(IconProperty, value); +// } + +// public static DependencyProperty IconProperty { get; } = +// DependencyProperty.Register( +// name: nameof(Icon), +// propertyType: typeof(IconElement), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); + +// public string KeyboardAcceleratorTextOverride +// { +// get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; +// set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); +// } + +// public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = +// DependencyProperty.Register( +// name: nameof(KeyboardAcceleratorTextOverride), +// propertyType: typeof(string), +// ownerType: typeof(MenuFlyoutItem), +// typeMetadata: new FrameworkPropertyMetadata(default(string))); + +// public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } + +//#pragma warning disable CS0108 +// public event RoutedEventHandler Click; +//#pragma warning restore CS0108 + +// internal void InvokeClick() +// { +// Click?.Invoke(this, new RoutedEventArgs(this)); +// Command.ExecuteIfPossible(this.CommandParameter); +// } + +// public MenuFlyoutItem() +// { +// m_bIsPointerOver = false; +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; +// m_shouldPerformActions = false; + +// DefaultStyleKey = typeof(MenuFlyoutItem); + +// Initialize(); +// } + +// // Prepares object's state +// void Initialize() +// { +// Loaded += (s, e) => ClearStateFlags(); + +// this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); +// } + +// // Apply a template to the + +// protected override void OnApplyTemplate() +// { +// base.OnApplyTemplate(); + +// TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; +// m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; + +// SuppressIsEnabled(false); +// UpdateCanExecute(); + +// // Sync the logical and visual states of the control +// UpdateVisualState(); +// } + +// // PointerPressed event handler. +// protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerPressed(pArgs); +// var handled = pArgs.Handled; +// if (!handled) +// { +// PointerPoint spPointerPoint; +// PointerPointProperties spPointerProperties; +// spPointerPoint = pArgs.GetCurrentPoint(this); +// spPointerProperties = spPointerPoint.Properties; +// var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; +// if (bIsLeftButtonPressed) +// { +// m_bIsPointerLeftButtonDown = true; +// m_bIsPressed = true; + +// pArgs.Handled = true; +// UpdateVisualState(); +// } +// } +// } + +// // PointerReleased event handler. +// protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerReleased(pArgs); + +// bool handled = false; + +// handled = pArgs.Handled; +// if (!handled) +// { +// m_bIsPointerLeftButtonDown = false; + +// m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; + +//#if false +// // UNO TODO +// if (m_shouldPerformActions) +// { +// GestureModes gestureFollowing = GestureModes.None; + +// m_bIsPressed = false; + +// gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); + +// // Note that we are intentionally NOT handling the args +// // if we do not fall through here because basically we are no_opting in that case. +// if (gestureFollowing != GestureModes.RightTapped) +// { +// pArgs.Handled = true; +// PerformPointerUpAction(); +// } +// } +//#else +// PerformPointerUpAction(); +//#endif +// } +// } + +// private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// PerformPointerUpAction(); +// pArgs.Handled = true; +// } +// } + +// // Contains the logic to be employed if we decide to handle pointer released. +// void PerformPointerUpAction() +// { +// if (m_shouldPerformActions) +// { +// Focus(FocusState.Pointer); +// Invoke(); +// } +// } + +// // Performs appropriate actions upon a mouse/keyboard invocation of a +// internal virtual void Invoke() +// { +// RoutedEventArgs spArgs; +// MenuFlyoutPresenter spParentMenuFlyoutPresenter; + +// // Create the args +// spArgs = new RoutedEventArgs(); +// spArgs.OriginalSource = this; + +// // Raise the event +// Click?.Invoke(this, spArgs); + +// // UNO TODO +// //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); + +// // Execute command associated with the button +// ExecuteCommand(); + +// bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; +// if (!shouldPreventDismissOnPointer) +// { +// // Close the MenuFlyout. +// spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); +// if (spParentMenuFlyoutPresenter != null) +// { +// IMenu owningMenu; + +// owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; +// if (owningMenu != null) +// { +// // We need to make close all menu flyout sub items before we hide the parent menu flyout, +// // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a +// // significant amount of time before closing the sub menu. +// (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); +// owningMenu.Close(); +// } +// } +// } + +// ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); +// } + +// // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) +// //{ +// // DependencyObject peer = pMenuFlyoutItem; + +// // if (peer == null) +// // { +// // return; +// // } + +// // MenuFlyoutItem peerAsMenuFlyoutItem; +// // (peer.As(&peerAsMenuFlyoutItem)); +// // IFCPTR_RETURN(peerAsMenuFlyoutItem); + +// // (peerAsAddProofingItemHandler(eventHandler)); + +// // return S_OK; +// //} + +// // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) +// //{ +// // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) +// // { +// // DependencyObject spSender; +// // EventArgs spArgs; + +// // IFCPTR_RETURN(pSender); +// // IFCPTR_RETURN(pArgs); + +// // (ctl.do_query_interface(spSender, pSender)); +// // (ctl.do_query_interface(spArgs, pArgs)); + +// // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); + +// // return S_OK; +// // })); + +// // return S_OK; +// //} + +// // Executes Command if CanExecute() returns true. +// void ExecuteCommand() +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// object spCommandParameter; +// bool canExecute; + +// spCommandParameter = CommandParameter; +// canExecute = spCommand.CanExecute(spCommandParameter); +// if (canExecute) +// { +// spCommand.Execute(spCommandParameter); +// } +// } +// } + +// // PointerEnter event handler. +// protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerEntered(pArgs); + +// MenuFlyoutPresenter spParentPresenter; + +// m_bIsPointerOver = true; + +// spParentPresenter = GetParentMenuFlyoutPresenter(); +// if (spParentPresenter != null) +// { +// IMenuPresenter subPresenter; + +// subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; +// if (subPresenter != null) +// { +// ISubMenuOwner subPresenterOwner; + +// subPresenterOwner = subPresenter.Owner; + +// if (subPresenterOwner != null) +// { +// subPresenterOwner.DelayCloseSubMenu(); +// } +// } +// } + +// UpdateVisualState(); +// } + +// // PointerExited event handler. +// protected override void OnPointerExited(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerExited(pArgs); + +// // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// UpdateVisualState(); +// } + +// // PointerCaptureLost event handler. +// protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) +// { +// base.OnPointerCaptureLost(pArgs); + +// // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// UpdateVisualState(); +// } + +// // Called when the IsEnabled property changes. +// private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) +// { +// if (!e.NewValue) +// { +// ClearStateFlags(); +// } +// else +// { +// UpdateVisualState(); +// } + +// base.OnIsEnabledChanged(e); +// } + +// // Called when the control got focus. +// protected override void OnGotFocus(RoutedEventArgs pArgs) +// { +// UpdateVisualState(); +// } + +// // LostFocus event handler. +// protected override void OnLostFocus(RoutedEventArgs pArgs) +// { +// if (!m_bIsPointerLeftButtonDown) +// { +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; +// m_bIsPressed = false; +// } + +// UpdateVisualState(); +// } + +// // KeyDown event handler. +// protected override void OnKeyDown(KeyRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// var key = pArgs.Key; +// handled = KeyPressMenuFlyout.KeyDown(key, this); +// pArgs.Handled = handled; +// } +// } + +// // KeyUp event handler. +// protected override void OnKeyUp(KeyRoutedEventArgs pArgs) +// { +// var handled = pArgs.Handled; +// if (!handled) +// { +// var key = (pArgs.Key); +// KeyPressMenuFlyout.KeyUp(key, this); +// pArgs.Handled = true; +// } +// } + +// // Handle the custom property changed event and call the OnPropertyChanged2 methods. +// internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) +// { +// if (args.Property == UIElement.VisibilityProperty) +// { +// OnVisibilityChanged(); +// } +// else if (args.Property == MenuFlyoutItem.CommandProperty) +// { +// OnCommandChanged(args.OldValue, args.NewValue); +// } +// else if (args.Property == MenuFlyoutItem.CommandParameterProperty) +// { +// UpdateCanExecute(); +// } +// } + +// // Update the visual states when the Visibility property is changed. +// void OnVisibilityChanged() +// { +// Visibility visibility = Visibility; + +// if (Visibility.Visible != visibility) +// { +// ClearStateFlags(); +// } +// } + +// private protected override void OnLoaded() +// { +// base.OnLoaded(); + +// if (m_epCanExecuteChangedHandler == null) +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// void OnCanExecuteChanged(object sender, object args) +// { +// UpdateCanExecute(); +// } + +// spCommand.CanExecuteChanged += OnCanExecuteChanged; + +// m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); +// } +// } + +// // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, +// // we need to update our value now. +// UpdateCanExecute(); +// } + +// private protected override void OnUnloaded() +// { +// base.OnUnloaded(); + +// if (m_epCanExecuteChangedHandler != null) +// { +// ICommand spCommand = Command; + +// if (spCommand != null) +// { +// m_epCanExecuteChangedHandler.Dispose(); +// } +// } +// } + +// // Called when the Command property changes. +// void +// OnCommandChanged( +// object pOldValue, +// object pNewValue) +// { +// // Remove handler for CanExecuteChanged from the old value +// m_epCanExecuteChangedHandler?.Dispose(); + +// if (pOldValue != null) +// { +// XamlUICommand oldCommand = pOldValue as XamlUICommand; + +// if (oldCommand != null) +// { +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); +// CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); +// } +// } + +// // Subscribe to the CanExecuteChanged event on the new value +// if (pNewValue != null) +// { +// var spNewCommand = pNewValue as ICommand; + +// void OnCanExecuteUpdated(object sender, object args) +// { +// UpdateCanExecute(); +// } + +// spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; +// m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); + +// var newCommandAsUICommand = spNewCommand as XamlUICommand; + +// if (newCommandAsUICommand != null) +// { +// CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); +// CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); +// CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); +// CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); +// CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); +// } +// } + +// // Coerce the button enabled state with the CanExecute state of the command. +// UpdateCanExecute(); +// } + +// // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. +// void UpdateCanExecute() +// { +// ICommand spCommand; +// object spCommandParameter; +// bool canExecute = true; + +// spCommand = Command; +// if (spCommand != null) +// { +// spCommandParameter = CommandParameter; +// canExecute = spCommand.CanExecute(spCommandParameter); +// } + +// SuppressIsEnabled(!canExecute); +// } + +// // Change to the correct visual state for the +// private protected override void ChangeVisualState( +// // true to use transitions when updating the visual state, false +// // to snap directly to the new visual state. +// bool bUseTransitions) +// { +// bool hasToggleMenuItem = false; +// bool hasIconMenuItem = false; +// bool hasMenuItemWithKeyboardAcceleratorText = false; +// bool isKeyboardPresent = false; + +// MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); +// if (spPresenter != null) +// { +// hasToggleMenuItem = spPresenter.GetContainsToggleItems(); +// hasIconMenuItem = spPresenter.GetContainsIconItems(); +// hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); +// } + +// var bIsEnabled = IsEnabled; +// var focusState = FocusState; +// var shouldBeNarrow = GetShouldBeNarrow(); + +// // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, +// // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. +// if (hasMenuItemWithKeyboardAcceleratorText) +// { +// // UNO TODO +// // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); +// isKeyboardPresent = true; +// } + +// // CommonStates +// if (!bIsEnabled) +// { +// VisualStateManager.GoToState(this, "Disabled", bUseTransitions); +// } +// else if (m_bIsPressed) +// { +// VisualStateManager.GoToState(this, "Pressed", bUseTransitions); +// } +// else if (m_bIsPointerOver) +// { +// VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "Normal", bUseTransitions); +// } + +// // FocusStates +// if (FocusState.Unfocused != focusState && bIsEnabled) +// { +// if (FocusState.Pointer == focusState) +// { +// VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "Focused", bUseTransitions); +// } +// } +// else +// { +// VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); +// } + +// // CheckPlaceholderStates +// if (hasToggleMenuItem && hasIconMenuItem) +// { +// VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); +// } +// else if (hasToggleMenuItem) +// { +// VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); +// } +// else if (hasIconMenuItem) +// { +// VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); +// } + +// // PaddingSizeStates +// if (shouldBeNarrow) +// { +// VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); +// } + +// // We'll make the accelerator text visible if any item has accelerator text, +// // as this causes the margin to be applied which reserves space, ensuring that accelerator text +// // in one item won't be at the same horizontal position as label text in another item. +// if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) +// { +// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); +// } +// else +// { +// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); +// } +// } + +// // Clear flags relating to the visual state. Called when IsEnabled is set to false +// // or when Visibility is set to Hidden or Collapsed. +// void ClearStateFlags() +// { +// m_bIsPressed = false; +// m_bIsPointerLeftButtonDown = false; +// m_bIsPointerOver = false; +// m_bIsSpaceOrEnterKeyDown = false; +// m_bIsNavigationAcceptOrGamepadAKeyDown = false; + +// UpdateVisualState(); +// } + +// // Create MenuFlyoutItemAutomationPeer to represent the +// protected override AutomationPeer OnCreateAutomationPeer() +// { +// return new MenuFlyoutItemAutomationPeer(this); +// } + +// private protected override string GetPlainText() => Text; + +// internal string KeyboardAcceleratorTextOverrideImpl +// { +// get +// { +// var pValue = KeyboardAcceleratorTextOverride; + +// // If we have no keyboard accelerator text already provided by the app, +// // then we'll see if we can ruct it ourselves based on keyboard accelerators +// // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" +// // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". +// if (pValue == null) +// { +// pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); + +// // If we were able to get a string representation from keyboard accelerators, +// // then we should now set that as the value of KeyboardAcceleratorText. +// if (pValue != null) +// { +// KeyboardAcceleratorTextOverrideImpl = pValue; +// } +// } + +// return pValue; +// } +// set +// { +// KeyboardAcceleratorTextOverride = value; +// } +// } + +// internal Size GetKeyboardAcceleratorTextDesiredSize() +// { +// var desiredSize = new Size(0, 0); + +// if (!m_isTemplateApplied) +// { +// bool templateApplied = ApplyTemplate(); +// m_isTemplateApplied = templateApplied; +// } + +// if (m_tpKeyboardAcceleratorTextBlock != null) +// { +// Thickness margin; + +// m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); +// desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; +// margin = m_tpKeyboardAcceleratorTextBlock.Margin; + +// desiredSize.Width -= (float)(margin.Left + margin.Right); +// desiredSize.Height -= (float)(margin.Top + margin.Bottom); +// } + +// return desiredSize; +// } + +// internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) +// { +// if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) +// { +// m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; + +// MenuFlyoutItemTemplateSettings templateSettings; +// templateSettings = TemplateSettings; + +// if (templateSettings == null) +// { +// MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); +// TemplateSettings = templateSettingsImplementation; +// templateSettings = templateSettingsImplementation; +// } + +// templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; +// } +// } + +// internal virtual bool HasToggle() +// { +// return false; +// } +// } +//} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs index 4a7083afe4b4..a676b96bb9fe 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs @@ -23,488 +23,504 @@ using Windows.UI.Input; #endif -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial class MenuFlyoutPresenter : ItemsControl, IMenuPresenter { - public partial class MenuFlyoutPresenter : ItemsControl, IMenuPresenter - { - // Can be negative. (-1) means nothing focused. - internal int m_iFocusedIndex; + // Can be negative. (-1) means nothing focused. + internal int m_iFocusedIndex; - // Weak reference to the menu that ultimately owns this MenuFlyoutPresenter. - private WeakReference m_wrOwningMenu; + // Weak reference to the menu that ultimately owns this MenuFlyoutPresenter. + private WeakReference m_wrOwningMenu; - // Weak reference to the parent MenuFlyout. - private WeakReference m_wrParentMenuFlyout; + // Weak reference to the parent MenuFlyout. + private WeakReference m_wrParentMenuFlyout; - // Weak reference to the owner of this menu. - // Only populated if this is the presenter for an ISubMenuOwner. - private WeakReference m_wrOwner; + // Weak reference to the owner of this menu. + // Only populated if this is the presenter for an ISubMenuOwner. + private WeakReference m_wrOwner; - // Weak reference to the sub-presenter that was created by a child menu owner. - private WeakReference m_wrSubPresenter; + // Weak reference to the sub-presenter that was created by a child menu owner. + private WeakReference m_wrSubPresenter; - // Whether ItemsSource contains at least one ToggleMenuFlyoutItem. - private bool m_containsToggleItems; + // Whether ItemsSource contains at least one ToggleMenuFlyoutItem. + private bool m_containsToggleItems; - // Whether ItemsSource contains at least one MenuFlyoutItem with an Icon. - private bool m_containsIconItems; + // Whether ItemsSource contains at least one MenuFlyoutItem with an Icon. + private bool m_containsIconItems; - // Whether ItemsSource contains at least one MenuFlyoutItem or ToggleMenuFlyoutItem with KeyboardAcceleratorText. - private bool m_containsItemsWithKeyboardAcceleratorText; + // Whether ItemsSource contains at least one MenuFlyoutItem or ToggleMenuFlyoutItem with KeyboardAcceleratorText. + private bool m_containsItemsWithKeyboardAcceleratorText; - // UNO TODO - // private bool m_animationInProgress; + // UNO TODO + // private bool m_animationInProgress; - private bool m_isSubPresenter; + private bool m_isSubPresenter; - private int m_depth; + private int m_depth; - //private FlyoutBase.MajorPlacementMode m_mostRecentPlacement; + //private FlyoutBase.MajorPlacementMode m_mostRecentPlacement; - private ScrollViewer m_tpScrollViewer; + private ScrollViewer m_tpScrollViewer; - public MenuFlyoutPresenterTemplateSettings TemplateSettings { get; } = new MenuFlyoutPresenterTemplateSettings(); + public MenuFlyoutPresenterTemplateSettings TemplateSettings { get; } = new MenuFlyoutPresenterTemplateSettings(); - public MenuFlyoutPresenter() - { - m_iFocusedIndex = -1; - m_containsToggleItems = false; - m_containsIconItems = false; - m_containsItemsWithKeyboardAcceleratorText = false; - // UNO TODO - // m_animationInProgress = false; - m_isSubPresenter = false; - //m_mostRecentPlacement = FlyoutBase.MajorPlacementMode.Bottom; - - DefaultStyleKey = typeof(MenuFlyoutPresenter); - } + public MenuFlyoutPresenter() + { + m_iFocusedIndex = -1; + m_containsToggleItems = false; + m_containsIconItems = false; + m_containsItemsWithKeyboardAcceleratorText = false; + // UNO TODO + // m_animationInProgress = false; + m_isSubPresenter = false; + //m_mostRecentPlacement = FlyoutBase.MajorPlacementMode.Bottom; - internal bool IsSubPresenter { get => m_isSubPresenter; set => m_isSubPresenter = value; } + DefaultStyleKey = typeof(MenuFlyoutPresenter); + } - // Responds to the KeyDown event. + internal bool IsSubPresenter { get => m_isSubPresenter; set => m_isSubPresenter = value; } - protected override void OnKeyDown(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; + // Responds to the KeyDown event. - if (!handled) - { - var key = pArgs.Key; - pArgs.Handled = KeyPressMenuFlyoutPresenter.KeyDown(key, this); - } - } + protected override void OnKeyDown(KeyRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; - internal void HandleUpOrDownKey(bool isDownKey) + if (!handled) { - CycleFocus(isDownKey, FocusState.Keyboard); + var key = pArgs.Key; + pArgs.Handled = KeyPressMenuFlyoutPresenter.KeyDown(key, this); } + } - void CycleFocus(bool shouldCycleDown, FocusState focusState) - { + internal void HandleUpOrDownKey(bool isDownKey) + { + CycleFocus(isDownKey, FocusState.Keyboard); + } - // Ensure the initial focus index to validate m_iFocusedIndex when the focused item - // is set by application's code like as MenuFlyout Opened event. - EnsureInitialFocusIndex(); + void CycleFocus(bool shouldCycleDown, FocusState focusState) + { + + // Ensure the initial focus index to validate m_iFocusedIndex when the focused item + // is set by application's code like as MenuFlyout Opened event. + EnsureInitialFocusIndex(); - var originalFocusedIndex = m_iFocusedIndex; + var originalFocusedIndex = m_iFocusedIndex; - var parentFlyout = GetParentMenuFlyout(); + var parentFlyout = GetParentMenuFlyout(); - // We should wrap around at the bottom or the top of the presenter if the user isn't using a gamepad or remote. - var shouldWrap = parentFlyout != null ? (parentFlyout.InputDeviceTypeUsedToOpen != FocusInputDeviceKind.GameController) : true; + // We should wrap around at the bottom or the top of the presenter if the user isn't using a gamepad or remote. + var shouldWrap = parentFlyout != null ? (parentFlyout.InputDeviceTypeUsedToOpen != FocusInputDeviceKind.GameController) : true; - var nCount = Items.Size; + var nCount = Items.Size; - // Determine direction of index movement based on the Up/Down key. - var deltaIndex = shouldCycleDown ? 1 : -1; + // Determine direction of index movement based on the Up/Down key. + var deltaIndex = shouldCycleDown ? 1 : -1; - // Set index by moving deltaIndex amount from the current focused item. - var index = m_iFocusedIndex + deltaIndex; + // Set index by moving deltaIndex amount from the current focused item. + var index = m_iFocusedIndex + deltaIndex; - // We have two locations where we want to wrap, so we'll encapsulate the wrapping behavior in a function object that we can call. - int wrapIndexIfNeeded(int indexToWrap) + // We have two locations where we want to wrap, so we'll encapsulate the wrapping behavior in a function object that we can call. + int wrapIndexIfNeeded(int indexToWrap) + { + if (shouldWrap) { - if (shouldWrap) + if (indexToWrap < 0) { - if (indexToWrap < 0) - { - indexToWrap = (int)(nCount) - 1; - } - else if (indexToWrap >= (int)(nCount)) - { - indexToWrap = 0; - } + indexToWrap = (int)(nCount) - 1; + } + else if (indexToWrap >= (int)(nCount)) + { + indexToWrap = 0; } - - return indexToWrap; } - // If there is no item focused right now, then set index to 0 for Down key or to n-1 for Up key. - // Otherwise, if we should be wrapping, we'll do an initial check for whether we should wrap before we enter the loop. - if (m_iFocusedIndex == -1) - { - index = shouldCycleDown ? 0 : (int)(nCount) - 1; + return indexToWrap; + } - // If the focused index is -1, then our value of -1 for originalFocusedIndex will not successfully stop the loop. - // In this case, we'll make originalFocusedIndex one step in the opposite direction from the initial index, - // so that way the loop will go all the way through the list of items before stopping. - originalFocusedIndex = wrapIndexIfNeeded(index - deltaIndex); - } - else - { - index = wrapIndexIfNeeded(index); - } + // If there is no item focused right now, then set index to 0 for Down key or to n-1 for Up key. + // Otherwise, if we should be wrapping, we'll do an initial check for whether we should wrap before we enter the loop. + if (m_iFocusedIndex == -1) + { + index = shouldCycleDown ? 0 : (int)(nCount) - 1; - // We need to examine all items with indices [0, m_iFocusedIndex) or (m_iFocusedIndex, nCount-1] for Down/Up keys. - // While index is within the range, we keep going through the item list until we are successfully able to focus an item, - // at which point we update the m_iFocusedIndex and break out of the loop. - while (0 <= index && index < (int)(nCount)) - { - var spItemAsDependencyObject = Items[index] as DependencyObject; + // If the focused index is -1, then our value of -1 for originalFocusedIndex will not successfully stop the loop. + // In this case, we'll make originalFocusedIndex one step in the opposite direction from the initial index, + // so that way the loop will go all the way through the list of items before stopping. + originalFocusedIndex = wrapIndexIfNeeded(index - deltaIndex); + } + else + { + index = wrapIndexIfNeeded(index); + } - var isFocusable = false; - // We determine whether the item is a focusable MenuFlyoutItem or MenuFlyoutSubItem because we want to exclude MenuSeparators here. - var spItem = spItemAsDependencyObject as MenuFlyoutItem; - if (spItem != null) - { - isFocusable = spItem.IsFocusable; - } - else - { - MenuFlyoutSubItem spSubItem; - spSubItem = spItemAsDependencyObject as MenuFlyoutSubItem; - if (spSubItem != null) - { - isFocusable = spSubItem.IsFocusable; - } - } + // We need to examine all items with indices [0, m_iFocusedIndex) or (m_iFocusedIndex, nCount-1] for Down/Up keys. + // While index is within the range, we keep going through the item list until we are successfully able to focus an item, + // at which point we update the m_iFocusedIndex and break out of the loop. + while (0 <= index && index < (int)(nCount)) + { + var spItemAsDependencyObject = Items[index] as DependencyObject; - // If the item is focusable, move the focus to it, update the m_iFocusedIndex and break out of the loop. - if (isFocusable) + var isFocusable = false; + // We determine whether the item is a focusable MenuFlyoutItem or MenuFlyoutSubItem because we want to exclude MenuSeparators here. + var spItem = spItemAsDependencyObject as MenuFlyoutItem; + if (spItem != null) + { + isFocusable = spItem.IsFocusable; + } + else + { + MenuFlyoutSubItem spSubItem; + spSubItem = spItemAsDependencyObject as MenuFlyoutSubItem; + if (spSubItem != null) { - var spSubItem = spItemAsDependencyObject as Control; - if (spSubItem != null) - { - spSubItem.Focus(focusState); - m_iFocusedIndex = index; - break; - } + isFocusable = spSubItem.IsFocusable; } + } - // If we've gone all the way around the list of items and still have not found a suitable focus candidate, - // then we'll stop - there's nothing else for us to do. - if (index == originalFocusedIndex) + // If the item is focusable, move the focus to it, update the m_iFocusedIndex and break out of the loop. + if (isFocusable) + { + var spSubItem = spItemAsDependencyObject as Control; + if (spSubItem != null) { + spSubItem.Focus(focusState); + m_iFocusedIndex = index; break; } + } - index += deltaIndex; - - // If we should be wrapping, then we'll perform the wrap at this point. - index = wrapIndexIfNeeded(index); + // If we've gone all the way around the list of items and still have not found a suitable focus candidate, + // then we'll stop - there's nothing else for us to do. + if (index == originalFocusedIndex) + { + break; } - } + index += deltaIndex; - internal void HandleKeyDownLeftOrEscape() - { - (this as IMenuPresenter).CloseSubMenu(); + // If we should be wrapping, then we'll perform the wrap at this point. + index = wrapIndexIfNeeded(index); } + } - protected override void PrepareContainerForItemOverride( - DependencyObject pElement, - object pItem) - { - base.PrepareContainerForItemOverride(pElement, pItem); + internal void HandleKeyDownLeftOrEscape() + { + (this as IMenuPresenter).CloseSubMenu(); + } - var spMenuFlyoutItemBase = pElement as MenuFlyoutItemBase; - spMenuFlyoutItemBase.SetParentMenuFlyoutPresenter(this); + protected override void PrepareContainerForItemOverride( + DependencyObject pElement, + object pItem) + { + base.PrepareContainerForItemOverride(pElement, pItem); - SynchronizeTemplatedParent(spMenuFlyoutItemBase); - } + var spMenuFlyoutItemBase = pElement as MenuFlyoutItemBase; + + spMenuFlyoutItemBase.SetParentMenuFlyoutPresenter(this); - private void SynchronizeTemplatedParent(MenuFlyoutItemBase spMenuFlyoutItemBase) + SynchronizeTemplatedParent(spMenuFlyoutItemBase); + } + + private void SynchronizeTemplatedParent(MenuFlyoutItemBase spMenuFlyoutItemBase) + { + // Manual propagation of the templated parent to the content properly + // until we get the propagation running properly + if (spMenuFlyoutItemBase is FrameworkElement content) { - // Manual propagation of the templated parent to the content properly - // until we get the propagation running properly - if (spMenuFlyoutItemBase is FrameworkElement content) - { - content.TemplatedParent = TemplatedParent; - } + content.TemplatedParent = TemplatedParent; } + } - protected override void ClearContainerForItemOverride( - DependencyObject pElement, - object pItem) - { - base.ClearContainerForItemOverride(pElement, pItem); + protected override void ClearContainerForItemOverride( + DependencyObject pElement, + object pItem) + { + base.ClearContainerForItemOverride(pElement, pItem); - (pElement as MenuFlyoutItemBase).SetParentMenuFlyoutPresenter(null); - } + (pElement as MenuFlyoutItemBase).SetParentMenuFlyoutPresenter(null); + } - // Get the parent MenuFlyout. - internal MenuFlyout GetParentMenuFlyout() - { - return m_wrParentMenuFlyout?.Target as MenuFlyout; - } + // Get the parent MenuFlyout. + internal MenuFlyout GetParentMenuFlyout() + { + return m_wrParentMenuFlyout?.Target as MenuFlyout; + } - // Sets the parent MenuFlyout. - internal void SetParentMenuFlyout(MenuFlyout pParentMenuFlyout) - { - m_wrParentMenuFlyout = new WeakReference(pParentMenuFlyout); - } + // Sets the parent MenuFlyout. + internal void SetParentMenuFlyout(MenuFlyout pParentMenuFlyout) + { + m_wrParentMenuFlyout = new WeakReference(pParentMenuFlyout); + } - // Called when the ItemsSource property changes. - protected override void OnItemsSourceChanged(DependencyPropertyChangedEventArgs args) - { - base.OnItemsSourceChanged(args); - var pNewValue = args.NewValue; + // Called when the ItemsSource property changes. + protected override void OnItemsSourceChanged(DependencyPropertyChangedEventArgs args) + { + base.OnItemsSourceChanged(args); + var pNewValue = args.NewValue; - m_iFocusedIndex = -1; - m_containsToggleItems = false; - m_containsIconItems = false; - m_containsItemsWithKeyboardAcceleratorText = false; + m_iFocusedIndex = -1; + m_containsToggleItems = false; + m_containsIconItems = false; + m_containsItemsWithKeyboardAcceleratorText = false; - if (pNewValue != null) + if (pNewValue != null) + { + IList spItems; + spItems = pNewValue as IList; + if (spItems == null) { - IList spItems; - spItems = pNewValue as IList; - if (spItems == null) - { - // MenuFlyoutPresenter could be used outside of MenuFlyout, but at this time - // we don't support that usage. If a customer is using MenuFlyoutPresenter - // independently and uses an ItemsSource that is not an IVector, - // we throw E_INVALIDARG to indicate that this usage is invalid. - // If we decide to allow this usage, we need to override and implement - // IsItemItsOwnContainerOverride() and GetContainerForItemOverride(). - // - // GetContainerForItemOverride() is tricky since MenuFlyoutPresenter supports 3 different - // kinds of children (MenuFlyoutSeparator, MenuFlyoutItem, ToggleMenuFlyoutItem), - // so we are punting on that scenario for now. - throw new InvalidOperationException("Cannot use MenuFlyoutPresenter outside of a MenuFlyout"); - } + // MenuFlyoutPresenter could be used outside of MenuFlyout, but at this time + // we don't support that usage. If a customer is using MenuFlyoutPresenter + // independently and uses an ItemsSource that is not an IVector, + // we throw E_INVALIDARG to indicate that this usage is invalid. + // If we decide to allow this usage, we need to override and implement + // IsItemItsOwnContainerOverride() and GetContainerForItemOverride(). + // + // GetContainerForItemOverride() is tricky since MenuFlyoutPresenter supports 3 different + // kinds of children (MenuFlyoutSeparator, MenuFlyoutItem, ToggleMenuFlyoutItem), + // so we are punting on that scenario for now. + throw new InvalidOperationException("Cannot use MenuFlyoutPresenter outside of a MenuFlyout"); + } - // MenuFlyoutItem's alignment changes based on the layout of other MenuFlyoutItems. - // This check looks through all MenuFlyoutItems in our items source and checks for - // ToggleMenuFlyoutItems and the presence of Icons, which can change the layout of - // all MenuFlyoutItems in the presenter. - var nCount = spItems.Count; - for (var i = 0; i < nCount; ++i) - { - MenuFlyoutItemBase item; - MenuFlyoutItem itemAsMenuItem; - MenuFlyoutSubItem itemAsMenuSubItem; - IconElement iconElement; - string keyboardAcceleratorText; - - item = spItems[i] as MenuFlyoutItemBase; - - // To prevent casting the same item more than we need to, each cast is conditional - // on the previous one failing. This way we only cast each item as many times as we - // need to. - itemAsMenuItem = item as MenuFlyoutItem; - if (itemAsMenuItem != null) - { - m_containsToggleItems = m_containsToggleItems || (itemAsMenuItem as MenuFlyoutItem).HasToggle(); + // MenuFlyoutItem's alignment changes based on the layout of other MenuFlyoutItems. + // This check looks through all MenuFlyoutItems in our items source and checks for + // ToggleMenuFlyoutItems and the presence of Icons, which can change the layout of + // all MenuFlyoutItems in the presenter. + var nCount = spItems.Count; + for (var i = 0; i < nCount; ++i) + { + MenuFlyoutItemBase item; + MenuFlyoutItem itemAsMenuItem; + MenuFlyoutSubItem itemAsMenuSubItem; + IconElement iconElement; + string keyboardAcceleratorText; - iconElement = (itemAsMenuItem as MenuFlyoutItem).Icon; - m_containsIconItems = m_containsIconItems || iconElement != null; + item = spItems[i] as MenuFlyoutItemBase; - keyboardAcceleratorText = (itemAsMenuItem as MenuFlyoutItem).KeyboardAcceleratorTextOverride; - m_containsItemsWithKeyboardAcceleratorText = m_containsItemsWithKeyboardAcceleratorText || !string.IsNullOrEmpty(keyboardAcceleratorText); - } - else - { - itemAsMenuSubItem = item as MenuFlyoutSubItem; - if (itemAsMenuSubItem != null) - { - iconElement = (itemAsMenuSubItem as MenuFlyoutSubItem).Icon; - m_containsIconItems = m_containsIconItems || iconElement != null; - } - } + // To prevent casting the same item more than we need to, each cast is conditional + // on the previous one failing. This way we only cast each item as many times as we + // need to. + itemAsMenuItem = item as MenuFlyoutItem; + if (itemAsMenuItem != null) + { + m_containsToggleItems = m_containsToggleItems || (itemAsMenuItem as MenuFlyoutItem).HasToggle(); - if (m_containsIconItems && m_containsToggleItems && m_containsItemsWithKeyboardAcceleratorText) + iconElement = (itemAsMenuItem as MenuFlyoutItem).Icon; + m_containsIconItems = m_containsIconItems || iconElement != null; + + keyboardAcceleratorText = (itemAsMenuItem as MenuFlyoutItem).KeyboardAcceleratorTextOverride; + m_containsItemsWithKeyboardAcceleratorText = m_containsItemsWithKeyboardAcceleratorText || !string.IsNullOrEmpty(keyboardAcceleratorText); + } + else + { + itemAsMenuSubItem = item as MenuFlyoutSubItem; + if (itemAsMenuSubItem != null) { - break; + iconElement = (itemAsMenuSubItem as MenuFlyoutSubItem).Icon; + m_containsIconItems = m_containsIconItems || iconElement != null; } } - UpdateTemplateSettings(); + if (m_containsIconItems && m_containsToggleItems && m_containsItemsWithKeyboardAcceleratorText) + { + break; + } } - } - // Create MenuFlyoutPresenterAutomationPeer to represent the - protected override AutomationPeer OnCreateAutomationPeer() - { - return new MenuFlyoutPresenterAutomationPeer(this); + UpdateTemplateSettings(); } + } + + // Create MenuFlyoutPresenterAutomationPeer to represent the + protected override AutomationPeer OnCreateAutomationPeer() + { + return new MenuFlyoutPresenterAutomationPeer(this); + } #if false - void UpdateVisualStateForPlacement(FlyoutBase.MajorPlacementMode placement) - { - m_mostRecentPlacement = placement; + void UpdateVisualStateForPlacement(FlyoutBase.MajorPlacementMode placement) + { + m_mostRecentPlacement = placement; - UpdateVisualState(false); - } + UpdateVisualState(false); + } - void ResetVisualState() - { - VisualStateManager.GoToState(this, "None", false); - } + void ResetVisualState() + { + VisualStateManager.GoToState(this, "None", false); + } #endif - private protected override void ChangeVisualState( - // true to use transitions when updating the visual state, false - // to snap directly to the new visual state. - bool bUseTransitions) - { - } - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); + private protected override void ChangeVisualState( + // true to use transitions when updating the visual state, false + // to snap directly to the new visual state. + bool bUseTransitions) + { + } - ScrollViewer spScrollViewer; + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); - // Get the ScrollViewer template part - spScrollViewer = this.GetTemplateChild("MenuFlyoutPresenterScrollViewer") as ScrollViewer; - m_tpScrollViewer = spScrollViewer; + ScrollViewer spScrollViewer; - // Apply a shadow + // Get the ScrollViewer template part + spScrollViewer = this.GetTemplateChild("MenuFlyoutPresenterScrollViewer") as ScrollViewer; + m_tpScrollViewer = spScrollViewer; - // UNO TODO - //bool isDefaultShadowEnabled = IsDefaultShadowEnabled; - //if (isDefaultShadowEnabled) - //{ - // DependencyObject spChild; - // VisualTreeHelper.GetChildStatic(this, 0, &spChild); - // var spChildAsUIE = spChild as UIElement; - - // if (spChildAsUIE != null) - // { - // ApplyElevationEffect(spChildAsUIE, GetDepth()); - // } - //} - } + // Apply a shadow // UNO TODO - // - //Timeline AttachEntranceAnimationCompleted( - // string pszStateName, - // uint nStateNameLength, - // TimelineCompletedEventCallback pCompletedEvent) + //bool isDefaultShadowEnabled = IsDefaultShadowEnabled; + //if (isDefaultShadowEnabled) //{ - // bool found = false; - // VisualState spState; - - // VisualStateManager.TryGetState(this, pszStateName, null, &spState, &found); + // DependencyObject spChild; + // VisualTreeHelper.GetChildStatic(this, 0, &spChild); + // var spChildAsUIE = spChild as UIElement; - // if (found && spState) + // if (spChildAsUIE != null) // { - // Storyboard spStoryboard; + // ApplyElevationEffect(spChildAsUIE, GetDepth()); + // } + //} + } - // spStoryboard = (spState.Storyboard); - // if (spStoryboard != null) - // { - // Timeline spTimeline; - // (spStoryboard.As(&spTimeline)); + // UNO TODO + // + //Timeline AttachEntranceAnimationCompleted( + // string pszStateName, + // uint nStateNameLength, + // TimelineCompletedEventCallback pCompletedEvent) + //{ + // bool found = false; + // VisualState spState; - // (pCompletedEvent.AttachEventHandler(spTimeline, - // std.bind(&OnEntranceAnimationCompleted, this, _1, _2))); + // VisualStateManager.TryGetState(this, pszStateName, null, &spState, &found); - // (spTimeline.CopyTo(ppTimeline)); - // } - // } - //} + // if (found && spState) + // { + // Storyboard spStoryboard; + + // spStoryboard = (spState.Storyboard); + // if (spStoryboard != null) + // { + // Timeline spTimeline; + // (spStoryboard.As(&spTimeline)); + + // (pCompletedEvent.AttachEventHandler(spTimeline, + // std.bind(&OnEntranceAnimationCompleted, this, _1, _2))); + + // (spTimeline.CopyTo(ppTimeline)); + // } + // } + //} #if false - void DetachEntranceAnimationCompletedHandlers() - { - // Marked as no longer used - // - //if (m_epTopPortraitCompletedHandler) - //{ - // ASSERT(m_tpTopPortraitTimeline); - // (m_epTopPortraitCompletedHandler.DetachEventHandler(m_tpTopPortraitTimeline)); - // m_tpTopPortraitTimeline.Clear(); - //} - - //if (m_epBottomPortraitCompletedHandler) - //{ - // ASSERT(m_tpBottomPortraitTimeline); - // (m_epBottomPortraitCompletedHandler.DetachEventHandler(m_tpBottomPortraitTimeline)); - // m_tpBottomPortraitTimeline.Clear(); - //} - - //if (m_epLeftLandscapeCompletedHandler) - //{ - // ASSERT(m_tpLeftLandscapeTimeline); - // (m_epLeftLandscapeCompletedHandler.DetachEventHandler(m_tpLeftLandscapeTimeline)); - // m_tpLeftLandscapeTimeline.Clear(); - //} - - //if (m_epRightLandscapeCompletedHandler) - //{ - // ASSERT(m_tpRightLandscapeTimeline); - // (m_epRightLandscapeCompletedHandler.DetachEventHandler(m_tpRightLandscapeTimeline)); - // m_tpRightLandscapeTimeline.Clear(); - //} - } + void DetachEntranceAnimationCompletedHandlers() + { + // Marked as no longer used + // + //if (m_epTopPortraitCompletedHandler) + //{ + // ASSERT(m_tpTopPortraitTimeline); + // (m_epTopPortraitCompletedHandler.DetachEventHandler(m_tpTopPortraitTimeline)); + // m_tpTopPortraitTimeline.Clear(); + //} - void OnEntranceAnimationCompleted( - DependencyObject pSender, - DependencyObject pArgs) - { - // UNO TODO - // m_animationInProgress = false; + //if (m_epBottomPortraitCompletedHandler) + //{ + // ASSERT(m_tpBottomPortraitTimeline); + // (m_epBottomPortraitCompletedHandler.DetachEventHandler(m_tpBottomPortraitTimeline)); + // m_tpBottomPortraitTimeline.Clear(); + //} - Focus(FocusState.Programmatic); - } + //if (m_epLeftLandscapeCompletedHandler) + //{ + // ASSERT(m_tpLeftLandscapeTimeline); + // (m_epLeftLandscapeCompletedHandler.DetachEventHandler(m_tpLeftLandscapeTimeline)); + // m_tpLeftLandscapeTimeline.Clear(); + //} + + //if (m_epRightLandscapeCompletedHandler) + //{ + // ASSERT(m_tpRightLandscapeTimeline); + // (m_epRightLandscapeCompletedHandler.DetachEventHandler(m_tpRightLandscapeTimeline)); + // m_tpRightLandscapeTimeline.Clear(); + //} + } + + void OnEntranceAnimationCompleted( + DependencyObject pSender, + DependencyObject pArgs) + { + // UNO TODO + // m_animationInProgress = false; + + Focus(FocusState.Programmatic); + } #endif - protected override void OnPointerExited(PointerRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; + protected override void OnPointerExited(PointerRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; - if (!handled) + if (!handled) + { + Pointer spPointer; + spPointer = (pArgs.Pointer); + var pointerDeviceType = (spPointer.PointerDeviceType); + if (PointerDeviceType.Mouse == (PointerDeviceType)pointerDeviceType && !m_isSubPresenter) { - Pointer spPointer; - spPointer = (pArgs.Pointer); - var pointerDeviceType = (spPointer.PointerDeviceType); - if (PointerDeviceType.Mouse == (PointerDeviceType)pointerDeviceType && !m_isSubPresenter) + var isHitVerticalScrollBarOrSubPresenter = false; + IMenuPresenter subPresenter; + + // Hit test the current position for the vertical ScrollBar and the sub presenter. + // Close the existing sub presenter if the current mouse position isn't hit + // the vertical ScrollBar nor sub presenter. + + subPresenter = (this as IMenuPresenter).SubPresenter; + + if (subPresenter != null) { - var isHitVerticalScrollBarOrSubPresenter = false; - IMenuPresenter subPresenter; + PointerPoint spPointerPoint; + UIElement spSubPresenterAsUIE; - // Hit test the current position for the vertical ScrollBar and the sub presenter. - // Close the existing sub presenter if the current mouse position isn't hit - // the vertical ScrollBar nor sub presenter. + spSubPresenterAsUIE = subPresenter as UIElement; - subPresenter = (this as IMenuPresenter).SubPresenter; + spPointerPoint = pArgs.GetCurrentPoint(null /* relativeTo*/); + var clientLogicalPointerPosition = spPointerPoint.Position; - if (subPresenter != null) + if (m_tpScrollViewer != null) { - PointerPoint spPointerPoint; - UIElement spSubPresenterAsUIE; - - spSubPresenterAsUIE = subPresenter as UIElement; + UIElement spVerticalScrollBarAsUE; + Control spScrollViewerAsControl; + DependencyObject spVerticalScrollBarAsDO; - spPointerPoint = pArgs.GetCurrentPoint(null /* relativeTo*/); - var clientLogicalPointerPosition = spPointerPoint.Position; + spScrollViewerAsControl = m_tpScrollViewer as Control; + spVerticalScrollBarAsDO = spScrollViewerAsControl.GetTemplateChild("VerticalScrollBar"); + spVerticalScrollBarAsUE = spVerticalScrollBarAsDO as UIElement; - if (m_tpScrollViewer != null) + if (spSubPresenterAsUIE != null || spVerticalScrollBarAsUE != null) { - UIElement spVerticalScrollBarAsUE; - Control spScrollViewerAsControl; - DependencyObject spVerticalScrollBarAsDO; + var spElements = VisualTreeHelper.FindElementsInHostCoordinates(clientLogicalPointerPosition, spVerticalScrollBarAsUE, true /* includeAllElements */); - spScrollViewerAsControl = m_tpScrollViewer as Control; - spVerticalScrollBarAsDO = spScrollViewerAsControl.GetTemplateChild("VerticalScrollBar"); - spVerticalScrollBarAsUE = spVerticalScrollBarAsDO as UIElement; + foreach (var spElement in spElements) + { + DependencyObject pElementAsCDO = spElement; - if (spSubPresenterAsUIE != null || spVerticalScrollBarAsUE != null) + if ((pElementAsCDO is ScrollBar && spVerticalScrollBarAsUE == spElement) || + (spSubPresenterAsUIE == spElement)) + { + isHitVerticalScrollBarOrSubPresenter = true; + break; + } + } + + if (!isHitVerticalScrollBarOrSubPresenter) { - var spElements = VisualTreeHelper.FindElementsInHostCoordinates(clientLogicalPointerPosition, spVerticalScrollBarAsUE, true /* includeAllElements */); + spElements = VisualTreeHelper.FindElementsInHostCoordinates(clientLogicalPointerPosition, spSubPresenterAsUIE, true /* includeAllElements */); foreach (var spElement in spElements) { @@ -517,469 +533,452 @@ protected override void OnPointerExited(PointerRoutedEventArgs pArgs) break; } } - - if (!isHitVerticalScrollBarOrSubPresenter) - { - spElements = VisualTreeHelper.FindElementsInHostCoordinates(clientLogicalPointerPosition, spSubPresenterAsUIE, true /* includeAllElements */); - - foreach (var spElement in spElements) - { - DependencyObject pElementAsCDO = spElement; - - if ((pElementAsCDO is ScrollBar && spVerticalScrollBarAsUE == spElement) || - (spSubPresenterAsUIE == spElement)) - { - isHitVerticalScrollBarOrSubPresenter = true; - break; - } - } - } } } + } - // The opened MenuFlyoutSubItem won't to be closed if the mouse position is - // on the vertical ScrollBar or sub presenter. - if (!isHitVerticalScrollBarOrSubPresenter) - { - var subPresenterBoundsLogical = GetSubPresenterBounds(spSubPresenterAsUIE); - var containsSubPresenter = subPresenterBoundsLogical.Contains(clientLogicalPointerPosition); + // The opened MenuFlyoutSubItem won't to be closed if the mouse position is + // on the vertical ScrollBar or sub presenter. + if (!isHitVerticalScrollBarOrSubPresenter) + { + var subPresenterBoundsLogical = GetSubPresenterBounds(spSubPresenterAsUIE); + var containsSubPresenter = subPresenterBoundsLogical.Contains(clientLogicalPointerPosition); - if (!containsSubPresenter) - { - DelayCloseMenuFlyoutSubItem(); - } + if (!containsSubPresenter) + { + DelayCloseMenuFlyoutSubItem(); } } } - - pArgs.Handled = true; } - } - internal bool GetContainsToggleItems() - { - return m_containsToggleItems; + pArgs.Handled = true; } + } - // Returns true if the ItemsSource contains at least one MenuFlyoutItem with an Icon; false otherwise. - internal bool GetContainsIconItems() - { - return m_containsIconItems; - } + internal bool GetContainsToggleItems() + { + return m_containsToggleItems; + } - // Returns true if the ItemsSource contains at least one MenuFlyoutItem with an Icon; false otherwise. - internal bool GetContainsItemsWithKeyboardAcceleratorText() - { - return m_containsItemsWithKeyboardAcceleratorText; - } + // Returns true if the ItemsSource contains at least one MenuFlyoutItem with an Icon; false otherwise. + internal bool GetContainsIconItems() + { + return m_containsIconItems; + } - private Rect GetSubPresenterBounds(UIElement pSubPresenterAsUIE) - { - var t = pSubPresenterAsUIE.TransformToVisual(null); - var r = t.TransformBounds(pSubPresenterAsUIE.LayoutSlotWithMarginsAndAlignments); + // Returns true if the ItemsSource contains at least one MenuFlyoutItem with an Icon; false otherwise. + internal bool GetContainsItemsWithKeyboardAcceleratorText() + { + return m_containsItemsWithKeyboardAcceleratorText; + } - return r; - } + private Rect GetSubPresenterBounds(UIElement pSubPresenterAsUIE) + { + var t = pSubPresenterAsUIE.TransformToVisual(null); + var r = t.TransformBounds(pSubPresenterAsUIE.LayoutSlotWithMarginsAndAlignments); - protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) - { - base.OnPointerEntered(pArgs); + return r; + } + + protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) + { + base.OnPointerEntered(pArgs); - var handled = pArgs.Handled; + var handled = pArgs.Handled; - if (!handled) + if (!handled) + { + Pointer spPointer; + spPointer = pArgs.Pointer; + var pointerDeviceType = spPointer.PointerDeviceType; + if (PointerDeviceType.Mouse == (PointerDeviceType)pointerDeviceType && m_isSubPresenter) { - Pointer spPointer; - spPointer = pArgs.Pointer; - var pointerDeviceType = spPointer.PointerDeviceType; - if (PointerDeviceType.Mouse == (PointerDeviceType)pointerDeviceType && m_isSubPresenter) - { - CancelCloseMenuFlyoutSubItem(); - var owner = (this as IMenuPresenter).Owner; + CancelCloseMenuFlyoutSubItem(); + var owner = (this as IMenuPresenter).Owner; - if (owner != null) + if (owner != null) + { + var parentSubItem = owner as MenuFlyoutSubItem; + if (parentSubItem != null) { - var parentSubItem = owner as MenuFlyoutSubItem; - if (parentSubItem != null) + var presenter = parentSubItem.GetParentMenuFlyoutPresenter(); + if (presenter != null) { - var presenter = parentSubItem.GetParentMenuFlyoutPresenter(); - if (presenter != null) - { - // When the mouse enters a MenuFlyoutPresenter that is a sub menu - // we have to tell the parent presenter to cancel any plans it had - // to close this sub - presenter.CancelCloseMenuFlyoutSubItem(); - } + // When the mouse enters a MenuFlyoutPresenter that is a sub menu + // we have to tell the parent presenter to cancel any plans it had + // to close this sub + presenter.CancelCloseMenuFlyoutSubItem(); } } } - - pArgs.Handled = true; } - } - private protected override string GetPlainText() - { - string automationName = null; + pArgs.Handled = true; + } + } - var ownerFlyout = m_wrParentMenuFlyout.Target as DependencyObject; + private protected override string GetPlainText() + { + string automationName = null; - if (ownerFlyout != null) - { - // If an automation name is set on the parent flyout, we'll use that as our plain text. - // Otherwise, we'll report the default plain text. - automationName = AutomationProperties.GetName(ownerFlyout); - } + var ownerFlyout = m_wrParentMenuFlyout.Target as DependencyObject; - if (automationName != null) - { - return automationName; - } - else - { - // UNO TODO + if (ownerFlyout != null) + { + // If an automation name is set on the parent flyout, we'll use that as our plain text. + // Otherwise, we'll report the default plain text. + automationName = AutomationProperties.GetName(ownerFlyout); + } - // If we have no title, we'll fall back to the default implementation, - // which retrieves our content as plain text (e.g., if our content is a string, - // it returns that; if our content is a TextBlock, it returns its Text value, etc.) - // MenuFlyoutPresenterGenerated.GetPlainText(strPlainText); + if (automationName != null) + { + return automationName; + } + else + { + // UNO TODO - // If we get the plain text from the content, then we want to truncate it, - // in case the resulting automation name is very long. - // Popup.TruncateAutomationName(strPlainText); - } + // If we have no title, we'll fall back to the default implementation, + // which retrieves our content as plain text (e.g., if our content is a string, + // it returns that; if our content is a TextBlock, it returns its Text value, etc.) + // MenuFlyoutPresenterGenerated.GetPlainText(strPlainText); - return automationName; + // If we get the plain text from the content, then we want to truncate it, + // in case the resulting automation name is very long. + // Popup.TruncateAutomationName(strPlainText); } - void IMenuPresenter.CloseSubMenu() - { - var subPresenter = m_wrSubPresenter?.Target as IMenuPresenter; + return automationName; + } - if (subPresenter != null) - { - subPresenter.CloseSubMenu(); - } + void IMenuPresenter.CloseSubMenu() + { + var subPresenter = m_wrSubPresenter?.Target as IMenuPresenter; - ISubMenuOwner owner; - owner = m_wrOwner?.Target as ISubMenuOwner; + if (subPresenter != null) + { + subPresenter.CloseSubMenu(); + } - if (owner != null) - { - owner.CloseSubMenu(); - } + ISubMenuOwner owner; + owner = m_wrOwner?.Target as ISubMenuOwner; - // Reset the focused index not to cached the previous focused index when - // the sub menu is opened in the next time - m_iFocusedIndex = -1; + if (owner != null) + { + owner.CloseSubMenu(); } - void DelayCloseMenuFlyoutSubItem() + // Reset the focused index not to cached the previous focused index when + // the sub menu is opened in the next time + m_iFocusedIndex = -1; + } + + void DelayCloseMenuFlyoutSubItem() + { + var subMenuPresenter = m_wrSubPresenter?.Target as IMenuPresenter; + + if (subMenuPresenter != null) { - var subMenuPresenter = m_wrSubPresenter?.Target as IMenuPresenter; + ISubMenuOwner subMenuOwner; + subMenuOwner = (subMenuPresenter.Owner); - if (subMenuPresenter != null) + if (subMenuOwner != null) { - ISubMenuOwner subMenuOwner; - subMenuOwner = (subMenuPresenter.Owner); - - if (subMenuOwner != null) - { - subMenuOwner.DelayCloseSubMenu(); - } + subMenuOwner.DelayCloseSubMenu(); } } + } - void CancelCloseMenuFlyoutSubItem() + void CancelCloseMenuFlyoutSubItem() + { + var subMenuPresenter = m_wrSubPresenter?.Target as IMenuPresenter; + + if (subMenuPresenter != null) { - var subMenuPresenter = m_wrSubPresenter?.Target as IMenuPresenter; + ISubMenuOwner subMenuOwner; + subMenuOwner = (subMenuPresenter.Owner); - if (subMenuPresenter != null) + if (subMenuOwner != null) { - ISubMenuOwner subMenuOwner; - subMenuOwner = (subMenuPresenter.Owner); - - if (subMenuOwner != null) - { - subMenuOwner.CancelCloseSubMenu(); - } + subMenuOwner.CancelCloseSubMenu(); } } + } #if false - DependencyObject GetParentMenuFlyoutSubItem(DependencyObject nativeDO) - { - var spThis = nativeDO as MenuFlyoutPresenter; - ISubMenuOwner spResult; - spResult = (spThis as IMenuPresenter).Owner; + DependencyObject GetParentMenuFlyoutSubItem(DependencyObject nativeDO) + { + var spThis = nativeDO as MenuFlyoutPresenter; + ISubMenuOwner spResult; + spResult = (spThis as IMenuPresenter).Owner; - var spResultAsMenuFlyoutSubItem = spResult as MenuFlyoutSubItem; + var spResultAsMenuFlyoutSubItem = spResult as MenuFlyoutSubItem; - if (spResultAsMenuFlyoutSubItem != null) - { - return spResultAsMenuFlyoutSubItem as DependencyObject; - } - else - { - return null; - } + if (spResultAsMenuFlyoutSubItem != null) + { + return spResultAsMenuFlyoutSubItem as DependencyObject; + } + else + { + return null; } + } #endif - internal void UpdateTemplateSettings() - { - var templateSettings = TemplateSettings; - var templateSettingsConcrete = templateSettings as MenuFlyoutPresenterTemplateSettings; + internal void UpdateTemplateSettings() + { + var templateSettings = TemplateSettings; + var templateSettingsConcrete = templateSettings as MenuFlyoutPresenterTemplateSettings; - var ownerFlyout = GetParentMenuFlyout(); + var ownerFlyout = GetParentMenuFlyout(); - if (ownerFlyout != null && templateSettingsConcrete != null) - { - // Query MenuFlyout Content MinWidth, given the input mode, from resource dictionary. - var flyoutContentMinWidth = ResourceResolver.ResolveTopLevelResourceDouble( - (ownerFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Touch || ownerFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.GameController) - ? "FlyoutThemeTouchMinWidth" : "FlyoutThemeMinWidth" - ); - var visibleBounds = this.LayoutSlotWithMarginsAndAlignments; - // DXamlCore.GetCurrent().GetVisibleContentBoundsForElement(GetHandle(), &visibleBounds); - - templateSettingsConcrete.FlyoutContentMinWidth = Math.Min(visibleBounds.Width, flyoutContentMinWidth); - } + if (ownerFlyout != null && templateSettingsConcrete != null) + { + // Query MenuFlyout Content MinWidth, given the input mode, from resource dictionary. + var flyoutContentMinWidth = ResourceResolver.ResolveTopLevelResourceDouble( + (ownerFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Touch || ownerFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.GameController) + ? "FlyoutThemeTouchMinWidth" : "FlyoutThemeMinWidth" + ); + var visibleBounds = this.LayoutSlotWithMarginsAndAlignments; + // DXamlCore.GetCurrent().GetVisibleContentBoundsForElement(GetHandle(), &visibleBounds); - double maxItemKeyboardAcceleratorTextWidth = 0; + templateSettingsConcrete.FlyoutContentMinWidth = Math.Min(visibleBounds.Width, flyoutContentMinWidth); + } - IObservableVector menuItems = Items; + double maxItemKeyboardAcceleratorTextWidth = 0; - // MenuFlyoutItem's alignment changes based on the layout of other MenuFlyoutItems. - // This check looks through all MenuFlyoutItems in our items source and finds the max - // keyboard accelerator label width, which affects the look of other items. - var nCount = (menuItems as ItemCollection).Size; - for (var i = 0; i < nCount; ++i) - { - MenuFlyoutItemBase item; - MenuFlyoutItem itemAsMenuItem; + IObservableVector menuItems = Items; - item = Items[i] as MenuFlyoutItemBase; + // MenuFlyoutItem's alignment changes based on the layout of other MenuFlyoutItems. + // This check looks through all MenuFlyoutItems in our items source and finds the max + // keyboard accelerator label width, which affects the look of other items. + var nCount = (menuItems as ItemCollection).Size; + for (var i = 0; i < nCount; ++i) + { + MenuFlyoutItemBase item; + MenuFlyoutItem itemAsMenuItem; - itemAsMenuItem = item as MenuFlyoutItem; - if (itemAsMenuItem != null) - { - var desiredSize = (itemAsMenuItem as MenuFlyoutItem).GetKeyboardAcceleratorTextDesiredSize(); - var desiredWidth = desiredSize.Width; - if (desiredWidth > maxItemKeyboardAcceleratorTextWidth) - { - maxItemKeyboardAcceleratorTextWidth = desiredWidth; - } - } - } + item = Items[i] as MenuFlyoutItemBase; - for (var i = 0; i < nCount; ++i) + itemAsMenuItem = item as MenuFlyoutItem; + if (itemAsMenuItem != null) { - MenuFlyoutItemBase item; - MenuFlyoutItem itemAsMenuItem; - - item = Items[i] as MenuFlyoutItemBase; - - itemAsMenuItem = item as MenuFlyoutItem; - if (itemAsMenuItem != null) + var desiredSize = (itemAsMenuItem as MenuFlyoutItem).GetKeyboardAcceleratorTextDesiredSize(); + var desiredWidth = desiredSize.Width; + if (desiredWidth > maxItemKeyboardAcceleratorTextWidth) { - (itemAsMenuItem as MenuFlyoutItem).UpdateTemplateSettings(maxItemKeyboardAcceleratorTextWidth); + maxItemKeyboardAcceleratorTextWidth = desiredWidth; } } } - protected override void OnGotFocus(RoutedEventArgs pArgs) + for (var i = 0; i < nCount; ++i) { - var focusState = FocusState; - if (m_iFocusedIndex == -1 && - focusState != FocusState.Unfocused) + MenuFlyoutItemBase item; + MenuFlyoutItem itemAsMenuItem; + + item = Items[i] as MenuFlyoutItemBase; + + itemAsMenuItem = item as MenuFlyoutItem; + if (itemAsMenuItem != null) { - // The MenuFlyoutPresenter gets focused for the first time right after it is opened. - // In this case we want to send focus to the first focusable item. - CycleFocus(true /* shouldCycleDown */, focusState); + (itemAsMenuItem as MenuFlyoutItem).UpdateTemplateSettings(maxItemKeyboardAcceleratorTextWidth); } - else if (focusState == FocusState.Unfocused) + } + } + + protected override void OnGotFocus(RoutedEventArgs pArgs) + { + var focusState = FocusState; + if (m_iFocusedIndex == -1 && + focusState != FocusState.Unfocused) + { + // The MenuFlyoutPresenter gets focused for the first time right after it is opened. + // In this case we want to send focus to the first focusable item. + CycleFocus(true /* shouldCycleDown */, focusState); + } + else if (focusState == FocusState.Unfocused) + { + // A child element got focus, so make sure we keep m_iFocusedIndex in sync + // with it. + var focusedElement = (XamlRoot is null ? + FocusManager.GetFocusedElement() : + FocusManager.GetFocusedElement(XamlRoot)) as DependencyObject; + + // Since GotFocus is an async event, the focused element could be null if we got it + // after the popup closes, which clears focus. + if (focusedElement != null) { - // A child element got focus, so make sure we keep m_iFocusedIndex in sync - // with it. - var focusedElement = (XamlRoot is null ? - FocusManager.GetFocusedElement() : - FocusManager.GetFocusedElement(XamlRoot)) as DependencyObject; - - // Since GotFocus is an async event, the focused element could be null if we got it - // after the popup closes, which clears focus. - if (focusedElement != null) + var focusedElementIndex = IndexFromContainer(focusedElement); + if (focusedElementIndex != -1) { - var focusedElementIndex = IndexFromContainer(focusedElement); - if (focusedElementIndex != -1) - { - m_iFocusedIndex = focusedElementIndex; - } + m_iFocusedIndex = focusedElementIndex; } } + } - } + } - void EnsureInitialFocusIndex() + void EnsureInitialFocusIndex() + { + if (m_iFocusedIndex == -1) { - if (m_iFocusedIndex == -1) + var focusedElement = XamlRoot is null ? + FocusManager.GetFocusedElement() : + FocusManager.GetFocusedElement(XamlRoot); + + if (this != focusedElement) { - var focusedElement = XamlRoot is null ? - FocusManager.GetFocusedElement() : - FocusManager.GetFocusedElement(XamlRoot); + var menuItemsCount = Items.Count; - if (this != focusedElement) + for (var i = 0; i < menuItemsCount; ++i) { - var menuItemsCount = Items.Count; - - for (var i = 0; i < menuItemsCount; ++i) - { - var itemAsDependencyObject = Items[i] as DependencyObject; - MenuFlyoutItem menuItem; + var itemAsDependencyObject = Items[i] as DependencyObject; + MenuFlyoutItem menuItem; - menuItem = itemAsDependencyObject as MenuFlyoutItem; + menuItem = itemAsDependencyObject as MenuFlyoutItem; - if (menuItem == focusedElement) - { - m_iFocusedIndex = i; - break; - } + if (menuItem == focusedElement) + { + m_iFocusedIndex = i; + break; } - - global::System.Diagnostics.Debug.Assert(m_iFocusedIndex != -1); } + + global::System.Diagnostics.Debug.Assert(m_iFocusedIndex != -1); } } + } #if false - int GetPositionInSetHelper(MenuFlyoutItemBase item) - { - var returnValue = -1; + int GetPositionInSetHelper(MenuFlyoutItemBase item) + { + var returnValue = -1; + + var presenter = item.GetParentMenuFlyoutPresenter(); - var presenter = item.GetParentMenuFlyoutPresenter(); + if (presenter != null) + { + var indexOfItem = Items.IndexOf(item); - if (presenter != null) + if (indexOfItem != -1) { - var indexOfItem = Items.IndexOf(item); + // Iterate through the items preceding this item and subtract the number + // of separaters and collapsed items to get its position in the set. + var positionInSet = (int)(indexOfItem); - if (indexOfItem != -1) + for (var i = 0; i < indexOfItem; ++i) { - // Iterate through the items preceding this item and subtract the number - // of separaters and collapsed items to get its position in the set. - var positionInSet = (int)(indexOfItem); + var child = Items[i] as DependencyObject; - for (var i = 0; i < indexOfItem; ++i) + if (child != null) { - var child = Items[i] as DependencyObject; - - if (child != null) + if (child is MenuFlyoutSeparator) { - if (child is MenuFlyoutSeparator) - { - --positionInSet; - } - else + --positionInSet; + } + else + { + if (child is UIElement itemAsUI) { - if (child is UIElement itemAsUI) - { - var visibility = itemAsUI.Visibility; + var visibility = itemAsUI.Visibility; - if (Visibility.Visible != visibility) - { - --positionInSet; - } + if (Visibility.Visible != visibility) + { + --positionInSet; } } } } + } - global::System.Diagnostics.Debug.Assert(positionInSet >= 0); + global::System.Diagnostics.Debug.Assert(positionInSet >= 0); - returnValue = positionInSet + 1; // Add 1 to convert from a 0-based index to a 1-based index. - } + returnValue = positionInSet + 1; // Add 1 to convert from a 0-based index to a 1-based index. } - - return returnValue; } - int GetSizeOfSetHelper(MenuFlyoutItemBase item) + return returnValue; + } + + int GetSizeOfSetHelper(MenuFlyoutItemBase item) + { + var returnValue = -1; + + var presenter = item.GetParentMenuFlyoutPresenter(); + + if (presenter != null) { - var returnValue = -1; + var itemsCount = Items.Count; - var presenter = item.GetParentMenuFlyoutPresenter(); + // Iterate through the parent presenters items and subtract the + // number of separaters and collapsed items from the total count + // to get the size of the set. + var sizeOfSet = (int)(itemsCount); - if (presenter != null) + for (var i = 0; i < itemsCount; ++i) { - var itemsCount = Items.Count; - - // Iterate through the parent presenters items and subtract the - // number of separaters and collapsed items from the total count - // to get the size of the set. - var sizeOfSet = (int)(itemsCount); + var child = Items[i] as DependencyObject; - for (var i = 0; i < itemsCount; ++i) + if (child != null) { - var child = Items[i] as DependencyObject; - - if (child != null) + if (child is MenuFlyoutSeparator) { - if (child is MenuFlyoutSeparator) - { - --sizeOfSet; - } - else + --sizeOfSet; + } + else + { + var itemAsUI = child as UIElement; + if (itemAsUI != null) { - var itemAsUI = child as UIElement; - if (itemAsUI != null) - { - var visibility = itemAsUI.Visibility; + var visibility = itemAsUI.Visibility; - if (Visibility.Visible != visibility) - { - --sizeOfSet; - } + if (Visibility.Visible != visibility) + { + --sizeOfSet; } } } } - - global::System.Diagnostics.Debug.Assert(sizeOfSet >= 0); - - returnValue = sizeOfSet; } - return returnValue; + global::System.Diagnostics.Debug.Assert(sizeOfSet >= 0); + + returnValue = sizeOfSet; } + + return returnValue; + } #endif - ISubMenuOwner IMenuPresenter.Owner - { - get => m_wrOwner?.Target as ISubMenuOwner; - set => m_wrOwner = new WeakReference(value); - } + ISubMenuOwner IMenuPresenter.Owner + { + get => m_wrOwner?.Target as ISubMenuOwner; + set => m_wrOwner = new WeakReference(value); + } - IMenu IMenuPresenter.OwningMenu - { - get => m_wrOwningMenu?.Target as IMenu; - set => m_wrOwningMenu = new WeakReference(value); - } + IMenu IMenuPresenter.OwningMenu + { + get => m_wrOwningMenu?.Target as IMenu; + set => m_wrOwningMenu = new WeakReference(value); + } - IMenuPresenter IMenuPresenter.SubPresenter - { - get => m_wrSubPresenter?.Target as IMenuPresenter; - set => m_wrSubPresenter = new WeakReference(value); - } + IMenuPresenter IMenuPresenter.SubPresenter + { + get => m_wrSubPresenter?.Target as IMenuPresenter; + set => m_wrSubPresenter = new WeakReference(value); + } - internal bool IsTextScaleFactorEnabledInternal { get; set; } + internal bool IsTextScaleFactorEnabledInternal { get; set; } - internal void SetDepth(int depth) - { - m_depth = depth; - } + internal void SetDepth(int depth) + { + m_depth = depth; + } - internal int GetDepth() - { - return m_depth; - } + internal int GetDepth() + { + return m_depth; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuPopupThemeTransition.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuPopupThemeTransition.cs index 9743885e49fe..ec8b3622f59c 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuPopupThemeTransition.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuPopupThemeTransition.cs @@ -32,6 +32,4 @@ public AnimationDirection Direction public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register(nameof(Direction), typeof(AnimationDirection), typeof(MenuPopupThemeTransition), new FrameworkPropertyMetadata(AnimationDirection.Left)); - - } From b12bb9d4d1038e6e38cd5b18bbd69ff00147e272 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 20 Jun 2024 11:05:46 +0200 Subject: [PATCH 10/28] feat: MenuFlyoutItemAutomationPeer --- .../MenuFlyoutItemAutomationPeer.cs | 6 +- .../Automation/ElementNotEnabledException.cs | 21 ++-- .../Peers/MenuFlyoutItemAutomationPeer.cs | 104 ++++++++++++++---- 3 files changed, 97 insertions(+), 34 deletions(-) diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/MenuFlyoutItemAutomationPeer.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/MenuFlyoutItemAutomationPeer.cs index 3911cc4e3bd4..b3dee3aa8546 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/MenuFlyoutItemAutomationPeer.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/MenuFlyoutItemAutomationPeer.cs @@ -3,12 +3,12 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Automation.Peers { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false [global::Uno.NotImplemented] #endif public partial class MenuFlyoutItemAutomationPeer : global::Microsoft.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer, global::Microsoft.UI.Xaml.Automation.Provider.IInvokeProvider { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] public MenuFlyoutItemAutomationPeer(global::Microsoft.UI.Xaml.Controls.MenuFlyoutItem owner) : base(owner) { @@ -16,7 +16,7 @@ public MenuFlyoutItemAutomationPeer(global::Microsoft.UI.Xaml.Controls.MenuFlyou } #endif // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutItemAutomationPeer.MenuFlyoutItemAutomationPeer(Microsoft.UI.Xaml.Controls.MenuFlyoutItem) -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] public void Invoke() { diff --git a/src/Uno.UI/UI/Xaml/Automation/ElementNotEnabledException.cs b/src/Uno.UI/UI/Xaml/Automation/ElementNotEnabledException.cs index 0c00c54fc395..d79888287556 100644 --- a/src/Uno.UI/UI/Xaml/Automation/ElementNotEnabledException.cs +++ b/src/Uno.UI/UI/Xaml/Automation/ElementNotEnabledException.cs @@ -1,19 +1,18 @@ using System; -namespace Microsoft.UI.Xaml.Automation +namespace Microsoft.UI.Xaml.Automation; + +public class ElementNotEnabledException : Exception { - public class ElementNotEnabledException : Exception + public ElementNotEnabledException() : base() { - public ElementNotEnabledException() : base() - { - } + } - public ElementNotEnabledException(string message) : base(message) - { - } + public ElementNotEnabledException(string message) : base(message) + { + } - public ElementNotEnabledException(string message, Exception innerException) : base(message, innerException) - { - } + public ElementNotEnabledException(string message, Exception innerException) : base(message, innerException) + { } } diff --git a/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutItemAutomationPeer.cs b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutItemAutomationPeer.cs index 3911cc4e3bd4..415fdc813399 100644 --- a/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutItemAutomationPeer.cs +++ b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutItemAutomationPeer.cs @@ -1,28 +1,92 @@ -// -#pragma warning disable 108 // new keyword hiding -#pragma warning disable 114 // new keyword hiding -namespace Microsoft.UI.Xaml.Automation.Peers +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutItemAutomationPeer_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using Microsoft.UI.Xaml.Automation.Provider; +using Microsoft.UI.Xaml.Controls; + +namespace Microsoft.UI.Xaml.Automation.Peers; + +/// +/// Exposes MenuFlyoutItem types to Microsoft UI Automation. +/// +public partial class MenuFlyoutItemAutomationPeer : FrameworkElementAutomationPeer, IInvokeProvider { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented] -#endif - public partial class MenuFlyoutItemAutomationPeer : global::Microsoft.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer, global::Microsoft.UI.Xaml.Automation.Provider.IInvokeProvider + /// + /// Initializes a new instance of the MenuFlyoutItemAutomationPeer class. + /// + /// The owner element to create for. + public MenuFlyoutItemAutomationPeer(MenuFlyoutItem owner) : base(owner) { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public MenuFlyoutItemAutomationPeer(global::Microsoft.UI.Xaml.Controls.MenuFlyoutItem owner) : base(owner) + } + + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (patternInterface == PatternInterface.Invoke) + { + return this; + } + + return base.GetPatternCore(patternInterface); + } + + protected override string GetClassNameCore() => nameof(MenuFlyoutItem); + + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.MenuItem; + + protected override string GetAcceleratorKeyCore() + { + var returnValue = base.GetAcceleratorKeyCore(); + if (string.IsNullOrEmpty(returnValue)) + { + var owner = Owner as MenuFlyoutItem; + if (owner != null) + { + var keyboardAcceleratorTextOverride = owner.KeyboardAcceleratorTextOverride; + returnValue = GetTrimmedKeyboardAcceleratorTextOverride(keyboardAcceleratorTextOverride); + } + } + return returnValue; + } + + protected override int GetPositionInSetCore() + { + // First retrieve any valid value being directly set on the container, that value will get precedence. + var returnValue = base.GetPositionInSetCore(); + + // if it still is default value, calculate it ourselves. + if (returnValue == -1) + { + returnValue = MenuFlyoutPresenter.GetPositionInSetHelper((MenuFlyoutItemBase)Owner); + } + + return returnValue; + } + + protected override int GetSizeOfSetCore() + { + // First retrieve any valid value being directly set on the container, that value will get precedence. + var returnValue = base.GetSizeOfSetCore(); + + // if it still is default value, calculate it ourselves. + if (returnValue == -1) { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutItemAutomationPeer", "MenuFlyoutItemAutomationPeer.MenuFlyoutItemAutomationPeer(MenuFlyoutItem owner)"); + returnValue = MenuFlyoutPresenter.GetSizeOfSetHelper((MenuFlyoutItemBase)Owner); } -#endif - // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutItemAutomationPeer.MenuFlyoutItemAutomationPeer(Microsoft.UI.Xaml.Controls.MenuFlyoutItem) -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void Invoke() + + return returnValue; + } + + /// + /// Sends a request to invoke the menu flyout associated with the automation peer. + /// + public void Invoke() + { + if (!IsEnabled()) { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutItemAutomationPeer", "void MenuFlyoutItemAutomationPeer.Invoke()"); + throw new ElementNotEnabledException(); } -#endif - // Processing: Microsoft.UI.Xaml.Automation.Provider.IInvokeProvider + + ((MenuFlyoutItem)Owner).Invoke(); } } From 9b21beefe586f6bb83f7bfa70c33598258486c24 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 20 Jun 2024 11:37:35 +0200 Subject: [PATCH 11/28] feat: MenuFlyoutPresenterAutomationPeer --- .../MenuFlyoutPresenterAutomationPeer.cs | 4 +- .../MenuFlyoutPresenterAutomationPeer.cs | 54 ++++++++++++++----- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/MenuFlyoutPresenterAutomationPeer.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/MenuFlyoutPresenterAutomationPeer.cs index 62ac9f7716a1..415166a17e17 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/MenuFlyoutPresenterAutomationPeer.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/MenuFlyoutPresenterAutomationPeer.cs @@ -3,12 +3,12 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Automation.Peers { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false [global::Uno.NotImplemented] #endif public partial class MenuFlyoutPresenterAutomationPeer : global::Microsoft.UI.Xaml.Automation.Peers.ItemsControlAutomationPeer { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] public MenuFlyoutPresenterAutomationPeer(global::Microsoft.UI.Xaml.Controls.MenuFlyoutPresenter owner) : base(owner) { diff --git a/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutPresenterAutomationPeer.cs b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutPresenterAutomationPeer.cs index 62ac9f7716a1..cad534dcfa9d 100644 --- a/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutPresenterAutomationPeer.cs +++ b/src/Uno.UI/UI/Xaml/Automation/Peers/MenuFlyoutPresenterAutomationPeer.cs @@ -1,20 +1,46 @@ -// -#pragma warning disable 108 // new keyword hiding -#pragma warning disable 114 // new keyword hiding -namespace Microsoft.UI.Xaml.Automation.Peers +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutPresenterAutomationPeer_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using Microsoft.UI.Xaml.Controls; + +namespace Microsoft.UI.Xaml.Automation.Peers; + +/// +/// Exposes MenuFlyoutPresenter types to Microsoft UI Automation. +/// +public partial class MenuFlyoutPresenterAutomationPeer : ItemsControlAutomationPeer { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented] -#endif - public partial class MenuFlyoutPresenterAutomationPeer : global::Microsoft.UI.Xaml.Automation.Peers.ItemsControlAutomationPeer + /// + /// Initializes a new instance of the MenuFlyoutPresenterAutomationPeer class. + /// + /// The owner element to create for. + public MenuFlyoutPresenterAutomationPeer(MenuFlyoutPresenter owner) : base(owner) { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public MenuFlyoutPresenterAutomationPeer(global::Microsoft.UI.Xaml.Controls.MenuFlyoutPresenter owner) : base(owner) + } + + protected override string GetAutomationIdCore() + { + var result = base.GetAutomationIdCore(); + if (string.IsNullOrEmpty(result)) + { + result = ((MenuFlyoutPresenter)Owner).GetOwnerName(); + } + return result; + } + + protected override string GetClassNameCore() => nameof(MenuFlyoutPresenter); + + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Menu; + + protected override ItemAutomationPeer OnCreateItemAutomationPeer(object item) + { + // Not providing automation peer for separator element + if (item is MenuFlyoutSeparator) { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutPresenterAutomationPeer", "MenuFlyoutPresenterAutomationPeer.MenuFlyoutPresenterAutomationPeer(MenuFlyoutPresenter owner)"); + return null; } -#endif - // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.MenuFlyoutPresenterAutomationPeer.MenuFlyoutPresenterAutomationPeer(Microsoft.UI.Xaml.Controls.MenuFlyoutPresenter) + + return new ItemAutomationPeer(item, this); // TODO:MZ: Currently throws } } From 7c481e7f5674e04f0bac7a894e5183ca734f90f3 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 20 Jun 2024 14:29:56 +0200 Subject: [PATCH 12/28] feat: Update MenuFlyoutItem to winui3/release/1.5.4 --- .../MenuFlyout/MenuFlyoutItem.Properties.cs | 28 +- .../Controls/MenuFlyout/MenuFlyoutItem.cs | 836 +-------- .../MenuFlyout/MenuFlyoutItem.h.mux.cs | 860 +--------- .../Controls/MenuFlyout/MenuFlyoutItem.mux.cs | 1506 ++++++++--------- .../Controls/MenuFlyout/MenuFlyoutItemBase.cs | 64 +- .../MenuFlyout/ToggleMenuFlyoutItem.Uno.cs | 15 - .../MenuFlyout/ToggleMenuFlyoutItem.h.mux.cs | 13 + 7 files changed, 807 insertions(+), 2515 deletions(-) delete mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Uno.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.h.mux.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs index 9768dacedaa6..818065846f5a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs @@ -1,4 +1,9 @@ -using System.Windows.Input; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutItem_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System.Windows.Input; +using Microsoft.UI.Xaml.Controls.Primitives; namespace Microsoft.UI.Xaml.Controls; @@ -9,8 +14,8 @@ partial class MenuFlyoutItem : MenuFlyoutItemBase /// public ICommand Command { - get { return (ICommand)GetValue(CommandProperty); } - set { SetValue(CommandProperty, value); } + get => (ICommand)GetValue(CommandProperty); + set => SetValue(CommandProperty, value); } /// @@ -28,8 +33,8 @@ public ICommand Command /// public object CommandParameter { - get { return (object)GetValue(CommandParameterProperty); } - set { SetValue(CommandParameterProperty, value); } + get => (object)GetValue(CommandParameterProperty); + set => SetValue(CommandParameterProperty, value); } /// @@ -65,7 +70,12 @@ public IconElement Icon /// public string KeyboardAcceleratorTextOverride { - get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; + get + { + InitializeKeyboardAcceleratorText() + return (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; + } + set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); } @@ -89,8 +99,8 @@ public string KeyboardAcceleratorTextOverride /// public string Text { - get { return (string)GetValue(TextProperty) ?? ""; } - set { SetValue(TextProperty, value); } + get => (string)GetValue(TextProperty) ?? ""; + set => SetValue(TextProperty, value); } /// @@ -103,6 +113,8 @@ public string Text ownerType: typeof(MenuFlyoutItem), typeMetadata: new FrameworkPropertyMetadata(default(string))); + internal bool PreventDismissOnPointer { get; set; } + /// /// Occurs when a menu item is clicked. /// diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs index 42826f7a255e..ca13c626e64d 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs @@ -1,828 +1,14 @@ -//using System; -//using Uno.Client; -//using Uno.Disposables; -//using Windows.Foundation; -//using Microsoft.UI.Xaml.Automation; -//using Microsoft.UI.Xaml.Automation.Peers; -//using Microsoft.UI.Xaml.Controls.Primitives; -//using Microsoft.UI.Xaml.Input; -//using ICommand = System.Windows.Input.ICommand; -//using Microsoft.UI.Xaml.Markup; +using Microsoft.UI.Xaml.Markup; -//#if HAS_UNO_WINUI -//using Microsoft.UI.Input; -//#else -//using Windows.Devices.Input; -//using Windows.UI.Input; -//#endif +namespace Microsoft.UI.Xaml.Controls; -//namespace Microsoft.UI.Xaml.Controls -//{ -// [ContentProperty(Name = nameof(Text))] -// public partial class MenuFlyoutItem : MenuFlyoutItemBase -// { -// // Whether the pointer is currently over the -// bool m_bIsPointerOver = true; +[ContentProperty(Name = nameof(Text))] +public partial class MenuFlyoutItem : MenuFlyoutItemBase +{ + public MenuFlyoutItem() + { + DefaultStyleKey = typeof(MenuFlyoutItem); -// // Whether the pointer is currently pressed over the -// internal bool m_bIsPressed = true; - -// // Whether the pointer's left button is currently down. -// internal bool m_bIsPointerLeftButtonDown = true; - -// // True if the SPACE or ENTER key is currently pressed, false otherwise. -// internal bool m_bIsSpaceOrEnterKeyDown = true; - -// // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. -// internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; - -// // On pointer released we perform some actions depending on control. We decide to whether to perform them -// // depending on some parameters including but not limited to whether released is followed by a pressed, which -// // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. -// bool m_shouldPerformActions = true; - -// // Event pointer for the ICommand.CanExecuteChanged event. -// IDisposable m_epCanExecuteChangedHandler; - -// // UNO TODO -// // Event pointer for the Loaded event. -// // IDisposable m_epLoadedHandler; - -// // UNO TODO -// // IDisposable m_epMenuFlyoutItemClickEventCallback; - -// double m_maxKeyboardAcceleratorTextWidth; -// TextBlock m_tpKeyboardAcceleratorTextBlock; - -// bool m_isTemplateApplied; - -// #region CommandParameter - -// public object CommandParameter -// { -// get { return (object)GetValue(CommandParameterProperty); } -// set { SetValue(CommandParameterProperty, value); } -// } - -// public static DependencyProperty CommandParameterProperty { get; } = -// DependencyProperty.Register( -// "CommandParameter", typeof(object), -// typeof(Controls.MenuFlyoutItem), -// new FrameworkPropertyMetadata(default(object))); - -// #endregion - -// #region Command - -// public ICommand Command -// { -// get { return (ICommand)GetValue(CommandProperty); } -// set { SetValue(CommandProperty, value); } -// } - -// public static DependencyProperty CommandProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Command), -// propertyType: typeof(ICommand), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); - -// #endregion - -// #region Text - -// public string Text -// { -// get { return (string)GetValue(TextProperty) ?? ""; } -// set { SetValue(TextProperty, value); } -// } - -// public static DependencyProperty TextProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Text), -// propertyType: typeof(string), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(string))); - -// #endregion - -// public IconElement Icon -// { -// get => (IconElement)this.GetValue(IconProperty); -// set => this.SetValue(IconProperty, value); -// } - -// public static DependencyProperty IconProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Icon), -// propertyType: typeof(IconElement), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); - -// public string KeyboardAcceleratorTextOverride -// { -// get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; -// set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); -// } - -// public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = -// DependencyProperty.Register( -// name: nameof(KeyboardAcceleratorTextOverride), -// propertyType: typeof(string), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(string))); - -// public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } - -//#pragma warning disable CS0108 -// public event RoutedEventHandler Click; -//#pragma warning restore CS0108 - -// internal void InvokeClick() -// { -// Click?.Invoke(this, new RoutedEventArgs(this)); -// Command.ExecuteIfPossible(this.CommandParameter); -// } - -// public MenuFlyoutItem() -// { -// m_bIsPointerOver = false; -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; -// m_shouldPerformActions = false; - -// DefaultStyleKey = typeof(MenuFlyoutItem); - -// Initialize(); -// } - -// // Prepares object's state -// void Initialize() -// { -// Loaded += (s, e) => ClearStateFlags(); - -// this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); -// } - -// // Apply a template to the - -// protected override void OnApplyTemplate() -// { -// base.OnApplyTemplate(); - -// TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; -// m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; - -// SuppressIsEnabled(false); -// UpdateCanExecute(); - -// // Sync the logical and visual states of the control -// UpdateVisualState(); -// } - -// // PointerPressed event handler. -// protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerPressed(pArgs); -// var handled = pArgs.Handled; -// if (!handled) -// { -// PointerPoint spPointerPoint; -// PointerPointProperties spPointerProperties; -// spPointerPoint = pArgs.GetCurrentPoint(this); -// spPointerProperties = spPointerPoint.Properties; -// var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; -// if (bIsLeftButtonPressed) -// { -// m_bIsPointerLeftButtonDown = true; -// m_bIsPressed = true; - -// pArgs.Handled = true; -// UpdateVisualState(); -// } -// } -// } - -// // PointerReleased event handler. -// protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerReleased(pArgs); - -// bool handled = false; - -// handled = pArgs.Handled; -// if (!handled) -// { -// m_bIsPointerLeftButtonDown = false; - -// m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; - -//#if false -// // UNO TODO -// if (m_shouldPerformActions) -// { -// GestureModes gestureFollowing = GestureModes.None; - -// m_bIsPressed = false; - -// gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); - -// // Note that we are intentionally NOT handling the args -// // if we do not fall through here because basically we are no_opting in that case. -// if (gestureFollowing != GestureModes.RightTapped) -// { -// pArgs.Handled = true; -// PerformPointerUpAction(); -// } -// } -//#else -// PerformPointerUpAction(); -//#endif -// } -// } - -// private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// PerformPointerUpAction(); -// pArgs.Handled = true; -// } -// } - -// // Contains the logic to be employed if we decide to handle pointer released. -// void PerformPointerUpAction() -// { -// if (m_shouldPerformActions) -// { -// Focus(FocusState.Pointer); -// Invoke(); -// } -// } - -// // Performs appropriate actions upon a mouse/keyboard invocation of a -// internal virtual void Invoke() -// { -// RoutedEventArgs spArgs; -// MenuFlyoutPresenter spParentMenuFlyoutPresenter; - -// // Create the args -// spArgs = new RoutedEventArgs(); -// spArgs.OriginalSource = this; - -// // Raise the event -// Click?.Invoke(this, spArgs); - -// // UNO TODO -// //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); - -// // Execute command associated with the button -// ExecuteCommand(); - -// bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; -// if (!shouldPreventDismissOnPointer) -// { -// // Close the MenuFlyout. -// spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); -// if (spParentMenuFlyoutPresenter != null) -// { -// IMenu owningMenu; - -// owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; -// if (owningMenu != null) -// { -// // We need to make close all menu flyout sub items before we hide the parent menu flyout, -// // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a -// // significant amount of time before closing the sub menu. -// (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); -// owningMenu.Close(); -// } -// } -// } - -// ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); -// } - -// // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) -// //{ -// // DependencyObject peer = pMenuFlyoutItem; - -// // if (peer == null) -// // { -// // return; -// // } - -// // MenuFlyoutItem peerAsMenuFlyoutItem; -// // (peer.As(&peerAsMenuFlyoutItem)); -// // IFCPTR_RETURN(peerAsMenuFlyoutItem); - -// // (peerAsAddProofingItemHandler(eventHandler)); - -// // return S_OK; -// //} - -// // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) -// //{ -// // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) -// // { -// // DependencyObject spSender; -// // EventArgs spArgs; - -// // IFCPTR_RETURN(pSender); -// // IFCPTR_RETURN(pArgs); - -// // (ctl.do_query_interface(spSender, pSender)); -// // (ctl.do_query_interface(spArgs, pArgs)); - -// // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); - -// // return S_OK; -// // })); - -// // return S_OK; -// //} - -// // Executes Command if CanExecute() returns true. -// void ExecuteCommand() -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// object spCommandParameter; -// bool canExecute; - -// spCommandParameter = CommandParameter; -// canExecute = spCommand.CanExecute(spCommandParameter); -// if (canExecute) -// { -// spCommand.Execute(spCommandParameter); -// } -// } -// } - -// // PointerEnter event handler. -// protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerEntered(pArgs); - -// MenuFlyoutPresenter spParentPresenter; - -// m_bIsPointerOver = true; - -// spParentPresenter = GetParentMenuFlyoutPresenter(); -// if (spParentPresenter != null) -// { -// IMenuPresenter subPresenter; - -// subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; -// if (subPresenter != null) -// { -// ISubMenuOwner subPresenterOwner; - -// subPresenterOwner = subPresenter.Owner; - -// if (subPresenterOwner != null) -// { -// subPresenterOwner.DelayCloseSubMenu(); -// } -// } -// } - -// UpdateVisualState(); -// } - -// // PointerExited event handler. -// protected override void OnPointerExited(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerExited(pArgs); - -// // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// UpdateVisualState(); -// } - -// // PointerCaptureLost event handler. -// protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerCaptureLost(pArgs); - -// // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// UpdateVisualState(); -// } - -// // Called when the IsEnabled property changes. -// private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) -// { -// if (!e.NewValue) -// { -// ClearStateFlags(); -// } -// else -// { -// UpdateVisualState(); -// } - -// base.OnIsEnabledChanged(e); -// } - -// // Called when the control got focus. -// protected override void OnGotFocus(RoutedEventArgs pArgs) -// { -// UpdateVisualState(); -// } - -// // LostFocus event handler. -// protected override void OnLostFocus(RoutedEventArgs pArgs) -// { -// if (!m_bIsPointerLeftButtonDown) -// { -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; -// m_bIsPressed = false; -// } - -// UpdateVisualState(); -// } - -// // KeyDown event handler. -// protected override void OnKeyDown(KeyRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// var key = pArgs.Key; -// handled = KeyPressMenuFlyout.KeyDown(key, this); -// pArgs.Handled = handled; -// } -// } - -// // KeyUp event handler. -// protected override void OnKeyUp(KeyRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// var key = (pArgs.Key); -// KeyPressMenuFlyout.KeyUp(key, this); -// pArgs.Handled = true; -// } -// } - -// // Handle the custom property changed event and call the OnPropertyChanged2 methods. -// internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) -// { -// if (args.Property == UIElement.VisibilityProperty) -// { -// OnVisibilityChanged(); -// } -// else if (args.Property == MenuFlyoutItem.CommandProperty) -// { -// OnCommandChanged(args.OldValue, args.NewValue); -// } -// else if (args.Property == MenuFlyoutItem.CommandParameterProperty) -// { -// UpdateCanExecute(); -// } -// } - -// // Update the visual states when the Visibility property is changed. -// void OnVisibilityChanged() -// { -// Visibility visibility = Visibility; - -// if (Visibility.Visible != visibility) -// { -// ClearStateFlags(); -// } -// } - -// private protected override void OnLoaded() -// { -// base.OnLoaded(); - -// if (m_epCanExecuteChangedHandler == null) -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// void OnCanExecuteChanged(object sender, object args) -// { -// UpdateCanExecute(); -// } - -// spCommand.CanExecuteChanged += OnCanExecuteChanged; - -// m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); -// } -// } - -// // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, -// // we need to update our value now. -// UpdateCanExecute(); -// } - -// private protected override void OnUnloaded() -// { -// base.OnUnloaded(); - -// if (m_epCanExecuteChangedHandler != null) -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// m_epCanExecuteChangedHandler.Dispose(); -// } -// } -// } - -// // Called when the Command property changes. -// void -// OnCommandChanged( -// object pOldValue, -// object pNewValue) -// { -// // Remove handler for CanExecuteChanged from the old value -// m_epCanExecuteChangedHandler?.Dispose(); - -// if (pOldValue != null) -// { -// XamlUICommand oldCommand = pOldValue as XamlUICommand; - -// if (oldCommand != null) -// { -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); -// } -// } - -// // Subscribe to the CanExecuteChanged event on the new value -// if (pNewValue != null) -// { -// var spNewCommand = pNewValue as ICommand; - -// void OnCanExecuteUpdated(object sender, object args) -// { -// UpdateCanExecute(); -// } - -// spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; -// m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); - -// var newCommandAsUICommand = spNewCommand as XamlUICommand; - -// if (newCommandAsUICommand != null) -// { -// CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); -// CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); -// CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); -// CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); -// CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); -// } -// } - -// // Coerce the button enabled state with the CanExecute state of the command. -// UpdateCanExecute(); -// } - -// // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. -// void UpdateCanExecute() -// { -// ICommand spCommand; -// object spCommandParameter; -// bool canExecute = true; - -// spCommand = Command; -// if (spCommand != null) -// { -// spCommandParameter = CommandParameter; -// canExecute = spCommand.CanExecute(spCommandParameter); -// } - -// SuppressIsEnabled(!canExecute); -// } - -// // Change to the correct visual state for the -// private protected override void ChangeVisualState( -// // true to use transitions when updating the visual state, false -// // to snap directly to the new visual state. -// bool bUseTransitions) -// { -// bool hasToggleMenuItem = false; -// bool hasIconMenuItem = false; -// bool hasMenuItemWithKeyboardAcceleratorText = false; -// bool isKeyboardPresent = false; - -// MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); -// if (spPresenter != null) -// { -// hasToggleMenuItem = spPresenter.GetContainsToggleItems(); -// hasIconMenuItem = spPresenter.GetContainsIconItems(); -// hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); -// } - -// var bIsEnabled = IsEnabled; -// var focusState = FocusState; -// var shouldBeNarrow = GetShouldBeNarrow(); - -// // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, -// // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. -// if (hasMenuItemWithKeyboardAcceleratorText) -// { -// // UNO TODO -// // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); -// isKeyboardPresent = true; -// } - -// // CommonStates -// if (!bIsEnabled) -// { -// VisualStateManager.GoToState(this, "Disabled", bUseTransitions); -// } -// else if (m_bIsPressed) -// { -// VisualStateManager.GoToState(this, "Pressed", bUseTransitions); -// } -// else if (m_bIsPointerOver) -// { -// VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "Normal", bUseTransitions); -// } - -// // FocusStates -// if (FocusState.Unfocused != focusState && bIsEnabled) -// { -// if (FocusState.Pointer == focusState) -// { -// VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "Focused", bUseTransitions); -// } -// } -// else -// { -// VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); -// } - -// // CheckPlaceholderStates -// if (hasToggleMenuItem && hasIconMenuItem) -// { -// VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); -// } -// else if (hasToggleMenuItem) -// { -// VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); -// } -// else if (hasIconMenuItem) -// { -// VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); -// } - -// // PaddingSizeStates -// if (shouldBeNarrow) -// { -// VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); -// } - -// // We'll make the accelerator text visible if any item has accelerator text, -// // as this causes the margin to be applied which reserves space, ensuring that accelerator text -// // in one item won't be at the same horizontal position as label text in another item. -// if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) -// { -// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); -// } -// } - -// // Clear flags relating to the visual state. Called when IsEnabled is set to false -// // or when Visibility is set to Hidden or Collapsed. -// void ClearStateFlags() -// { -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; - -// UpdateVisualState(); -// } - -// // Create MenuFlyoutItemAutomationPeer to represent the -// protected override AutomationPeer OnCreateAutomationPeer() -// { -// return new MenuFlyoutItemAutomationPeer(this); -// } - -// private protected override string GetPlainText() => Text; - -// internal string KeyboardAcceleratorTextOverrideImpl -// { -// get -// { -// var pValue = KeyboardAcceleratorTextOverride; - -// // If we have no keyboard accelerator text already provided by the app, -// // then we'll see if we can ruct it ourselves based on keyboard accelerators -// // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" -// // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". -// if (pValue == null) -// { -// pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); - -// // If we were able to get a string representation from keyboard accelerators, -// // then we should now set that as the value of KeyboardAcceleratorText. -// if (pValue != null) -// { -// KeyboardAcceleratorTextOverrideImpl = pValue; -// } -// } - -// return pValue; -// } -// set -// { -// KeyboardAcceleratorTextOverride = value; -// } -// } - -// internal Size GetKeyboardAcceleratorTextDesiredSize() -// { -// var desiredSize = new Size(0, 0); - -// if (!m_isTemplateApplied) -// { -// bool templateApplied = ApplyTemplate(); -// m_isTemplateApplied = templateApplied; -// } - -// if (m_tpKeyboardAcceleratorTextBlock != null) -// { -// Thickness margin; - -// m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); -// desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; -// margin = m_tpKeyboardAcceleratorTextBlock.Margin; - -// desiredSize.Width -= (float)(margin.Left + margin.Right); -// desiredSize.Height -= (float)(margin.Top + margin.Bottom); -// } - -// return desiredSize; -// } - -// internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) -// { -// if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) -// { -// m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; - -// MenuFlyoutItemTemplateSettings templateSettings; -// templateSettings = TemplateSettings; - -// if (templateSettings == null) -// { -// MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); -// TemplateSettings = templateSettingsImplementation; -// templateSettings = templateSettingsImplementation; -// } - -// templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; -// } -// } - -// internal virtual bool HasToggle() -// { -// return false; -// } -// } -//} + Initialize(); + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs index 42826f7a255e..5679e8d152cd 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs @@ -1,828 +1,68 @@ -//using System; -//using Uno.Client; -//using Uno.Disposables; -//using Windows.Foundation; -//using Microsoft.UI.Xaml.Automation; -//using Microsoft.UI.Xaml.Automation.Peers; -//using Microsoft.UI.Xaml.Controls.Primitives; -//using Microsoft.UI.Xaml.Input; -//using ICommand = System.Windows.Input.ICommand; -//using Microsoft.UI.Xaml.Markup; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutItem_Partial.h, tag winui3/release/1.5.4, commit 98a60c8 -//#if HAS_UNO_WINUI -//using Microsoft.UI.Input; -//#else -//using Windows.Devices.Input; -//using Windows.UI.Input; -//#endif +using System; +using Uno.Client; +using Uno.Disposables; +using Windows.Foundation; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using ICommand = System.Windows.Input.ICommand; +using Microsoft.UI.Xaml.Markup; -//namespace Microsoft.UI.Xaml.Controls -//{ -// [ContentProperty(Name = nameof(Text))] -// public partial class MenuFlyoutItem : MenuFlyoutItemBase -// { -// // Whether the pointer is currently over the -// bool m_bIsPointerOver = true; +#if HAS_UNO_WINUI +using Microsoft.UI.Input; +#else +using Windows.Devices.Input; +using Windows.UI.Input; +#endif -// // Whether the pointer is currently pressed over the -// internal bool m_bIsPressed = true; +namespace Microsoft.UI.Xaml.Controls; -// // Whether the pointer's left button is currently down. -// internal bool m_bIsPointerLeftButtonDown = true; +partial class MenuFlyoutItem +{ + // Whether the pointer is currently over the + private bool m_bIsPointerOver; -// // True if the SPACE or ENTER key is currently pressed, false otherwise. -// internal bool m_bIsSpaceOrEnterKeyDown = true; + // Whether the pointer is currently pressed over the + private bool m_bIsPressed; -// // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. -// internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; + // Whether the pointer's left button is currently down. + private bool m_bIsPointerLeftButtonDown; -// // On pointer released we perform some actions depending on control. We decide to whether to perform them -// // depending on some parameters including but not limited to whether released is followed by a pressed, which -// // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. -// bool m_shouldPerformActions = true; + // True if the SPACE or ENTER key is currently pressed, false otherwise. + private bool m_bIsSpaceOrEnterKeyDown; -// // Event pointer for the ICommand.CanExecuteChanged event. -// IDisposable m_epCanExecuteChangedHandler; + // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. + private bool m_bIsNavigationAcceptOrGamepadAKeyDown; -// // UNO TODO -// // Event pointer for the Loaded event. -// // IDisposable m_epLoadedHandler; + // On pointer released we perform some actions depending on control. We decide to whether to perform them + // depending on some parameters including but not limited to whether released is followed by a pressed, which + // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. + private bool m_shouldPerformActions; -// // UNO TODO -// // IDisposable m_epMenuFlyoutItemClickEventCallback; + // Event pointer for the ICommand.CanExecuteChanged event. + private readonly SerialDisposable m_epCanExecuteChangedHandler = new(); // TODO:MZ: Should be WeakReference -// double m_maxKeyboardAcceleratorTextWidth; -// TextBlock m_tpKeyboardAcceleratorTextBlock; + // Event pointer for the Loaded event. + private readonly SerialDisposable m_epLoadedHandler = new(); -// bool m_isTemplateApplied; + private readonly SerialDisposable m_epMenuFlyoutItemClickEventCallback = new(); -// #region CommandParameter + private double m_maxKeyboardAcceleratorTextWidth; + private TextBlock m_tpKeyboardAcceleratorTextBlock; -// public object CommandParameter -// { -// get { return (object)GetValue(CommandParameterProperty); } -// set { SetValue(CommandParameterProperty, value); } -// } + private bool m_isTemplateApplied; -// public static DependencyProperty CommandParameterProperty { get; } = -// DependencyProperty.Register( -// "CommandParameter", typeof(object), -// typeof(Controls.MenuFlyoutItem), -// new FrameworkPropertyMetadata(default(object))); + private bool m_isSettingKeyboardAcceleratorTextOverride; + private bool m_ownsKeyboardAcceleratorTextOverride = true; -// #endregion + internal virtual bool HasToggle() => false; -// #region Command + private bool GetIsPressed() => m_bIsPressed; -// public ICommand Command -// { -// get { return (ICommand)GetValue(CommandProperty); } -// set { SetValue(CommandProperty, value); } -// } - -// public static DependencyProperty CommandProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Command), -// propertyType: typeof(ICommand), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); - -// #endregion - -// #region Text - -// public string Text -// { -// get { return (string)GetValue(TextProperty) ?? ""; } -// set { SetValue(TextProperty, value); } -// } - -// public static DependencyProperty TextProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Text), -// propertyType: typeof(string), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(string))); - -// #endregion - -// public IconElement Icon -// { -// get => (IconElement)this.GetValue(IconProperty); -// set => this.SetValue(IconProperty, value); -// } - -// public static DependencyProperty IconProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Icon), -// propertyType: typeof(IconElement), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); - -// public string KeyboardAcceleratorTextOverride -// { -// get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; -// set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); -// } - -// public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = -// DependencyProperty.Register( -// name: nameof(KeyboardAcceleratorTextOverride), -// propertyType: typeof(string), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(string))); - -// public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } - -//#pragma warning disable CS0108 -// public event RoutedEventHandler Click; -//#pragma warning restore CS0108 - -// internal void InvokeClick() -// { -// Click?.Invoke(this, new RoutedEventArgs(this)); -// Command.ExecuteIfPossible(this.CommandParameter); -// } - -// public MenuFlyoutItem() -// { -// m_bIsPointerOver = false; -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; -// m_shouldPerformActions = false; - -// DefaultStyleKey = typeof(MenuFlyoutItem); - -// Initialize(); -// } - -// // Prepares object's state -// void Initialize() -// { -// Loaded += (s, e) => ClearStateFlags(); - -// this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); -// } - -// // Apply a template to the - -// protected override void OnApplyTemplate() -// { -// base.OnApplyTemplate(); - -// TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; -// m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; - -// SuppressIsEnabled(false); -// UpdateCanExecute(); - -// // Sync the logical and visual states of the control -// UpdateVisualState(); -// } - -// // PointerPressed event handler. -// protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerPressed(pArgs); -// var handled = pArgs.Handled; -// if (!handled) -// { -// PointerPoint spPointerPoint; -// PointerPointProperties spPointerProperties; -// spPointerPoint = pArgs.GetCurrentPoint(this); -// spPointerProperties = spPointerPoint.Properties; -// var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; -// if (bIsLeftButtonPressed) -// { -// m_bIsPointerLeftButtonDown = true; -// m_bIsPressed = true; - -// pArgs.Handled = true; -// UpdateVisualState(); -// } -// } -// } - -// // PointerReleased event handler. -// protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerReleased(pArgs); - -// bool handled = false; - -// handled = pArgs.Handled; -// if (!handled) -// { -// m_bIsPointerLeftButtonDown = false; - -// m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; - -//#if false -// // UNO TODO -// if (m_shouldPerformActions) -// { -// GestureModes gestureFollowing = GestureModes.None; - -// m_bIsPressed = false; - -// gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); - -// // Note that we are intentionally NOT handling the args -// // if we do not fall through here because basically we are no_opting in that case. -// if (gestureFollowing != GestureModes.RightTapped) -// { -// pArgs.Handled = true; -// PerformPointerUpAction(); -// } -// } -//#else -// PerformPointerUpAction(); -//#endif -// } -// } - -// private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// PerformPointerUpAction(); -// pArgs.Handled = true; -// } -// } - -// // Contains the logic to be employed if we decide to handle pointer released. -// void PerformPointerUpAction() -// { -// if (m_shouldPerformActions) -// { -// Focus(FocusState.Pointer); -// Invoke(); -// } -// } - -// // Performs appropriate actions upon a mouse/keyboard invocation of a -// internal virtual void Invoke() -// { -// RoutedEventArgs spArgs; -// MenuFlyoutPresenter spParentMenuFlyoutPresenter; - -// // Create the args -// spArgs = new RoutedEventArgs(); -// spArgs.OriginalSource = this; - -// // Raise the event -// Click?.Invoke(this, spArgs); - -// // UNO TODO -// //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); - -// // Execute command associated with the button -// ExecuteCommand(); - -// bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; -// if (!shouldPreventDismissOnPointer) -// { -// // Close the MenuFlyout. -// spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); -// if (spParentMenuFlyoutPresenter != null) -// { -// IMenu owningMenu; - -// owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; -// if (owningMenu != null) -// { -// // We need to make close all menu flyout sub items before we hide the parent menu flyout, -// // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a -// // significant amount of time before closing the sub menu. -// (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); -// owningMenu.Close(); -// } -// } -// } - -// ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); -// } - -// // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) -// //{ -// // DependencyObject peer = pMenuFlyoutItem; - -// // if (peer == null) -// // { -// // return; -// // } - -// // MenuFlyoutItem peerAsMenuFlyoutItem; -// // (peer.As(&peerAsMenuFlyoutItem)); -// // IFCPTR_RETURN(peerAsMenuFlyoutItem); - -// // (peerAsAddProofingItemHandler(eventHandler)); - -// // return S_OK; -// //} - -// // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) -// //{ -// // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) -// // { -// // DependencyObject spSender; -// // EventArgs spArgs; - -// // IFCPTR_RETURN(pSender); -// // IFCPTR_RETURN(pArgs); - -// // (ctl.do_query_interface(spSender, pSender)); -// // (ctl.do_query_interface(spArgs, pArgs)); - -// // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); - -// // return S_OK; -// // })); - -// // return S_OK; -// //} - -// // Executes Command if CanExecute() returns true. -// void ExecuteCommand() -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// object spCommandParameter; -// bool canExecute; - -// spCommandParameter = CommandParameter; -// canExecute = spCommand.CanExecute(spCommandParameter); -// if (canExecute) -// { -// spCommand.Execute(spCommandParameter); -// } -// } -// } - -// // PointerEnter event handler. -// protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerEntered(pArgs); - -// MenuFlyoutPresenter spParentPresenter; - -// m_bIsPointerOver = true; - -// spParentPresenter = GetParentMenuFlyoutPresenter(); -// if (spParentPresenter != null) -// { -// IMenuPresenter subPresenter; - -// subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; -// if (subPresenter != null) -// { -// ISubMenuOwner subPresenterOwner; - -// subPresenterOwner = subPresenter.Owner; - -// if (subPresenterOwner != null) -// { -// subPresenterOwner.DelayCloseSubMenu(); -// } -// } -// } - -// UpdateVisualState(); -// } - -// // PointerExited event handler. -// protected override void OnPointerExited(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerExited(pArgs); - -// // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// UpdateVisualState(); -// } - -// // PointerCaptureLost event handler. -// protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerCaptureLost(pArgs); - -// // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// UpdateVisualState(); -// } - -// // Called when the IsEnabled property changes. -// private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) -// { -// if (!e.NewValue) -// { -// ClearStateFlags(); -// } -// else -// { -// UpdateVisualState(); -// } - -// base.OnIsEnabledChanged(e); -// } - -// // Called when the control got focus. -// protected override void OnGotFocus(RoutedEventArgs pArgs) -// { -// UpdateVisualState(); -// } - -// // LostFocus event handler. -// protected override void OnLostFocus(RoutedEventArgs pArgs) -// { -// if (!m_bIsPointerLeftButtonDown) -// { -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; -// m_bIsPressed = false; -// } - -// UpdateVisualState(); -// } - -// // KeyDown event handler. -// protected override void OnKeyDown(KeyRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// var key = pArgs.Key; -// handled = KeyPressMenuFlyout.KeyDown(key, this); -// pArgs.Handled = handled; -// } -// } - -// // KeyUp event handler. -// protected override void OnKeyUp(KeyRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// var key = (pArgs.Key); -// KeyPressMenuFlyout.KeyUp(key, this); -// pArgs.Handled = true; -// } -// } - -// // Handle the custom property changed event and call the OnPropertyChanged2 methods. -// internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) -// { -// if (args.Property == UIElement.VisibilityProperty) -// { -// OnVisibilityChanged(); -// } -// else if (args.Property == MenuFlyoutItem.CommandProperty) -// { -// OnCommandChanged(args.OldValue, args.NewValue); -// } -// else if (args.Property == MenuFlyoutItem.CommandParameterProperty) -// { -// UpdateCanExecute(); -// } -// } - -// // Update the visual states when the Visibility property is changed. -// void OnVisibilityChanged() -// { -// Visibility visibility = Visibility; - -// if (Visibility.Visible != visibility) -// { -// ClearStateFlags(); -// } -// } - -// private protected override void OnLoaded() -// { -// base.OnLoaded(); - -// if (m_epCanExecuteChangedHandler == null) -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// void OnCanExecuteChanged(object sender, object args) -// { -// UpdateCanExecute(); -// } - -// spCommand.CanExecuteChanged += OnCanExecuteChanged; - -// m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); -// } -// } - -// // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, -// // we need to update our value now. -// UpdateCanExecute(); -// } - -// private protected override void OnUnloaded() -// { -// base.OnUnloaded(); - -// if (m_epCanExecuteChangedHandler != null) -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// m_epCanExecuteChangedHandler.Dispose(); -// } -// } -// } - -// // Called when the Command property changes. -// void -// OnCommandChanged( -// object pOldValue, -// object pNewValue) -// { -// // Remove handler for CanExecuteChanged from the old value -// m_epCanExecuteChangedHandler?.Dispose(); - -// if (pOldValue != null) -// { -// XamlUICommand oldCommand = pOldValue as XamlUICommand; - -// if (oldCommand != null) -// { -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); -// } -// } - -// // Subscribe to the CanExecuteChanged event on the new value -// if (pNewValue != null) -// { -// var spNewCommand = pNewValue as ICommand; - -// void OnCanExecuteUpdated(object sender, object args) -// { -// UpdateCanExecute(); -// } - -// spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; -// m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); - -// var newCommandAsUICommand = spNewCommand as XamlUICommand; - -// if (newCommandAsUICommand != null) -// { -// CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); -// CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); -// CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); -// CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); -// CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); -// } -// } - -// // Coerce the button enabled state with the CanExecute state of the command. -// UpdateCanExecute(); -// } - -// // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. -// void UpdateCanExecute() -// { -// ICommand spCommand; -// object spCommandParameter; -// bool canExecute = true; - -// spCommand = Command; -// if (spCommand != null) -// { -// spCommandParameter = CommandParameter; -// canExecute = spCommand.CanExecute(spCommandParameter); -// } - -// SuppressIsEnabled(!canExecute); -// } - -// // Change to the correct visual state for the -// private protected override void ChangeVisualState( -// // true to use transitions when updating the visual state, false -// // to snap directly to the new visual state. -// bool bUseTransitions) -// { -// bool hasToggleMenuItem = false; -// bool hasIconMenuItem = false; -// bool hasMenuItemWithKeyboardAcceleratorText = false; -// bool isKeyboardPresent = false; - -// MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); -// if (spPresenter != null) -// { -// hasToggleMenuItem = spPresenter.GetContainsToggleItems(); -// hasIconMenuItem = spPresenter.GetContainsIconItems(); -// hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); -// } - -// var bIsEnabled = IsEnabled; -// var focusState = FocusState; -// var shouldBeNarrow = GetShouldBeNarrow(); - -// // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, -// // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. -// if (hasMenuItemWithKeyboardAcceleratorText) -// { -// // UNO TODO -// // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); -// isKeyboardPresent = true; -// } - -// // CommonStates -// if (!bIsEnabled) -// { -// VisualStateManager.GoToState(this, "Disabled", bUseTransitions); -// } -// else if (m_bIsPressed) -// { -// VisualStateManager.GoToState(this, "Pressed", bUseTransitions); -// } -// else if (m_bIsPointerOver) -// { -// VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "Normal", bUseTransitions); -// } - -// // FocusStates -// if (FocusState.Unfocused != focusState && bIsEnabled) -// { -// if (FocusState.Pointer == focusState) -// { -// VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "Focused", bUseTransitions); -// } -// } -// else -// { -// VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); -// } - -// // CheckPlaceholderStates -// if (hasToggleMenuItem && hasIconMenuItem) -// { -// VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); -// } -// else if (hasToggleMenuItem) -// { -// VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); -// } -// else if (hasIconMenuItem) -// { -// VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); -// } - -// // PaddingSizeStates -// if (shouldBeNarrow) -// { -// VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); -// } - -// // We'll make the accelerator text visible if any item has accelerator text, -// // as this causes the margin to be applied which reserves space, ensuring that accelerator text -// // in one item won't be at the same horizontal position as label text in another item. -// if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) -// { -// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); -// } -// } - -// // Clear flags relating to the visual state. Called when IsEnabled is set to false -// // or when Visibility is set to Hidden or Collapsed. -// void ClearStateFlags() -// { -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; - -// UpdateVisualState(); -// } - -// // Create MenuFlyoutItemAutomationPeer to represent the -// protected override AutomationPeer OnCreateAutomationPeer() -// { -// return new MenuFlyoutItemAutomationPeer(this); -// } - -// private protected override string GetPlainText() => Text; - -// internal string KeyboardAcceleratorTextOverrideImpl -// { -// get -// { -// var pValue = KeyboardAcceleratorTextOverride; - -// // If we have no keyboard accelerator text already provided by the app, -// // then we'll see if we can ruct it ourselves based on keyboard accelerators -// // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" -// // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". -// if (pValue == null) -// { -// pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); - -// // If we were able to get a string representation from keyboard accelerators, -// // then we should now set that as the value of KeyboardAcceleratorText. -// if (pValue != null) -// { -// KeyboardAcceleratorTextOverrideImpl = pValue; -// } -// } - -// return pValue; -// } -// set -// { -// KeyboardAcceleratorTextOverride = value; -// } -// } - -// internal Size GetKeyboardAcceleratorTextDesiredSize() -// { -// var desiredSize = new Size(0, 0); - -// if (!m_isTemplateApplied) -// { -// bool templateApplied = ApplyTemplate(); -// m_isTemplateApplied = templateApplied; -// } - -// if (m_tpKeyboardAcceleratorTextBlock != null) -// { -// Thickness margin; - -// m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); -// desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; -// margin = m_tpKeyboardAcceleratorTextBlock.Margin; - -// desiredSize.Width -= (float)(margin.Left + margin.Right); -// desiredSize.Height -= (float)(margin.Top + margin.Bottom); -// } - -// return desiredSize; -// } - -// internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) -// { -// if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) -// { -// m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; - -// MenuFlyoutItemTemplateSettings templateSettings; -// templateSettings = TemplateSettings; - -// if (templateSettings == null) -// { -// MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); -// TemplateSettings = templateSettingsImplementation; -// templateSettings = templateSettingsImplementation; -// } - -// templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; -// } -// } - -// internal virtual bool HasToggle() -// { -// return false; -// } -// } -//} + private bool GetIsPointerOver() => m_bIsPointerOver; +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs index 42826f7a255e..8b1ea96ed265 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs @@ -1,828 +1,678 @@ -//using System; -//using Uno.Client; -//using Uno.Disposables; -//using Windows.Foundation; -//using Microsoft.UI.Xaml.Automation; -//using Microsoft.UI.Xaml.Automation.Peers; -//using Microsoft.UI.Xaml.Controls.Primitives; -//using Microsoft.UI.Xaml.Input; -//using ICommand = System.Windows.Input.ICommand; -//using Microsoft.UI.Xaml.Markup; - -//#if HAS_UNO_WINUI -//using Microsoft.UI.Input; -//#else -//using Windows.Devices.Input; -//using Windows.UI.Input; -//#endif - -//namespace Microsoft.UI.Xaml.Controls -//{ -// [ContentProperty(Name = nameof(Text))] -// public partial class MenuFlyoutItem : MenuFlyoutItemBase -// { -// // Whether the pointer is currently over the -// bool m_bIsPointerOver = true; - -// // Whether the pointer is currently pressed over the -// internal bool m_bIsPressed = true; - -// // Whether the pointer's left button is currently down. -// internal bool m_bIsPointerLeftButtonDown = true; - -// // True if the SPACE or ENTER key is currently pressed, false otherwise. -// internal bool m_bIsSpaceOrEnterKeyDown = true; - -// // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. -// internal bool m_bIsNavigationAcceptOrGamepadAKeyDown = true; - -// // On pointer released we perform some actions depending on control. We decide to whether to perform them -// // depending on some parameters including but not limited to whether released is followed by a pressed, which -// // mouse button is pressed, what type of pointer is it etc. This bool keeps our decision. -// bool m_shouldPerformActions = true; - -// // Event pointer for the ICommand.CanExecuteChanged event. -// IDisposable m_epCanExecuteChangedHandler; - -// // UNO TODO -// // Event pointer for the Loaded event. -// // IDisposable m_epLoadedHandler; - -// // UNO TODO -// // IDisposable m_epMenuFlyoutItemClickEventCallback; - -// double m_maxKeyboardAcceleratorTextWidth; -// TextBlock m_tpKeyboardAcceleratorTextBlock; - -// bool m_isTemplateApplied; - -// #region CommandParameter - -// public object CommandParameter -// { -// get { return (object)GetValue(CommandParameterProperty); } -// set { SetValue(CommandParameterProperty, value); } -// } - -// public static DependencyProperty CommandParameterProperty { get; } = -// DependencyProperty.Register( -// "CommandParameter", typeof(object), -// typeof(Controls.MenuFlyoutItem), -// new FrameworkPropertyMetadata(default(object))); - -// #endregion - -// #region Command - -// public ICommand Command -// { -// get { return (ICommand)GetValue(CommandProperty); } -// set { SetValue(CommandProperty, value); } -// } - -// public static DependencyProperty CommandProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Command), -// propertyType: typeof(ICommand), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(ICommand))); - -// #endregion - -// #region Text - -// public string Text -// { -// get { return (string)GetValue(TextProperty) ?? ""; } -// set { SetValue(TextProperty, value); } -// } - -// public static DependencyProperty TextProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Text), -// propertyType: typeof(string), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(string))); - -// #endregion - -// public IconElement Icon -// { -// get => (IconElement)this.GetValue(IconProperty); -// set => this.SetValue(IconProperty, value); -// } - -// public static DependencyProperty IconProperty { get; } = -// DependencyProperty.Register( -// name: nameof(Icon), -// propertyType: typeof(IconElement), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(IconElement))); - -// public string KeyboardAcceleratorTextOverride -// { -// get => (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; -// set => this.SetValue(KeyboardAcceleratorTextOverrideProperty, value); -// } - -// public static DependencyProperty KeyboardAcceleratorTextOverrideProperty { get; } = -// DependencyProperty.Register( -// name: nameof(KeyboardAcceleratorTextOverride), -// propertyType: typeof(string), -// ownerType: typeof(MenuFlyoutItem), -// typeMetadata: new FrameworkPropertyMetadata(default(string))); - -// public MenuFlyoutItemTemplateSettings TemplateSettings { get; internal set; } - -//#pragma warning disable CS0108 -// public event RoutedEventHandler Click; -//#pragma warning restore CS0108 - -// internal void InvokeClick() -// { -// Click?.Invoke(this, new RoutedEventArgs(this)); -// Command.ExecuteIfPossible(this.CommandParameter); -// } - -// public MenuFlyoutItem() -// { -// m_bIsPointerOver = false; -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; -// m_shouldPerformActions = false; - -// DefaultStyleKey = typeof(MenuFlyoutItem); - -// Initialize(); -// } - -// // Prepares object's state -// void Initialize() -// { -// Loaded += (s, e) => ClearStateFlags(); - -// this.RegisterDisposablePropertyChangedCallback((s, e, args) => OnPropertyChanged2(args)); -// } - -// // Apply a template to the - -// protected override void OnApplyTemplate() -// { -// base.OnApplyTemplate(); - -// TextBlock keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; -// m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; - -// SuppressIsEnabled(false); -// UpdateCanExecute(); - -// // Sync the logical and visual states of the control -// UpdateVisualState(); -// } - -// // PointerPressed event handler. -// protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerPressed(pArgs); -// var handled = pArgs.Handled; -// if (!handled) -// { -// PointerPoint spPointerPoint; -// PointerPointProperties spPointerProperties; -// spPointerPoint = pArgs.GetCurrentPoint(this); -// spPointerProperties = spPointerPoint.Properties; -// var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; -// if (bIsLeftButtonPressed) -// { -// m_bIsPointerLeftButtonDown = true; -// m_bIsPressed = true; - -// pArgs.Handled = true; -// UpdateVisualState(); -// } -// } -// } - -// // PointerReleased event handler. -// protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerReleased(pArgs); - -// bool handled = false; - -// handled = pArgs.Handled; -// if (!handled) -// { -// m_bIsPointerLeftButtonDown = false; - -// m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; - -//#if false -// // UNO TODO -// if (m_shouldPerformActions) -// { -// GestureModes gestureFollowing = GestureModes.None; - -// m_bIsPressed = false; - -// gestureFollowing = ((PointerRoutedEventArgs*)(pArgs).GestureFollowing); - -// // Note that we are intentionally NOT handling the args -// // if we do not fall through here because basically we are no_opting in that case. -// if (gestureFollowing != GestureModes.RightTapped) -// { -// pArgs.Handled = true; -// PerformPointerUpAction(); -// } -// } -//#else -// PerformPointerUpAction(); -//#endif -// } -// } - -// private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// PerformPointerUpAction(); -// pArgs.Handled = true; -// } -// } - -// // Contains the logic to be employed if we decide to handle pointer released. -// void PerformPointerUpAction() -// { -// if (m_shouldPerformActions) -// { -// Focus(FocusState.Pointer); -// Invoke(); -// } -// } - -// // Performs appropriate actions upon a mouse/keyboard invocation of a -// internal virtual void Invoke() -// { -// RoutedEventArgs spArgs; -// MenuFlyoutPresenter spParentMenuFlyoutPresenter; - -// // Create the args -// spArgs = new RoutedEventArgs(); -// spArgs.OriginalSource = this; - -// // Raise the event -// Click?.Invoke(this, spArgs); - -// // UNO TODO -// //AutomationPeer.RaiseEventIfListener(this, xaml_automation_peers.AutomationEvents_InvokePatternOnInvoked); - -// // Execute command associated with the button -// ExecuteCommand(); - -// bool shouldPreventDismissOnPointer = false; // UNO TODO PreventDismissOnPointer; -// if (!shouldPreventDismissOnPointer) -// { -// // Close the MenuFlyout. -// spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); -// if (spParentMenuFlyoutPresenter != null) -// { -// IMenu owningMenu; - -// owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; -// if (owningMenu != null) -// { -// // We need to make close all menu flyout sub items before we hide the parent menu flyout, -// // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a -// // significant amount of time before closing the sub menu. -// (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); -// owningMenu.Close(); -// } -// } -// } - -// ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); -// } - -// // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) -// //{ -// // DependencyObject peer = pMenuFlyoutItem; - -// // if (peer == null) -// // { -// // return; -// // } - -// // MenuFlyoutItem peerAsMenuFlyoutItem; -// // (peer.As(&peerAsMenuFlyoutItem)); -// // IFCPTR_RETURN(peerAsMenuFlyoutItem); - -// // (peerAsAddProofingItemHandler(eventHandler)); - -// // return S_OK; -// //} - -// // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) -// //{ -// // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) -// // { -// // DependencyObject spSender; -// // EventArgs spArgs; - -// // IFCPTR_RETURN(pSender); -// // IFCPTR_RETURN(pArgs); - -// // (ctl.do_query_interface(spSender, pSender)); -// // (ctl.do_query_interface(spArgs, pArgs)); - -// // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); - -// // return S_OK; -// // })); - -// // return S_OK; -// //} - -// // Executes Command if CanExecute() returns true. -// void ExecuteCommand() -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// object spCommandParameter; -// bool canExecute; - -// spCommandParameter = CommandParameter; -// canExecute = spCommand.CanExecute(spCommandParameter); -// if (canExecute) -// { -// spCommand.Execute(spCommandParameter); -// } -// } -// } - -// // PointerEnter event handler. -// protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerEntered(pArgs); - -// MenuFlyoutPresenter spParentPresenter; - -// m_bIsPointerOver = true; - -// spParentPresenter = GetParentMenuFlyoutPresenter(); -// if (spParentPresenter != null) -// { -// IMenuPresenter subPresenter; - -// subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; -// if (subPresenter != null) -// { -// ISubMenuOwner subPresenterOwner; - -// subPresenterOwner = subPresenter.Owner; - -// if (subPresenterOwner != null) -// { -// subPresenterOwner.DelayCloseSubMenu(); -// } -// } -// } - -// UpdateVisualState(); -// } - -// // PointerExited event handler. -// protected override void OnPointerExited(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerExited(pArgs); - -// // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// UpdateVisualState(); -// } - -// // PointerCaptureLost event handler. -// protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) -// { -// base.OnPointerCaptureLost(pArgs); - -// // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// UpdateVisualState(); -// } - -// // Called when the IsEnabled property changes. -// private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) -// { -// if (!e.NewValue) -// { -// ClearStateFlags(); -// } -// else -// { -// UpdateVisualState(); -// } - -// base.OnIsEnabledChanged(e); -// } - -// // Called when the control got focus. -// protected override void OnGotFocus(RoutedEventArgs pArgs) -// { -// UpdateVisualState(); -// } - -// // LostFocus event handler. -// protected override void OnLostFocus(RoutedEventArgs pArgs) -// { -// if (!m_bIsPointerLeftButtonDown) -// { -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; -// m_bIsPressed = false; -// } - -// UpdateVisualState(); -// } - -// // KeyDown event handler. -// protected override void OnKeyDown(KeyRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// var key = pArgs.Key; -// handled = KeyPressMenuFlyout.KeyDown(key, this); -// pArgs.Handled = handled; -// } -// } - -// // KeyUp event handler. -// protected override void OnKeyUp(KeyRoutedEventArgs pArgs) -// { -// var handled = pArgs.Handled; -// if (!handled) -// { -// var key = (pArgs.Key); -// KeyPressMenuFlyout.KeyUp(key, this); -// pArgs.Handled = true; -// } -// } - -// // Handle the custom property changed event and call the OnPropertyChanged2 methods. -// internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) -// { -// if (args.Property == UIElement.VisibilityProperty) -// { -// OnVisibilityChanged(); -// } -// else if (args.Property == MenuFlyoutItem.CommandProperty) -// { -// OnCommandChanged(args.OldValue, args.NewValue); -// } -// else if (args.Property == MenuFlyoutItem.CommandParameterProperty) -// { -// UpdateCanExecute(); -// } -// } - -// // Update the visual states when the Visibility property is changed. -// void OnVisibilityChanged() -// { -// Visibility visibility = Visibility; - -// if (Visibility.Visible != visibility) -// { -// ClearStateFlags(); -// } -// } - -// private protected override void OnLoaded() -// { -// base.OnLoaded(); - -// if (m_epCanExecuteChangedHandler == null) -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// void OnCanExecuteChanged(object sender, object args) -// { -// UpdateCanExecute(); -// } - -// spCommand.CanExecuteChanged += OnCanExecuteChanged; - -// m_epCanExecuteChangedHandler = Disposable.Create(() => spCommand.CanExecuteChanged -= OnCanExecuteChanged); -// } -// } - -// // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, -// // we need to update our value now. -// UpdateCanExecute(); -// } - -// private protected override void OnUnloaded() -// { -// base.OnUnloaded(); - -// if (m_epCanExecuteChangedHandler != null) -// { -// ICommand spCommand = Command; - -// if (spCommand != null) -// { -// m_epCanExecuteChangedHandler.Dispose(); -// } -// } -// } - -// // Called when the Command property changes. -// void -// OnCommandChanged( -// object pOldValue, -// object pNewValue) -// { -// // Remove handler for CanExecuteChanged from the old value -// m_epCanExecuteChangedHandler?.Dispose(); - -// if (pOldValue != null) -// { -// XamlUICommand oldCommand = pOldValue as XamlUICommand; - -// if (oldCommand != null) -// { -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, TextProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, IconProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, KeyboardAcceleratorsProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AccessKeyProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, AutomationProperties.HelpTextProperty); -// CommandingHelpers.ClearBindingIfSet(oldCommand, this, ToolTipService.ToolTipProperty); -// } -// } - -// // Subscribe to the CanExecuteChanged event on the new value -// if (pNewValue != null) -// { -// var spNewCommand = pNewValue as ICommand; - -// void OnCanExecuteUpdated(object sender, object args) -// { -// UpdateCanExecute(); -// } - -// spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; -// m_epCanExecuteChangedHandler = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); - -// var newCommandAsUICommand = spNewCommand as XamlUICommand; - -// if (newCommandAsUICommand != null) -// { -// CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); -// CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); -// CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); -// CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); -// CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); -// } -// } - -// // Coerce the button enabled state with the CanExecute state of the command. -// UpdateCanExecute(); -// } - -// // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. -// void UpdateCanExecute() -// { -// ICommand spCommand; -// object spCommandParameter; -// bool canExecute = true; - -// spCommand = Command; -// if (spCommand != null) -// { -// spCommandParameter = CommandParameter; -// canExecute = spCommand.CanExecute(spCommandParameter); -// } - -// SuppressIsEnabled(!canExecute); -// } - -// // Change to the correct visual state for the -// private protected override void ChangeVisualState( -// // true to use transitions when updating the visual state, false -// // to snap directly to the new visual state. -// bool bUseTransitions) -// { -// bool hasToggleMenuItem = false; -// bool hasIconMenuItem = false; -// bool hasMenuItemWithKeyboardAcceleratorText = false; -// bool isKeyboardPresent = false; - -// MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); -// if (spPresenter != null) -// { -// hasToggleMenuItem = spPresenter.GetContainsToggleItems(); -// hasIconMenuItem = spPresenter.GetContainsIconItems(); -// hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); -// } - -// var bIsEnabled = IsEnabled; -// var focusState = FocusState; -// var shouldBeNarrow = GetShouldBeNarrow(); - -// // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, -// // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. -// if (hasMenuItemWithKeyboardAcceleratorText) -// { -// // UNO TODO -// // isKeyboardPresent = DXamlCore.GetCurrent().GetIsKeyboardPresent(); -// isKeyboardPresent = true; -// } - -// // CommonStates -// if (!bIsEnabled) -// { -// VisualStateManager.GoToState(this, "Disabled", bUseTransitions); -// } -// else if (m_bIsPressed) -// { -// VisualStateManager.GoToState(this, "Pressed", bUseTransitions); -// } -// else if (m_bIsPointerOver) -// { -// VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "Normal", bUseTransitions); -// } - -// // FocusStates -// if (FocusState.Unfocused != focusState && bIsEnabled) -// { -// if (FocusState.Pointer == focusState) -// { -// VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "Focused", bUseTransitions); -// } -// } -// else -// { -// VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); -// } - -// // CheckPlaceholderStates -// if (hasToggleMenuItem && hasIconMenuItem) -// { -// VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); -// } -// else if (hasToggleMenuItem) -// { -// VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); -// } -// else if (hasIconMenuItem) -// { -// VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); -// } - -// // PaddingSizeStates -// if (shouldBeNarrow) -// { -// VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); -// } - -// // We'll make the accelerator text visible if any item has accelerator text, -// // as this causes the margin to be applied which reserves space, ensuring that accelerator text -// // in one item won't be at the same horizontal position as label text in another item. -// if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) -// { -// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); -// } -// else -// { -// VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); -// } -// } - -// // Clear flags relating to the visual state. Called when IsEnabled is set to false -// // or when Visibility is set to Hidden or Collapsed. -// void ClearStateFlags() -// { -// m_bIsPressed = false; -// m_bIsPointerLeftButtonDown = false; -// m_bIsPointerOver = false; -// m_bIsSpaceOrEnterKeyDown = false; -// m_bIsNavigationAcceptOrGamepadAKeyDown = false; - -// UpdateVisualState(); -// } - -// // Create MenuFlyoutItemAutomationPeer to represent the -// protected override AutomationPeer OnCreateAutomationPeer() -// { -// return new MenuFlyoutItemAutomationPeer(this); -// } - -// private protected override string GetPlainText() => Text; - -// internal string KeyboardAcceleratorTextOverrideImpl -// { -// get -// { -// var pValue = KeyboardAcceleratorTextOverride; - -// // If we have no keyboard accelerator text already provided by the app, -// // then we'll see if we can ruct it ourselves based on keyboard accelerators -// // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" -// // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". -// if (pValue == null) -// { -// pValue = KeyboardAccelerator.GetStringRepresentationForUIElement(this); - -// // If we were able to get a string representation from keyboard accelerators, -// // then we should now set that as the value of KeyboardAcceleratorText. -// if (pValue != null) -// { -// KeyboardAcceleratorTextOverrideImpl = pValue; -// } -// } - -// return pValue; -// } -// set -// { -// KeyboardAcceleratorTextOverride = value; -// } -// } - -// internal Size GetKeyboardAcceleratorTextDesiredSize() -// { -// var desiredSize = new Size(0, 0); - -// if (!m_isTemplateApplied) -// { -// bool templateApplied = ApplyTemplate(); -// m_isTemplateApplied = templateApplied; -// } - -// if (m_tpKeyboardAcceleratorTextBlock != null) -// { -// Thickness margin; - -// m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); -// desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; -// margin = m_tpKeyboardAcceleratorTextBlock.Margin; - -// desiredSize.Width -= (float)(margin.Left + margin.Right); -// desiredSize.Height -= (float)(margin.Top + margin.Bottom); -// } - -// return desiredSize; -// } - -// internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) -// { -// if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) -// { -// m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; - -// MenuFlyoutItemTemplateSettings templateSettings; -// templateSettings = TemplateSettings; - -// if (templateSettings == null) -// { -// MenuFlyoutItemTemplateSettings templateSettingsImplementation = new MenuFlyoutItemTemplateSettings(); -// TemplateSettings = templateSettingsImplementation; -// templateSettings = templateSettingsImplementation; -// } - -// templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; -// } -// } - -// internal virtual bool HasToggle() -// { -// return false; -// } -// } -//} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutItem_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System; +using DirectUI; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Uno.Disposables; +using Windows.Foundation; +using ICommand = System.Windows.Input.ICommand; + +#if HAS_UNO_WINUI +#else +using Windows.Devices.Input; +using Windows.UI.Input; +#endif + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyoutItem +{ + // Prepares object's state + private void Initialize() + { + //base.Initialize(); + Loaded += (s, e) => ClearStateFlags(); + + +#if HAS_UNO // Ensure Enter/Leave are called. + Loaded += (s, e) => EnterImpl(true, false, false, false); + Unloaded += (s, e) => LeaveImpl(true, false, false, false); +#endif + } + + // Apply a template to the + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + var keyboardAcceleratorTextBlock = this.GetTemplateChild("KeyboardAcceleratorTextBlock") as TextBlock; + m_tpKeyboardAcceleratorTextBlock = keyboardAcceleratorTextBlock; + + SuppressIsEnabled(false); + UpdateCanExecute(); + + InitializeKeyboardAcceleratorText(); + + // Sync the logical and visual states of the control + UpdateVisualState(); + } + + // PointerPressed event handler. + protected override void OnPointerPressed(PointerRoutedEventArgs pArgs) + { + base.OnPointerPressed(pArgs); + var handled = pArgs.Handled; + if (!handled) + { + var spPointerPoint = pArgs.GetCurrentPoint(this); + var spPointerProperties = spPointerPoint.Properties; + var bIsLeftButtonPressed = spPointerProperties.IsLeftButtonPressed; + if (bIsLeftButtonPressed) + { + m_bIsPointerLeftButtonDown = true; + m_bIsPressed = true; + + pArgs.Handled = true; + UpdateVisualState(); + } + } + } + + // PointerReleased event handler. + protected override void OnPointerReleased(PointerRoutedEventArgs pArgs) + { + base.OnPointerReleased(pArgs); + + bool handled = false; + + handled = pArgs.Handled; + if (!handled) + { + m_bIsPointerLeftButtonDown = false; + + m_shouldPerformActions = m_bIsPressed && !m_bIsSpaceOrEnterKeyDown && !m_bIsNavigationAcceptOrGamepadAKeyDown; + + if (m_shouldPerformActions) + { + GestureModes gestureFollowing = GestureModes.None; + + m_bIsPressed = false; + + UpdateVisualState(); + gestureFollowing = pArgs.GestureFollowing; + + // Note that we are intentionally NOT handling the args + // if we do not fall through here because basically we are no_opting in that case. + if (gestureFollowing != GestureModes.RightTapped) + { + pArgs.Handled = true; + PerformPointerUpAction(); + } + } + } + } + + private protected override void OnRightTappedUnhandled(RightTappedRoutedEventArgs pArgs) + { + base.OnRightTappedUnhandled(pArgs); + + var handled = pArgs.Handled; + if (!handled) + { + PerformPointerUpAction(); + pArgs.Handled = true; + } + } + + // Contains the logic to be employed if we decide to handle pointer released. + private void PerformPointerUpAction() + { + if (m_shouldPerformActions) + { + Focus(FocusState.Pointer); + Invoke(); + } + + m_shouldPerformActions = false; + } + + // Performs appropriate actions upon a mouse/keyboard invocation of a + internal virtual void Invoke() + { + // Create the args + var spArgs = new RoutedEventArgs(); + spArgs.OriginalSource = this; + + // Raise the event + Click?.Invoke(this, spArgs); + + AutomationPeer.RaiseEventIfListener(this, AutomationEvents.InvokePatternOnInvoked); + + // Execute command associated with the button + ExecuteCommand(); + + bool shouldPreventDismissOnPointer = PreventDismissOnPointer; + if (!shouldPreventDismissOnPointer) + { + // Close the MenuFlyout. + var spParentMenuFlyoutPresenter = GetParentMenuFlyoutPresenter(); + if (spParentMenuFlyoutPresenter != null) + { + IMenu owningMenu; + + owningMenu = (spParentMenuFlyoutPresenter as IMenuPresenter).OwningMenu; + if (owningMenu != null) + { + // We need to make close all menu flyout sub items before we hide the parent menu flyout, + // otherwise selecting an MenuFlyoutItem in a sub menu will hide the parent menu a + // significant amount of time before closing the sub menu. + (spParentMenuFlyoutPresenter as IMenuPresenter).CloseSubMenu(); + owningMenu.Close(); + } + } + } + + ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, this); + } + + // void AddProofingItemHandlerStatic(DependencyObject pMenuFlyoutItem, INTERNAL_EVENT_HANDLER eventHandler) + //{ + // DependencyObject peer = pMenuFlyoutItem; + + // if (peer == null) + // { + // return; + // } + + // MenuFlyoutItem peerAsMenuFlyoutItem; + // (peer.As(&peerAsMenuFlyoutItem)); + // IFCPTR_RETURN(peerAsMenuFlyoutItem); + + // (peerAsAddProofingItemHandler(eventHandler)); + + // return S_OK; + //} + + // void AddProofingItemHandler( INTERNAL_EVENT_HANDLER eventHandler) + //{ + // (m_epMenuFlyoutItemClickEventCallback.AttachEventHandler(this, [eventHandler](DependencyObject pSender, DependencyObject pArgs) + // { + // DependencyObject spSender; + // EventArgs spArgs; + + // IFCPTR_RETURN(pSender); + // IFCPTR_RETURN(pArgs); + + // (ctl.do_query_interface(spSender, pSender)); + // (ctl.do_query_interface(spArgs, pArgs)); + + // eventHandler(spSender.GetHandle(), spArgs.GetCorePeer()); + + // return S_OK; + // })); + + // return S_OK; + //} + + // Executes Command if CanExecute() returns true. + private void ExecuteCommand() + { + var spCommand = Command; + + if (spCommand is not null) + { + var spCommandParameter = CommandParameter; + var canExecute = spCommand.CanExecute(spCommandParameter); + if (canExecute) + { + spCommand.Execute(spCommandParameter); + } + } + } + + // PointerEnter event handler. + protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) + { + base.OnPointerEntered(pArgs); + + m_bIsPointerOver = true; + + var spParentPresenter = GetParentMenuFlyoutPresenter(); + if (spParentPresenter is not null) + { + IMenuPresenter subPresenter; + + subPresenter = (spParentPresenter as IMenuPresenter).SubPresenter; + if (subPresenter != null) + { + ISubMenuOwner subPresenterOwner; + + subPresenterOwner = subPresenter.Owner; + + if (subPresenterOwner is not null) + { + subPresenterOwner.DelayCloseSubMenu(); + } + } + } + + UpdateVisualState(); + } + + // PointerExited event handler. + protected override void OnPointerExited(PointerRoutedEventArgs pArgs) + { + base.OnPointerExited(pArgs); + + // MenuFlyoutItem does not capture pointer, so PointerExit means the item is no longer pressed. + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + UpdateVisualState(); + } + + // PointerCaptureLost event handler. + protected override void OnPointerCaptureLost(PointerRoutedEventArgs pArgs) + { + base.OnPointerCaptureLost(pArgs); + + // MenuFlyoutItem does not capture pointer, so PointerCaptureLost means the item is no longer pressed. + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + UpdateVisualState(); + } + + // Called when the IsEnabled property changes. + private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) + { + base.OnIsEnabledChanged(e); + + if (!e.NewValue) + { + ClearStateFlags(); + } + else + { + UpdateVisualState(); + } + } + + // Called when the control got focus. + protected override void OnGotFocus(RoutedEventArgs pArgs) + { + base.OnGotFocus(pArgs); + + UpdateVisualState(); + } + + // LostFocus event handler. + protected override void OnLostFocus(RoutedEventArgs pArgs) + { + base.OnLostFocus(pArgs); + + if (!m_bIsPointerLeftButtonDown) + { + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + m_bIsPressed = false; + } + + UpdateVisualState(); + } + + // KeyDown event handler. + protected override void OnKeyDown(KeyRoutedEventArgs pArgs) + { + base.OnKeyDown(pArgs); + + var handled = pArgs.Handled; + if (!handled) + { + var key = pArgs.Key; + handled = KeyPressMenuFlyout.KeyDown(key, this); + pArgs.Handled = handled; + } + } + + // KeyUp event handler. + protected override void OnKeyUp(KeyRoutedEventArgs pArgs) + { + base.OnKeyUp(pArgs); + + var handled = pArgs.Handled; + if (!handled) + { + var key = (pArgs.Key); + KeyPressMenuFlyout.KeyUp(key, this); + pArgs.Handled = true; + } + } + + // Handle the custom property changed event and call the OnPropertyChanged2 methods. + internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) + { + base.OnPropertyChanged2(args); + + if (args.Property == UIElement.VisibilityProperty) + { + OnVisibilityChanged(); + } + else if (args.Property == MenuFlyoutItem.CommandProperty) + { + OnCommandChanged(args.OldValue, args.NewValue); + } + else if (args.Property == MenuFlyoutItem.CommandParameterProperty) + { + UpdateCanExecute(); + } + else if (args.Property == KeyboardAcceleratorTextOverrideProperty) + { + // If KeyboardAcceleratorTextOverride is being set outside of us internally setting the value, + // then we no longer own its value, and we should not override it with anything. + if (!m_isSettingKeyboardAcceleratorTextOverride) + { + m_ownsKeyboardAcceleratorTextOverride = false; + } + } + } + + // Update the visual states when the Visibility property is changed. + private void OnVisibilityChanged() + { + Visibility visibility = Visibility; + + if (Visibility.Visible != visibility) + { + ClearStateFlags(); + } + } + + private void EnterImpl(bool isLive, bool skipNameRegistration, bool coercedIsEnabled, bool useLayoutRounding) + { + //base.EnterImpl(isLive, skipNameRegistration, coercedIsEnabled, useLayoutRounding); + + if (isLive) + { + var command = Command; + + if (command is not null) + { + m_epCanExecuteChangedHandler.Disposable = null; + void OnCanExecuteChanged(object sender, EventArgs args) + { + UpdateCanExecute(); + }; + + command.CanExecuteChanged += OnCanExecuteChanged; + m_epCanExecuteChangedHandler.Disposable = Disposable.Create(() => command.CanExecuteChanged -= OnCanExecuteChanged); + } + + // In case we missed an update to CanExecute while the CanExecuteChanged handler was unhooked, + // we need to update our value now. + UpdateCanExecute(); + } + } + + private void LeaveImpl(bool isLive, bool skipNameRegistration, bool coercedIsEnabled, bool useLayoutRounding) + { + //base.LeaveImpl(isLive, skipNameRegistration, coercedIsEnabled, useLayoutRounding); + + if (isLive && m_epCanExecuteChangedHandler.Disposable is not null) + { + var spCommand = Command; + + if (spCommand is not null) + { + m_epCanExecuteChangedHandler.Disposable = null; + } + } + } + + // Called when the Command property changes. + private void OnCommandChanged(object pOldValue, object pNewValue) + { + // Remove handler for CanExecuteChanged from the old value + m_epCanExecuteChangedHandler.Disposable = null; + + if (pOldValue is not null) + { + if (pOldValue is XamlUICommand oldCommandAsUICommand) + { + CommandingHelpers.ClearBindingIfSet(oldCommandAsUICommand, this, TextProperty); + CommandingHelpers.ClearBindingIfSet(oldCommandAsUICommand, this, IconProperty); + CommandingHelpers.ClearBindingIfSet(oldCommandAsUICommand, this, KeyboardAcceleratorsProperty); + CommandingHelpers.ClearBindingIfSet(oldCommandAsUICommand, this, AccessKeyProperty); + CommandingHelpers.ClearBindingIfSet(oldCommandAsUICommand, this, AutomationProperties.HelpTextProperty); + CommandingHelpers.ClearBindingIfSet(oldCommandAsUICommand, this, ToolTipService.ToolTipProperty); + } + } + + // Subscribe to the CanExecuteChanged event on the new value + if (pNewValue is not null) + { + var spNewCommand = pNewValue as ICommand; + + void OnCanExecuteUpdated(object sender, object args) + { + UpdateCanExecute(); + } + + spNewCommand.CanExecuteChanged += OnCanExecuteUpdated; + m_epCanExecuteChangedHandler.Disposable = Disposable.Create(() => spNewCommand.CanExecuteChanged -= OnCanExecuteUpdated); + + if (spNewCommand is XamlUICommand newCommandAsUICommand) + { + CommandingHelpers.BindToLabelPropertyIfUnset(newCommandAsUICommand, this, TextProperty); + CommandingHelpers.BindToIconPropertyIfUnset(newCommandAsUICommand, this, IconProperty); + CommandingHelpers.BindToKeyboardAcceleratorsIfUnset(newCommandAsUICommand, this); + CommandingHelpers.BindToAccessKeyIfUnset(newCommandAsUICommand, this); + CommandingHelpers.BindToDescriptionPropertiesIfUnset(newCommandAsUICommand, this); + } + } + + // Coerce the button enabled state with the CanExecute state of the command. + UpdateCanExecute(); + } + + // Coerces the MenuFlyoutItem's enabled state with the CanExecute state of the Command. + private void UpdateCanExecute() + { + bool canExecute = true; + + var spCommand = Command; + if (spCommand is not null) + { + var spCommandParameter = CommandParameter; + canExecute = spCommand.CanExecute(spCommandParameter); + } + + SuppressIsEnabled(!canExecute); + } + + // Change to the correct visual state for the + private protected override void ChangeVisualState( + // true to use transitions when updating the visual state, false + // to snap directly to the new visual state. + bool bUseTransitions) + { + bool hasToggleMenuItem = false; + bool hasIconMenuItem = false; + bool hasMenuItemWithKeyboardAcceleratorText = false; + bool isKeyboardPresent = false; + + var spPresenter = GetParentMenuFlyoutPresenter(); + if (spPresenter != null) + { + hasToggleMenuItem = spPresenter.GetContainsToggleItems(); + hasIconMenuItem = spPresenter.GetContainsIconItems(); + hasMenuItemWithKeyboardAcceleratorText = spPresenter.GetContainsItemsWithKeyboardAcceleratorText(); + } + + var bIsEnabled = IsEnabled; + var focusState = FocusState; + var shouldBeNarrow = GetShouldBeNarrow(); + + // We only care about finding if we have a keyboard if we also have a menu item with accelerator text, + // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. + if (hasMenuItemWithKeyboardAcceleratorText) + { + isKeyboardPresent = DXamlCore.Current.GetIsKeyboardPresent(); + } + + // CommonStates + if (!bIsEnabled) + { + VisualStateManager.GoToState(this, "Disabled", bUseTransitions); + } + else if (m_bIsPressed) + { + VisualStateManager.GoToState(this, "Pressed", bUseTransitions); + } + else if (m_bIsPointerOver) + { + VisualStateManager.GoToState(this, "PointerOver", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Normal", bUseTransitions); + } + + // FocusStates + if (FocusState.Unfocused != focusState && bIsEnabled) + { + if (FocusState.Pointer == focusState) + { + VisualStateManager.GoToState(this, "PointerFocused", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "Focused", bUseTransitions); + } + } + else + { + VisualStateManager.GoToState(this, "Unfocused", bUseTransitions); + } + + // CheckPlaceholderStates + if (hasToggleMenuItem && hasIconMenuItem) + { + VisualStateManager.GoToState(this, "CheckAndIconPlaceholder", bUseTransitions); + } + else if (hasToggleMenuItem) + { + VisualStateManager.GoToState(this, "CheckPlaceholder", bUseTransitions); + } + else if (hasIconMenuItem) + { + VisualStateManager.GoToState(this, "IconPlaceholder", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "NoPlaceholder", bUseTransitions); + } + + // PaddingSizeStates + if (shouldBeNarrow) + { + VisualStateManager.GoToState(this, "NarrowPadding", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "DefaultPadding", bUseTransitions); + } + + // We'll make the accelerator text visible if any item has accelerator text, + // as this causes the margin to be applied which reserves space, ensuring that accelerator text + // in one item won't be at the same horizontal position as label text in another item. + if (hasMenuItemWithKeyboardAcceleratorText && isKeyboardPresent) + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextVisible", bUseTransitions); + } + else + { + VisualStateManager.GoToState(this, "KeyboardAcceleratorTextCollapsed", bUseTransitions); + } + } + + // Clear flags relating to the visual state. Called when IsEnabled is set to false + // or when Visibility is set to Hidden or Collapsed. + private void ClearStateFlags() + { + m_bIsPressed = false; + m_bIsPointerLeftButtonDown = false; + m_bIsPointerOver = false; + m_bIsSpaceOrEnterKeyDown = false; + m_bIsNavigationAcceptOrGamepadAKeyDown = false; + + UpdateVisualState(); + } + + // Create MenuFlyoutItemAutomationPeer to represent the + protected override AutomationPeer OnCreateAutomationPeer() => new MenuFlyoutItemAutomationPeer(this); + + private protected override string GetPlainText() => Text; + + internal Size GetKeyboardAcceleratorTextDesiredSize() + { + var desiredSize = new Size(0, 0); + + if (!m_isTemplateApplied) + { + bool templateApplied = ApplyTemplate(); + m_isTemplateApplied = templateApplied; + } + + if (m_tpKeyboardAcceleratorTextBlock is not null) + { + m_tpKeyboardAcceleratorTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + desiredSize = m_tpKeyboardAcceleratorTextBlock.DesiredSize; + var margin = m_tpKeyboardAcceleratorTextBlock.Margin; + + desiredSize.Width -= (float)(margin.Left + margin.Right); + desiredSize.Height -= (float)(margin.Top + margin.Bottom); + } + + return desiredSize; + } + + private void InitializeKeyboardAcceleratorText() + { + // If we have no keyboard accelerator text already provided by the app, + // then we'll see if we can construct it ourselves based on keyboard accelerators + // set on this item. For example, if a keyboard accelerator with key "S" and modifier "Control" + // is set, then we'll convert that into the keyboard accelerator text "Ctrl+S". + if (m_ownsKeyboardAcceleratorTextOverride) + { + var keyboardAcceleratorTextOverride = KeyboardAccelerator.GetStringRepresentationForUIElement(this); + + // If we were able to get a string representation from keyboard accelerators, + // then we should now set that as the value of KeyboardAcceleratorText. + if (keyboardAcceleratorTextOverride is not null) + { + using var guard = Disposable.Create(() => + { + m_isSettingKeyboardAcceleratorTextOverride = false; + }); + + m_isSettingKeyboardAcceleratorTextOverride = true; + KeyboardAcceleratorTextOverride = keyboardAcceleratorTextOverride; + } + } + } + + internal void UpdateTemplateSettings(double maxKeyboardAcceleratorTextWidth) + { + if (m_maxKeyboardAcceleratorTextWidth != maxKeyboardAcceleratorTextWidth) + { + m_maxKeyboardAcceleratorTextWidth = maxKeyboardAcceleratorTextWidth; + + MenuFlyoutItemTemplateSettings templateSettings = TemplateSettings; + + if (templateSettings is null) + { + MenuFlyoutItemTemplateSettings templateSettingsImplementation = new(); + TemplateSettings = templateSettingsImplementation; + templateSettings = templateSettingsImplementation; + } + + templateSettings.KeyboardAcceleratorTextMinWidth = m_maxKeyboardAcceleratorTextWidth; + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBase.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBase.cs index 59008301b896..51d8358c2d26 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBase.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBase.cs @@ -1,47 +1,53 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutItemBase_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System; using System.Collections.Generic; using System.Text; using Microsoft.UI.Xaml.Input; +using Uno.UI.DataBinding; + +namespace Microsoft.UI.Xaml.Controls; -namespace Microsoft.UI.Xaml.Controls +/// +/// Represents the base class for items in a MenuFlyout control. +/// +public abstract partial class MenuFlyoutItemBase : Control { - public abstract partial class MenuFlyoutItemBase : Control + private ManagedWeakReference m_wrParentMenuFlyoutPresenter; + + public MenuFlyoutItemBase() { - private WeakReference m_wrParentMenuFlyoutPresenter; + } - public MenuFlyoutItemBase() - { + // Get the parent MenuFlyoutPresenter. + internal MenuFlyoutPresenter GetParentMenuFlyoutPresenter() + => m_wrParentMenuFlyoutPresenter is { IsAlive: true } ? m_wrParentMenuFlyoutPresenter.Target as MenuFlyoutPresenter : null; - } + // Sets the parent MenuFlyoutPresenter. + internal void SetParentMenuFlyoutPresenter(MenuFlyoutPresenter pParentMenuFlyoutPresenter) + => m_wrParentMenuFlyoutPresenter = WeakReferencePool.RentWeakReference(this, pParentMenuFlyoutPresenter); - // Get the parent MenuFlyoutPresenter. - internal MenuFlyoutPresenter GetParentMenuFlyoutPresenter() - => m_wrParentMenuFlyoutPresenter?.Target as MenuFlyoutPresenter; + internal bool GetShouldBeNarrow() + { + MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); - // Sets the parent MenuFlyoutPresenter. - internal void SetParentMenuFlyoutPresenter(MenuFlyoutPresenter pParentMenuFlyoutPresenter) - => m_wrParentMenuFlyoutPresenter = new WeakReference(pParentMenuFlyoutPresenter); + var shouldBeNarrow = false; - internal bool GetShouldBeNarrow() + if (spPresenter != null) { - MenuFlyoutPresenter spPresenter = GetParentMenuFlyoutPresenter(); - - var shouldBeNarrow = false; + MenuFlyout spParentFlyout = spPresenter.GetParentMenuFlyout(); - if (spPresenter != null) + if (spParentFlyout != null) { - MenuFlyout spParentFlyout = spPresenter.GetParentMenuFlyout(); - - if (spParentFlyout != null) - { - shouldBeNarrow = - (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Mouse) || - (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Pen) || - (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Keyboard); - } + shouldBeNarrow = + (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Mouse) || + (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Pen) || + (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Keyboard); } - - return shouldBeNarrow; } + + return shouldBeNarrow; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Uno.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Uno.cs deleted file mode 100644 index 31b7e197b6a0..000000000000 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.Uno.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.UI.Xaml.Controls; - -partial class ToggleMenuFlyoutItem -{ - /// - /// Test hook used to determine if the item is a toggle. - /// - internal override bool HasToggle() => true; -} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.h.mux.cs new file mode 100644 index 000000000000..4cc1fd42d1d0 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ToggleMenuFlyoutItem.h.mux.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\ToggleMenuFlyoutItem_Partial.h, tag winui3/release/1.5.4, commit 98a60c8 + +namespace Microsoft.UI.Xaml.Controls; + +partial class ToggleMenuFlyoutItem +{ + /// + /// Test hook used to determine if the item is a toggle. + /// + internal override bool HasToggle() => true; +} From 45cba9afc4897e9a27dedb1f0fbe94712cc4412f Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 20 Jun 2024 15:03:36 +0200 Subject: [PATCH 13/28] chore: Updating MenuFlyout --- .../UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs | 429 +---------------- .../MenuFlyout/MenuFlyout.partial.h.mux.cs | 25 + .../MenuFlyout/MenuFlyout.partial.mux.cs | 455 ++++++++++++++++++ .../Controls/MenuFlyout/MenuFlyoutItem.cs | 6 + .../MenuFlyoutItemBaseCollection.cs | 28 +- .../MenuFlyoutSubItem.partial.mux.cs | 7 +- 6 files changed, 510 insertions(+), 440 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.h.mux.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs index 84505974260e..030d83ef3e30 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.cs @@ -1,433 +1,12 @@ -#pragma warning disable 649 -#pragma warning disable 414 // assigned but its value is never used - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Linq; -using System.Text; -using Uno.Extensions; -using Uno.UI; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Windows.Networking.Sockets; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Markup; -using Microsoft.UI.Xaml.Media.Animation; namespace Microsoft.UI.Xaml.Controls; +/// +/// Represents a flyout that displays a menu of commands. +/// [ContentProperty(Name = nameof(Items))] public partial class MenuFlyout : FlyoutBase, IMenu { - private readonly MenuFlyoutItemBaseCollection m_tpItems; - - // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. - // UNO TODO private Transition m_tpMenuPopupThemeTransition = null; - - private WeakReference m_wrParentMenu; - - private bool m_openWindowed = false; // TODO Uno specific: Always false in Uno for now. - - //private bool m_openingWindowedInProgress; - - public MenuFlyout() - { - m_isPositionedAtPoint = true; - m_inputDeviceTypeUsedToOpen = FocusInputDeviceKind.None; - - PrepareState(); - } - - // Prepares object's state - private void PrepareState() - { - base.PrepareState(); - - MenuFlyoutItemBaseCollection spItems = new(); - CoreImports.Collection_SetOwner(spItems, this); - m_tpItems = spItems; - - Items = spItems; - } - - /* - _Check_return_ HRESULT MenuFlyout::DisconnectFrameworkPeerCore() -{ - HRESULT hr = S_OK; - - // - // DXAML Structure - // --------------- - // DXAML::MenuFlyout (this) --------------> DXAML::MenuFlyoutItemBase - // | | - // | +---------> DXAML::MenuFlyoutItemBase - // V - // DXAML::MenuFlyoutItemBaseCollection (m_tpItems) - // - // CORE Structure - // -------------- - // Core::CControl (this) < - - + < - - + - // | : : - // V : : - // Core::CMenuFlyoutItemBaseCollection - - + (m_pOwner) : - // | | : - // V V : - // Core::CControl Core::CControl - - - - - - - - - - + (m_pParent) - // - // To clear the m_pParent association of the MenuFlyoutItemBase, we have to clear the - // Core::CMenuFlyoutItemBaseCollection's children, which calls SetParent(NULL) on each of its - // children. Once this association to MenuFlyout is broken, we can safely destroy MenuFlyout. - // - - // clear the children in the MenuFlyoutItemBaseCollection - if (m_tpItems.GetAsCoreDO() != nullptr) - { - IFC(CoreImports::Collection_Clear(static_cast(m_tpItems.GetAsCoreDO()))); - IFC(CoreImports::Collection_SetOwner(static_cast(m_tpItems.GetAsCoreDO()), nullptr)); - } - - IFC(MenuFlyoutGenerated::DisconnectFrameworkPeerCore()); - -Cleanup: - RRETURN(hr); -} - */ - - internal void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) - { - if (e.Property == MenuFlyoutPresenterStyleProperty && - GetPresenter() != null) - { - SetPresenterStyle(GetPresenter(), e.NewValue as Style); - } - } - - protected override Control CreatePresenter() - { - MenuFlyoutPresenter presenter = new(); - presenter.SetParentMenuFlyout(this); - - var style = MenuFlyoutPresenterStyle; - SetPresenterStyle(presenter, style); - - return presenter; - } - - private protected override void ShowAtCore(FrameworkElement pPlacementTarget, FlyoutShowOptions showOptions) - { - openDelayed = false; - if (m_openWindowed) - { - SetIsWindowedPopup(); - } - else - { - m_openWindowed = true; - } - - var placementTarget = pPlacementTarget; - CacheInputDeviceTypeUsedToOpen(placementTarget); - - base.ShowAtCore(pPlacementTarget, showOptions); - } - - // Raise Opening event. - private protected override void OnOpening() - { - // Update the TemplateSettings as it is about to open. - Control presenter = GetPresenter(); - MenuFlyoutPresenter menuFlyoutPresenter = presenter as MenuFlyoutPresenter; - - if (menuFlyoutPresenter is not null) - { - IMenu parentMenu = (this as IMenu).ParentMenu as IMenu; - - (menuFlyoutPresenter as IMenuPresenter).OwningMenu = parentMenu != null ? parentMenu : this; - menuFlyoutPresenter.UpdateTemplateSettings(); - } - - base.OnOpening(); - - if (menuFlyoutPresenter is not null) - { - // Reset the presenter's ItemsSource. Since Items is not an IObservableVector, we don't - // automatically respond to changes within the vector. Clearing the property when the presenter - // unloads and resetting it before we reopen ensures any changes to Items are reflected - // when the MenuFlyoutPresenter shows. It also allows sharing of MenuFlyouts; since MenuFlyoutItemBases - // are UIElements they must be unparented when leaving the tree before they can be inserted elsewhere. - menuFlyoutPresenter.ItemsSource = m_tpItems; - - AutomationPeer.RaiseEventIfListener(menuFlyoutPresenter, AutomationEvents.MenuOpened); - } - } - - internal override void OnClosing(ref bool cancel) - { - base.OnClosing(ref cancel); - - if (!cancel) - { - CloseSubMenu(); - } - } - - private protected override void OnClosed() - { - CloseSubMenu(); - - AutomationPeer.RaiseEventIfListener(GetPresenter(), AutomationEvents.MenuClosed); - - base.OnClosed(); - - if (GetPresenter() is MenuFlyoutPresenter presenter) - { - presenter.m_iFocusedIndex = -1; - presenter.ItemsSource = null; - } - } - - private void CloseSubMenu() - { - var presenter = (MenuFlyoutPresenter)GetPresenter(); - - if (presenter is not null) - { - var subPresenter = (presenter as IMenuPresenter).SubPresenter; - - if (subPresenter != null) - { - subPresenter.CloseSubMenu(); - } - } - } - - internal FocusInputDeviceKind InputDeviceTypeUsedToOpen => m_inputDeviceTypeUsedToOpen; - - internal protected override void OnDataContextChanged(DependencyPropertyChangedEventArgs e) - { - base.OnDataContextChanged(e); - - SetFlyoutItemsDataContext(); - } - - private void SetFlyoutItemsDataContext() - { - // This is present to force the dataContext to be passed to the popup of the flyout since it is not directly a child in the visual tree of the flyout. - Items?.ForEach(item => item?.SetValue( - UIElement.DataContextProperty, - this.DataContext, - precedence: DependencyPropertyValuePrecedences.Inheritance - )); - } - - - public void ShowAt(UIElement targetElement, Point point) - { - ShowAtCore((FrameworkElement)targetElement, new FlyoutShowOptions { Position = point }); - } - - - - - - - - - -#if false - void PreparePopupTheme( - Popup pPopup, - MajorPlacementMode placementMode, - FrameworkElement pPlacementTarget) - { - bool areOpenCloseAnimationsEnabled = false; - areOpenCloseAnimationsEnabled = AreOpenCloseAnimationsEnabled; - - if (!areOpenCloseAnimationsEnabled) - { - return; - } - - // On Threshold, we use the MenuPopupThemeTransition for MenuFlyouts. - double openedLength = 0; - - // UNO TODO - //if (m_tpMenuPopupThemeTransition == null) - //{ - // Transition spMenuPopupChildTransition; - // spMenuPopupChildTransition = PreparePopupThemeTransitionsAndShadows(pPopup, 0.5 /* closedRatioConstant */, 0 /* depth */); - // m_tpMenuPopupThemeTransition = spMenuPopupChildTransition; - //} - - openedLength = ((Control)GetPresenter()).ActualHeight; - - // UNO TODO - // (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; - var direction = (placementMode == MajorPlacementMode.Top) ? AnimationDirection.Bottom : AnimationDirection.Top; - - // UNO TODO - // (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).Direction = direction); - } - - ///*static*/ - //Transition PreparePopupThemeTransitionsAndShadows( - // Popup popup, - // double closedRatioConstant, - // uint depth) - //{ - // Transition spTransition; - // TransitionCollection spTransitionCollection; - // MenuPopupThemeTransition spMenuPopupChildTransition; - - // *transition = null; - - // (ctl.make(&spMenuPopupChildTransition)); - // (ctl.make(&spTransitionCollection)); - - // spMenuPopupChildTransition.ClosedRatio = closedRatioConstant; - - // FrameworkElement overlayElement; - // overlayElement = popup.OverlayElement; - // spMenuPopupChildTransition.SetOverlayElement(overlayElement); - - // spTransition = spMenuPopupChildTransition; - // spTransitionCollection.Append(spTransition)); - - // // UNO TODO Windowed Popups are not supported - // // - // // For windowed popups, the transition needs to target the grandchild of the popup. - // // Otherwise, the transition LTE is going to live in the main window and get clipped. - // //if ((CPopup*)(popup.GetHandle()).IsWindowed()) - // //{ - // // UIElement popupChild; - // // popupChild = popup.Child; - - // // if (popupChild) - // // { - // // int childrenCount; - // // (VisualTreeHelper.GetChildrenCountStatic((UIElement*)(popupChild), &childrenCount)); - - // // if (childrenCount == 1) - // // { - // // xaml.IDependencyObject popupGrandChildAsDO; - // // (VisualTreeHelper.GetChildStatic((UIElement*)(popupChild), 0, &popupGrandChildAsDO)); - - // // if (popupGrandChildAsDO) - // // { - // // UIElement popupGrandChildAsUE; - // // (popupGrandChildAsDO.As(&popupGrandChildAsUE)); - // // (popupGrandChildAsUE.Transitions = spTransitionCollection); - // // (popupGrandChildAsUE.InvalidateMeasure()); - // // } - // // } - // // } - // //} - // //else - // { - // popup.ChildTransitions = spTransitionCollection; - // } - //} - - void AutoAdjustPlacement(MajorPlacementMode pPlacement) - { - // UNO TODO - // Rect windowRect = default; - // (DXamlCore.GetCurrent().GetContentBoundsForElement(GetHandle(), &windowRect)); - } - - void ShowAtImpl(UIElement pTargetElement, Point targetPoint) - { - var targetElement = pTargetElement; - - var showOptions = new FlyoutShowOptions(); - showOptions.Position = targetPoint; - - try - { - m_openingWindowedInProgress = true; - - ShowAt(targetElement, showOptions); - } - finally - { - m_openingWindowedInProgress = false; - } - } -#endif - - void CacheInputDeviceTypeUsedToOpen(UIElement pTargetElement) - { - // UNO TODO - //CContentRoot* contentRoot = VisualTree.GetContentRootForElement(pTargetElement); - //InputDeviceTypeUsedToOpen = contentRoot.GetInputManager().GetLastInputDeviceType(); - } - -#if false - // Callback for ShowAt() from core layer - void ShowAtStatic(MenuFlyout pCoreMenuFlyout, - UIElement pCoreTarget, - Point point) - { - Debug.Assert(pCoreMenuFlyout != null); - Debug.Assert(pCoreTarget != null); - - DependencyObject target = pCoreTarget; - - pCoreMenuFlyout.ShowAtImpl(target as FrameworkElement, point); - } - - void OnProcessKeyboardAcceleratorsImpl(ProcessKeyboardAcceleratorEventArgs pArgs) - { - if (m_tpItems != null) - { - var itemCount = m_tpItems.Count; - for (int i = 0; i < itemCount; i++) - { - MenuFlyoutItemBase spItem = m_tpItems[i]; - (spItem as MenuFlyoutItemBase).TryInvokeKeyboardAccelerator(pArgs); - } - } - } -#endif - - IMenu IMenu.ParentMenu - { - get => m_wrParentMenu?.Target as IMenu; - set - { - m_wrParentMenu = new WeakReference(value); - - // If we have a parent menu, then we want to disable the light-dismiss overlay - - // in that circumstance, the parent menu will have a light-dismiss overlay that we'll use instead. - IsLightDismissOverlayEnabled = value == null; - - Control presenter = GetPresenter(); - MenuFlyoutPresenter menuFlyoutPresenter = presenter as MenuFlyoutPresenter; - - if (menuFlyoutPresenter is { }) - { - menuFlyoutPresenter.IsSubPresenter = value != null; - } - } - } - - void IMenu.Close() - { - Hide(); - } - -#if false - bool IsWindowedPopup() - { - return false; - - // UNO TODO - // return CPopup.DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle()) && (FlyoutBase.IsWindowedPopup() || m_openingWindowedInProgress); - } -#endif } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.h.mux.cs new file mode 100644 index 000000000000..be18ac95a794 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.h.mux.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyout_Partial.h, tag winui3/release/1.5.4, commit 98a60c8 + +using Microsoft.UI.Xaml.Media.Animation; +using Uno.UI.DataBinding; +using Uno.UI.Xaml.Input; + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyout +{ + private MenuFlyoutItemBaseCollection m_tpItems; + + // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. + private Transition m_tpMenuPopupThemeTransition; + + private InputDeviceType m_inputDeviceTypeUsedToOpen; + + private ManagedWeakReference m_wrParentMenu; + + private bool m_openWindowed; // TODO Uno specific: Always false in Uno for now. + + private bool m_itemsSourceRefreshPending; +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs new file mode 100644 index 000000000000..e83318f6e01a --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs @@ -0,0 +1,455 @@ +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media.Animation; +using Uno.UI.DataBinding; +using Windows.Foundation; +using static Uno.UI.FeatureConfiguration; + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyout +{ + /// + /// Initializes a new instance of the MenuFlyout class. + /// + public MenuFlyout() + { + m_isPositionedAtPoint = true; + m_inputDeviceTypeUsedToOpen = Uno.UI.Xaml.Input.InputDeviceType.None; + + PrepareState(); + } + + // Prepares object's state + private void PrepareState() + { + //base.PrepareState(); + + MenuFlyoutItemBaseCollection spItems = new(this); + m_tpItems = spItems; + + Items = spItems; + } + + /* + _Check_return_ HRESULT MenuFlyout::DisconnectFrameworkPeerCore() +{ + HRESULT hr = S_OK; + + // + // DXAML Structure + // --------------- + // DXAML::MenuFlyout (this) --------------> DXAML::MenuFlyoutItemBase + // | | + // | +---------> DXAML::MenuFlyoutItemBase + // V + // DXAML::MenuFlyoutItemBaseCollection (m_tpItems) + // + // CORE Structure + // -------------- + // Core::CControl (this) < - - + < - - + + // | : : + // V : : + // Core::CMenuFlyoutItemBaseCollection - - + (m_pOwner) : + // | | : + // V V : + // Core::CControl Core::CControl - - - - - - - - - - + (m_pParent) + // + // To clear the m_pParent association of the MenuFlyoutItemBase, we have to clear the + // Core::CMenuFlyoutItemBaseCollection's children, which calls SetParent(NULL) on each of its + // children. Once this association to MenuFlyout is broken, we can safely destroy MenuFlyout. + // + + // clear the children in the MenuFlyoutItemBaseCollection + if (m_tpItems.GetAsCoreDO() != nullptr) + { + IFC(CoreImports::Collection_Clear(static_cast(m_tpItems.GetAsCoreDO()))); + IFC(CoreImports::Collection_SetOwner(static_cast(m_tpItems.GetAsCoreDO()), nullptr)); + } + + IFC(MenuFlyoutGenerated::DisconnectFrameworkPeerCore()); + +Cleanup: + RRETURN(hr); +} + */ + + internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs args) + { + base.OnPropertyChanged2(args); + + if (args.Property == MenuFlyoutPresenterStyleProperty && + GetPresenter() is { } presenter) + { + SetPresenterStyle(presenter, args.NewValue as Style); + } + } + + protected override Control CreatePresenter() + { + MenuFlyoutPresenter presenter = new(); + presenter.SetParentMenuFlyout(this); + + var style = MenuFlyoutPresenterStyle; + SetPresenterStyle(presenter, style); + + return presenter; + } + + private protected override void ShowAtCore(FrameworkElement pPlacementTarget, FlyoutShowOptions showOptions) + { + // TODO Uno: OpenDelayed is not available yet + // openDelayed = false; + + if (m_openWindowed) + { + SetIsWindowedPopup(); + } + else + { + m_openWindowed = true; + } + + var placementTarget = pPlacementTarget; + CacheInputDeviceTypeUsedToOpen(placementTarget); + + base.ShowAtCore(pPlacementTarget, showOptions); + } + + // Raise Opening event. + private protected override void OnOpening() + { + // Update the TemplateSettings as it is about to open. + var presenter = GetPresenter(); + var menuFlyoutPresenter = presenter as MenuFlyoutPresenter; + + if (menuFlyoutPresenter is not null) + { + IMenu parentMenu = (this as IMenu).ParentMenu as IMenu; + + (menuFlyoutPresenter as IMenuPresenter).OwningMenu = parentMenu != null ? parentMenu : this; + menuFlyoutPresenter.UpdateTemplateSettings(); + } + + base.OnOpening(); + + // Reset the presenter's ItemsSource. Since Items is not an IObservableVector, we don't + // automatically respond to changes within the vector. Clearing the property when the presenter + // unloads and resetting it before we reopen ensures any changes to Items are reflected + // when the MenuFlyoutPresenter shows. It also allows sharing of MenuFlyouts; since MenuFlyoutItemBases + // are UIElements they must be unparented when leaving the tree before they can be inserted elsewhere. + menuFlyoutPresenter.ItemsSource = m_tpItems; + + AutomationPeer.RaiseEventIfListener(menuFlyoutPresenter, AutomationEvents.MenuOpened); + } + + internal override void OnClosing(ref bool cancel) + { + base.OnClosing(ref cancel); + + if (!cancel) + { + CloseSubMenu(); + } + } + + private protected override void OnClosed() + { + CloseSubMenu(); + + AutomationPeer.RaiseEventIfListener(GetPresenter(), AutomationEvents.MenuClosed); + + base.OnClosed(); + + if (GetPresenter() is MenuFlyoutPresenter presenter) + { + presenter.m_iFocusedIndex = -1; + presenter.ItemsSource = null; + } + } + + private void CloseSubMenu() + { + var presenter = (MenuFlyoutPresenter)GetPresenter(); + + if (presenter is not null) + { + var subPresenter = (presenter as IMenuPresenter).SubPresenter; + + if (subPresenter != null) + { + subPresenter.CloseSubMenu(); + } + } + } + + private void PreparePopupTheme(Popup pPopup, MajorPlacementMode placementMode, FrameworkElement pPlacementTarget) + { + // UNO TODO + //BOOLEAN areOpenCloseAnimationsEnabled = FALSE; + //IFC_RETURN(get_AreOpenCloseAnimationsEnabled(&areOpenCloseAnimationsEnabled)); + + //if (!areOpenCloseAnimationsEnabled) + //{ + // return S_OK; + //} + + //double openedLength = 0; + //xaml_primitives::AnimationDirection direction = xaml_primitives::AnimationDirection_Bottom; + + //if (!m_tpMenuPopupThemeTransition) + //{ + // ctl::ComPtr spMenuPopupChildTransition; + // IFC_RETURN(MenuFlyout::PreparePopupThemeTransitionsAndShadows(pPopup, 0.5 /* closedRatioConstant */, 0 /* depth */, &spMenuPopupChildTransition)); + // SetPtrValue(m_tpMenuPopupThemeTransition, spMenuPopupChildTransition.Get()); + //} + + //IFC_RETURN(static_cast(GetPresenter())->get_ActualHeight(&openedLength)); + //IFC_RETURN(m_tpMenuPopupThemeTransition.Cast()->put_OpenedLength(openedLength)); + //direction = (placementMode == MajorPlacementMode::Top) ? xaml_primitives::AnimationDirection_Bottom : xaml_primitives::AnimationDirection_Top; + //IFC_RETURN(m_tpMenuPopupThemeTransition.Cast()->put_Direction(direction)); + + //return S_OK; + } + + private static Transition PreparePopupThemeTransitionsAndShadows(Popup popup, double closedRatioConstant, int depth) + { + return null; + // UNO TODO + //ctl::ComPtr spTransition; + //ctl::ComPtr spTransitionCollection; + //ctl::ComPtr spMenuPopupChildTransition; + + //*transition = nullptr; + + //IFC_RETURN(ctl::make(&spMenuPopupChildTransition)); + //IFC_RETURN(ctl::make(&spTransitionCollection)); + + //IFC_RETURN(spMenuPopupChildTransition->put_ClosedRatio(closedRatioConstant)); + + //ctl::ComPtr overlayElement; + //IFC_RETURN(popup->get_OverlayElement(&overlayElement)); + //spMenuPopupChildTransition->SetOverlayElement(overlayElement.Get()); + + //IFC_RETURN(spMenuPopupChildTransition.As(&spTransition)); + //IFC_RETURN(spTransitionCollection->Append(spTransition.Get())); + + //// For windowed popups, the transition needs to target the grandchild of the popup. + //// Otherwise, the transition LTE is going to live in the main window and get clipped. + //if (static_cast(popup->GetHandle())->IsWindowed()) + //{ + // ctl::ComPtr popupChild; + // IFC_RETURN(popup->get_Child(&popupChild)); + + // if (popupChild) + // { + // int childrenCount; + // IFC_RETURN(VisualTreeHelper::GetChildrenCountStatic(static_cast(popupChild.Get()), &childrenCount)); + + // if (childrenCount == 1) + // { + // ctl::ComPtr popupGrandChildAsDO; + // IFC_RETURN(VisualTreeHelper::GetChildStatic(static_cast(popupChild.Get()), 0, &popupGrandChildAsDO)); + + // if (popupGrandChildAsDO) + // { + // ctl::ComPtr popupGrandChildAsUE; + // IFC_RETURN(popupGrandChildAsDO.As(&popupGrandChildAsUE)); + // IFC_RETURN(popupGrandChildAsUE->put_Transitions(spTransitionCollection.Get())); + // IFC_RETURN(popupGrandChildAsUE->InvalidateMeasure()); + // } + // } + // } + //} + //else + //{ + // IFC_RETURN(popup->put_ChildTransitions(spTransitionCollection.Get())); + //} + + //*transition = spMenuPopupChildTransition.Detach(); + //return S_OK; + } + // TODO:MZ: FROM HERE + + void AutoAdjustPlacement(MajorPlacementMode pPlacement) + { + // UNO TODO + // Rect windowRect = default; + // (DXamlCore.GetCurrent().GetContentBoundsForElement(GetHandle(), &windowRect)); + } + + public void ShowAt(UIElement targetElement, Point point) + { + ShowAtCore((FrameworkElement)targetElement, new FlyoutShowOptions { Position = point }); + } + + + void ShowAtImpl(UIElement pTargetElement, Point targetPoint) + { + var targetElement = pTargetElement; + + var showOptions = new FlyoutShowOptions(); + showOptions.Position = targetPoint; + + try + { + m_openingWindowedInProgress = true; + + ShowAt(targetElement, showOptions); + } + finally + { + m_openingWindowedInProgress = false; + } + } + + internal FocusInputDeviceKind InputDeviceTypeUsedToOpen => m_inputDeviceTypeUsedToOpen; + + internal protected override void OnDataContextChanged(DependencyPropertyChangedEventArgs e) + { + base.OnDataContextChanged(e); + + SetFlyoutItemsDataContext(); + } + + private void SetFlyoutItemsDataContext() + { + // This is present to force the dataContext to be passed to the popup of the flyout since it is not directly a child in the visual tree of the flyout. + Items?.ForEach(item => item?.SetValue( + UIElement.DataContextProperty, + this.DataContext, + precedence: DependencyPropertyValuePrecedences.Inheritance + )); + } + + + void CacheInputDeviceTypeUsedToOpen(UIElement pTargetElement) + { + // UNO TODO + //CContentRoot* contentRoot = VisualTree.GetContentRootForElement(pTargetElement); + //InputDeviceTypeUsedToOpen = contentRoot.GetInputManager().GetLastInputDeviceType(); + } + +#if false + // Callback for ShowAt() from core layer + void ShowAtStatic(MenuFlyout pCoreMenuFlyout, + UIElement pCoreTarget, + Point point) + { + Debug.Assert(pCoreMenuFlyout != null); + Debug.Assert(pCoreTarget != null); + + DependencyObject target = pCoreTarget; + + pCoreMenuFlyout.ShowAtImpl(target as FrameworkElement, point); + } + + void OnProcessKeyboardAcceleratorsImpl(ProcessKeyboardAcceleratorEventArgs pArgs) + { + + } +#endif + + + +#if false + bool IsWindowedPopup() + { + return false; + + // UNO TODO + // return CPopup.DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle()) && (FlyoutBase.IsWindowedPopup() || m_openingWindowedInProgress); + } +#endif + + protected override void OnProcessKeyboardAccelerators(ProcessKeyboardAcceleratorEventArgs args) + { + if (m_tpItems != null) + { + for (int i = 0; i < m_tpItems.Count; i++) + { + MenuFlyoutItemBase spItem = m_tpItems[i]; + spItem.TryInvokeKeyboardAccelerator(args); + } + } + } + + IMenu IMenu.ParentMenu + { + get => m_wrParentMenu?.IsAlive == true ? m_wrParentMenu.Target as IMenu : null; + set + { + m_wrParentMenu = WeakReferencePool.RentWeakReference(this, value); + + // If we have a parent menu, then we want to disable the light-dismiss overlay - + // in that circumstance, the parent menu will have a light-dismiss overlay that we'll use instead. + IsLightDismissOverlayEnabled = value is null; + + EnsurePopupAndPresenter(); + + var presenter = GetPresenter(); + MenuFlyoutPresenter menuFlyoutPresenter = presenter as MenuFlyoutPresenter; + + bool isSubMenu = value is not null; + + // TODO:MZ: Needed? + // m_tpPopup.IsSubMenu = isSubMenu; + if (menuFlyoutPresenter is { }) + { + menuFlyoutPresenter.IsSubPresenter = isSubMenu; + } + } + } + + void IMenu.Close() + { + Hide(); + } + + internal void QueueRefreshItemsSource() + { + // The items source might change multiple times in a single tick, so we'll coalesce the refresh + // into a single event once all of the changes have completed. + if (GetPresenter() is not null && !m_itemsSourceRefreshPending) + { + var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + + var wrThis = WeakReferencePool.RentSelfWeakReference(this); + + dispatcherQueue.TryEnqueue( + () => + { + if (!wrThis.IsAlive) + { + return; + } + + var thisMenuFlyoutSubItem = wrThis.Target as MenuFlyout; + + if (thisMenuFlyoutSubItem is not null) + { + thisMenuFlyoutSubItem.RefreshItemsSource(); + } + }); + + m_itemsSourceRefreshPending = true; + } + } + + private void RefreshItemsSource() + { + m_itemsSourceRefreshPending = false; + + var presenter = GetPresenter(); + + global::System.Diagnostics.Debug.Assert(presenter is not null); + + // Setting the items source to null and then back to Items causes the presenter to pick up any changes. + if (presenter is MenuFlyoutPresenter menuFlyoutPresenter) + { + menuFlyoutPresenter.ItemsSource = null; + menuFlyoutPresenter.ItemsSource = m_tpItems; + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs index ca13c626e64d..dafc73be2dc1 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.cs @@ -2,9 +2,15 @@ namespace Microsoft.UI.Xaml.Controls; +/// +/// Represents a command in a MenuFlyout control. +/// [ContentProperty(Name = nameof(Text))] public partial class MenuFlyoutItem : MenuFlyoutItemBase { + /// + /// Initializes a new instance of the MenuFlyoutItem class. + /// public MenuFlyoutItem() { DefaultStyleKey = typeof(MenuFlyoutItem); diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs index d8e19b138809..12d4329d9941 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs @@ -8,30 +8,30 @@ namespace Microsoft.UI.Xaml.Controls; internal class MenuFlyoutItemBaseCollection : DependencyObjectCollection { + private readonly DependencyObject _owner; + + public MenuFlyoutItemBaseCollection(DependencyObject owner) : base(owner) + { + _owner = owner; + } + #if HAS_UNO // Our API is a bit different from WinUI private protected override void OnCollectionChanged() => NotifyMenuFlyoutOfCollectionChange(); #endif private void NotifyMenuFlyoutOfCollectionChange() { - ctl::ComPtr ownerAsDO; - IFC_RETURN(DXamlCore::GetCurrent()->GetPeer(static_cast(GetHandle())->GetOwner(), &ownerAsDO)); - - auto ownerAsMenuFlyout = ownerAsDO.AsOrNull(); - - if (ownerAsMenuFlyout) + if (_owner is MenuFlyout menuFlyout) + { + menuFlyout.QueueRefreshItemsSource(); + } + else if (_owner is MenuFlyoutSubItem menuFlyoutSubItem) { - IFC_RETURN(ownerAsMenuFlyout.Cast()->QueueRefreshItemsSource()); + menuFlyoutSubItem.QueueRefreshItemsSource(); } else { - auto ownerAsMenuFlyoutSubItem = ownerAsDO.AsOrNull(); - - // MenuFlyoutItemBaseCollection is only used by MenuFlyout and MenuFlyoutSubItem. - // If another type is added, this will need to change. - IFCEXPECT_RETURN(ownerAsMenuFlyoutSubItem != nullptr); - - IFC_RETURN(ownerAsMenuFlyoutSubItem.Cast()->QueueRefreshItemsSource()); + throw new InvalidOperationException("Unknown owner of MenuFlyoutItemBaseCollection."); } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs index f35bf3df54aa..f74e1352462a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs @@ -28,7 +28,7 @@ void PrepareState() //base.PrepareState(); // Create the sub menu items collection and set the owner - var spItems = new DependencyObjectCollection(this); + var spItems = new MenuFlyoutItemBaseCollection(this); m_tpItems = spItems; Items = spItems; @@ -729,6 +729,11 @@ internal void QueueRefreshItemsSource() dispatcherQueue.TryEnqueue( () => { + if (!wrThis.IsAlive) + { + return; + } + var thisMenuFlyoutSubItem = wrThis.Target as MenuFlyoutSubItem; if (thisMenuFlyoutSubItem is not null) From dad45d8e13a057f9e64e8a388b78b35d3606b332 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 20 Jun 2024 17:04:39 +0200 Subject: [PATCH 14/28] feat: Update MenuFlyout to winui3/release/1.5.4 --- .../UI/Xaml/Controls/Flyout/FlyoutBase.cs | 4 +- .../UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs | 34 +++++ .../Controls/MenuFlyout/MenuFlyout.Uno.cs | 24 ++++ .../Controls/MenuFlyout/MenuFlyout.mux.cs | 77 ++++++----- .../MenuFlyout/MenuFlyout.partial.mux.cs | 121 +++++++++--------- src/Uno.UI/UI/Xaml/EnterParams.cs | 2 + .../KeyboardAcceleratorCollection.h.mux.cs | 8 +- src/Uno.UI/UI/Xaml/LeaveParams.cs | 2 + 8 files changed, 165 insertions(+), 107 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Uno.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs index bae5c415b04f..e4abbcebdb48 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.cs @@ -75,6 +75,8 @@ protected FlyoutBase() internal static IReadOnlyList OpenFlyouts => _openFlyouts.AsReadOnly(); + internal void EnsurePopupAndPresenter() => EnsurePopupCreated(); // TODO Uno: Approximation of WinUI EnsurePopupAndPresenter + private void EnsurePopupCreated() { if (_popup == null) @@ -515,7 +517,7 @@ private protected virtual void OnOpening() Opening?.Invoke(this, EventArgs.Empty); } - private void OnClosing(ref bool cancel) + private protected virtual void OnClosing(ref bool cancel) { var closing = new FlyoutBaseClosingEventArgs(); Closing?.Invoke(this, closing); diff --git a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs index 1999c8ccf466..4a73ad46796b 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs @@ -18,4 +18,38 @@ public partial class FlyoutBase protected virtual void OnProcessKeyboardAccelerators(ProcessKeyboardAcceleratorEventArgs args) { } + + internal void SetIsWindowedPopup() + { + // TODO: Uno + //HRESULT hr = S_OK; + + //IFC(EnsurePopupAndPresenter()); + + //// Set popup to be windowed, if we can, in order to support rendering the popup out of the XAML window. + //if (CPopup::DoesPlatformSupportWindowedPopup(DXamlCore::GetCurrent()->GetHandle())) + //{ + // // Set popup to be windowed, if we can, in order to support rendering the popup out of the XAML window. + // if (m_tpPopup && !static_cast(m_tpPopup.Cast()->GetHandle())->IsWindowed()) + // { + // CPopup* popup = static_cast(m_tpPopup.Cast()->GetHandle()); + + // if (!popup->WasEverOpened()) + // { + // IFC(popup->SetIsWindowed()); + // global::System.Diagnostics.Debug.Assert(popup.IsWindowed()); + // } + // } + //} + } + + private bool IsWindowedPopup() + { + return false; + // TODO: Uno + //bool areWindowedPopupsSupported = CPopup::DoesPlatformSupportWindowedPopup(DXamlCore::GetCurrent()->GetHandle()); + //bool isPopupWindowed = m_tpPopup ? static_cast(m_tpPopup.Cast()->GetHandle())->IsWindowed() : false; + + //return areWindowedPopupsSupported && (isPopupWindowed || m_openingWindowedInProgress); + } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Uno.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Uno.cs new file mode 100644 index 000000000000..4a3ba2135393 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Uno.cs @@ -0,0 +1,24 @@ +using Microsoft.UI.Xaml; +using Uno.Extensions; + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyout +{ + internal protected override void OnDataContextChanged(DependencyPropertyChangedEventArgs e) + { + base.OnDataContextChanged(e); + + SetFlyoutItemsDataContext(); + } + + private void SetFlyoutItemsDataContext() + { + // This is present to force the dataContext to be passed to the popup of the flyout since it is not directly a child in the visual tree of the flyout. + Items?.ForEach(item => item?.SetValue( + UIElement.DataContextProperty, + this.DataContext, + precedence: DependencyPropertyValuePrecedences.Inheritance + )); + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs index 45c2579f60a1..348dd0a13952 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs @@ -1,59 +1,58 @@ -namespace Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Uno.UI.Xaml; + +namespace Microsoft.UI.Xaml.Controls; partial class MenuFlyout { internal static void KeyboardAcceleratorFlyoutItemEnter( - DependencyObject* element, - DependencyObject* pNamescopeOwner, - KnownPropertyIndex collectionPropertyIndex, + DependencyObject element, + DependencyObject pNamescopeOwner, + DependencyProperty property, EnterParams parameters) { - CValue value; - IFC_RETURN(element->GetValueByIndex(collectionPropertyIndex, &value)); - - if (CMenuFlyoutItemBaseCollection * const items = do_pointer_cast(value.AsObject())) - - { - // This is a dead enter to register any keyboard accelerators that may be present in the MenuFlyout items - // to the list of live accelerators - params.fIsForKeyboardAccelerator = true; - params.fIsLive = false; - params.fSkipNameRegistration = true; - params.fUseLayoutRounding = false; - params.fCoercedIsEnabled = false; + var value = element.GetValue(property); + if (value is MenuFlyoutItemBaseCollection items) + { + // This is a dead enter to register any keyboard accelerators that may be present in the MenuFlyout items + // to the list of live accelerators + parameters = new EnterParams { IsForKeyboardAccelerator = true, IsLive = false }; + //params.fSkipNameRegistration = true; + //params.fUseLayoutRounding = false; + //params.fCoercedIsEnabled = false; - for (CDependencyObject* item : *items) + foreach (MenuFlyoutItemBase item in items) { - IFC_RETURN(item->Enter(pNamescopeOwner, params)); + if (item.KeyboardAccelerators is KeyboardAcceleratorCollection kac) + { + kac.Enter(pNamescopeOwner, parameters); + } } } - - return S_OK; } internal static void KeyboardAcceleratorFlyoutItemLeave( DependencyObject element, DependencyObject pNamescopeOwner, - KnownPropertyIndex collectionPropertyIndex, + DependencyProperty property, LeaveParams parameters) { - CValue value; - IFC_RETURN(element->GetValueByIndex(collectionPropertyIndex, &value)); - - if (CMenuFlyoutItemBaseCollection * const items = do_pointer_cast(value.AsObject())) - - { - // This is a dead leave to remove any keyboard accelerators that may be present in the MenuFlyout items - // from the list of live accelerators - params.fIsForKeyboardAccelerator = true; - params.fIsLive = false; - params.fSkipNameRegistration = true; - params.fUseLayoutRounding = false; - params.fCoercedIsEnabled = false; + var value = element.GetValue(property); + if (value is MenuFlyoutItemBaseCollection items) + { + // This is a dead leave to remove any keyboard accelerators that may be present in the MenuFlyout items + // from the list of live accelerators + parameters = new LeaveParams { IsForKeyboardAccelerator = true, IsLive = false }; + //params.fSkipNameRegistration = true; + //params.fUseLayoutRounding = false; + //params.fCoercedIsEnabled = false; - for (CDependencyObject* item : *items) + foreach (MenuFlyoutItemBase item in items) { - IFC_RETURN(item->Leave(pNamescopeOwner, params)); + if (item.KeyboardAccelerators is KeyboardAcceleratorCollection kac) + { + kac.Leave(pNamescopeOwner, parameters); + } } } } @@ -61,12 +60,12 @@ internal static void KeyboardAcceleratorFlyoutItemLeave( private void EnterImpl(DependencyObject namescopeOwner, EnterParams parameters) { //base.EnterImpl(pNamescopeOwner, params)); - IFC_RETURN(KeyboardAcceleratorFlyoutItemEnter(this, pNamescopeOwner, KnownPropertyIndex::MenuFlyout_Items, params)); + KeyboardAcceleratorFlyoutItemEnter(this, namescopeOwner, MenuFlyout.ItemsProperty, parameters); } private void LeaveImpl(DependencyObject namescopeOwner, LeaveParams parameters) { //base.LeaveImpl(pNamescopeOwner, params); - IFC_RETURN(KeyboardAcceleratorFlyoutItemLeave(this, pNamescopeOwner, KnownPropertyIndex::MenuFlyout_Items, params)); + KeyboardAcceleratorFlyoutItemLeave(this, namescopeOwner, MenuFlyout.ItemsProperty, parameters); } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs index e83318f6e01a..85735f3d86bf 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs @@ -1,10 +1,16 @@ -using Microsoft.UI.Xaml.Automation.Peers; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyout_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media.Animation; using Uno.UI.DataBinding; +using Uno.UI.Xaml; +using Uno.UI.Xaml.Core; +using Uno.UI.Xaml.Input; using Windows.Foundation; -using static Uno.UI.FeatureConfiguration; namespace Microsoft.UI.Xaml.Controls; @@ -19,8 +25,23 @@ public MenuFlyout() m_inputDeviceTypeUsedToOpen = Uno.UI.Xaml.Input.InputDeviceType.None; PrepareState(); + +#if HAS_UNO // Uno specific: Simulate enter/leave lifecycle events + ListenToParentLifecycle(); +#endif } +#if HAS_UNO // TODO: Uno specific - workaround for the lack of support for Enter/Leave on DOs. + private ParentVisualTreeListener _parentVisualTreeListener; + + private void ListenToParentLifecycle() + { + _parentVisualTreeListener = new ParentVisualTreeListener(this); + _parentVisualTreeListener.ParentLoaded += (s, e) => EnterImpl(null, new EnterParams(true)); + _parentVisualTreeListener.ParentUnloaded += (s, e) => LeaveImpl(null, new LeaveParams(true)); + } +#endif + // Prepares object's state private void PrepareState() { @@ -144,7 +165,7 @@ private protected override void OnOpening() AutomationPeer.RaiseEventIfListener(menuFlyoutPresenter, AutomationEvents.MenuOpened); } - internal override void OnClosing(ref bool cancel) + private protected override void OnClosing(ref bool cancel) { base.OnClosing(ref cancel); @@ -270,65 +291,56 @@ private static Transition PreparePopupThemeTransitionsAndShadows(Popup popup, do //*transition = spMenuPopupChildTransition.Detach(); //return S_OK; } - // TODO:MZ: FROM HERE - void AutoAdjustPlacement(MajorPlacementMode pPlacement) + private void UpdatePresenterVisualState(MajorPlacementMode placement, bool doForceTransitions) + { + //base.UpdatePresenterVisualState(placement); + + // MenuFlyoutPresenter has different visual states depending on the flyout's placement. + //if (doForceTransitions) + //{ + // // In order to play the storyboards of the visual states that belong to the + // // MenuFlyoutPresenter, we need to force a visual state transition. + // GetPresenter().ResetVisualState(); + //} + + //GetPresenter().UpdateVisualStateForPlacement(placement); + } + + private void AutoAdjustPlacement(MajorPlacementMode pPlacement) { // UNO TODO // Rect windowRect = default; // (DXamlCore.GetCurrent().GetContentBoundsForElement(GetHandle(), &windowRect)); } - public void ShowAt(UIElement targetElement, Point point) - { - ShowAtCore((FrameworkElement)targetElement, new FlyoutShowOptions { Position = point }); - } + private void UpdatePresenterVisualState(MajorPlacementMode placement) => UpdatePresenterVisualState(placement, true); + //internal override void ShowAt(FrameworkElement placementTarget) + //{ + // m_openWindowed = false; + // ShowAtCore(placementTarget, new FlyoutShowOptions()); + //} - void ShowAtImpl(UIElement pTargetElement, Point targetPoint) + /// + /// Shows the flyout placed at the specified offset in relation to the specified target element. + /// + /// The element to use as the flyout's placement target. + /// The point at which to offset the flyout from the specified target element. + public void ShowAt(UIElement targetElement, Point point) { - var targetElement = pTargetElement; - var showOptions = new FlyoutShowOptions(); - showOptions.Position = targetPoint; + showOptions.Position = point; - try - { - m_openingWindowedInProgress = true; - - ShowAt(targetElement, showOptions); - } - finally - { - m_openingWindowedInProgress = false; - } - } - - internal FocusInputDeviceKind InputDeviceTypeUsedToOpen => m_inputDeviceTypeUsedToOpen; - - internal protected override void OnDataContextChanged(DependencyPropertyChangedEventArgs e) - { - base.OnDataContextChanged(e); - - SetFlyoutItemsDataContext(); - } - - private void SetFlyoutItemsDataContext() - { - // This is present to force the dataContext to be passed to the popup of the flyout since it is not directly a child in the visual tree of the flyout. - Items?.ForEach(item => item?.SetValue( - UIElement.DataContextProperty, - this.DataContext, - precedence: DependencyPropertyValuePrecedences.Inheritance - )); + ShowAt(targetElement, showOptions); } + internal InputDeviceType InputDeviceTypeUsedToOpen => m_inputDeviceTypeUsedToOpen; - void CacheInputDeviceTypeUsedToOpen(UIElement pTargetElement) + private void CacheInputDeviceTypeUsedToOpen(UIElement pTargetElement) { - // UNO TODO - //CContentRoot* contentRoot = VisualTree.GetContentRootForElement(pTargetElement); - //InputDeviceTypeUsedToOpen = contentRoot.GetInputManager().GetLastInputDeviceType(); + ContentRoot contentRoot = VisualTree.GetContentRootForElement(pTargetElement); + m_inputDeviceTypeUsedToOpen = contentRoot.InputManager.LastInputDeviceType; } #if false @@ -344,23 +356,6 @@ void ShowAtStatic(MenuFlyout pCoreMenuFlyout, pCoreMenuFlyout.ShowAtImpl(target as FrameworkElement, point); } - - void OnProcessKeyboardAcceleratorsImpl(ProcessKeyboardAcceleratorEventArgs pArgs) - { - - } -#endif - - - -#if false - bool IsWindowedPopup() - { - return false; - - // UNO TODO - // return CPopup.DoesPlatformSupportWindowedPopup(DXamlCore.GetCurrent().GetHandle()) && (FlyoutBase.IsWindowedPopup() || m_openingWindowedInProgress); - } #endif protected override void OnProcessKeyboardAccelerators(ProcessKeyboardAcceleratorEventArgs args) diff --git a/src/Uno.UI/UI/Xaml/EnterParams.cs b/src/Uno.UI/UI/Xaml/EnterParams.cs index fe60a21738f4..6a6e0db2369f 100644 --- a/src/Uno.UI/UI/Xaml/EnterParams.cs +++ b/src/Uno.UI/UI/Xaml/EnterParams.cs @@ -4,6 +4,8 @@ internal struct EnterParams { public bool IsLive; + public bool IsForKeyboardAccelerator; + public EnterParams() { IsLive = true; diff --git a/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAcceleratorCollection.h.mux.cs b/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAcceleratorCollection.h.mux.cs index 63cf8e442d17..8634e3844793 100644 --- a/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAcceleratorCollection.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAcceleratorCollection.h.mux.cs @@ -20,11 +20,11 @@ public KeyboardAcceleratorCollection(DependencyObject parent) : base(parent, tru } #endif - private void Enter(DependencyObject pNamescopeOwner, EnterParams enterParams) + internal void Enter(DependencyObject pNamescopeOwner, EnterParams enterParams) { //base.Enter(pNamescopeOwner, enterParams); - if (enterParams.IsLive)// || enterParams.IsForKeyboardAccelerator) + if (enterParams.IsLive || enterParams.IsForKeyboardAccelerator) { ContentRoot pContentRoot = VisualTree.GetContentRootForElement(this); if (pContentRoot != null) @@ -34,11 +34,11 @@ private void Enter(DependencyObject pNamescopeOwner, EnterParams enterParams) } } - private void Leave(DependencyObject pNamescopeOwner, LeaveParams leaveParams) + internal void Leave(DependencyObject pNamescopeOwner, LeaveParams leaveParams) { //base.Leave(pNamescopeOwner, leaveParams); - if (leaveParams.IsLive)// || leaveParams.IsForKeyboardAccelerator) + if (leaveParams.IsLive || leaveParams.IsForKeyboardAccelerator) { ContentRoot pContentRoot = VisualTree.GetContentRootForElement(this); if (pContentRoot != null) diff --git a/src/Uno.UI/UI/Xaml/LeaveParams.cs b/src/Uno.UI/UI/Xaml/LeaveParams.cs index 392e445f5575..a42d68aba431 100644 --- a/src/Uno.UI/UI/Xaml/LeaveParams.cs +++ b/src/Uno.UI/UI/Xaml/LeaveParams.cs @@ -4,6 +4,8 @@ internal struct LeaveParams { public bool IsLive; + public bool IsForKeyboardAccelerator; + public LeaveParams() { IsLive = true; From 93041b0059ee4fd0749def8c91cd84e44d922501 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 20 Jun 2024 17:21:06 +0200 Subject: [PATCH 15/28] chore: Adjustments to resolve errors --- .../Given_MenuFlyout.cs | 4 +- src/Uno.UI/DirectUI/DXamlCore.cs | 14 +- .../MenuFlyout/CascadingMenuHelper.cs | 14 +- .../MenuFlyout/MenuFlyout.partial.h.mux.cs | 2 +- .../MenuFlyout/MenuFlyout.partial.mux.cs | 2 +- .../MenuFlyout/MenuFlyoutItem.Properties.cs | 2 +- .../MenuFlyout/MenuFlyoutItem.h.mux.cs | 8 +- .../Controls/MenuFlyout/MenuFlyoutItem.mux.cs | 2 +- .../Controls/MenuFlyout/MenuFlyoutItemBase.cs | 6 +- .../MenuFlyout/MenuFlyoutKeyPressProcess.cs | 223 +++++++++--------- .../MenuFlyout/MenuFlyoutPresenter.cs | 38 +-- .../Controls/MenuFlyout/MenuFlyoutSubItem.cs | 5 + .../MenuFlyoutSubItem.partial.h.mux.cs | 2 +- .../MenuFlyoutSubItem.partial.mux.cs | 10 +- 14 files changed, 175 insertions(+), 157 deletions(-) diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_MenuFlyout.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_MenuFlyout.cs index 410b219675b8..62ebbd3a6289 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_MenuFlyout.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_MenuFlyout.cs @@ -319,7 +319,7 @@ public async Task When_MenuFlyoutItem_CommandChanging() await WindowHelper.WaitForLoaded(flyoutItem); - flyoutItem.InvokeClick(); + flyoutItem.Invoke(); // Force close the flyout as InvokeClick does not do so. item.CloseMenuFlyout(); @@ -330,7 +330,7 @@ public async Task When_MenuFlyoutItem_CommandChanging() Assert.IsFalse(flyoutItem.IsEnabled); - flyoutItem.InvokeClick(); + flyoutItem.Invoke(); item.CloseMenuFlyout(); } diff --git a/src/Uno.UI/DirectUI/DXamlCore.cs b/src/Uno.UI/DirectUI/DXamlCore.cs index 06cbe06e1ad1..50f7cfb6c2f8 100644 --- a/src/Uno.UI/DirectUI/DXamlCore.cs +++ b/src/Uno.UI/DirectUI/DXamlCore.cs @@ -6,13 +6,12 @@ using System; using System.Collections.Generic; +using Microsoft.UI.Xaml.Controls; using Uno.UI.Helpers.WinUI; using Uno.UI.Xaml.Controls; using Uno.UI.Xaml.Core; -using Windows.ApplicationModel.Resources; -using Windows.Foundation; -using Microsoft.UI.Xaml.Controls; using Uno.UI.Xaml.Core.Scaling; +using Windows.Foundation; namespace DirectUI { @@ -98,5 +97,14 @@ internal void OnCompositionContentStateChangedForUWP() // TODO Uno: Not needed now. // OnUWPWindowSizeChanged(); } + + internal bool IsKeyboardPresent + { + get + { + // TODO:MZ: Detect HW keyboard where possible. + return true; + } + } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs index 4655428ccf8a..6ddf00b0e3aa 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs @@ -1,13 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Uno.Disposables; -using Uno.UI.Xaml.Core; using Windows.Foundation; using Windows.System; -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; @@ -680,7 +674,7 @@ internal void ClearStateFlags() UpdateOwnerVisualState(); } - internal void OnIsEnabledChanged() + internal void OnIsEnabledChanged(IsEnabledChangedEventArgs args) { Control ownerAsControl = m_wpOwner?.Target as Control; @@ -965,4 +959,10 @@ void UpdateOwnerVisualState() ownerAsControl.UpdateVisualState(true /* useTransitions */); } } + + internal bool IsDelayCloseTimerRunning() + { + // TODO Uno: Implement this + return false; + } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.h.mux.cs index be18ac95a794..9d626568e5ea 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.h.mux.cs @@ -13,7 +13,7 @@ partial class MenuFlyout private MenuFlyoutItemBaseCollection m_tpItems; // In Threshold, MenuFlyout uses the MenuPopupThemeTransition. - private Transition m_tpMenuPopupThemeTransition; + // private Transition m_tpMenuPopupThemeTransition; private InputDeviceType m_inputDeviceTypeUsedToOpen; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs index 85735f3d86bf..1cc9730741f0 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs @@ -234,7 +234,7 @@ private void PreparePopupTheme(Popup pPopup, MajorPlacementMode placementMode, F //return S_OK; } - private static Transition PreparePopupThemeTransitionsAndShadows(Popup popup, double closedRatioConstant, int depth) + internal static Transition PreparePopupThemeTransitionsAndShadows(Popup popup, double closedRatioConstant, int depth) { return null; // UNO TODO diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs index 818065846f5a..12445282a23a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs @@ -72,7 +72,7 @@ public string KeyboardAcceleratorTextOverride { get { - InitializeKeyboardAcceleratorText() + InitializeKeyboardAcceleratorText(); return (string)this.GetValue(KeyboardAcceleratorTextOverrideProperty) ?? ""; } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs index 5679e8d152cd..6a008d8fccac 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs @@ -28,16 +28,16 @@ partial class MenuFlyoutItem private bool m_bIsPointerOver; // Whether the pointer is currently pressed over the - private bool m_bIsPressed; + internal bool m_bIsPressed; // Whether the pointer's left button is currently down. - private bool m_bIsPointerLeftButtonDown; + internal bool m_bIsPointerLeftButtonDown; // True if the SPACE or ENTER key is currently pressed, false otherwise. - private bool m_bIsSpaceOrEnterKeyDown; + internal bool m_bIsSpaceOrEnterKeyDown; // True if the NAVIGATION_ACCEPT or GAMEPAD_A vkey is currently pressed, false otherwise. - private bool m_bIsNavigationAcceptOrGamepadAKeyDown; + internal bool m_bIsNavigationAcceptOrGamepadAKeyDown; // On pointer released we perform some actions depending on control. We decide to whether to perform them // depending on some parameters including but not limited to whether released is followed by a pressed, which diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs index 8b1ea96ed265..c28aa8ecfb84 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs @@ -512,7 +512,7 @@ private protected override void ChangeVisualState( // since if we don't have any menu items with accelerator text, we won't be showing any accelerator text anyway. if (hasMenuItemWithKeyboardAcceleratorText) { - isKeyboardPresent = DXamlCore.Current.GetIsKeyboardPresent(); + isKeyboardPresent = DXamlCore.Current.IsKeyboardPresent; } // CommonStates diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBase.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBase.cs index 51d8358c2d26..f646360f690d 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBase.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBase.cs @@ -42,9 +42,9 @@ internal bool GetShouldBeNarrow() if (spParentFlyout != null) { shouldBeNarrow = - (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Mouse) || - (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Pen) || - (spParentFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Keyboard); + (spParentFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.Mouse) || + (spParentFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.Pen) || + (spParentFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.Keyboard); } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutKeyPressProcess.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutKeyPressProcess.cs index e1af563326d3..834f39747639 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutKeyPressProcess.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutKeyPressProcess.cs @@ -3,150 +3,149 @@ using System.Text; using Windows.System; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +class KeyPressMenuFlyoutPresenter { - class KeyPressMenuFlyoutPresenter + internal static bool KeyDown(VirtualKey key, MenuFlyoutPresenter control) { - internal static bool KeyDown(VirtualKey key, MenuFlyoutPresenter control) + var pbHandled = false; + + if (key == VirtualKey.Up || + key == VirtualKey.GamepadDPadUp || + key == VirtualKey.GamepadLeftThumbstickUp) + { + control.HandleUpOrDownKey(false); + pbHandled = true; + } + else if (key == VirtualKey.Down || + key == VirtualKey.GamepadDPadDown || + key == VirtualKey.GamepadLeftThumbstickDown) { - var pbHandled = false; + control.HandleUpOrDownKey(true); + pbHandled = true; + } + else if (key == VirtualKey.Tab) + { + pbHandled = true; + } - if (key == VirtualKey.Up || - key == VirtualKey.GamepadDPadUp || - key == VirtualKey.GamepadLeftThumbstickUp) + // Handle the left key to close the opened MenuFlyoutSubItem. + // The right arrow key is directly handled from the MenuFlyoutSubItem + // to open the sub menu item. + else if (key == VirtualKey.Left || + key == VirtualKey.Escape) + { + if (control.IsSubPresenter) { - control.HandleUpOrDownKey(false); + control.HandleKeyDownLeftOrEscape(); pbHandled = true; } - else if (key == VirtualKey.Down || - key == VirtualKey.GamepadDPadDown || - key == VirtualKey.GamepadLeftThumbstickDown) + else { - control.HandleUpOrDownKey(true); - pbHandled = true; + // If this is the top-level menu, let Popup close it (on Escape key press) + pbHandled = false; } - else if (key == VirtualKey.Tab) + } + + return pbHandled; + } +} + +class KeyPressMenuFlyout +{ + internal static bool KeyDown(VirtualKey key, MenuFlyoutItem control) + { + var pbHandled = false; + + // If SPACE/ENTER/NAVIGATION_ACCEPT/GAMEPAD_A is already down and a different key is now pressed, + // then cancel the SPACE/ENTER/NAVIGATION_ACCEPT/GAMEPAD_A press. + if (control.m_bIsSpaceOrEnterKeyDown || control.m_bIsNavigationAcceptOrGamepadAKeyDown) + { + if (key != VirtualKey.Space && key != VirtualKey.Enter && control.m_bIsSpaceOrEnterKeyDown) { - pbHandled = true; + control.m_bIsSpaceOrEnterKeyDown = false; } - - // Handle the left key to close the opened MenuFlyoutSubItem. - // The right arrow key is directly handled from the MenuFlyoutSubItem - // to open the sub menu item. - else if (key == VirtualKey.Left || - key == VirtualKey.Escape) + if (control.m_bIsNavigationAcceptOrGamepadAKeyDown && // The key down flag is set + key != VirtualKey.GamepadA) // AND it's not the GamepadA key { - if (control.IsSubPresenter) - { - control.HandleKeyDownLeftOrEscape(); - pbHandled = true; - } - else - { - // If this is the top-level menu, let Popup close it (on Escape key press) - pbHandled = false; - } + control.m_bIsNavigationAcceptOrGamepadAKeyDown = false; } - return pbHandled; + control.m_bIsPressed = false; + control.UpdateVisualState(); } - } - class KeyPressMenuFlyout - { - internal static bool KeyDown(VirtualKey key, MenuFlyoutItem control) + if (key == VirtualKey.Up || + key == VirtualKey.GamepadDPadUp || + key == VirtualKey.GamepadLeftThumbstickUp) { - var pbHandled = false; - - // If SPACE/ENTER/NAVIGATION_ACCEPT/GAMEPAD_A is already down and a different key is now pressed, - // then cancel the SPACE/ENTER/NAVIGATION_ACCEPT/GAMEPAD_A press. - if (control.m_bIsSpaceOrEnterKeyDown || control.m_bIsNavigationAcceptOrGamepadAKeyDown) + MenuFlyoutPresenter spParentMenuFlyoutPresenter = control.GetParentMenuFlyoutPresenter(); + if (spParentMenuFlyoutPresenter != null) { - if (key != VirtualKey.Space && key != VirtualKey.Enter && control.m_bIsSpaceOrEnterKeyDown) - { - control.m_bIsSpaceOrEnterKeyDown = false; - } - if (control.m_bIsNavigationAcceptOrGamepadAKeyDown && // The key down flag is set - key != VirtualKey.GamepadA) // AND it's not the GamepadA key - { - control.m_bIsNavigationAcceptOrGamepadAKeyDown = false; - } - - control.m_bIsPressed = false; - control.UpdateVisualState(); + spParentMenuFlyoutPresenter.HandleUpOrDownKey(false); + pbHandled = true; } - - if (key == VirtualKey.Up || - key == VirtualKey.GamepadDPadUp || - key == VirtualKey.GamepadLeftThumbstickUp) + } + else if (key == VirtualKey.Down || + key == VirtualKey.GamepadDPadDown || + key == VirtualKey.GamepadLeftThumbstickDown) + { + MenuFlyoutPresenter spParentMenuFlyoutPresenter = control.GetParentMenuFlyoutPresenter(); + if (spParentMenuFlyoutPresenter != null) { - MenuFlyoutPresenter spParentMenuFlyoutPresenter = control.GetParentMenuFlyoutPresenter(); - if (spParentMenuFlyoutPresenter != null) - { - spParentMenuFlyoutPresenter.HandleUpOrDownKey(false); - pbHandled = true; - } + spParentMenuFlyoutPresenter.HandleUpOrDownKey(true); + pbHandled = true; } - else if (key == VirtualKey.Down || - key == VirtualKey.GamepadDPadDown || - key == VirtualKey.GamepadLeftThumbstickDown) + } + else if (key == VirtualKey.Space || + key == VirtualKey.Enter || + key == VirtualKey.GamepadA) + { + control.m_bIsPressed = true; + + if (key == VirtualKey.Space || key == VirtualKey.Enter) { - MenuFlyoutPresenter spParentMenuFlyoutPresenter = control.GetParentMenuFlyoutPresenter(); - if (spParentMenuFlyoutPresenter != null) - { - spParentMenuFlyoutPresenter.HandleUpOrDownKey(true); - pbHandled = true; - } + control.m_bIsSpaceOrEnterKeyDown = true; } - else if (key == VirtualKey.Space || - key == VirtualKey.Enter || - key == VirtualKey.GamepadA) + else if (key == VirtualKey.GamepadA) { - control.m_bIsPressed = true; - - if (key == VirtualKey.Space || key == VirtualKey.Enter) - { - control.m_bIsSpaceOrEnterKeyDown = true; - } - else if (key == VirtualKey.GamepadA) - { - control.m_bIsNavigationAcceptOrGamepadAKeyDown = true; - } - - control.UpdateVisualState(); - pbHandled = true; + control.m_bIsNavigationAcceptOrGamepadAKeyDown = true; } - return pbHandled; + control.UpdateVisualState(); + pbHandled = true; } - internal static bool KeyUp(VirtualKey key, MenuFlyoutItem control) - { - var pbHandled = false; + return pbHandled; + } + + internal static bool KeyUp(VirtualKey key, MenuFlyoutItem control) + { + var pbHandled = false; - if (key == VirtualKey.Space || - key == VirtualKey.Enter || - key == VirtualKey.GamepadA) + if (key == VirtualKey.Space || + key == VirtualKey.Enter || + key == VirtualKey.GamepadA) + { + if (key == VirtualKey.Space || key == VirtualKey.Enter) + { + control.m_bIsSpaceOrEnterKeyDown = false; + } + else if (key == VirtualKey.GamepadA) { - if (key == VirtualKey.Space || key == VirtualKey.Enter) - { - control.m_bIsSpaceOrEnterKeyDown = false; - } - else if (key == VirtualKey.GamepadA) - { - control.m_bIsNavigationAcceptOrGamepadAKeyDown = false; - } - - if (control.m_bIsPressed && !control.m_bIsPointerLeftButtonDown) - { - control.m_bIsPressed = false; - control.UpdateVisualState(); - control.Invoke(); - pbHandled = true; - } + control.m_bIsNavigationAcceptOrGamepadAKeyDown = false; } - return pbHandled; + if (control.m_bIsPressed && !control.m_bIsPointerLeftButtonDown) + { + control.m_bIsPressed = false; + control.UpdateVisualState(); + control.Invoke(); + pbHandled = true; + } } + + return pbHandled; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs index a676b96bb9fe..daca9d1d18af 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs @@ -1,20 +1,15 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Uno.UI; using Windows.Foundation; using Windows.Foundation.Collections; -using Windows.System; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Automation; using Microsoft.UI.Xaml.Automation.Peers; -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.Media.Animation; + #if HAS_UNO_WINUI using Microsoft.UI.Input; @@ -111,7 +106,7 @@ void CycleFocus(bool shouldCycleDown, FocusState focusState) var parentFlyout = GetParentMenuFlyout(); // We should wrap around at the bottom or the top of the presenter if the user isn't using a gamepad or remote. - var shouldWrap = parentFlyout != null ? (parentFlyout.InputDeviceTypeUsedToOpen != FocusInputDeviceKind.GameController) : true; + var shouldWrap = parentFlyout != null ? (parentFlyout.InputDeviceTypeUsedToOpen != Uno.UI.Xaml.Input.InputDeviceType.GamepadOrRemote) : true; var nCount = Items.Size; @@ -388,6 +383,18 @@ protected override void OnApplyTemplate() //} } + internal string GetOwnerName() + { + var parentMenuFlyout = GetParentMenuFlyout(); + + if (parentMenuFlyout is not null) + { + return parentMenuFlyout.GetValue(FrameworkElement.NameProperty) as string; // TODO Uno: Should be DependencyObject.Name + } + + return null; + } + // UNO TODO // //Timeline AttachEntranceAnimationCompleted( @@ -737,7 +744,7 @@ internal void UpdateTemplateSettings() { // Query MenuFlyout Content MinWidth, given the input mode, from resource dictionary. var flyoutContentMinWidth = ResourceResolver.ResolveTopLevelResourceDouble( - (ownerFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.Touch || ownerFlyout.InputDeviceTypeUsedToOpen == FocusInputDeviceKind.GameController) + (ownerFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.Touch || ownerFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.GamepadOrRemote) ? "FlyoutThemeTouchMinWidth" : "FlyoutThemeMinWidth" ); var visibleBounds = this.LayoutSlotWithMarginsAndAlignments; @@ -852,8 +859,7 @@ void EnsureInitialFocusIndex() } } -#if false - int GetPositionInSetHelper(MenuFlyoutItemBase item) + internal static int GetPositionInSetHelper(MenuFlyoutItemBase item) { var returnValue = -1; @@ -861,7 +867,7 @@ int GetPositionInSetHelper(MenuFlyoutItemBase item) if (presenter != null) { - var indexOfItem = Items.IndexOf(item); + var indexOfItem = presenter.Items.IndexOf(item); if (indexOfItem != -1) { @@ -871,7 +877,7 @@ int GetPositionInSetHelper(MenuFlyoutItemBase item) for (var i = 0; i < indexOfItem; ++i) { - var child = Items[i] as DependencyObject; + var child = presenter.Items[i] as DependencyObject; if (child != null) { @@ -903,7 +909,7 @@ int GetPositionInSetHelper(MenuFlyoutItemBase item) return returnValue; } - int GetSizeOfSetHelper(MenuFlyoutItemBase item) + internal static int GetSizeOfSetHelper(MenuFlyoutItemBase item) { var returnValue = -1; @@ -911,7 +917,7 @@ int GetSizeOfSetHelper(MenuFlyoutItemBase item) if (presenter != null) { - var itemsCount = Items.Count; + var itemsCount = presenter.Items.Count; // Iterate through the parent presenters items and subtract the // number of separaters and collapsed items from the total count @@ -920,7 +926,7 @@ int GetSizeOfSetHelper(MenuFlyoutItemBase item) for (var i = 0; i < itemsCount; ++i) { - var child = Items[i] as DependencyObject; + var child = presenter.Items[i] as DependencyObject; if (child != null) { @@ -951,7 +957,7 @@ int GetSizeOfSetHelper(MenuFlyoutItemBase item) return returnValue; } -#endif + ISubMenuOwner IMenuPresenter.Owner { get => m_wrOwner?.Target as ISubMenuOwner; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs index bac1862061fe..a6a20a762af3 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.cs @@ -16,5 +16,10 @@ public MenuFlyoutSubItem() DefaultStyleKey = typeof(MenuFlyoutSubItem); PrepareState(); + +#if HAS_UNO // Uno specific: Simulate enter/leave lifecycle events + this.Loaded += (s, e) => EnterImpl(this, new Uno.UI.Xaml.EnterParams()); + this.Unloaded += (s, e) => LeaveImpl(this, new Uno.UI.Xaml.LeaveParams()); +#endif } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs index 3bfb347da51a..f4dc5dc102ce 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.h.mux.cs @@ -35,7 +35,7 @@ partial class MenuFlyoutSubItem private readonly SerialDisposable m_epLoadedHandler = new(); // Event pointer for the size changed on the MenuFlyoutSubItem's presenter - private readonly SerialDisposable m_epPresenterSizeChangedHandler; + private readonly SerialDisposable m_epPresenterSizeChangedHandler = new(); // Helper to which to delegate cascading menu functionality. private CascadingMenuHelper m_menuHelper; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs index f74e1352462a..1a0afcc665c8 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. // MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutSubItem_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 -using System.Linq; using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; @@ -183,12 +182,13 @@ private void EnsurePopupAndPresenter() { var spParentMenuFlyout = spParentMenuFlyoutPresenter.GetParentMenuFlyout(); // Set the windowed Popup if the MenuFlyout is set the windowed Popup - if (spParentMenuFlyout is not null && spParentMenuFlyout.IsWindowedPopup) + // TODO Uno: Windowed popup support + if (spParentMenuFlyout is not null) //&& spParentMenuFlyout.IsWindowedPopup()) { - spPopup.SetIsWindowed(); + //spPopup.SetIsWindowed(); // Ensure the sub menu is the windowed Popup - global::System.Diagnostics.Debug.Assert((spPopup.IsWindowed()) + // global::System.Diagnostics.Debug.Assert((spPopup.IsWindowed()) XamlRoot xamlRoot = XamlRoot.GetForElement(spParentMenuFlyoutPresenter); if (xamlRoot is not null) @@ -733,7 +733,7 @@ internal void QueueRefreshItemsSource() { return; } - + var thisMenuFlyoutSubItem = wrThis.Target as MenuFlyoutSubItem; if (thisMenuFlyoutSubItem is not null) From f92f97b603085c57a052baadce0aeb0341aa1768 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 20 Jun 2024 17:21:22 +0200 Subject: [PATCH 16/28] test: Re-enable MenuFlyout Keyboard Accelerator tests --- .../KeyboardAccelerators/KeyboardAcceleratorTests.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs b/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs index 9a148193ed33..df17c0cb4b76 100644 --- a/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs +++ b/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs @@ -1437,7 +1437,6 @@ await TestServices.RunOnUIThread(() => [TestMethod] [TestProperty("Description", "Validates that Button control fires the accelerators on its attached MenuFlyout.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyButtonFlyoutWithMenuFlyoutCanInvokeAcceleratorDefinedOnMenuItem() { const string rootPanelXaml = @@ -1501,7 +1500,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Verify accelerators submenuitem in menuflyout on button control.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyAcceleratorsDefinedOnSubMenuItemInMenuFlyoutOnButton() { const string rootPanelXaml = @@ -1578,7 +1576,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Verify accelerators submenuitem in menuflyout on menubar control.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyAcceleratorsDefinedOnSubMenuItemInMenuFlyoutOnMenuBar() { const string rootPanelXaml = @@ -1655,7 +1652,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Validates that accelerators on MenuBar works when menu item is collapsed.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyAcceleratorDefinedOnMenuBarMenuItems() { const string rootPanelXaml = @@ -1715,7 +1711,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Validates that accelerators on MenuBar works when menu item is opened up.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyAcceleratorDefinedOnMenuBarMenuItemsWhenItsOpened() { const string rootPanelXaml = @@ -1781,7 +1776,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Validates that accelerators on MenuBar works after menu item is opened up and closed again.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyAcceleratorDefinedOnMenuBarMenuItemsWhenItsOpenedAndClosed() { const string rootPanelXaml = @@ -1850,7 +1844,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Validates that StandardUICommands defined on MenuBar after setting window content, works when menu item is collapsed.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyStandarUICommandsDefinedOnMenuBarMenuItemsAfterSettingWindowContent() { const string rootPanelXaml = @@ -1901,7 +1894,6 @@ await TestServices.RunOnUIThread(() => [TestMethod] [TestProperty("Description", "Validates that StandardUICommands on MenuBar works when menu item closed.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyStandarUICommandsDefinedOnMenuBarMenuItems() { const string rootPanelXaml = @@ -1949,7 +1941,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Validates that StandarUICommands on MenuBar works when menu item is opened up.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyStandarUICommandsDefinedOnMenuBarMenuItemsWhenItsOpened() { const string rootPanelXaml = @@ -2003,7 +1994,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Validates that StandarUICommands on MenuBar works after menu item opened up and closed again.")] - [Ignore("Requires MenuFlyout support for Keyboard Accelerators #17133")] public async Task VerifyStandarUICommandsDefinedOnMenuBarMenuItemsWhenItsOpenedAndClosed() { const string rootPanelXaml = @@ -2289,7 +2279,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Validates that Button control fires the global accelerators on its attached MenuFlyout.")] - [Ignore("Requires MenuFlyout and ContextFlyout support for Keyboard Accelerators #17133 and #17134")] public async Task VerifyButtonContextFlyoutWithMenuFlyoutCanInvokeAcceleratorDefinedOnMenuFlyout() { const string rootPanelXaml = From f1097564e8654d9ab2142ca51f6fe31457d50d0c Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Fri, 21 Jun 2024 14:01:41 +0200 Subject: [PATCH 17/28] chore: Ensure KeyboardAccelerators are registered in OnPropertyChanged --- .../KeyboardAcceleratorTests.cs | 16 ++++++++------ .../Controls/MenuFlyout/CommandingHelpers.cs | 21 +++++++++++++++---- .../Controls/MenuFlyout/MenuFlyout.mux.cs | 14 +++++++++++++ .../MenuFlyout/MenuFlyout.partial.mux.cs | 7 ++++--- .../MenuFlyoutSubItem.Properties.cs | 2 +- .../MenuFlyout/MenuFlyoutSubItem.mux.cs | 4 ++-- .../MenuFlyoutSubItem.partial.mux.cs | 5 ++++- .../KeyboardAccelerator.cs | 6 ++++-- .../KeyboardAcceleratorCollection.h.mux.cs | 7 ++++--- .../UI/Xaml/ParentVisualTreeListener.cs | 20 +++++++----------- src/Uno.UI/UI/Xaml/UIElement.Properties.cs | 12 ++++++++++- 11 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs b/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs index df17c0cb4b76..3a0e7551e48a 100644 --- a/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs +++ b/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs @@ -9,13 +9,12 @@ using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Markup; -using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Tests.Common; using MUXControlsTestApp.Utilities; using Private.Infrastructure; using Windows.System; -using MenuBarItem = Microsoft/* UWP don't rename */.UI.Xaml.Controls.MenuBarItem; using FocusHelper = Uno.UI.RuntimeTests.MUX.Input.Focus.FocusHelper; +using MenuBarItem = Microsoft/* UWP don't rename */.UI.Xaml.Controls.MenuBarItem; #if !HAS_UNO_WINUI using Microsoft/* UWP don't rename */.UI.Xaml.Tests.Common; @@ -1545,8 +1544,10 @@ await TestServices.RunOnUIThread(async () => ButtonWithFlyout = (Button)rootPanel.FindName("ButtonWithFlyout"); MenuItem_KA = ((MenuFlyoutItem)rootPanel.FindName("MenuItem")).KeyboardAccelerators[0]; - Sub_MenuItem_KA = ((MenuFlyoutItem)rootPanel.FindName("Sub_MenuItem_KA")).KeyboardAccelerators[0]; - Sub_Sub_MenuItem_KA = ((MenuFlyoutItem)rootPanel.FindName("Sub_Sub_MenuItem_KA")).KeyboardAccelerators[0]; + var menuFlyout = (ButtonWithFlyout.Flyout as MenuFlyout); + var rootSubItem = (MenuFlyoutSubItem)menuFlyout.Items[1]; + Sub_MenuItem_KA = ((MenuFlyoutItem)rootSubItem.Items[1]).KeyboardAccelerators[0]; + Sub_Sub_MenuItem_KA = ((MenuFlyoutItem)((MenuFlyoutSubItem)rootSubItem.Items[0]).Items[0]).KeyboardAccelerators[0]; }); await TestServices.WindowHelper.WaitForIdle(); @@ -1621,8 +1622,11 @@ await TestServices.RunOnUIThread(async () => ButtonWithoutFlyout = (Button)rootPanel.FindName("ButtonWithoutFlyout"); MenuItem_KA = ((MenuFlyoutItem)rootPanel.FindName("MenuItem")).KeyboardAccelerators[0]; - Sub_MenuItem_KA = ((MenuFlyoutItem)rootPanel.FindName("Sub_MenuItem_KA")).KeyboardAccelerators[0]; - Sub_Sub_MenuItem_KA = ((MenuFlyoutItem)rootPanel.FindName("Sub_Sub_MenuItem_KA")).KeyboardAccelerators[0]; + var menuBar = (MenuBar)rootPanel.Children[0]; + var mainItem = menuBar.Items[0]; + var rootSubItem = (MenuFlyoutSubItem)mainItem.Items[1]; + Sub_MenuItem_KA = ((MenuFlyoutItem)rootSubItem.Items[1]).KeyboardAccelerators[0]; + Sub_Sub_MenuItem_KA = ((MenuFlyoutItem)((MenuFlyoutSubItem)rootSubItem.Items[0]).Items[0]).KeyboardAccelerators[0]; }); await TestServices.WindowHelper.WaitForIdle(); diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CommandingHelpers.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CommandingHelpers.cs index 51f597c002fa..4f3fbfc6cdd8 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CommandingHelpers.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CommandingHelpers.cs @@ -6,7 +6,7 @@ using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Markup; - +using Uno.UI.DataBinding; using ICommand = System.Windows.Input.ICommand; namespace Microsoft.UI.Xaml.Controls; @@ -44,14 +44,23 @@ public object ConvertBack(object value, Type targetType, object parameter, strin class KeyboardAcceleratorCopyConverter : IValueConverter { +#if HAS_UNO // We have to pass in the target element so that we can set the parent of KeyboardAcceleratorCollection. + private readonly ManagedWeakReference _targetWeakRef; + + public KeyboardAcceleratorCopyConverter(ManagedWeakReference targetWeakRef) + { + _targetWeakRef = targetWeakRef; + } +#endif + public object Convert(object value, Type targetType, object parameter, string language) { - if (value != null) + if (value != null && _targetWeakRef.IsAlive && _targetWeakRef.Target is DependencyObject element) { object valueAsI = value; IList valueAsKeyboardAccelerators; - var returnValueAsKeyboardAcceleratorCollection = new DependencyObjectCollection(); + var returnValueAsKeyboardAcceleratorCollection = new KeyboardAcceleratorCollection(element); valueAsKeyboardAccelerators = valueAsI as IList; int keyboardAcceleratorCount; @@ -72,6 +81,9 @@ public object Convert(object value, Type targetType, object parameter, string la keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.KeyProperty, new Binding { Path = "Key", Source = keyboardAccelerator }); keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.ModifiersProperty, new Binding { Path = "Modifiers", Source = keyboardAccelerator }); keyboardAcceleratorCopy.SetBinding(KeyboardAccelerator.ScopeOwnerProperty, new Binding { Path = "ScopeOwner", Source = keyboardAccelerator }); + returnValueAsKeyboardAcceleratorCollection.Add(keyboardAcceleratorCopy); + + } return returnValueAsKeyboardAcceleratorCollection; @@ -162,7 +174,8 @@ internal static void BindToKeyboardAcceleratorsIfUnset( if (targetKeyboardAcceleratorCount == 0) { - var converter = new KeyboardAcceleratorCopyConverter(); + var weakReference = WeakReferencePool.RentSelfWeakReference(target); + var converter = new KeyboardAcceleratorCopyConverter(weakReference); target.SetBinding(UIElement.KeyboardAcceleratorsProperty, new Binding { Path = "KeyboardAccelerators", Source = uiCommand, Converter = converter }); } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs index 348dd0a13952..5f7ee9c0b431 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs @@ -21,13 +21,20 @@ internal static void KeyboardAcceleratorFlyoutItemEnter( //params.fUseLayoutRounding = false; //params.fCoercedIsEnabled = false; +#if HAS_UNO // Custom implementation: Simulate enter/leave for KAs on all targets foreach (MenuFlyoutItemBase item in items) { if (item.KeyboardAccelerators is KeyboardAcceleratorCollection kac) { kac.Enter(pNamescopeOwner, parameters); } + + if (item is MenuFlyoutSubItem subItem) + { + subItem.EnterImpl(pNamescopeOwner, parameters); + } } +#endif } } @@ -47,13 +54,20 @@ internal static void KeyboardAcceleratorFlyoutItemLeave( //params.fUseLayoutRounding = false; //params.fCoercedIsEnabled = false; +#if HAS_UNO // Custom implementation: Simulate enter/leave for KAs on all targets foreach (MenuFlyoutItemBase item in items) { if (item.KeyboardAccelerators is KeyboardAcceleratorCollection kac) { kac.Leave(pNamescopeOwner, parameters); } + + if (item is MenuFlyoutSubItem subItem) + { + subItem.LeaveImpl(pNamescopeOwner, parameters); + } } +#endif } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs index 1cc9730741f0..c93601a68f63 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs @@ -36,9 +36,10 @@ public MenuFlyout() private void ListenToParentLifecycle() { - _parentVisualTreeListener = new ParentVisualTreeListener(this); - _parentVisualTreeListener.ParentLoaded += (s, e) => EnterImpl(null, new EnterParams(true)); - _parentVisualTreeListener.ParentUnloaded += (s, e) => LeaveImpl(null, new LeaveParams(true)); + _parentVisualTreeListener = new ParentVisualTreeListener( + this, + () => EnterImpl(null, new EnterParams(true)), + () => LeaveImpl(null, new LeaveParams(true))); } #endif diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs index 7be7f8c39301..b9c828bad579 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.Properties.cs @@ -43,7 +43,7 @@ public IList Items DependencyProperty.Register( nameof(Items), typeof(IList), - typeof(MenuFlyout), + typeof(MenuFlyoutSubItem), new FrameworkPropertyMetadata(defaultValue: null)); /// diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs index c9bbd44e0093..bb858ac4548f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.mux.cs @@ -13,13 +13,13 @@ namespace Microsoft.UI.Xaml.Controls; partial class MenuFlyoutSubItem { - private void EnterImpl(DependencyObject pNamescopeOwner, EnterParams parameters) + internal void EnterImpl(DependencyObject pNamescopeOwner, EnterParams parameters) { //base.EnterImpl(pNamescopeOwner, parameters); MenuFlyout.KeyboardAcceleratorFlyoutItemEnter(this, pNamescopeOwner, MenuFlyoutSubItem.ItemsProperty, parameters); } - private void LeaveImpl(DependencyObject pNamescopeOwner, LeaveParams parameters) + internal void LeaveImpl(DependencyObject pNamescopeOwner, LeaveParams parameters) { //base.LeaveImpl(pNamescopeOwner, parameters); MenuFlyout.KeyboardAcceleratorFlyoutItemLeave(this, pNamescopeOwner, MenuFlyoutSubItem.ItemsProperty, parameters); diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs index 1a0afcc665c8..5b54063e4845 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs @@ -544,7 +544,10 @@ private void OnPresenterSizeChanged(object pSender, SizeChangedEventArgs args) // Update the OpenedLength property of the ThemeTransition. double openedLength = (m_tpPresenter as Control).ActualHeight; - (m_tpMenuPopupThemeTransition as MenuPopupThemeTransition).OpenedLength = openedLength; + if (m_tpMenuPopupThemeTransition is MenuPopupThemeTransition menuTransition) + { + menuTransition.OpenedLength = openedLength; + } } private void ClearStateFlags() diff --git a/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAccelerator.cs b/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAccelerator.cs index fea22cb47f5b..b1e2c95fd576 100644 --- a/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAccelerator.cs +++ b/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAccelerator.cs @@ -18,8 +18,10 @@ public partial class KeyboardAccelerator : DependencyObject public KeyboardAccelerator() { #if HAS_UNO - _parentVisualTreeListener = new ParentVisualTreeListener(this); - _parentVisualTreeListener.ParentLoaded += (s, e) => EnterImpl(null, new EnterParams(true)); + _parentVisualTreeListener = new ParentVisualTreeListener( + this, + () => EnterImpl(null, new EnterParams(true)), + null); #endif } } diff --git a/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAcceleratorCollection.h.mux.cs b/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAcceleratorCollection.h.mux.cs index 8634e3844793..d16dcbf4a570 100644 --- a/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAcceleratorCollection.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Input/KeyboardAccelerator/KeyboardAcceleratorCollection.h.mux.cs @@ -14,9 +14,10 @@ internal class KeyboardAcceleratorCollection : DependencyObjectCollection Enter(null, new EnterParams(true)); - _parentVisualTreeListener.ParentUnloaded += (s, e) => Leave(null, new LeaveParams(true)); + _parentVisualTreeListener = new ParentVisualTreeListener( + this, + () => Enter(null, new EnterParams(true)), + () => Leave(null, new LeaveParams(true))); } #endif diff --git a/src/Uno.UI/UI/Xaml/ParentVisualTreeListener.cs b/src/Uno.UI/UI/Xaml/ParentVisualTreeListener.cs index 3f6ef630237b..571b7555059e 100644 --- a/src/Uno.UI/UI/Xaml/ParentVisualTreeListener.cs +++ b/src/Uno.UI/UI/Xaml/ParentVisualTreeListener.cs @@ -10,12 +10,15 @@ namespace Uno.UI.Xaml; internal class ParentVisualTreeListener { private readonly DependencyObject _owner; + private readonly Action _onLoaded; + private readonly Action _onUnloaded; private readonly SerialDisposable _registrations = new(); - public ParentVisualTreeListener(DependencyObject owner) + public ParentVisualTreeListener(DependencyObject owner, Action onLoaded, Action onUnloaded) { _owner = owner; - + _onLoaded = onLoaded; + _onUnloaded = onUnloaded; owner.RegisterParentChangedCallbackStrong(null, OnParentChanged); if (_owner.GetParent() is { } existingParent) @@ -24,17 +27,10 @@ public ParentVisualTreeListener(DependencyObject owner) } } - public event EventHandler? ParentChanging; - - public event EventHandler? ParentLoaded; - - public event EventHandler? ParentUnloaded; - private void OnParentChanged(object instance, object? key, DependencyObjectParentChangedEventArgs args) => OnParentChanged(args.NewParent); private void OnParentChanged(object? newParent) { - ParentChanging?.Invoke(this, EventArgs.Empty); // Unsubscribe from the previous parent _registrations.Disposable = null; @@ -45,7 +41,7 @@ private void OnParentChanged(object? newParent) if (nearestFeParent.IsLoaded) { - ParentLoaded?.Invoke(this, EventArgs.Empty); + _onLoaded?.Invoke(); } _registrations.Disposable = Disposable.Create(() => @@ -56,7 +52,7 @@ private void OnParentChanged(object? newParent) } } - private void OnParentLoaded(object sender, RoutedEventArgs e) => ParentLoaded?.Invoke(this, EventArgs.Empty); + private void OnParentLoaded(object sender, RoutedEventArgs e) => _onLoaded?.Invoke(); - private void OnParentUnloaded(object sender, RoutedEventArgs e) => ParentUnloaded?.Invoke(this, EventArgs.Empty); + private void OnParentUnloaded(object sender, RoutedEventArgs e) => _onUnloaded?.Invoke(); } diff --git a/src/Uno.UI/UI/Xaml/UIElement.Properties.cs b/src/Uno.UI/UI/Xaml/UIElement.Properties.cs index f8974facfdf1..82ba9c46d7d0 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.Properties.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.Properties.cs @@ -86,7 +86,7 @@ public FlyoutBase ContextFlyout set => SetContextFlyoutValue(value); } - [GeneratedDependencyProperty(DefaultValue = null)] + [GeneratedDependencyProperty(DefaultValue = null, ChangedCallback = true)] internal static DependencyProperty KeyboardAcceleratorsProperty { get; } = CreateKeyboardAcceleratorsProperty(); public IList KeyboardAccelerators @@ -95,6 +95,16 @@ public IList KeyboardAccelerators private set => SetKeyboardAcceleratorsValue(value); } + private void OnKeyboardAcceleratorsChanged() + { +#if HAS_UNO // TODO: Uno specific - WinUI does analogous action in EnterEffectiveValue + if (KeyboardAccelerators is KeyboardAcceleratorCollection collection) + { + collection.Enter(null, new EnterParams(IsLoaded) { IsForKeyboardAccelerator = true }); + } +#endif + } + /// /// Gets or sets a value that indicates whether the control tooltip displays /// the key combination for its associated keyboard accelerator. From a5c575ffc9f0037b4e848893d581582620076ba6 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Fri, 21 Jun 2024 14:43:32 +0200 Subject: [PATCH 18/28] chore: Use Invoke instead of InvokeClick --- .../UI/Xaml/Controls/MenuBar/NativeMenuBarPresenter.macOS.cs | 4 ++-- src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs | 2 +- src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs | 2 +- src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/MenuBar/NativeMenuBarPresenter.macOS.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/MenuBar/NativeMenuBarPresenter.macOS.cs index 8d233bb71744..57f8b840c717 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/MenuBar/NativeMenuBarPresenter.macOS.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/MenuBar/NativeMenuBarPresenter.macOS.cs @@ -75,7 +75,7 @@ private void ProcessMenuItems(NSMenuItem platformItem, IList case MenuFlyoutItem flyoutItem: var subPlatformItem2 = new NSMenuItem(flyoutItem.Text, (s, e) => OnItemActivated(flyoutItem)) { Enabled = true }; - flyoutItem.InvokeClick(); + flyoutItem.Invoke(); platformItem.Submenu.AddItem(subPlatformItem2); break; @@ -89,7 +89,7 @@ private void ProcessMenuItems(NSMenuItem platformItem, IList private void OnItemActivated(MenuFlyoutItem flyoutItem) { - flyoutItem.InvokeClick(); + flyoutItem.Invoke(); } protected override Size MeasureOverride(Size availableSize) diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs index e46749ddff26..2103de6db7ac 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.Android.cs @@ -82,7 +82,7 @@ private void OnMenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) var items = Items.OfType().Where(i => i.Visibility == Visibility.Visible).ToArray(); var item = items[e.Item.ItemId]; - item.InvokeClick(); + item.Invoke(); } private View GetActualTarget() diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs index 97a12c2726cf..56a83e755a66 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS7.iOS.cs @@ -40,7 +40,7 @@ private void ShowActionSheet(UIView placementTarget) if (item != null) { - item.InvokeClick(); + item.Invoke(); Hide(); } }; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs index 6e2338968d36..53e142ec3370 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.iOS8.iOS.cs @@ -59,7 +59,7 @@ private void ShowAlert(UIView placementTarget) true == (item.GetValue(IsDestructiveProperty) as bool?) ? UIAlertActionStyle.Destructive : UIAlertActionStyle.Default, _ => { - item.InvokeClick(); + item.Invoke(); Hide(); } )) From f9c308dec71b60f909f5c3d191553708f8a6dea9 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Mon, 24 Jun 2024 19:02:13 +0200 Subject: [PATCH 19/28] feat: CascadingMenuHelper update to winui3/release/1.5.4 --- .../Controls/AppBar/AppBarButton.Partial.cs | 2 + .../UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs | 10 - .../MenuFlyout/CascadingMenuHelper.cs | 968 ------------------ .../MenuFlyout/CascadingMenuHelper.h.mux.cs | 45 + .../MenuFlyout/CascadingMenuHelper.mux.cs | 827 +++++++++++++++ .../Xaml/Controls/MenuFlyout/ISubMenuOwner.cs | 2 + .../MenuFlyoutSubItem.partial.mux.cs | 4 +- src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs | 7 + 8 files changed, 886 insertions(+), 979 deletions(-) delete mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.h.mux.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.mux.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/AppBar/AppBarButton.Partial.cs b/src/Uno.UI/UI/Xaml/Controls/AppBar/AppBarButton.Partial.cs index a36a8c496ef3..76afdf5f9055 100644 --- a/src/Uno.UI/UI/Xaml/Controls/AppBar/AppBarButton.Partial.cs +++ b/src/Uno.UI/UI/Xaml/Controls/AppBar/AppBarButton.Partial.cs @@ -882,5 +882,7 @@ private void GetTemplatePart(string name, out T? element) where T : class { element = GetTemplateChild(name) as T; } + + public void SetSubMenuDirection(bool isSubMenuDirectionUp) { } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs index 4a73ad46796b..df10b676782f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutBase.mux.cs @@ -42,14 +42,4 @@ internal void SetIsWindowedPopup() // } //} } - - private bool IsWindowedPopup() - { - return false; - // TODO: Uno - //bool areWindowedPopupsSupported = CPopup::DoesPlatformSupportWindowedPopup(DXamlCore::GetCurrent()->GetHandle()); - //bool isPopupWindowed = m_tpPopup ? static_cast(m_tpPopup.Cast()->GetHandle())->IsWindowed() : false; - - //return areWindowedPopupsSupported && (isPopupWindowed || m_openingWindowedInProgress); - } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs deleted file mode 100644 index 6ddf00b0e3aa..000000000000 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.cs +++ /dev/null @@ -1,968 +0,0 @@ -using System; -using Uno.Disposables; -using Windows.Foundation; -using Windows.System; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; - -#if HAS_UNO_WINUI -using Microsoft.UI.Input; -#else -using Windows.Devices.Input; -using Windows.UI.Input; -#endif - -namespace Microsoft.UI.Xaml.Controls; - -internal class CascadingMenuHelper -{ - - // The overlapped menu pixels between the main main menu presenter and the sub presenter - const int m_subMenuOverlapPixels = 4; - - int m_subMenuShowDelay; - - // Owner of the cascading menu - WeakReference m_wpOwner; - - // Presenter of the sub-menu - WeakReference m_wpSubMenuPresenter; - - // Event pointer for the Loaded event - IDisposable m_loadedHandler; - - // Dispatcher timer to delay showing the sub menu flyout - DispatcherTimer m_delayOpenMenuTimer; - - // Dispatcher timer to delay hiding the sub menu flyout - DispatcherTimer m_delayCloseMenuTimer; - - // Indicate the pointer is over the cascading menu owner - bool m_isPointerOver = true; - - // Indicate the pointer is pressed on the cascading menu owner - bool m_isPressed = true; - - internal bool IsPressed => m_isPressed; - - internal bool IsPointerOver => m_isPointerOver; - - - // This fallback is used if we fail to retrieve a value from the MenuShowDelay RegKey - const int DefaultMenuShowDelay = 400; // in milliseconds - - // Uno-specific workaround (see comment below) - private Point? _lastTargetPoint; - - public CascadingMenuHelper() - { - m_isPointerOver = false; - m_isPressed = false; - m_subMenuShowDelay = -1; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ", this)); -#endif // CMH_DEBUG - } - - ~CascadingMenuHelper() - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~", this)); -#endif // CMH_DEBUG - - var delayOpenMenuTimer = m_delayOpenMenuTimer; - if (delayOpenMenuTimer != null) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~CascadingMenuHelper - Stopping m_delayOpenMenuTimer.", this)); -#endif // CMH_DEBUG - - delayOpenMenuTimer.Stop(); - } - - var delayCloseMenuTimer = m_delayCloseMenuTimer; - if (delayCloseMenuTimer != null) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ~CascadingMenuHelper - Stopping m_delayCloseMenuTimer.", this)); -#endif // CMH_DEBUG - delayCloseMenuTimer.Stop(); - } - } - - internal void Initialize(FrameworkElement owner) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: Initialize.", this)); -#endif // CMH_DEBUG - - FrameworkElement ownerLocal = owner; - m_wpOwner = new WeakReference(ownerLocal); - - void OnLoadedClearFlags(object pSender, RoutedEventArgs args) - { - ClearStateFlags(); - } - - owner.Loaded += OnLoadedClearFlags; - m_loadedHandler = Disposable.Create(() => owner.Loaded -= OnLoadedClearFlags); - -#if false - // Try and read from Reg Key - HKEY key = null; - - if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, "Control Panel\\Desktop\\", 0, KEY_QUERY_VALUE, &key)) - { - char Buffer[32] = { 0 }; - DWORD BufferSize = sizeof(Buffer); - DWORD dwOutType; - - if (RegQueryValueEx(key, "MenuShowDelay", NULL, &dwOutType/*REG_SZ*/, (LPBYTE)Buffer, &BufferSize) == ERROR_SUCCESS) - { - m_subMenuShowDelay = _wtoi(Buffer); - } - RegCloseKey(key); - } -#endif - - // If the field wasn't successfully populated from the reg key - // Or if the reg key contained a negative number - // Then use a default value - if (m_subMenuShowDelay < 0) - { - m_subMenuShowDelay = DefaultMenuShowDelay; - } - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: Initialize - m_subMenuShowDelay=%d.", this, m_subMenuShowDelay)); -#endif // CMH_DEBUG - - - // Cascading menu owners should be access key scopes by default. - ownerLocal.IsAccessKeyScope = true; - } - - internal void OnApplyTemplate() - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnApplyTemplate.", this)); -#endif // CMH_DEBUG - - UpdateOwnerVisualState(); - - } - - // PointerEntered event handler that shows the sub menu - // whenever the pointer is over the sub menu owner. - // In case of touch, the sub menu item will be shown by - // PointerReleased event. - internal void OnPointerEntered(PointerRoutedEventArgs args) - { - bool handled = false; - - m_isPointerOver = true; - - handled = args.Handled; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - handled=%d.", this, handled)); -#endif // CMH_DEBUG - - if (!handled) - { - var owner = m_wpOwner?.Target as ISubMenuOwner; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - owner=0x%p.", this, owner)); -#endif // CMH_DEBUG - - if (owner != null) - { - ISubMenuOwner parentOwner; - parentOwner = owner.ParentOwner; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - parentOwner=0x%p.", this, parentOwner)); -#endif // CMH_DEBUG - - if (parentOwner != null) - { - parentOwner.CancelCloseSubMenu(); - } - } - - Pointer pointer = args.Pointer; - var pointerDeviceType = pointer.PointerDeviceType; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - pointerDeviceType=%d.", this, pointerDeviceType)); -#endif // CMH_DEBUG - - if (pointerDeviceType != PointerDeviceType.Touch) - { - CancelCloseSubMenu(); - - EnsureDelayOpenMenuTimer(); - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerEntered - Starting m_delayOpenMenuTimer.", this)); -#endif // CMH_DEBUG - - m_delayOpenMenuTimer.Start(); - - UpdateOwnerVisualState(); - } - - args.Handled = true; - } - - - } - - // PointerExited event handler that ensures we close the sub menu - // whenever the pointer leaves the current sub menu or - // the main presenter. If the exited point is on the sub menu owner - // or the sub menu, we want to keep the sub menu open. - internal void OnPointerExited( - PointerRoutedEventArgs args, - bool parentIsSubMenu) - { - bool handled = false; - - m_isPointerOver = false; - m_isPressed = false; - - handled = args.Handled; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - handled=%d, parentIsSubMenu=%d.", this, handled, parentIsSubMenu)); -#endif // CMH_DEBUG - - if (m_delayOpenMenuTimer != null) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - Stopping m_delayOpenMenuTimer.", this)); -#endif // CMH_DEBUG - - m_delayOpenMenuTimer.Stop(); - } - - var ownerAsUIE = m_wpOwner?.Target as FrameworkElement; - - if (!handled && ownerAsUIE != null && ownerAsUIE.IsLoaded) - { - Pointer pointer; - - pointer = args.Pointer; - var pointerDeviceType = pointer.PointerDeviceType; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerExited - pointerDeviceType=%d.", this, pointerDeviceType)); -#endif // CMH_DEBUG - - if (PointerDeviceType.Mouse == pointerDeviceType && !parentIsSubMenu) - { - UIElement subMenuPresenterAsUIE = m_wpSubMenuPresenter?.Target as UIElement; - - if (subMenuPresenterAsUIE != null) - { - bool isOwnerOrSubMenuHit = false; - var pointerPoint = args.GetCurrentPoint(null); - var point = pointerPoint.Position; - - var elements = VisualTreeHelper.FindElementsInHostCoordinates(point, ownerAsUIE, true /* includeAllElements */); - - foreach (var element in elements) - { - if (ownerAsUIE == element || subMenuPresenterAsUIE == element) - { - isOwnerOrSubMenuHit = true; - break; - } - } - - if (!isOwnerOrSubMenuHit) - { - elements = VisualTreeHelper.FindElementsInHostCoordinates(point, subMenuPresenterAsUIE, true /* includeAllElements */); - - foreach (var element in elements) - { - if (ownerAsUIE == element || subMenuPresenterAsUIE == element) - { - isOwnerOrSubMenuHit = true; - break; - } - } - } - - // To close the sub menu, the pointer must be outside of the opened chain of sub-menus. - if (!isOwnerOrSubMenuHit) - { - DelayCloseSubMenu(); - args.Handled = true; - } - } - } - - UpdateOwnerVisualState(); - } - } - - // PointerPressed event handler ensures that we're in the pressed state. - internal void OnPointerPressed(PointerRoutedEventArgs args) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerPressed.", this)); -#endif // CMH_DEBUG - - m_isPressed = true; - - args.Handled = true; - } - - // PointerReleased event handler shows the sub menu in the - // case of touch input. - internal void OnPointerReleased(PointerRoutedEventArgs args) - { - Pointer pointer; - PointerDeviceType pointerDeviceType; - - m_isPressed = false; - - pointer = args.Pointer; - pointerDeviceType = pointer.PointerDeviceType; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPointerReleased - pointerDeviceType=%d.", this, pointerDeviceType)); -#endif // CMH_DEBUG - - // Show the sub menu in the case of touch input. - // In case of the mouse device or pen, the sub menu will be shown whenever the pointer is over - // the sub menu owner. - if (PointerDeviceType.Touch == pointerDeviceType) - { - OpenSubMenu(); - } - - args.Handled = true; - } - - internal void OnGotFocus(RoutedEventArgs args) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnGotFocus.", this)); -#endif // CMH_DEBUG - - UpdateOwnerVisualState(); - } - - internal void OnLostFocus(RoutedEventArgs args) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnLostFocus.", this)); -#endif // CMH_DEBUG - - m_isPressed = false; - UpdateOwnerVisualState(); - } - - // KeyDown event handler that handles the keyboard navigation between - // the menu items and shows the sub menu in the case where we hit - // the enter, space, or right arrow keys. - internal void OnKeyDown(KeyRoutedEventArgs args) - { - bool handled = false; - - handled = args.Handled; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnKeyDown - handled=%d.", this, handled)); -#endif // CMH_DEBUG - - if (!handled) - { - var key = args.Key; - - // Show the sub menu with the enter, space, or right arrow keys - if (key == VirtualKey.Enter || - key == VirtualKey.Right || - key == VirtualKey.Space) - { - OpenSubMenu(); - args.Handled = true; - } - } - } - - internal void OnKeyUp(KeyRoutedEventArgs args) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnKeyUp.", this)); -#endif // CMH_DEBUG - - UpdateOwnerVisualState(); - args.Handled = true; - } - - // Creates a DispatcherTimer for delaying showing the sub menu flyout - void EnsureDelayOpenMenuTimer() - { - if (m_delayOpenMenuTimer == null) - { - m_delayOpenMenuTimer = new DispatcherTimer(); - m_delayOpenMenuTimer.Tick += (s, e) => DelayOpenMenuTimerTickHandler(); - - TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(m_subMenuShowDelay); - m_delayOpenMenuTimer.Interval = delayTimeSpan; - } - } - - // Handler for the Tick event on the delay open menu timer. - void DelayOpenMenuTimerTickHandler() - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayOpenMenuTimerTickHandler.", this)); -#endif // CMH_DEBUG - - EnsureCloseExistingSubItems(); - - // Open the current sub menu - OpenSubMenu(); - - if (m_delayOpenMenuTimer != null) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayOpenMenuTimerTickHandler - Stopping m_delayOpenMenuTimer.", this)); -#endif // CMH_DEBUG - - m_delayOpenMenuTimer.Stop(); - } - } - - // Creates a DispatcherTimer for delaying hiding the sub menu flyout - void EnsureDelayCloseMenuTimer() - { - if (m_delayCloseMenuTimer == null) - { - m_delayCloseMenuTimer = new DispatcherTimer(); - m_delayCloseMenuTimer.Tick += (s, e) => DelayCloseMenuTimerTickHandler(); - - TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(m_subMenuShowDelay); - m_delayCloseMenuTimer.Interval = delayTimeSpan; - } - } - - // Handler for the Tick event on the delay close menu timer. - void DelayCloseMenuTimerTickHandler() - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseMenuTimerTickHandler.", this)); -#endif // CMH_DEBUG - - CloseSubMenu(); - - if (m_delayCloseMenuTimer != null) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseMenuTimerTickHandler - Stopping m_delayCloseMenuTimer.", this)); -#endif // CMH_DEBUG - m_delayCloseMenuTimer.Stop(); - } - } - - // Ensure that any currently open sub menus are closed - void EnsureCloseExistingSubItems() - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: EnsureCloseExistingSubItems.", this)); -#endif // CMH_DEBUG - - ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; - - if (ownerAsSubMenuOwner != null) - { - ownerAsSubMenuOwner.ClosePeerSubMenus(); - } - } - - internal void SetSubMenuPresenter(FrameworkElement subMenuPresenter) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter.", this)); -#endif // CMH_DEBUG - - m_wpSubMenuPresenter = new WeakReference(subMenuPresenter); - - ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter - ownerAsSubMenuOwner=0x%p.", this, ownerAsSubMenuOwner)); -#endif // CMH_DEBUG - - if (ownerAsSubMenuOwner != null) - { - IMenuPresenter menuPresenter = subMenuPresenter as IMenuPresenter; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: SetSubMenuPresenter - menuPresenter=0x%p.", this, menuPresenter)); -#endif // CMH_DEBUG - - if (menuPresenter != null) - { - menuPresenter.Owner = ownerAsSubMenuOwner; - } - } - } - - // Shows the sub menu at the appropriate position. - // The sub menu will be adjusted if the sub presenter size changes. - internal void OpenSubMenu() - { - ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OpenSubMenu - ownerAsSubMenuOwner=0x%p.", this, ownerAsSubMenuOwner)); -#endif // CMH_DEBUG - - if (ownerAsSubMenuOwner != null) - { - ownerAsSubMenuOwner.PrepareSubMenu(); - - bool isSubMenuOpen = false; - isSubMenuOpen = ownerAsSubMenuOwner.IsSubMenuOpen; - - if (!isSubMenuOpen) - { - Control ownerAsControl = m_wpOwner?.Target as Control; - - if (ownerAsControl != null) - { - EnsureCloseExistingSubItems(); - - double subItemWidth = 0; - subItemWidth = ownerAsControl.ActualWidth; - - FlowDirection flowDirection = FlowDirection.LeftToRight; - flowDirection = ownerAsControl.FlowDirection; - - Point targetPoint = new Point(0, 0); - - bool isPositionedAbsolutely = false; - isPositionedAbsolutely = ownerAsSubMenuOwner.IsSubMenuPositionedAbsolutely; - - if (isPositionedAbsolutely) - { - GeneralTransform transformToRoot; - transformToRoot = ownerAsControl.TransformToVisual(null); - targetPoint = transformToRoot.TransformPoint(targetPoint); - } - - if (flowDirection == FlowDirection.RightToLeft) - { - targetPoint.X += (float)(m_subMenuOverlapPixels - subItemWidth); - } - else - { - targetPoint.X += (float)(subItemWidth - m_subMenuOverlapPixels); - } - - ownerAsSubMenuOwner.OpenSubMenu(targetPoint); - if (_lastTargetPoint is { } lastTargetPoint) - { - // Uno-specific workaround: reapply the location calculated in OnPresenterSizeChanged(), since that one properly - // adjusts to keep submenu within screen bounds. (WinUI seemingly relies upon presenter.SizeChanged being raised - // every time submenu opens? On Uno it isn't.) - ownerAsSubMenuOwner.PositionSubMenu(lastTargetPoint); - } - ownerAsSubMenuOwner.RaiseAutomationPeerExpandCollapse(true /* isOpen */); - ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, ownerAsControl); - } - } - } - } - - internal void CloseSubMenu() - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseSubMenu.", this)); -#endif // CMH_DEBUG - - CloseChildSubMenus(); - - ISubMenuOwner owner = m_wpOwner?.Target as ISubMenuOwner; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseSubMenu - owner=0x%p.", this, owner)); -#endif // CMH_DEBUG - - if (owner != null) - { - owner.CloseSubMenu(); - owner.RaiseAutomationPeerExpandCollapse(false /* isOpen */); - - DependencyObject ownerAsDO = owner as DependencyObject; - - if (ownerAsDO != null) - { - ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Hide, ownerAsDO as DependencyObject); - } - } - } - - internal void CloseChildSubMenus() - { - // WeakRefPtr fails an assert if we attempt to AsOrNull() to a type - // that we aren't sure if the contents of the weak reference - // implement that type. To avoid that assert, we first As() the - // weak reference contents to a known type that it's guaranteed - // to be (if it isn't null), and we then AsOrNull() the ComPtr, - // once it's safe to ask about a type that we're not sure of. - FrameworkElement subMenuPresenterAsFE = m_wpSubMenuPresenter?.Target as Frame; - - IMenuPresenter subMenuPresenter = null; - - if (subMenuPresenterAsFE != null) - { - subMenuPresenter = subMenuPresenterAsFE as IMenuPresenter; - } - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CloseChildSubMenus - subMenuPresenter=0x%p.", this, subMenuPresenter)); -#endif // CMH_DEBUG - - if (subMenuPresenter != null) - { - subMenuPresenter.CloseSubMenu(); - } - } - - internal void DelayCloseSubMenu() - { - EnsureDelayCloseMenuTimer(); - if (m_delayCloseMenuTimer != null) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: DelayCloseSubMenu - Starting m_delayCloseMenuTimer.", this)); -#endif // CMH_DEBUG - - m_delayCloseMenuTimer.Start(); - } - } - - internal void CancelCloseSubMenu() - { - if (m_delayCloseMenuTimer != null) - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: CancelCloseSubMenu - Stopping m_delayCloseMenuTimer.", this)); -#endif // CMH_DEBUG - - m_delayCloseMenuTimer.Stop(); - } - } - - internal void ClearStateFlags() - { -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: ClearStateFlags.", this)); -#endif // CMH_DEBUG - - m_isPressed = false; - m_isPointerOver = false; - UpdateOwnerVisualState(); - } - - internal void OnIsEnabledChanged(IsEnabledChangedEventArgs args) - { - Control ownerAsControl = m_wpOwner?.Target as Control; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnIsEnabledChanged - ownerAsControl=0x%p.", this, ownerAsControl)); -#endif // CMH_DEBUG - - if (ownerAsControl != null) - { - bool bIsEnabled = false; - bIsEnabled = ownerAsControl.IsEnabled; - - if (!bIsEnabled) - { - ClearStateFlags(); - } - else - { - ownerAsControl.UpdateVisualState(true /* useTransitions */); - } - } - } - - public void OnVisibilityChanged() - { - UIElement ownerAsUIE = m_wpOwner?.Target as UIElement; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnVisibilityChanged - ownerAsUIE=0x%p.", this, ownerAsUIE)); -#endif // CMH_DEBUG - - if (ownerAsUIE != null) - { - Visibility visibility = ownerAsUIE.Visibility; - - if (Visibility.Visible != visibility) - { - ClearStateFlags(); - } - } - } - - internal void OnPresenterSizeChanged( - object pSender, - SizeChangedEventArgs args, - Popup popup) - { - Control ownerAsControl = m_wpOwner?.Target as Control; - - ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; - - Control presenterAsControl = m_wpSubMenuPresenter?.Target as Control; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: OnPresenterSizeChanged - ownerAsControl=0x%p, ownerAsSubMenuOwner=0x%p.", this, ownerAsControl, ownerAsSubMenuOwner)); -#endif // CMH_DEBUG - - if (ownerAsControl != null && ownerAsSubMenuOwner != null && presenterAsControl != null) - { - Size newPresenterSize = args.NewSize; - - FlowDirection flowDirection = ownerAsControl.FlowDirection; - - // We sometimes will only want to change one of the two XY-positions of the menu, - // but some menus (e.g. AppBarButton.Flyout) don't allow you to individually change - // one axis of the position - you need to close and reopen the menu in a different location. - // This necessitates a single function call that takes a Point parameter rather than - // two function calls that individually change the X and then the Y position of the menu, - // since otherwise we'd be closing and reopening the menu twice if we needed to change - // both positions, which would be visually disruptive. - // As such, we need a way to tell PositionSubMenu to leave one of the positions as it was. - // We'll use negative infinity as a sentinel value that means "don't change this coordinate value". - Point targetPoint = new Point(float.NegativeInfinity, float.NegativeInfinity); - - bool isPositionedAbsolutely = false; - isPositionedAbsolutely = ownerAsSubMenuOwner.IsSubMenuPositionedAbsolutely; - - Point positionPoint = new Point(0, 0); - - if (isPositionedAbsolutely) - { - // Get the current sub menu item target position as the client point - GeneralTransform transformToRoot; - transformToRoot = ownerAsControl.TransformToVisual(null); - positionPoint = transformToRoot.TransformPoint(positionPoint); - } - - // Get the current sub menu item width and height - double width = 0; - double height = 0; - width = ownerAsControl.ActualWidth; - height = ownerAsControl.ActualHeight; - - // Get the current presenter max width/height - var maxWidth = presenterAsControl.MaxWidth; - var maxHeight = presenterAsControl.MaxHeight; - -#if false // UNO TODO Windowed menus are not supported - // If the current menu is a windowed popup, the position setting will be within the current nearest - // monitor boundary. Otherwise, the position will ensure within the Xaml window boundary. - if ((CPopup*)(popup.GetHandle()).IsWindowed()) - { - Rect targetBounds = { - positionPoint.X, - positionPoint.Y, - (FLOAT)(width), - (FLOAT)(height)}; - - // Get the available monitor bounds from the current nearest monitor. - // IHM bounds will be excluded from the monitor bounds. - Rect availableMonitorRect = default; - (DXamlCore.GetCurrent().CalculateAvailableMonitorRect(popup, positionPoint, &availableMonitorRect)); - - // Set the max width and height with the available monitor bounds - (presenterAsControl.put_MaxWidth( - DoubleUtil.IsNaN(maxWidth) ? availableMonitorRect.Width : DoubleUtil.Min(maxWidth, availableMonitorRect.Width))); - (presenterAsControl.put_MaxHeight( - DoubleUtil.IsNaN(maxHeight) ? availableMonitorRect.Height : DoubleUtil.Min(maxHeight, availableMonitorRect.Height))); - - // Get the available bottom space from the current nearest monitor bounds - double bottomSpace = (availableMonitorRect.Y + availableMonitorRect.Height) - (targetBounds.Y); - - if (flowDirection == FlowDirection_LeftToRight) - { - // Get the available right space from the current nearest monitor bounds - double rightSpace = (availableMonitorRect.X + availableMonitorRect.Width) - (targetBounds.X + targetBounds.Width); - // If the current sub presenter width isn't enough in the default right space, - // the MenuFlyoutSubItem will be positioned on the left side if the current presenter - // width is less than the sub item left(X) position. Otherwise, it will be aligned to - // the right side of the available monitor rect. - if (newPresenterSize.Width > rightSpace) - { - if (newPresenterSize.Width < availableMonitorRect.Width - rightSpace - targetBounds.Width) - { - targetPoint.X = (float)(positionPoint.X - newPresenterSize.Width + m_subMenuOverlapPixels); - } - else - { - targetPoint.X = (float)(positionPoint.X + width + rightSpace - newPresenterSize.Width); - } - } - } - else - { - // Get the available left space from the current nearest monitor bounds - double leftSpace = targetBounds.X - availableMonitorRect.X - targetBounds.Width; - // If the current sub presenter width isn't enough in the default left space, - // the MenuFlyoutSubItem will be positioned on the right side if the current presenter - // width is less than the sub item right(X) position. Otherwise, it will be aligned to - // the left side of the available monitor rect. - if (newPresenterSize.Width > leftSpace) - { - if (newPresenterSize.Width < (availableMonitorRect.Width - leftSpace - targetBounds.Width)) - { - targetPoint.X = (float)(positionPoint.X + newPresenterSize.Width - m_subMenuOverlapPixels); - } - else - { - targetPoint.X = (float)(positionPoint.X - width - leftSpace + newPresenterSize.Width); - } - } - else - { - targetPoint.X = (float)(positionPoint.X - targetBounds.Width + m_subMenuOverlapPixels); - } - } - - // If the current sub presenter doesn't have space to fit in the default bottom position, - // then the MenuFlyoutSubItem will be aligned with the bottom of the target bounds. - // If the MenuFlyoutSubItem is too tall to fit when bottom aligned with the target bounds - // then it will be bottom aligned with the edge of the monitor. - if (newPresenterSize.Height > bottomSpace) - { - if ((targetBounds.Y + targetBounds.Height) > newPresenterSize.Height) - { - targetPoint.Y = (float)(positionPoint.Y - newPresenterSize.Height + targetBounds.Height); - } - else - { - targetPoint.Y = (float)(positionPoint.Y - DoubleUtil.Min(newPresenterSize.Height - bottomSpace, availableMonitorRect.Height - bottomSpace)); - } - } - } - else -#endif // UNO TODO Windowed menus are not supported - { - // Get the available window rect - Rect availableWindowRect = FlyoutBase.CalculateAvailableWindowRect( - true /* isMenuFlyout */, - popup, - null /* placementTarget */, - true /* hasTargetPosition */, - positionPoint, - false /* isFull */); - - // Set the max width and height with the available windows bounds - presenterAsControl.MaxWidth = - double.IsNaN(maxWidth) ? availableWindowRect.Width : Math.Min(maxWidth, availableWindowRect.Width); - presenterAsControl.MaxHeight = - double.IsNaN(maxHeight) ? availableWindowRect.Height : Math.Min(maxHeight, availableWindowRect.Height); - - // Get the available bottom space to set the MenuFlyoutSubItem - double bottomSpace = availableWindowRect.Height - positionPoint.Y; - - if (flowDirection == FlowDirection.LeftToRight) - { - // Get the available right space to set the MenuFlyoutSubItem - double rightSpace = availableWindowRect.Width - (positionPoint.X + width); - // If the current sub presenter width isn't enough in the default right space, - // the MenuFlyoutSubItem will be positioned on the left side if the current presenter - // width is less than the sub item left(X) position. Otherwise, it will be aligned - // right side of the available window rect. - if (newPresenterSize.Width > rightSpace) - { - if (newPresenterSize.Width < positionPoint.X) - { - targetPoint.X = (float)(positionPoint.X - newPresenterSize.Width + m_subMenuOverlapPixels); - } - else - { - targetPoint.X = (float)(availableWindowRect.Width - newPresenterSize.Width); - } - } - } - else - { - // Get the available left space to set the MenuFlyoutSubItem - double leftSpace = positionPoint.X - availableWindowRect.X - width; - // If the current sub presenter width isn't enough in the default left space, - // the MenuFlyoutSubItem will be positioned on the right side if the current presenter - // width is less than the sub item right(X) position. Otherwise, it will be aligned - // left side of the available window rect. - if (newPresenterSize.Width > leftSpace) - { - if (newPresenterSize.Width < (availableWindowRect.Width + availableWindowRect.X - positionPoint.X)) - { - targetPoint.X = (float)(positionPoint.X + width - m_subMenuOverlapPixels); - } - else - { - targetPoint.X = (float)width; - } - } - else - { - targetPoint.X = (float)(positionPoint.X - width + m_subMenuOverlapPixels); - } - } - - // If the current sub presenter doesn't have space to fit in the default bottom position, - // then the MenuFlyoutSubItem will be aligned with the bottom of the target bounds. - // If the MenuFlyoutSubItem is too tall to fit when bottom aligned with the target bounds - // then it will be bottom aligned with the edge of the monitor. - if (newPresenterSize.Height > bottomSpace) - { - if ((positionPoint.Y + height) > newPresenterSize.Height) - { - targetPoint.Y = (float)(positionPoint.Y + height - newPresenterSize.Height); - } - else - { - targetPoint.Y = (float)(positionPoint.Y - Math.Min(newPresenterSize.Height - bottomSpace, availableWindowRect.Height - bottomSpace)); - } - } - } - - _lastTargetPoint = targetPoint; - ownerAsSubMenuOwner.PositionSubMenu(targetPoint); - } - } - - void UpdateOwnerVisualState() - { - Control ownerAsControl = m_wpOwner?.Target as Control; - -#if CMH_DEBUG - (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: UpdateOwnerVisualState - ownerAsControl=0x%p.", this, ownerAsControl)); -#endif // CMH_DEBUG - - if (ownerAsControl != null) - { - ownerAsControl.UpdateVisualState(true /* useTransitions */); - } - } - - internal bool IsDelayCloseTimerRunning() - { - // TODO Uno: Implement this - return false; - } -} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.h.mux.cs new file mode 100644 index 000000000000..c4e9f267fef1 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.h.mux.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\CascadingMenuHelper.h, tag winui3/release/1.5.4, commit 98a60c8 + +using System; +using Uno.Disposables; +using Uno.UI.DataBinding; + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class CascadingMenuHelper +{ + // This fallback is used if we fail to retrieve a value from the MenuShowDelay RegKey + private const int DefaultMenuShowDelay = 400; // in milliseconds + + internal bool IsPressed => m_isPressed; + + internal bool IsPointerOver => m_isPointerOver; + + // The overlapped menu pixels between the main main menu presenter and the sub presenter + private const int m_subMenuOverlapPixels = 4; + + private int m_subMenuShowDelay; + + // Owner of the cascading menu + private ManagedWeakReference m_wpOwner; + + // Presenter of the sub-menu + private ManagedWeakReference m_wpSubMenuPresenter; + + // Event pointer for the Loaded event + private readonly SerialDisposable m_loadedHandler = new(); + + // Dispatcher timer to delay showing the sub menu flyout + private DispatcherTimer m_delayOpenMenuTimer; + + // Dispatcher timer to delay hiding the sub menu flyout + private DispatcherTimer m_delayCloseMenuTimer; + + // Indicate the pointer is over the cascading menu owner + private bool m_isPointerOver; + + // Indicate the pointer is pressed on the cascading menu owner + private bool m_isPressed; +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.mux.cs new file mode 100644 index 000000000000..fc04d58d5bbf --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/CascadingMenuHelper.mux.cs @@ -0,0 +1,827 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\CascadingMenuHelper.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System; +using Uno.Disposables; +using Windows.Foundation; +using Windows.System; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Uno.UI.DataBinding; +using Uno.UI.Xaml.Core; + +#if HAS_UNO_WINUI +using Microsoft.UI.Input; +#else +using Windows.Devices.Input; +using Windows.UI.Input; +#endif + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class CascadingMenuHelper +{ + public CascadingMenuHelper() + { + m_isPointerOver = false; + m_isPressed = false; + m_subMenuShowDelay = -1; + } + + ~CascadingMenuHelper() + { + var delayOpenMenuTimer = m_delayOpenMenuTimer; + if (delayOpenMenuTimer != null) + { + delayOpenMenuTimer.Stop(); + } + + var delayCloseMenuTimer = m_delayCloseMenuTimer; + if (delayCloseMenuTimer != null) + { + delayCloseMenuTimer.Stop(); + } + } + + internal void Initialize(FrameworkElement owner) + { + m_loadedHandler.Disposable = null; + FrameworkElement ownerLocal = owner; + m_wpOwner = WeakReferencePool.RentSelfWeakReference(ownerLocal); + + void OnLoadedClearFlags(object pSender, RoutedEventArgs args) + { + ClearStateFlags(); + } + + owner.Loaded += OnLoadedClearFlags; + m_loadedHandler.Disposable = Disposable.Create(() => owner.Loaded -= OnLoadedClearFlags); + +#if !HAS_UNO // Uno does not read from the registry + // Try and read from Reg Key + HKEY key = null; + + if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, "Control Panel\\Desktop\\", 0, KEY_QUERY_VALUE, &key)) + { + char Buffer[32] = { 0 }; + DWORD BufferSize = sizeof(Buffer); + DWORD dwOutType; + + if (RegQueryValueEx(key, "MenuShowDelay", NULL, &dwOutType/*REG_SZ*/, (LPBYTE)Buffer, &BufferSize) == ERROR_SUCCESS) + { + m_subMenuShowDelay = _wtoi(Buffer); + } + RegCloseKey(key); + } +#endif + + // If the field wasn't successfully populated from the reg key + // Or if the reg key contained a negative number + // Then use a default value + if (m_subMenuShowDelay < 0) + { + m_subMenuShowDelay = DefaultMenuShowDelay; + } + + // Cascading menu owners should be access key scopes by default. + ownerLocal.IsAccessKeyScope = true; + } + + internal void OnApplyTemplate() => UpdateOwnerVisualState(); + + // PointerEntered event handler that shows the sub menu + // whenever the pointer is over the sub menu owner. + // In case of touch, the sub menu item will be shown by + // PointerReleased event. + internal void OnPointerEntered(PointerRoutedEventArgs args) + { + bool handled = false; + + m_isPointerOver = true; + + handled = args.Handled; + + if (!handled) + { + var owner = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as ISubMenuOwner : null; + + if (owner is not null) + { + ISubMenuOwner parentOwner; + parentOwner = owner.ParentOwner; + + if (parentOwner != null) + { + parentOwner.CancelCloseSubMenu(); + } + } + + Pointer pointer = args.Pointer; + var pointerDeviceType = pointer.PointerDeviceType; + + if (pointerDeviceType != PointerDeviceType.Touch) + { + CancelCloseSubMenu(); + + EnsureDelayOpenMenuTimer(); + + m_delayOpenMenuTimer.Start(); + + UpdateOwnerVisualState(); + } + + args.Handled = true; + } + + + } + + // PointerExited event handler that ensures we close the sub menu + // whenever the pointer leaves the current sub menu or + // the main presenter. If the exited point is on the sub menu owner + // or the sub menu, we want to keep the sub menu open. + internal void OnPointerExited( + PointerRoutedEventArgs args, + bool parentIsSubMenu) + { + bool handled = false; + + m_isPointerOver = false; + m_isPressed = false; + + handled = args.Handled; + + if (m_delayOpenMenuTimer != null) + { + m_delayOpenMenuTimer.Stop(); + } + + var ownerAsUIE = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as FrameworkElement : null; + + if (!handled && ownerAsUIE is not null && ownerAsUIE.IsInLiveTree) + { + var pointer = args.Pointer; + var pointerDeviceType = pointer.PointerDeviceType; + + if (PointerDeviceType.Mouse == pointerDeviceType && !parentIsSubMenu) + { + UIElement subMenuPresenterAsUIE = m_wpSubMenuPresenter?.IsAlive == true ? m_wpSubMenuPresenter?.Target as UIElement : null; + + if (subMenuPresenterAsUIE is not null && subMenuPresenterAsUIE.IsInLiveTree) + { + bool isOwnerOrSubMenuHit = false; + var pointerPoint = args.GetCurrentPoint(null); + var point = pointerPoint.Position; + + var elements = VisualTreeHelper.FindElementsInHostCoordinates(point, ownerAsUIE, true /* includeAllElements */); + + foreach (var element in elements) + { + if (ownerAsUIE == element || subMenuPresenterAsUIE == element) + { + isOwnerOrSubMenuHit = true; + break; + } + } + + if (!isOwnerOrSubMenuHit) + { + elements = VisualTreeHelper.FindElementsInHostCoordinates(point, subMenuPresenterAsUIE, true /* includeAllElements */); + + foreach (var element in elements) + { + if (ownerAsUIE == element || subMenuPresenterAsUIE == element) + { + isOwnerOrSubMenuHit = true; + break; + } + } + } + + // To close the sub menu, the pointer must be outside of the opened chain of sub-menus. + if (!isOwnerOrSubMenuHit) + { + DelayCloseSubMenu(); + args.Handled = true; + } + } + } + + UpdateOwnerVisualState(); + } + } + + // PointerPressed event handler ensures that we're in the pressed state. + internal void OnPointerPressed(PointerRoutedEventArgs args) + { + m_isPressed = true; + + args.Handled = true; + } + + // PointerReleased event handler shows the sub menu in the + // case of touch input. + internal void OnPointerReleased(PointerRoutedEventArgs args) + { + Pointer pointer; + PointerDeviceType pointerDeviceType; + + m_isPressed = false; + + pointer = args.Pointer; + pointerDeviceType = pointer.PointerDeviceType; + + // Show the sub menu in the case of touch input. + // In case of the mouse device or pen, the sub menu will be shown whenever the pointer is over + // the sub menu owner. + if (PointerDeviceType.Touch == pointerDeviceType) + { + OpenSubMenu(); + } + + args.Handled = true; + } + + internal void OnGotFocus(RoutedEventArgs args) + { + UpdateOwnerVisualState(); + } + + internal void OnLostFocus(RoutedEventArgs args) + { + m_isPressed = false; + UpdateOwnerVisualState(); + } + + // KeyDown event handler that handles the keyboard navigation between + // the menu items and shows the sub menu in the case where we hit + // the enter, space, or right arrow keys. + internal void OnKeyDown(KeyRoutedEventArgs args) + { + bool handled = false; + + handled = args.Handled; + + if (!handled) + { + var key = args.Key; + var keyStatus = args.KeyStatus; + + if (!keyStatus.IsMenuKeyDown) + { + // Show the sub menu with the enter, space, or right arrow keys + if (key == VirtualKey.Enter || + key == VirtualKey.Right || + key == VirtualKey.Space) + { + OpenSubMenu(); + args.Handled = true; + } + } + } + } + + internal void OnKeyUp(KeyRoutedEventArgs args) + { + UpdateOwnerVisualState(); + args.Handled = true; + } + + // Creates a DispatcherTimer for delaying showing the sub menu flyout + private void EnsureDelayOpenMenuTimer() + { + if (m_delayOpenMenuTimer is null) + { + m_delayOpenMenuTimer = new DispatcherTimer(); + m_delayOpenMenuTimer.Tick += (s, e) => DelayOpenMenuTimerTickHandler(); + + TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(m_subMenuShowDelay); + m_delayOpenMenuTimer.Interval = delayTimeSpan; + } + } + + // Handler for the Tick event on the delay open menu timer. + private void DelayOpenMenuTimerTickHandler() + { + EnsureCloseExistingSubItems(); + + // Open the current sub menu + OpenSubMenu(); + + if (m_delayOpenMenuTimer is not null) + { + m_delayOpenMenuTimer.Stop(); + } + } + + // Creates a DispatcherTimer for delaying hiding the sub menu flyout + private void EnsureDelayCloseMenuTimer() + { + if (m_delayCloseMenuTimer is null) + { + m_delayCloseMenuTimer = new DispatcherTimer(); + m_delayCloseMenuTimer.Tick += (s, e) => DelayCloseMenuTimerTickHandler(); + + TimeSpan delayTimeSpan = TimeSpan.FromMilliseconds(m_subMenuShowDelay); + m_delayCloseMenuTimer.Interval = delayTimeSpan; + } + } + + // Handler for the Tick event on the delay close menu timer. + private void DelayCloseMenuTimerTickHandler() + { + CloseSubMenu(); + + if (m_delayCloseMenuTimer != null) + { + m_delayCloseMenuTimer.Stop(); + UpdateOwnerVisualState(); + } + } + + // Ensure that any currently open sub menus are closed + private void EnsureCloseExistingSubItems() + { + ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.Target as ISubMenuOwner; + + if (ownerAsSubMenuOwner != null) + { + ownerAsSubMenuOwner.ClosePeerSubMenus(); + } + } + + internal void SetSubMenuPresenter(FrameworkElement subMenuPresenter) + { + m_wpSubMenuPresenter = WeakReferencePool.RentSelfWeakReference(subMenuPresenter); + + var ownerAsSubMenuOwner = m_wpOwner?.IsAlive == true ? m_wpOwner.Target as ISubMenuOwner : null; + + if (ownerAsSubMenuOwner != null) + { + var menuPresenter = subMenuPresenter as IMenuPresenter; + + if (menuPresenter != null) + { + menuPresenter.Owner = ownerAsSubMenuOwner; + } + } + } + + // Shows the sub menu at the appropriate position. + // The sub menu will be adjusted if the sub presenter size changes. + internal void OpenSubMenu() + { + var ownerAsSubMenuOwner = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as ISubMenuOwner : null; + + if (ownerAsSubMenuOwner is not null) + { + ownerAsSubMenuOwner.PrepareSubMenu(); + + bool isSubMenuOpen = false; + isSubMenuOpen = ownerAsSubMenuOwner.IsSubMenuOpen; + + if (!isSubMenuOpen) + { + var ownerAsControl = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as Control : null; + + if (ownerAsControl is not null) + { + EnsureCloseExistingSubItems(); + + double subItemWidth = 0; + subItemWidth = ownerAsControl.ActualWidth; + + FlowDirection flowDirection = FlowDirection.LeftToRight; + flowDirection = ownerAsControl.FlowDirection; + + Point subMenuPosition = new Point(0, 0); + + bool isPositionedAbsolutely = false; + isPositionedAbsolutely = ownerAsSubMenuOwner.IsSubMenuPositionedAbsolutely; + + if (isPositionedAbsolutely) + { + GeneralTransform transformToRoot; + transformToRoot = ownerAsControl.TransformToVisual(null); + subMenuPosition = transformToRoot.TransformPoint(subMenuPosition); + } + + var ownerAsMenuFlyoutSubItem = ownerAsControl as MenuFlyoutSubItem; + Popup popup = null; + Control menuFlyoutPresenter = null; + if (ownerAsMenuFlyoutSubItem is not null) + { + popup = ownerAsMenuFlyoutSubItem.GetPopup(); + menuFlyoutPresenter = ownerAsMenuFlyoutSubItem.GetMenuFlyoutPresenter(); + + VisualTree visualTree = VisualTree.GetForElement(ownerAsMenuFlyoutSubItem); + if (visualTree is not null) + { + // Put the popup on the same VisualTree as this flyout sub item to make sure it shows up in the right place + popup.SetAssociatedVisualTree(visualTree); + } + } + + if (flowDirection == FlowDirection.RightToLeft && isPositionedAbsolutely) + { + subMenuPosition.X += (float)(m_subMenuOverlapPixels - subItemWidth); + } + else + { + subMenuPosition.X += (float)(subItemWidth - m_subMenuOverlapPixels); + } + + if (popup is not null && menuFlyoutPresenter is not null) + { + double menuFlyoutPresenterWidth = menuFlyoutPresenter.ActualWidth; + double menuFlyoutPresenterHeight = menuFlyoutPresenter.ActualHeight; + + // GetPositionAndDirection is called to identify the submenu's direction alone. + GetPositionAndDirection( + menuFlyoutPresenterWidth, + menuFlyoutPresenterHeight, + popup, + out var subMenuPosition2, + out var isSubMenuDirectionUp, + out var positionAndDirectionSet); + + global::System.Diagnostics.Debug.Assert(subMenuPosition2.X == double.NegativeInfinity || subMenuPosition2.X == subMenuPosition.X); + global::System.Diagnostics.Debug.Assert(subMenuPosition2.Y == double.NegativeInfinity || subMenuPosition2.Y == subMenuPosition.Y); + + if (positionAndDirectionSet) + { + ownerAsSubMenuOwner.SetSubMenuDirection(isSubMenuDirectionUp); + } + } + + ownerAsSubMenuOwner.OpenSubMenu(subMenuPosition); +#if HAS_UNO + if (_lastTargetPoint is { } lastTargetPoint) + { + // Uno-specific workaround: reapply the location calculated in OnPresenterSizeChanged(), since that one properly + // adjusts to keep submenu within screen bounds. (WinUI seemingly relies upon presenter.SizeChanged being raised + // every time submenu opens? On Uno it isn't.) + ownerAsSubMenuOwner.PositionSubMenu(lastTargetPoint); + } +#endif + ownerAsSubMenuOwner.RaiseAutomationPeerExpandCollapse(true /* isOpen */); + ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Invoke, ownerAsControl); + } + } + } + } + + internal void CloseSubMenu() + { + CloseChildSubMenus(); + + ISubMenuOwner owner = m_wpOwner?.IsAlive == true ? m_wpOwner.Target as ISubMenuOwner : null; + + if (owner is not null) + { + owner.CloseSubMenu(); + owner.RaiseAutomationPeerExpandCollapse(false /* isOpen */); + + var ownerAsDO = owner as DependencyObject; + + if (ownerAsDO != null) + { + ElementSoundPlayer.RequestInteractionSoundForElement(ElementSoundKind.Hide, ownerAsDO as DependencyObject); + } + } + } + + internal void CloseChildSubMenus() + { + // WeakRefPtr fails an assert if we attempt to AsOrNull() to a type + // that we aren't sure if the contents of the weak reference + // implement that type. To avoid that assert, we first As() the + // weak reference contents to a known type that it's guaranteed + // to be (if it isn't null), and we then AsOrNull() the ComPtr, + // once it's safe to ask about a type that we're not sure of. + FrameworkElement subMenuPresenterAsFE = m_wpSubMenuPresenter?.IsAlive == true ? m_wpSubMenuPresenter?.Target as Frame : null; + + IMenuPresenter subMenuPresenter = null; + + if (subMenuPresenterAsFE != null) + { + subMenuPresenter = subMenuPresenterAsFE as IMenuPresenter; + } + + if (subMenuPresenter != null) + { + subMenuPresenter.CloseSubMenu(); + } + } + + internal void DelayCloseSubMenu() + { + EnsureDelayCloseMenuTimer(); + if (m_delayCloseMenuTimer != null) + { + m_delayCloseMenuTimer.Start(); + UpdateOwnerVisualState(); + } + } + + internal void CancelCloseSubMenu() + { + if (m_delayCloseMenuTimer != null) + { + m_delayCloseMenuTimer.Stop(); + UpdateOwnerVisualState(); + } + } + + internal void ClearStateFlags() + { + m_isPressed = false; + m_isPointerOver = false; + UpdateOwnerVisualState(); + } + + internal void OnIsEnabledChanged(IsEnabledChangedEventArgs args) + { + var ownerAsControl = m_wpOwner?.IsAlive == true ? m_wpOwner.Target as Control : null; + + if (ownerAsControl != null) + { + bool bIsEnabled = false; + bIsEnabled = ownerAsControl.IsEnabled; + + if (!bIsEnabled) + { + ClearStateFlags(); + } + else + { + ownerAsControl.UpdateVisualState(true /* useTransitions */); + } + } + } + + public void OnVisibilityChanged() + { + UIElement ownerAsUIE = m_wpOwner?.IsAlive == true ? m_wpOwner.Target as UIElement : null; + + if (ownerAsUIE != null) + { + Visibility visibility = ownerAsUIE.Visibility; + + if (Visibility.Visible != visibility) + { + ClearStateFlags(); + } + } + } + + internal void OnPresenterSizeChanged( + object pSender, + SizeChangedEventArgs args, + Popup popup) + { + ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as ISubMenuOwner : null; + + if (ownerAsSubMenuOwner is not null) + { + Size newPresenterSize = args.NewSize; + + GetPositionAndDirection( + newPresenterSize.Width, + newPresenterSize.Height, + popup, + out var subMenuPosition, + out var isSubMenuDirectionUp, + out var positionAndDirectionSet); + + if (positionAndDirectionSet) + { + _lastTargetPoint = subMenuPosition; + + ownerAsSubMenuOwner.PositionSubMenu(subMenuPosition); + ownerAsSubMenuOwner.SetSubMenuDirection(isSubMenuDirectionUp); + } + } + } + + private void GetPositionAndDirection( + double presenterWidth, + double presenterHeight, + Popup popup, + out Point subMenuPosition, + out bool isSubMenuDirectionUp, + out bool positionAndDirectionSet) + { + // We sometimes will only want to change one of the two XY-positions of the menu, + // but some menus (e.g. AppBarButton.Flyout) don't allow you to individually change + // one axis of the position - you need to close and reopen the menu in a different location. + // This necessitates a single function call that takes a Point parameter rather than + // two function calls that individually change the X and then the Y position of the menu, + // since otherwise we'd be closing and reopening the menu twice if we needed to change + // both positions, which would be visually disruptive. + // As such, we need a way to tell PositionSubMenu to leave one of the positions as it was. + // We'll use negative infinity as a sentinel value that means "don't change this coordinate value". + subMenuPosition = new Point(float.NegativeInfinity, float.NegativeInfinity); + + isSubMenuDirectionUp = false; + positionAndDirectionSet = false; + + Control ownerAsControl = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as Control : null; + + ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as ISubMenuOwner : null; + + Control presenterAsControl = m_wpSubMenuPresenter?.IsAlive == true ? m_wpSubMenuPresenter?.Target as Control : null; + + bool isPositionedAbsolutely = false; + isPositionedAbsolutely = ownerAsSubMenuOwner.IsSubMenuPositionedAbsolutely; + + Point ownerPosition = new Point(0, 0); + + if (isPositionedAbsolutely) + { + // Get the current sub menu item target position as the client point + GeneralTransform transformToRoot = ownerAsControl.TransformToVisual(null); + ownerPosition = transformToRoot.TransformPoint(ownerPosition); + } + +#if HAS_UNO + // TODO: We are missing part of the logic here connected to windowed menus. +#endif + + // Get the available window rect + Rect availableWindowRect = FlyoutBase.CalculateAvailableWindowRect( + true /* isMenuFlyout */, + popup, + null /* placementTarget */, + true /* hasTargetPosition */, + ownerPosition, + false /* isFull */); + + GetPositionAndDirection( + presenterWidth, + presenterHeight, + availableWindowRect, + ownerPosition, + out subMenuPosition, + out isSubMenuDirectionUp, + out positionAndDirectionSet); + } + + private void GetPositionAndDirection( + double presenterWidth, + double presenterHeight, + Rect availableBounds, + Point ownerPosition, + out Point subMenuPosition, + out bool isSubMenuDirectionUp, + out bool positionAndDirectionSet) + { + subMenuPosition = new Point(float.NegativeInfinity, float.NegativeInfinity); + isSubMenuDirectionUp = false; + positionAndDirectionSet = false; + + Control ownerAsControl = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as Control : null; + + ISubMenuOwner ownerAsSubMenuOwner = m_wpOwner?.IsAlive == true ? m_wpOwner?.Target as ISubMenuOwner : null; + + Control presenterAsControl = m_wpSubMenuPresenter?.IsAlive == true ? m_wpSubMenuPresenter?.Target as Control : null; + + if (ownerAsControl is null || presenterAsControl is null) + { + return; + } + + FlowDirection flowDirection = ownerAsControl.FlowDirection; + + // Get the current sub menu item width and height + var ownerWidth = ownerAsControl.ActualWidth; + var ownerHeight = ownerAsControl.ActualHeight; + + // Get the current presenter max width/height + var maxWidth = presenterAsControl.MaxWidth; + var maxHeight = presenterAsControl.MaxHeight; + + // Set the max width and height with the available windows bounds + presenterAsControl.MaxWidth = + double.IsNaN(maxWidth) ? availableBounds.Width : Math.Min(maxWidth, availableBounds.Width); + presenterAsControl.MaxHeight = + double.IsNaN(maxHeight) ? availableBounds.Height : Math.Min(maxHeight, availableBounds.Height); + + // Get the available bottom space to set the MenuFlyoutSubItem + double bottomSpace = availableBounds.Y + availableBounds.Height - ownerPosition.Y; + + if (flowDirection == FlowDirection.LeftToRight) + { + // Get the available right space to set the MenuFlyoutSubItem + double rightSpace = availableBounds.X + availableBounds.Width - ownerPosition.X - ownerWidth; + // If the current sub presenter width isn't enough in the default right space, + // the MenuFlyoutSubItem will be positioned on the left side if the current presenter + // width is less than the sub item left(X) position. Otherwise, it will be aligned + // right side of the available window rect. + if (presenterWidth > rightSpace) + { + if (presenterWidth < availableBounds.Width - rightSpace - ownerWidth) + { + subMenuPosition.X = ownerPosition.X - presenterWidth + m_subMenuOverlapPixels; + } + else + { + subMenuPosition.X = ownerPosition.X + ownerWidth + rightSpace - presenterWidth; + } + } + } + else + { + // Get the available left space to set the MenuFlyoutSubItem + double leftSpace = ownerPosition.X - availableBounds.X - ownerWidth; + // If the current sub presenter width isn't enough in the default left space, + // the MenuFlyoutSubItem will be positioned on the right side if the current presenter + // width is less than the sub item right(X) position. Otherwise, it will be aligned + // left side of the available window rect. + if (presenterWidth > leftSpace) + { + if (presenterWidth < (availableBounds.Width - leftSpace - ownerWidth)) + { + subMenuPosition.X = ownerPosition.X + presenterWidth - m_subMenuOverlapPixels; + } + else + { + subMenuPosition.X = ownerPosition.X - ownerWidth - leftSpace + presenterWidth; + } + } + else + { + subMenuPosition.X = ownerPosition.X - ownerWidth + m_subMenuOverlapPixels; + } + } + + // If the current sub presenter doesn't have space to fit in the default bottom position, + // then the MenuFlyoutSubItem will be aligned with the bottom of the target bounds. + // If the MenuFlyoutSubItem is too tall to fit when bottom aligned with the target bounds + // then it will be bottom aligned with the edge of the monitor. + if (presenterHeight > bottomSpace) + { + // There is not enough bottom space to align top of owner with top of presenter. + double topSpace = availableBounds.Height + ownerHeight - bottomSpace; + + if (topSpace >= presenterHeight) + { + // There is enough top space to align bottom of owner with bottom of presenter. + subMenuPosition.Y = ownerPosition.Y + ownerHeight - presenterHeight; + isSubMenuDirectionUp = true; + } + else + { + // presenterHeight > bottomSpace and presenterHeight > topSpace + if (bottomSpace < topSpace) + { + // Aligning top of presenter with top of available bounds. + subMenuPosition.Y = availableBounds.Y; + isSubMenuDirectionUp = true; + } + else + { + // Aligning top of presenter with top of available bounds. + subMenuPosition.Y = availableBounds.Y; + isSubMenuDirectionUp = true; + } + } + } + else + { + // There is enough bottom space to align top of owner with top of presenter. + subMenuPosition.Y = ownerPosition.Y; + } + + positionAndDirectionSet = true; + } + + private void UpdateOwnerVisualState() + { + Control ownerAsControl = m_wpOwner?.IsAlive == true ? m_wpOwner.Target as Control : null; + +#if CMH_DEBUG + (DebugTrace(XCP_TRACE_OUTPUT_MSG /*traceType*/, "CMH[0x%p]: UpdateOwnerVisualState - ownerAsControl=0x%p.", this, ownerAsControl)); +#endif // CMH_DEBUG + + if (ownerAsControl is not null) + { + ownerAsControl.UpdateVisualState(true /* useTransitions */); + } + } + + internal bool IsDelayCloseTimerRunning() + { + if (m_delayCloseMenuTimer is not null) + { + return m_delayCloseMenuTimer.IsEnabled; + } + + return false; + } + + // Uno-specific workaround (see comment below) + private Point? _lastTargetPoint; +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ISubMenuOwner.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ISubMenuOwner.cs index 4ad590d3e062..ed0a360f41ef 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ISubMenuOwner.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/ISubMenuOwner.cs @@ -22,6 +22,8 @@ public partial interface ISubMenuOwner void RaiseAutomationPeerExpandCollapse(bool isOpen); + void SetSubMenuDirection(bool isSubMenuDirectionUp); + bool IsSubMenuOpen { get; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs index 5b54063e4845..757143395559 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs @@ -550,10 +550,12 @@ private void OnPresenterSizeChanged(object pSender, SizeChangedEventArgs args) } } +#if false // Never called in WinUI private void ClearStateFlags() { m_menuHelper.ClearStateFlags(); } +#endif private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs args) { @@ -577,7 +579,7 @@ ISubMenuOwner ISubMenuOwner.ParentOwner set => m_wrParentOwner = WeakReferencePool.RentWeakReference(this, value); } - private void SetSubMenuDirection(bool isSubMenuDirectionUp) + void ISubMenuOwner.SetSubMenuDirection(bool isSubMenuDirectionUp) { if (m_tpMenuPopupThemeTransition is not null) { diff --git a/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs b/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs index 23f3b0ca1a7c..d1df97d269b2 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs @@ -10,6 +10,7 @@ using Uno.UI; using Uno; using Uno.UI.DataBinding; +using Uno.UI.Xaml.Core; namespace Microsoft.UI.Xaml.Controls.Primitives; @@ -321,4 +322,10 @@ internal Brush LightDismissOverlayBackground // On WinUi, a popup is not IsTabStop but is somehow focusable. This is a workaround to match that behaviour. internal override bool IsFocusableForFocusEngagement() => true; + + internal void SetAssociatedVisualTree(VisualTree visualTree) + { + // TODO Uno: Implement this + //visualTree.AttachElement(this); + } } From 1be2d2e817169d66e2b340cffa57c29d2abc297b Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 10:49:22 +0200 Subject: [PATCH 20/28] chore: Mark additional files updated --- .../Controls/MenuFlyout/MenuFlyout.mux.cs | 6 +- .../MenuFlyout/MenuFlyout.partial.mux.cs | 8 +- .../Controls/MenuFlyout/MenuFlyoutItem.mux.cs | 4 +- .../MenuFlyoutItemBaseCollection.cs | 6 +- .../MenuFlyoutItemTemplateSettings.cs | 19 +- .../MenuFlyout/MenuFlyoutKeyPressProcess.cs | 231 +++++++++--------- .../MenuFlyoutPresenterTemplateSettings.cs | 15 +- 7 files changed, 168 insertions(+), 121 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs index 5f7ee9c0b431..35227d42eff7 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.mux.cs @@ -1,4 +1,8 @@ -using Microsoft.UI.Xaml.Input; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\core\core\elements\MenuFlyout.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using Microsoft.UI.Xaml.Input; using Uno.UI.Xaml; namespace Microsoft.UI.Xaml.Controls; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs index c93601a68f63..7724fccb7ef9 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs @@ -12,6 +12,12 @@ using Uno.UI.Xaml.Input; using Windows.Foundation; +#if HAS_UNO_WINUI +using Microsoft.UI.Dispatching; +#else +using Windows.System; +#endif + namespace Microsoft.UI.Xaml.Controls; partial class MenuFlyout @@ -409,7 +415,7 @@ internal void QueueRefreshItemsSource() // into a single event once all of the changes have completed. if (GetPresenter() is not null && !m_itemsSourceRefreshPending) { - var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); var wrThis = WeakReferencePool.RentSelfWeakReference(this); diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs index c28aa8ecfb84..b08e29cd63ea 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.mux.cs @@ -325,7 +325,7 @@ protected override void OnKeyDown(KeyRoutedEventArgs pArgs) if (!handled) { var key = pArgs.Key; - handled = KeyPressMenuFlyout.KeyDown(key, this); + handled = MenuFlyout.KeyProcess.KeyDown(key, this); pArgs.Handled = handled; } } @@ -339,7 +339,7 @@ protected override void OnKeyUp(KeyRoutedEventArgs pArgs) if (!handled) { var key = (pArgs.Key); - KeyPressMenuFlyout.KeyUp(key, this); + MenuFlyout.KeyProcess.KeyUp(key, this); pArgs.Handled = true; } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs index 12d4329d9941..7ea012b75068 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemBaseCollection.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutItemBaseCollection_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemTemplateSettings.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemTemplateSettings.cs index d62e8aaf2178..a275c2d96efe 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemTemplateSettings.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItemTemplateSettings.cs @@ -1,4 +1,8 @@ -namespace Microsoft.UI.Xaml.Controls.Primitives; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\winrtgeneratedclasses\MenuFlyoutItemTemplateSettings.g.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +namespace Microsoft.UI.Xaml.Controls.Primitives; /// /// Provides calculated values that can be referenced as TemplatedParent sources when defining templates for a MenuFlyoutPresenter control. @@ -9,5 +13,16 @@ public partial class MenuFlyoutItemTemplateSettings : DependencyObject /// /// Gets the minimum width allocated for the accelerator key tip of an MenuFlyout. /// - public double KeyboardAcceleratorTextMinWidth { get; internal set; } + public double KeyboardAcceleratorTextMinWidth + { + get => (double)GetValue(KeyboardAcceleratorTextMinWidthProperty); + internal set => SetValue(KeyboardAcceleratorTextMinWidthProperty, value); + } + + internal static DependencyProperty KeyboardAcceleratorTextMinWidthProperty { get; } = + DependencyProperty.Register( + nameof(KeyboardAcceleratorTextMinWidth), + typeof(double), + typeof(MenuFlyoutItemTemplateSettings), + new FrameworkPropertyMetadata(0.0)); } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutKeyPressProcess.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutKeyPressProcess.cs index 834f39747639..3e12eddb07f3 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutKeyPressProcess.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutKeyPressProcess.cs @@ -1,151 +1,158 @@ -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\components\controls\KeyDownUp\inc\MenuFlyoutKeyPressProcess.h, tag winui3/release/1.5.4, commit 98a60c8 + using Windows.System; namespace Microsoft.UI.Xaml.Controls; -class KeyPressMenuFlyoutPresenter +partial class MenuFlyoutPresenter { - internal static bool KeyDown(VirtualKey key, MenuFlyoutPresenter control) + internal static class KeyProcess { - var pbHandled = false; - - if (key == VirtualKey.Up || - key == VirtualKey.GamepadDPadUp || - key == VirtualKey.GamepadLeftThumbstickUp) - { - control.HandleUpOrDownKey(false); - pbHandled = true; - } - else if (key == VirtualKey.Down || - key == VirtualKey.GamepadDPadDown || - key == VirtualKey.GamepadLeftThumbstickDown) + internal static bool KeyDown(VirtualKey key, MenuFlyoutPresenter control) { - control.HandleUpOrDownKey(true); - pbHandled = true; - } - else if (key == VirtualKey.Tab) - { - pbHandled = true; - } + var pbHandled = false; - // Handle the left key to close the opened MenuFlyoutSubItem. - // The right arrow key is directly handled from the MenuFlyoutSubItem - // to open the sub menu item. - else if (key == VirtualKey.Left || - key == VirtualKey.Escape) - { - if (control.IsSubPresenter) + if (key == VirtualKey.Up || + key == VirtualKey.GamepadDPadUp || + key == VirtualKey.GamepadLeftThumbstickUp) { - control.HandleKeyDownLeftOrEscape(); + control.HandleUpOrDownKey(false); pbHandled = true; } - else + else if (key == VirtualKey.Down || + key == VirtualKey.GamepadDPadDown || + key == VirtualKey.GamepadLeftThumbstickDown) { - // If this is the top-level menu, let Popup close it (on Escape key press) - pbHandled = false; + control.HandleUpOrDownKey(true); + pbHandled = true; } - } - - return pbHandled; - } -} - -class KeyPressMenuFlyout -{ - internal static bool KeyDown(VirtualKey key, MenuFlyoutItem control) - { - var pbHandled = false; - - // If SPACE/ENTER/NAVIGATION_ACCEPT/GAMEPAD_A is already down and a different key is now pressed, - // then cancel the SPACE/ENTER/NAVIGATION_ACCEPT/GAMEPAD_A press. - if (control.m_bIsSpaceOrEnterKeyDown || control.m_bIsNavigationAcceptOrGamepadAKeyDown) - { - if (key != VirtualKey.Space && key != VirtualKey.Enter && control.m_bIsSpaceOrEnterKeyDown) + else if (key == VirtualKey.Tab) { - control.m_bIsSpaceOrEnterKeyDown = false; + pbHandled = true; } - if (control.m_bIsNavigationAcceptOrGamepadAKeyDown && // The key down flag is set - key != VirtualKey.GamepadA) // AND it's not the GamepadA key + + // Handle the left key to close the opened MenuFlyoutSubItem. + // The right arrow key is directly handled from the MenuFlyoutSubItem + // to open the sub menu item. + else if (key == VirtualKey.Left || + key == VirtualKey.Escape) { - control.m_bIsNavigationAcceptOrGamepadAKeyDown = false; + if (control.IsSubPresenter) + { + control.HandleKeyDownLeftOrEscape(); + pbHandled = true; + } + else + { + // If this is the top-level menu, let Popup close it (on Escape key press) + pbHandled = false; + } } - control.m_bIsPressed = false; - control.UpdateVisualState(); + return pbHandled; } + } +} - if (key == VirtualKey.Up || - key == VirtualKey.GamepadDPadUp || - key == VirtualKey.GamepadLeftThumbstickUp) - { - MenuFlyoutPresenter spParentMenuFlyoutPresenter = control.GetParentMenuFlyoutPresenter(); - if (spParentMenuFlyoutPresenter != null) - { - spParentMenuFlyoutPresenter.HandleUpOrDownKey(false); - pbHandled = true; - } - } - else if (key == VirtualKey.Down || - key == VirtualKey.GamepadDPadDown || - key == VirtualKey.GamepadLeftThumbstickDown) +partial class MenuFlyout +{ + internal static class KeyProcess + { + internal static bool KeyDown(VirtualKey key, MenuFlyoutItem control) { - MenuFlyoutPresenter spParentMenuFlyoutPresenter = control.GetParentMenuFlyoutPresenter(); - if (spParentMenuFlyoutPresenter != null) + var pbHandled = false; + + // If SPACE/ENTER/NAVIGATION_ACCEPT/GAMEPAD_A is already down and a different key is now pressed, + // then cancel the SPACE/ENTER/NAVIGATION_ACCEPT/GAMEPAD_A press. + if (control.m_bIsSpaceOrEnterKeyDown || control.m_bIsNavigationAcceptOrGamepadAKeyDown) { - spParentMenuFlyoutPresenter.HandleUpOrDownKey(true); - pbHandled = true; + if (key != VirtualKey.Space && key != VirtualKey.Enter && control.m_bIsSpaceOrEnterKeyDown) + { + control.m_bIsSpaceOrEnterKeyDown = false; + } + if (control.m_bIsNavigationAcceptOrGamepadAKeyDown && // The key down flag is set + key != VirtualKey.GamepadA) // AND it's not the GamepadA key + { + control.m_bIsNavigationAcceptOrGamepadAKeyDown = false; + } + + control.m_bIsPressed = false; + control.UpdateVisualState(); } - } - else if (key == VirtualKey.Space || - key == VirtualKey.Enter || - key == VirtualKey.GamepadA) - { - control.m_bIsPressed = true; - if (key == VirtualKey.Space || key == VirtualKey.Enter) + if (key == VirtualKey.Up || + key == VirtualKey.GamepadDPadUp || + key == VirtualKey.GamepadLeftThumbstickUp) { - control.m_bIsSpaceOrEnterKeyDown = true; + MenuFlyoutPresenter spParentMenuFlyoutPresenter = control.GetParentMenuFlyoutPresenter(); + if (spParentMenuFlyoutPresenter != null) + { + spParentMenuFlyoutPresenter.HandleUpOrDownKey(false); + pbHandled = true; + } } - else if (key == VirtualKey.GamepadA) + else if (key == VirtualKey.Down || + key == VirtualKey.GamepadDPadDown || + key == VirtualKey.GamepadLeftThumbstickDown) { - control.m_bIsNavigationAcceptOrGamepadAKeyDown = true; + MenuFlyoutPresenter spParentMenuFlyoutPresenter = control.GetParentMenuFlyoutPresenter(); + if (spParentMenuFlyoutPresenter != null) + { + spParentMenuFlyoutPresenter.HandleUpOrDownKey(true); + pbHandled = true; + } } + else if (key == VirtualKey.Space || + key == VirtualKey.Enter || + key == VirtualKey.GamepadA) + { + control.m_bIsPressed = true; - control.UpdateVisualState(); - pbHandled = true; - } + if (key == VirtualKey.Space || key == VirtualKey.Enter) + { + control.m_bIsSpaceOrEnterKeyDown = true; + } + else if (key == VirtualKey.GamepadA) + { + control.m_bIsNavigationAcceptOrGamepadAKeyDown = true; + } - return pbHandled; - } + control.UpdateVisualState(); + pbHandled = true; + } - internal static bool KeyUp(VirtualKey key, MenuFlyoutItem control) - { - var pbHandled = false; + return pbHandled; + } - if (key == VirtualKey.Space || - key == VirtualKey.Enter || - key == VirtualKey.GamepadA) + internal static bool KeyUp(VirtualKey key, MenuFlyoutItem control) { - if (key == VirtualKey.Space || key == VirtualKey.Enter) - { - control.m_bIsSpaceOrEnterKeyDown = false; - } - else if (key == VirtualKey.GamepadA) - { - control.m_bIsNavigationAcceptOrGamepadAKeyDown = false; - } + var pbHandled = false; - if (control.m_bIsPressed && !control.m_bIsPointerLeftButtonDown) + if (key == VirtualKey.Space || + key == VirtualKey.Enter || + key == VirtualKey.GamepadA) { - control.m_bIsPressed = false; - control.UpdateVisualState(); - control.Invoke(); - pbHandled = true; + if (key == VirtualKey.Space || key == VirtualKey.Enter) + { + control.m_bIsSpaceOrEnterKeyDown = false; + } + else if (key == VirtualKey.GamepadA) + { + control.m_bIsNavigationAcceptOrGamepadAKeyDown = false; + } + + if (control.m_bIsPressed && !control.m_bIsPointerLeftButtonDown) + { + control.m_bIsPressed = false; + control.UpdateVisualState(); + control.Invoke(); + pbHandled = true; + } } - } - return pbHandled; + return pbHandled; + } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenterTemplateSettings.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenterTemplateSettings.cs index c0d90949e4ca..8dda86c0c400 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenterTemplateSettings.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenterTemplateSettings.cs @@ -1,4 +1,8 @@ -namespace Microsoft.UI.Xaml.Controls.Primitives; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\winrtgeneratedclasses\MenuFlyoutPresenterTemplateSettings.g.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +namespace Microsoft.UI.Xaml.Controls.Primitives; /// /// Provides calculated values that can be referenced as TemplatedParent sources when defining templates for a MenuFlyoutPresenter control. @@ -9,5 +13,12 @@ public partial class MenuFlyoutPresenterTemplateSettings : DependencyObject /// /// Gets the minimum width of flyout content. /// - public double FlyoutContentMinWidth { get; internal set; } + public double FlyoutContentMinWidth + { + get => (double)GetValue(FlyoutContentMinWidthProperty); + internal set => SetValue(FlyoutContentMinWidthProperty, value); + } + + internal static DependencyProperty FlyoutContentMinWidthProperty { get; } = + DependencyProperty.Register(nameof(FlyoutContentMinWidth), typeof(double), typeof(MenuFlyoutPresenterTemplateSettings), new FrameworkPropertyMetadata(0.0)); } From 842defd1312036dd6e2265f52ce6a3de9201ad08 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 11:31:26 +0200 Subject: [PATCH 21/28] feat: ElevationHelper --- src/Uno.UI/DirectUI/ElevationHelper.cs | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/Uno.UI/DirectUI/ElevationHelper.cs diff --git a/src/Uno.UI/DirectUI/ElevationHelper.cs b/src/Uno.UI/DirectUI/ElevationHelper.cs new file mode 100644 index 000000000000..dc7f7d4547de --- /dev/null +++ b/src/Uno.UI/DirectUI/ElevationHelper.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; + +namespace DirectUI; + +internal static class ElevationHelper +{ + // The initial Z offset applied to all elevated controls + private const float s_elevationBaseDepth = 32.0f; + // This additional Z offset will be applied for each tier of logically parented controls + private const float s_elevationIterativeDepth = 8.0f; + + internal static void ApplyThemeShadow(UIElement target) + { + var themeShadow = new ThemeShadow(); + target.Shadow = themeShadow; + } + + internal static void ApplyElevationEffect(UIElement target, int depth) + { + // Calculate the Z offset based on the depth of the shadow + var calculatedZDepth = s_elevationBaseDepth + (depth * s_elevationIterativeDepth); + + var endTranslation = new Vector3(0.0f, 0.0f, calculatedZDepth); + + // Apply a translation facade value + target.Translation = endTranslation; + + // Apply a shadow to the element + ApplyThemeShadow(target); + } +} From 0017c4ed5865843485ca791581c3cf26828d6a72 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 11:31:45 +0200 Subject: [PATCH 22/28] feat: Update MenuFlyoutPresenter to winui3/release/1.5.4 --- .../MenuFlyoutPresenter.Properties.cs | 8 + .../MenuFlyout/MenuFlyoutPresenter.cs | 987 +----------------- .../MenuFlyoutPresenter.partial.h.mux.cs | 68 ++ .../MenuFlyoutPresenter.partial.mux.cs | 959 +++++++++++++++++ 4 files changed, 1036 insertions(+), 986 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.Properties.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.h.mux.cs create mode 100644 src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.Properties.cs new file mode 100644 index 000000000000..454cc0c5b06a --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.Properties.cs @@ -0,0 +1,8 @@ +using Microsoft.UI.Xaml.Controls.Primitives; + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyoutPresenter +{ + public MenuFlyoutPresenterTemplateSettings TemplateSettings { get; private set; } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs index daca9d1d18af..f46990011d9f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.cs @@ -1,990 +1,5 @@ -using System; -using System.Collections.Generic; -using Uno.UI; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Automation; -using Microsoft.UI.Xaml.Automation.Peers; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; - - -#if HAS_UNO_WINUI -using Microsoft.UI.Input; -#else -using Windows.Devices.Input; -using Windows.UI.Input; -#endif - -namespace Microsoft.UI.Xaml.Controls; +namespace Microsoft.UI.Xaml.Controls; public partial class MenuFlyoutPresenter : ItemsControl, IMenuPresenter { - // Can be negative. (-1) means nothing focused. - internal int m_iFocusedIndex; - - // Weak reference to the menu that ultimately owns this MenuFlyoutPresenter. - private WeakReference m_wrOwningMenu; - - // Weak reference to the parent MenuFlyout. - private WeakReference m_wrParentMenuFlyout; - - // Weak reference to the owner of this menu. - // Only populated if this is the presenter for an ISubMenuOwner. - private WeakReference m_wrOwner; - - // Weak reference to the sub-presenter that was created by a child menu owner. - private WeakReference m_wrSubPresenter; - - // Whether ItemsSource contains at least one ToggleMenuFlyoutItem. - private bool m_containsToggleItems; - - // Whether ItemsSource contains at least one MenuFlyoutItem with an Icon. - private bool m_containsIconItems; - - // Whether ItemsSource contains at least one MenuFlyoutItem or ToggleMenuFlyoutItem with KeyboardAcceleratorText. - private bool m_containsItemsWithKeyboardAcceleratorText; - - // UNO TODO - // private bool m_animationInProgress; - - private bool m_isSubPresenter; - - private int m_depth; - - //private FlyoutBase.MajorPlacementMode m_mostRecentPlacement; - - private ScrollViewer m_tpScrollViewer; - - public MenuFlyoutPresenterTemplateSettings TemplateSettings { get; } = new MenuFlyoutPresenterTemplateSettings(); - - public MenuFlyoutPresenter() - { - m_iFocusedIndex = -1; - m_containsToggleItems = false; - m_containsIconItems = false; - m_containsItemsWithKeyboardAcceleratorText = false; - // UNO TODO - // m_animationInProgress = false; - m_isSubPresenter = false; - //m_mostRecentPlacement = FlyoutBase.MajorPlacementMode.Bottom; - - DefaultStyleKey = typeof(MenuFlyoutPresenter); - } - - internal bool IsSubPresenter { get => m_isSubPresenter; set => m_isSubPresenter = value; } - - // Responds to the KeyDown event. - - protected override void OnKeyDown(KeyRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - - if (!handled) - { - var key = pArgs.Key; - pArgs.Handled = KeyPressMenuFlyoutPresenter.KeyDown(key, this); - } - } - - internal void HandleUpOrDownKey(bool isDownKey) - { - CycleFocus(isDownKey, FocusState.Keyboard); - } - - void CycleFocus(bool shouldCycleDown, FocusState focusState) - { - - // Ensure the initial focus index to validate m_iFocusedIndex when the focused item - // is set by application's code like as MenuFlyout Opened event. - EnsureInitialFocusIndex(); - - var originalFocusedIndex = m_iFocusedIndex; - - var parentFlyout = GetParentMenuFlyout(); - - // We should wrap around at the bottom or the top of the presenter if the user isn't using a gamepad or remote. - var shouldWrap = parentFlyout != null ? (parentFlyout.InputDeviceTypeUsedToOpen != Uno.UI.Xaml.Input.InputDeviceType.GamepadOrRemote) : true; - - var nCount = Items.Size; - - // Determine direction of index movement based on the Up/Down key. - var deltaIndex = shouldCycleDown ? 1 : -1; - - // Set index by moving deltaIndex amount from the current focused item. - var index = m_iFocusedIndex + deltaIndex; - - // We have two locations where we want to wrap, so we'll encapsulate the wrapping behavior in a function object that we can call. - int wrapIndexIfNeeded(int indexToWrap) - { - if (shouldWrap) - { - if (indexToWrap < 0) - { - indexToWrap = (int)(nCount) - 1; - } - else if (indexToWrap >= (int)(nCount)) - { - indexToWrap = 0; - } - } - - return indexToWrap; - } - - // If there is no item focused right now, then set index to 0 for Down key or to n-1 for Up key. - // Otherwise, if we should be wrapping, we'll do an initial check for whether we should wrap before we enter the loop. - if (m_iFocusedIndex == -1) - { - index = shouldCycleDown ? 0 : (int)(nCount) - 1; - - // If the focused index is -1, then our value of -1 for originalFocusedIndex will not successfully stop the loop. - // In this case, we'll make originalFocusedIndex one step in the opposite direction from the initial index, - // so that way the loop will go all the way through the list of items before stopping. - originalFocusedIndex = wrapIndexIfNeeded(index - deltaIndex); - } - else - { - index = wrapIndexIfNeeded(index); - } - - // We need to examine all items with indices [0, m_iFocusedIndex) or (m_iFocusedIndex, nCount-1] for Down/Up keys. - // While index is within the range, we keep going through the item list until we are successfully able to focus an item, - // at which point we update the m_iFocusedIndex and break out of the loop. - while (0 <= index && index < (int)(nCount)) - { - var spItemAsDependencyObject = Items[index] as DependencyObject; - - var isFocusable = false; - // We determine whether the item is a focusable MenuFlyoutItem or MenuFlyoutSubItem because we want to exclude MenuSeparators here. - var spItem = spItemAsDependencyObject as MenuFlyoutItem; - if (spItem != null) - { - isFocusable = spItem.IsFocusable; - } - else - { - MenuFlyoutSubItem spSubItem; - spSubItem = spItemAsDependencyObject as MenuFlyoutSubItem; - if (spSubItem != null) - { - isFocusable = spSubItem.IsFocusable; - } - } - - // If the item is focusable, move the focus to it, update the m_iFocusedIndex and break out of the loop. - if (isFocusable) - { - var spSubItem = spItemAsDependencyObject as Control; - if (spSubItem != null) - { - spSubItem.Focus(focusState); - m_iFocusedIndex = index; - break; - } - } - - // If we've gone all the way around the list of items and still have not found a suitable focus candidate, - // then we'll stop - there's nothing else for us to do. - if (index == originalFocusedIndex) - { - break; - } - - index += deltaIndex; - - // If we should be wrapping, then we'll perform the wrap at this point. - index = wrapIndexIfNeeded(index); - } - - } - - internal void HandleKeyDownLeftOrEscape() - { - (this as IMenuPresenter).CloseSubMenu(); - } - - - protected override void PrepareContainerForItemOverride( - DependencyObject pElement, - object pItem) - { - base.PrepareContainerForItemOverride(pElement, pItem); - - var spMenuFlyoutItemBase = pElement as MenuFlyoutItemBase; - - spMenuFlyoutItemBase.SetParentMenuFlyoutPresenter(this); - - SynchronizeTemplatedParent(spMenuFlyoutItemBase); - } - - private void SynchronizeTemplatedParent(MenuFlyoutItemBase spMenuFlyoutItemBase) - { - // Manual propagation of the templated parent to the content properly - // until we get the propagation running properly - if (spMenuFlyoutItemBase is FrameworkElement content) - { - content.TemplatedParent = TemplatedParent; - } - } - - protected override void ClearContainerForItemOverride( - DependencyObject pElement, - object pItem) - { - base.ClearContainerForItemOverride(pElement, pItem); - - (pElement as MenuFlyoutItemBase).SetParentMenuFlyoutPresenter(null); - } - - // Get the parent MenuFlyout. - internal MenuFlyout GetParentMenuFlyout() - { - return m_wrParentMenuFlyout?.Target as MenuFlyout; - } - - // Sets the parent MenuFlyout. - internal void SetParentMenuFlyout(MenuFlyout pParentMenuFlyout) - { - m_wrParentMenuFlyout = new WeakReference(pParentMenuFlyout); - } - - // Called when the ItemsSource property changes. - protected override void OnItemsSourceChanged(DependencyPropertyChangedEventArgs args) - { - base.OnItemsSourceChanged(args); - var pNewValue = args.NewValue; - - m_iFocusedIndex = -1; - m_containsToggleItems = false; - m_containsIconItems = false; - m_containsItemsWithKeyboardAcceleratorText = false; - - if (pNewValue != null) - { - IList spItems; - spItems = pNewValue as IList; - if (spItems == null) - { - // MenuFlyoutPresenter could be used outside of MenuFlyout, but at this time - // we don't support that usage. If a customer is using MenuFlyoutPresenter - // independently and uses an ItemsSource that is not an IVector, - // we throw E_INVALIDARG to indicate that this usage is invalid. - // If we decide to allow this usage, we need to override and implement - // IsItemItsOwnContainerOverride() and GetContainerForItemOverride(). - // - // GetContainerForItemOverride() is tricky since MenuFlyoutPresenter supports 3 different - // kinds of children (MenuFlyoutSeparator, MenuFlyoutItem, ToggleMenuFlyoutItem), - // so we are punting on that scenario for now. - throw new InvalidOperationException("Cannot use MenuFlyoutPresenter outside of a MenuFlyout"); - } - - // MenuFlyoutItem's alignment changes based on the layout of other MenuFlyoutItems. - // This check looks through all MenuFlyoutItems in our items source and checks for - // ToggleMenuFlyoutItems and the presence of Icons, which can change the layout of - // all MenuFlyoutItems in the presenter. - var nCount = spItems.Count; - for (var i = 0; i < nCount; ++i) - { - MenuFlyoutItemBase item; - MenuFlyoutItem itemAsMenuItem; - MenuFlyoutSubItem itemAsMenuSubItem; - IconElement iconElement; - string keyboardAcceleratorText; - - item = spItems[i] as MenuFlyoutItemBase; - - // To prevent casting the same item more than we need to, each cast is conditional - // on the previous one failing. This way we only cast each item as many times as we - // need to. - itemAsMenuItem = item as MenuFlyoutItem; - if (itemAsMenuItem != null) - { - m_containsToggleItems = m_containsToggleItems || (itemAsMenuItem as MenuFlyoutItem).HasToggle(); - - iconElement = (itemAsMenuItem as MenuFlyoutItem).Icon; - m_containsIconItems = m_containsIconItems || iconElement != null; - - keyboardAcceleratorText = (itemAsMenuItem as MenuFlyoutItem).KeyboardAcceleratorTextOverride; - m_containsItemsWithKeyboardAcceleratorText = m_containsItemsWithKeyboardAcceleratorText || !string.IsNullOrEmpty(keyboardAcceleratorText); - } - else - { - itemAsMenuSubItem = item as MenuFlyoutSubItem; - if (itemAsMenuSubItem != null) - { - iconElement = (itemAsMenuSubItem as MenuFlyoutSubItem).Icon; - m_containsIconItems = m_containsIconItems || iconElement != null; - } - } - - if (m_containsIconItems && m_containsToggleItems && m_containsItemsWithKeyboardAcceleratorText) - { - break; - } - } - - UpdateTemplateSettings(); - } - } - - // Create MenuFlyoutPresenterAutomationPeer to represent the - protected override AutomationPeer OnCreateAutomationPeer() - { - return new MenuFlyoutPresenterAutomationPeer(this); - } - -#if false - void UpdateVisualStateForPlacement(FlyoutBase.MajorPlacementMode placement) - { - m_mostRecentPlacement = placement; - - UpdateVisualState(false); - } - - void ResetVisualState() - { - VisualStateManager.GoToState(this, "None", false); - } -#endif - private protected override void ChangeVisualState( - // true to use transitions when updating the visual state, false - // to snap directly to the new visual state. - bool bUseTransitions) - { - } - - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - ScrollViewer spScrollViewer; - - // Get the ScrollViewer template part - spScrollViewer = this.GetTemplateChild("MenuFlyoutPresenterScrollViewer") as ScrollViewer; - m_tpScrollViewer = spScrollViewer; - - // Apply a shadow - - // UNO TODO - //bool isDefaultShadowEnabled = IsDefaultShadowEnabled; - //if (isDefaultShadowEnabled) - //{ - // DependencyObject spChild; - // VisualTreeHelper.GetChildStatic(this, 0, &spChild); - // var spChildAsUIE = spChild as UIElement; - - // if (spChildAsUIE != null) - // { - // ApplyElevationEffect(spChildAsUIE, GetDepth()); - // } - //} - } - - internal string GetOwnerName() - { - var parentMenuFlyout = GetParentMenuFlyout(); - - if (parentMenuFlyout is not null) - { - return parentMenuFlyout.GetValue(FrameworkElement.NameProperty) as string; // TODO Uno: Should be DependencyObject.Name - } - - return null; - } - - // UNO TODO - // - //Timeline AttachEntranceAnimationCompleted( - // string pszStateName, - // uint nStateNameLength, - // TimelineCompletedEventCallback pCompletedEvent) - //{ - // bool found = false; - // VisualState spState; - - // VisualStateManager.TryGetState(this, pszStateName, null, &spState, &found); - - // if (found && spState) - // { - // Storyboard spStoryboard; - - // spStoryboard = (spState.Storyboard); - // if (spStoryboard != null) - // { - // Timeline spTimeline; - // (spStoryboard.As(&spTimeline)); - - // (pCompletedEvent.AttachEventHandler(spTimeline, - // std.bind(&OnEntranceAnimationCompleted, this, _1, _2))); - - // (spTimeline.CopyTo(ppTimeline)); - // } - // } - //} - -#if false - void DetachEntranceAnimationCompletedHandlers() - { - // Marked as no longer used - // - //if (m_epTopPortraitCompletedHandler) - //{ - // ASSERT(m_tpTopPortraitTimeline); - // (m_epTopPortraitCompletedHandler.DetachEventHandler(m_tpTopPortraitTimeline)); - // m_tpTopPortraitTimeline.Clear(); - //} - - //if (m_epBottomPortraitCompletedHandler) - //{ - // ASSERT(m_tpBottomPortraitTimeline); - // (m_epBottomPortraitCompletedHandler.DetachEventHandler(m_tpBottomPortraitTimeline)); - // m_tpBottomPortraitTimeline.Clear(); - //} - - //if (m_epLeftLandscapeCompletedHandler) - //{ - // ASSERT(m_tpLeftLandscapeTimeline); - // (m_epLeftLandscapeCompletedHandler.DetachEventHandler(m_tpLeftLandscapeTimeline)); - // m_tpLeftLandscapeTimeline.Clear(); - //} - - //if (m_epRightLandscapeCompletedHandler) - //{ - // ASSERT(m_tpRightLandscapeTimeline); - // (m_epRightLandscapeCompletedHandler.DetachEventHandler(m_tpRightLandscapeTimeline)); - // m_tpRightLandscapeTimeline.Clear(); - //} - } - - void OnEntranceAnimationCompleted( - DependencyObject pSender, - DependencyObject pArgs) - { - // UNO TODO - // m_animationInProgress = false; - - Focus(FocusState.Programmatic); - } -#endif - protected override void OnPointerExited(PointerRoutedEventArgs pArgs) - { - var handled = pArgs.Handled; - - if (!handled) - { - Pointer spPointer; - spPointer = (pArgs.Pointer); - var pointerDeviceType = (spPointer.PointerDeviceType); - if (PointerDeviceType.Mouse == (PointerDeviceType)pointerDeviceType && !m_isSubPresenter) - { - var isHitVerticalScrollBarOrSubPresenter = false; - IMenuPresenter subPresenter; - - // Hit test the current position for the vertical ScrollBar and the sub presenter. - // Close the existing sub presenter if the current mouse position isn't hit - // the vertical ScrollBar nor sub presenter. - - subPresenter = (this as IMenuPresenter).SubPresenter; - - if (subPresenter != null) - { - PointerPoint spPointerPoint; - UIElement spSubPresenterAsUIE; - - spSubPresenterAsUIE = subPresenter as UIElement; - - spPointerPoint = pArgs.GetCurrentPoint(null /* relativeTo*/); - var clientLogicalPointerPosition = spPointerPoint.Position; - - if (m_tpScrollViewer != null) - { - UIElement spVerticalScrollBarAsUE; - Control spScrollViewerAsControl; - DependencyObject spVerticalScrollBarAsDO; - - spScrollViewerAsControl = m_tpScrollViewer as Control; - spVerticalScrollBarAsDO = spScrollViewerAsControl.GetTemplateChild("VerticalScrollBar"); - spVerticalScrollBarAsUE = spVerticalScrollBarAsDO as UIElement; - - if (spSubPresenterAsUIE != null || spVerticalScrollBarAsUE != null) - { - var spElements = VisualTreeHelper.FindElementsInHostCoordinates(clientLogicalPointerPosition, spVerticalScrollBarAsUE, true /* includeAllElements */); - - foreach (var spElement in spElements) - { - DependencyObject pElementAsCDO = spElement; - - if ((pElementAsCDO is ScrollBar && spVerticalScrollBarAsUE == spElement) || - (spSubPresenterAsUIE == spElement)) - { - isHitVerticalScrollBarOrSubPresenter = true; - break; - } - } - - if (!isHitVerticalScrollBarOrSubPresenter) - { - spElements = VisualTreeHelper.FindElementsInHostCoordinates(clientLogicalPointerPosition, spSubPresenterAsUIE, true /* includeAllElements */); - - foreach (var spElement in spElements) - { - DependencyObject pElementAsCDO = spElement; - - if ((pElementAsCDO is ScrollBar && spVerticalScrollBarAsUE == spElement) || - (spSubPresenterAsUIE == spElement)) - { - isHitVerticalScrollBarOrSubPresenter = true; - break; - } - } - } - } - } - - // The opened MenuFlyoutSubItem won't to be closed if the mouse position is - // on the vertical ScrollBar or sub presenter. - if (!isHitVerticalScrollBarOrSubPresenter) - { - var subPresenterBoundsLogical = GetSubPresenterBounds(spSubPresenterAsUIE); - var containsSubPresenter = subPresenterBoundsLogical.Contains(clientLogicalPointerPosition); - - if (!containsSubPresenter) - { - DelayCloseMenuFlyoutSubItem(); - } - } - } - } - - pArgs.Handled = true; - } - } - - internal bool GetContainsToggleItems() - { - return m_containsToggleItems; - } - - // Returns true if the ItemsSource contains at least one MenuFlyoutItem with an Icon; false otherwise. - internal bool GetContainsIconItems() - { - return m_containsIconItems; - } - - // Returns true if the ItemsSource contains at least one MenuFlyoutItem with an Icon; false otherwise. - internal bool GetContainsItemsWithKeyboardAcceleratorText() - { - return m_containsItemsWithKeyboardAcceleratorText; - } - - private Rect GetSubPresenterBounds(UIElement pSubPresenterAsUIE) - { - var t = pSubPresenterAsUIE.TransformToVisual(null); - var r = t.TransformBounds(pSubPresenterAsUIE.LayoutSlotWithMarginsAndAlignments); - - return r; - } - - protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) - { - base.OnPointerEntered(pArgs); - - var handled = pArgs.Handled; - - if (!handled) - { - Pointer spPointer; - spPointer = pArgs.Pointer; - var pointerDeviceType = spPointer.PointerDeviceType; - if (PointerDeviceType.Mouse == (PointerDeviceType)pointerDeviceType && m_isSubPresenter) - { - CancelCloseMenuFlyoutSubItem(); - var owner = (this as IMenuPresenter).Owner; - - if (owner != null) - { - var parentSubItem = owner as MenuFlyoutSubItem; - if (parentSubItem != null) - { - var presenter = parentSubItem.GetParentMenuFlyoutPresenter(); - if (presenter != null) - { - // When the mouse enters a MenuFlyoutPresenter that is a sub menu - // we have to tell the parent presenter to cancel any plans it had - // to close this sub - presenter.CancelCloseMenuFlyoutSubItem(); - } - } - } - } - - pArgs.Handled = true; - } - } - - private protected override string GetPlainText() - { - string automationName = null; - - var ownerFlyout = m_wrParentMenuFlyout.Target as DependencyObject; - - if (ownerFlyout != null) - { - // If an automation name is set on the parent flyout, we'll use that as our plain text. - // Otherwise, we'll report the default plain text. - automationName = AutomationProperties.GetName(ownerFlyout); - } - - if (automationName != null) - { - return automationName; - } - else - { - // UNO TODO - - // If we have no title, we'll fall back to the default implementation, - // which retrieves our content as plain text (e.g., if our content is a string, - // it returns that; if our content is a TextBlock, it returns its Text value, etc.) - // MenuFlyoutPresenterGenerated.GetPlainText(strPlainText); - - // If we get the plain text from the content, then we want to truncate it, - // in case the resulting automation name is very long. - // Popup.TruncateAutomationName(strPlainText); - } - - return automationName; - } - - void IMenuPresenter.CloseSubMenu() - { - var subPresenter = m_wrSubPresenter?.Target as IMenuPresenter; - - if (subPresenter != null) - { - subPresenter.CloseSubMenu(); - } - - ISubMenuOwner owner; - owner = m_wrOwner?.Target as ISubMenuOwner; - - if (owner != null) - { - owner.CloseSubMenu(); - } - - // Reset the focused index not to cached the previous focused index when - // the sub menu is opened in the next time - m_iFocusedIndex = -1; - } - - void DelayCloseMenuFlyoutSubItem() - { - var subMenuPresenter = m_wrSubPresenter?.Target as IMenuPresenter; - - if (subMenuPresenter != null) - { - ISubMenuOwner subMenuOwner; - subMenuOwner = (subMenuPresenter.Owner); - - if (subMenuOwner != null) - { - subMenuOwner.DelayCloseSubMenu(); - } - } - } - - void CancelCloseMenuFlyoutSubItem() - { - var subMenuPresenter = m_wrSubPresenter?.Target as IMenuPresenter; - - if (subMenuPresenter != null) - { - ISubMenuOwner subMenuOwner; - subMenuOwner = (subMenuPresenter.Owner); - - if (subMenuOwner != null) - { - subMenuOwner.CancelCloseSubMenu(); - } - } - } - -#if false - DependencyObject GetParentMenuFlyoutSubItem(DependencyObject nativeDO) - { - var spThis = nativeDO as MenuFlyoutPresenter; - ISubMenuOwner spResult; - spResult = (spThis as IMenuPresenter).Owner; - - var spResultAsMenuFlyoutSubItem = spResult as MenuFlyoutSubItem; - - if (spResultAsMenuFlyoutSubItem != null) - { - return spResultAsMenuFlyoutSubItem as DependencyObject; - } - else - { - return null; - } - } -#endif - - internal void UpdateTemplateSettings() - { - var templateSettings = TemplateSettings; - var templateSettingsConcrete = templateSettings as MenuFlyoutPresenterTemplateSettings; - - var ownerFlyout = GetParentMenuFlyout(); - - if (ownerFlyout != null && templateSettingsConcrete != null) - { - // Query MenuFlyout Content MinWidth, given the input mode, from resource dictionary. - var flyoutContentMinWidth = ResourceResolver.ResolveTopLevelResourceDouble( - (ownerFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.Touch || ownerFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.GamepadOrRemote) - ? "FlyoutThemeTouchMinWidth" : "FlyoutThemeMinWidth" - ); - var visibleBounds = this.LayoutSlotWithMarginsAndAlignments; - // DXamlCore.GetCurrent().GetVisibleContentBoundsForElement(GetHandle(), &visibleBounds); - - templateSettingsConcrete.FlyoutContentMinWidth = Math.Min(visibleBounds.Width, flyoutContentMinWidth); - } - - double maxItemKeyboardAcceleratorTextWidth = 0; - - IObservableVector menuItems = Items; - - // MenuFlyoutItem's alignment changes based on the layout of other MenuFlyoutItems. - // This check looks through all MenuFlyoutItems in our items source and finds the max - // keyboard accelerator label width, which affects the look of other items. - var nCount = (menuItems as ItemCollection).Size; - for (var i = 0; i < nCount; ++i) - { - MenuFlyoutItemBase item; - MenuFlyoutItem itemAsMenuItem; - - item = Items[i] as MenuFlyoutItemBase; - - itemAsMenuItem = item as MenuFlyoutItem; - if (itemAsMenuItem != null) - { - var desiredSize = (itemAsMenuItem as MenuFlyoutItem).GetKeyboardAcceleratorTextDesiredSize(); - var desiredWidth = desiredSize.Width; - if (desiredWidth > maxItemKeyboardAcceleratorTextWidth) - { - maxItemKeyboardAcceleratorTextWidth = desiredWidth; - } - } - } - - for (var i = 0; i < nCount; ++i) - { - MenuFlyoutItemBase item; - MenuFlyoutItem itemAsMenuItem; - - item = Items[i] as MenuFlyoutItemBase; - - itemAsMenuItem = item as MenuFlyoutItem; - if (itemAsMenuItem != null) - { - (itemAsMenuItem as MenuFlyoutItem).UpdateTemplateSettings(maxItemKeyboardAcceleratorTextWidth); - } - } - } - - protected override void OnGotFocus(RoutedEventArgs pArgs) - { - var focusState = FocusState; - if (m_iFocusedIndex == -1 && - focusState != FocusState.Unfocused) - { - // The MenuFlyoutPresenter gets focused for the first time right after it is opened. - // In this case we want to send focus to the first focusable item. - CycleFocus(true /* shouldCycleDown */, focusState); - } - else if (focusState == FocusState.Unfocused) - { - // A child element got focus, so make sure we keep m_iFocusedIndex in sync - // with it. - var focusedElement = (XamlRoot is null ? - FocusManager.GetFocusedElement() : - FocusManager.GetFocusedElement(XamlRoot)) as DependencyObject; - - // Since GotFocus is an async event, the focused element could be null if we got it - // after the popup closes, which clears focus. - if (focusedElement != null) - { - var focusedElementIndex = IndexFromContainer(focusedElement); - if (focusedElementIndex != -1) - { - m_iFocusedIndex = focusedElementIndex; - } - } - } - - - } - - void EnsureInitialFocusIndex() - { - if (m_iFocusedIndex == -1) - { - var focusedElement = XamlRoot is null ? - FocusManager.GetFocusedElement() : - FocusManager.GetFocusedElement(XamlRoot); - - if (this != focusedElement) - { - var menuItemsCount = Items.Count; - - for (var i = 0; i < menuItemsCount; ++i) - { - var itemAsDependencyObject = Items[i] as DependencyObject; - MenuFlyoutItem menuItem; - - menuItem = itemAsDependencyObject as MenuFlyoutItem; - - if (menuItem == focusedElement) - { - m_iFocusedIndex = i; - break; - } - } - - global::System.Diagnostics.Debug.Assert(m_iFocusedIndex != -1); - } - } - } - - internal static int GetPositionInSetHelper(MenuFlyoutItemBase item) - { - var returnValue = -1; - - var presenter = item.GetParentMenuFlyoutPresenter(); - - if (presenter != null) - { - var indexOfItem = presenter.Items.IndexOf(item); - - if (indexOfItem != -1) - { - // Iterate through the items preceding this item and subtract the number - // of separaters and collapsed items to get its position in the set. - var positionInSet = (int)(indexOfItem); - - for (var i = 0; i < indexOfItem; ++i) - { - var child = presenter.Items[i] as DependencyObject; - - if (child != null) - { - if (child is MenuFlyoutSeparator) - { - --positionInSet; - } - else - { - if (child is UIElement itemAsUI) - { - var visibility = itemAsUI.Visibility; - - if (Visibility.Visible != visibility) - { - --positionInSet; - } - } - } - } - } - - global::System.Diagnostics.Debug.Assert(positionInSet >= 0); - - returnValue = positionInSet + 1; // Add 1 to convert from a 0-based index to a 1-based index. - } - } - - return returnValue; - } - - internal static int GetSizeOfSetHelper(MenuFlyoutItemBase item) - { - var returnValue = -1; - - var presenter = item.GetParentMenuFlyoutPresenter(); - - if (presenter != null) - { - var itemsCount = presenter.Items.Count; - - // Iterate through the parent presenters items and subtract the - // number of separaters and collapsed items from the total count - // to get the size of the set. - var sizeOfSet = (int)(itemsCount); - - for (var i = 0; i < itemsCount; ++i) - { - var child = presenter.Items[i] as DependencyObject; - - if (child != null) - { - if (child is MenuFlyoutSeparator) - { - --sizeOfSet; - } - else - { - var itemAsUI = child as UIElement; - if (itemAsUI != null) - { - var visibility = itemAsUI.Visibility; - - if (Visibility.Visible != visibility) - { - --sizeOfSet; - } - } - } - } - } - - global::System.Diagnostics.Debug.Assert(sizeOfSet >= 0); - - returnValue = sizeOfSet; - } - - return returnValue; - } - - ISubMenuOwner IMenuPresenter.Owner - { - get => m_wrOwner?.Target as ISubMenuOwner; - set => m_wrOwner = new WeakReference(value); - } - - IMenu IMenuPresenter.OwningMenu - { - get => m_wrOwningMenu?.Target as IMenu; - set => m_wrOwningMenu = new WeakReference(value); - } - - IMenuPresenter IMenuPresenter.SubPresenter - { - get => m_wrSubPresenter?.Target as IMenuPresenter; - set => m_wrSubPresenter = new WeakReference(value); - } - - internal bool IsTextScaleFactorEnabledInternal { get; set; } - - internal void SetDepth(int depth) - { - m_depth = depth; - } - - internal int GetDepth() - { - return m_depth; - } } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.h.mux.cs new file mode 100644 index 000000000000..8844fe7bb63f --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.h.mux.cs @@ -0,0 +1,68 @@ +using Microsoft.UI.Xaml.Media.Animation; +using Uno.UI.DataBinding; +using static Microsoft.UI.Xaml.Controls.Primitives.FlyoutBase; + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyoutPresenter +{ + // Can be negative. (-1) means nothing focused. + internal int m_iFocusedIndex; + + // Weak reference to the menu that ultimately owns this MenuFlyoutPresenter. + private ManagedWeakReference m_wrOwningMenu; + + // Weak reference to the parent MenuFlyout. + private ManagedWeakReference m_wrParentMenuFlyout; + + // Weak reference to the owner of this menu. + // Only populated if this is the presenter for an ISubMenuOwner. + private ManagedWeakReference m_wrOwner; + + // Weak reference to the sub-presenter that was created by a child menu owner. + private ManagedWeakReference m_wrSubPresenter; + + // Whether ItemsSource contains at least one ToggleMenuFlyoutItem. + private bool m_containsToggleItems; + + // Whether ItemsSource contains at least one MenuFlyoutItem with an Icon. + private bool m_containsIconItems; + + // Whether ItemsSource contains at least one MenuFlyoutItem or ToggleMenuFlyoutItem with KeyboardAcceleratorText. + private bool m_containsItemsWithKeyboardAcceleratorText; + + // private bool m_animationInProgress; TODO Animation not supported in Uno yet + + private bool m_isSubPresenter; + + private int m_depth; + + private MajorPlacementMode m_mostRecentPlacement; + + // References the panels in the template. + private UIElement m_tpOuterBorder; + + private Timeline m_tpTopPortraitTimeline; + private Timeline m_tpBottomPortraitTimeline; + private Timeline m_tpLeftLandscapeTimeline; + private Timeline m_tpRightLandscapeTimeline; + + private ScrollViewer m_tpScrollViewer; + + internal bool IsSubPresenter => m_isSubPresenter; + + /// + /// Returns true if the ItemsSource contains at least one ToggleMenuFlyoutItem; false otherwise. + /// + private bool GetContainsToggleItems() => m_containsToggleItems; + + /// + /// Returns true if the ItemsSource contains at least one MenuFlyoutItem with an Icon; false otherwise. + /// + private bool GetContainsIconItems() => m_containsIconItems; + + /// + /// Returns true if the ItemsSource contains at least one MenuFlyoutItem with an keyboard accelerator; false otherwise. + /// + private bool GetContainsItemsWithKeyboardAcceleratorText() => m_containsItemsWithKeyboardAcceleratorText; +} diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs new file mode 100644 index 000000000000..f87a0df28bb1 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs @@ -0,0 +1,959 @@ +using System; +using System.Collections.Generic; +using Uno.UI; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Uno.UI.DataBinding; +using Uno.UI.__Resources; +using Uno.UI.Xaml.Core; +using Uno.UI.Extensions; +using static DirectUI.ElevationHelper; + +#if HAS_UNO_WINUI +using Microsoft.UI.Input; +#else +using Windows.Devices.Input; +using Windows.UI.Input; +#endif + +namespace Microsoft.UI.Xaml.Controls; + +partial class MenuFlyoutPresenter +{ + public MenuFlyoutPresenter() + { + m_iFocusedIndex = -1; + m_containsToggleItems = false; + m_containsIconItems = false; + m_containsItemsWithKeyboardAcceleratorText = false; + //m_animationInProgress = false; + m_isSubPresenter = false; + m_mostRecentPlacement = FlyoutBase.MajorPlacementMode.Bottom; + + DefaultStyleKey = typeof(MenuFlyoutPresenter); + + PrepareState(); + } + + private void PrepareState() + { + // base.PrepareState(); + + TemplateSettings = new MenuFlyoutPresenterTemplateSettings(); + + Loaded += OnLoaded; + } + + // Responds to the KeyDown event. + + protected override void OnKeyDown(KeyRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + + if (!handled) + { + var key = pArgs.Key; + pArgs.Handled = KeyProcess.KeyDown(key, this); + } + } + + internal void HandleUpOrDownKey(bool isDownKey) + { + CycleFocus(isDownKey, FocusState.Keyboard); + } + + private void CycleFocus(bool shouldCycleDown, FocusState focusState) + { + + // Ensure the initial focus index to validate m_iFocusedIndex when the focused item + // is set by application's code like as MenuFlyout Opened event. + EnsureInitialFocusIndex(); + + var originalFocusedIndex = m_iFocusedIndex; + + var parentFlyout = GetParentMenuFlyout(); + + // We should wrap around at the bottom or the top of the presenter if the user isn't using a gamepad or remote. + var shouldWrap = parentFlyout != null ? (parentFlyout.InputDeviceTypeUsedToOpen != Uno.UI.Xaml.Input.InputDeviceType.GamepadOrRemote) : true; + + var nCount = Items.Size; + + // Determine direction of index movement based on the Up/Down key. + var deltaIndex = shouldCycleDown ? 1 : -1; + + // Set index by moving deltaIndex amount from the current focused item. + var index = m_iFocusedIndex + deltaIndex; + + // We have two locations where we want to wrap, so we'll encapsulate the wrapping behavior in a function object that we can call. + int wrapIndexIfNeeded(int indexToWrap) + { + if (shouldWrap) + { + if (indexToWrap < 0) + { + indexToWrap = (int)(nCount) - 1; + } + else if (indexToWrap >= (int)(nCount)) + { + indexToWrap = 0; + } + } + + return indexToWrap; + } + + // If there is no item focused right now, then set index to 0 for Down key or to n-1 for Up key. + // Otherwise, if we should be wrapping, we'll do an initial check for whether we should wrap before we enter the loop. + if (m_iFocusedIndex == -1) + { + index = shouldCycleDown ? 0 : (int)(nCount) - 1; + + // If the focused index is -1, then our value of -1 for originalFocusedIndex will not successfully stop the loop. + // In this case, we'll make originalFocusedIndex one step in the opposite direction from the initial index, + // so that way the loop will go all the way through the list of items before stopping. + originalFocusedIndex = wrapIndexIfNeeded(index - deltaIndex); + } + else + { + index = wrapIndexIfNeeded(index); + } + + // We need to examine all items with indices [0, m_iFocusedIndex) or (m_iFocusedIndex, nCount-1] for Down/Up keys. + // While index is within the range, we keep going through the item list until we are successfully able to focus an item, + // at which point we update the m_iFocusedIndex and break out of the loop. + while (0 <= index && index < (int)(nCount)) + { + var spItemAsDependencyObject = Items[index] as DependencyObject; + + var isFocusable = false; + + // We determine whether the item is a focusable MenuFlyoutItem or MenuFlyoutSubItem because we want to exclude MenuSeparators here. + var spItem = spItemAsDependencyObject as MenuFlyoutItem; + if (spItem != null) + { + isFocusable = spItem.IsFocusable; + } + else + { + MenuFlyoutSubItem spSubItem; + spSubItem = spItemAsDependencyObject as MenuFlyoutSubItem; + if (spSubItem != null) + { + isFocusable = spSubItem.IsFocusable; + } + } + + // If the item is focusable, move the focus to it, update the m_iFocusedIndex and break out of the loop. + if (isFocusable) + { + var spSubItem = spItemAsDependencyObject as Control; + if (spSubItem != null) + { + spSubItem.Focus(focusState); + + m_iFocusedIndex = index; + break; + } + } + + // If we've gone all the way around the list of items and still have not found a suitable focus candidate, + // then we'll stop - there's nothing else for us to do. + if (index == originalFocusedIndex) + { + break; + } + + index += deltaIndex; + + // If we should be wrapping, then we'll perform the wrap at this point. + index = wrapIndexIfNeeded(index); + } + + } + + internal void HandleKeyDownLeftOrEscape() + { + (this as IMenuPresenter).CloseSubMenu(); + } + + protected override void PrepareContainerForItemOverride( + DependencyObject pElement, + object pItem) + { + base.PrepareContainerForItemOverride(pElement, pItem); + + var spMenuFlyoutItemBase = pElement as MenuFlyoutItemBase; + + spMenuFlyoutItemBase?.SetParentMenuFlyoutPresenter(this); + +#if HAS_UNO + // Manual propagation of the templated parent to the content properly + // until we get the propagation running properly + if (spMenuFlyoutItemBase is FrameworkElement content) + { + content.TemplatedParent = TemplatedParent; + } +#endif + } + + protected override void ClearContainerForItemOverride( + DependencyObject pElement, + object pItem) + { + base.ClearContainerForItemOverride(pElement, pItem); + + (pElement as MenuFlyoutItemBase)?.SetParentMenuFlyoutPresenter(null); + } + + // Get the parent MenuFlyout. + internal MenuFlyout GetParentMenuFlyout() => m_wrParentMenuFlyout?.IsAlive == true ? m_wrParentMenuFlyout.Target as MenuFlyout : null; + + // Sets the parent MenuFlyout. + internal void SetParentMenuFlyout(MenuFlyout pParentMenuFlyout) => m_wrParentMenuFlyout = WeakReferencePool.RentWeakReference(this, pParentMenuFlyout); + + // Called when the ItemsSource property changes. + protected override void OnItemsSourceChanged(DependencyPropertyChangedEventArgs args) + { + base.OnItemsSourceChanged(args); + + var pNewValue = args.NewValue; + + m_iFocusedIndex = -1; + m_containsToggleItems = false; + m_containsIconItems = false; + m_containsItemsWithKeyboardAcceleratorText = false; + + if (pNewValue != null) + { + IList spItems; + spItems = pNewValue as IList; + if (spItems == null) + { + // MenuFlyoutPresenter could be used outside of MenuFlyout, but at this time + // we don't support that usage. If a customer is using MenuFlyoutPresenter + // independently and uses an ItemsSource that is not an IVector, + // we throw E_INVALIDARG to indicate that this usage is invalid. + // If we decide to allow this usage, we need to override and implement + // IsItemItsOwnContainerOverride() and GetContainerForItemOverride(). + // + // GetContainerForItemOverride() is tricky since MenuFlyoutPresenter supports 3 different + // kinds of children (MenuFlyoutSeparator, MenuFlyoutItem, ToggleMenuFlyoutItem), + // so we are punting on that scenario for now. + throw new InvalidOperationException("Cannot use MenuFlyoutPresenter outside of a MenuFlyout"); + } + + // MenuFlyoutItem's alignment changes based on the layout of other MenuFlyoutItems. + // This check looks through all MenuFlyoutItems in our items source and checks for + // ToggleMenuFlyoutItems and the presence of Icons, which can change the layout of + // all MenuFlyoutItems in the presenter. + var nCount = spItems.Count; + for (var i = 0; i < nCount; ++i) + { + MenuFlyoutItemBase item; + IconElement iconElement; + string keyboardAcceleratorText; + + item = spItems[i] as MenuFlyoutItemBase; + + // To prevent casting the same item more than we need to, each cast is conditional + // on the previous one failing. This way we only cast each item as many times as we + // need to. + if (item is MenuFlyoutItem itemAsMenuItem) + { + m_containsToggleItems = m_containsToggleItems || itemAsMenuItem.HasToggle(); + + iconElement = itemAsMenuItem.Icon; + m_containsIconItems = m_containsIconItems || iconElement != null; + + keyboardAcceleratorText = itemAsMenuItem.KeyboardAcceleratorTextOverride; + m_containsItemsWithKeyboardAcceleratorText = m_containsItemsWithKeyboardAcceleratorText || !string.IsNullOrEmpty(keyboardAcceleratorText); + } + else + { + if (item is MenuFlyoutSubItem itemAsMenuSubItem) + { + iconElement = itemAsMenuSubItem.Icon; + m_containsIconItems = m_containsIconItems || iconElement != null; + } + } + + if (m_containsIconItems && m_containsToggleItems && m_containsItemsWithKeyboardAcceleratorText) + { + break; + } + } + + UpdateTemplateSettings(); + } + } + + protected override AutomationPeer OnCreateAutomationPeer() => new MenuFlyoutPresenterAutomationPeer(this); + + private void UpdateVisualStateForPlacement(FlyoutBase.MajorPlacementMode placement) + { + m_mostRecentPlacement = placement; + + UpdateVisualState(false); + } + + private void ResetVisualState() + { + VisualStateManager.GoToState(this, "None", false); + } + + private protected override void ChangeVisualState( + // true to use transitions when updating the visual state, false + // to snap directly to the new visual state. + bool bUseTransitions) + { + base.ChangeVisualState(bUseTransitions); + + if (m_mostRecentPlacement == FlyoutBase.MajorPlacementMode.Left) + { + // m_animationInProgress = m_epLeftLandscapeCompletedHandler; + GoToState(bUseTransitions, "LeftLandscape"); + } + else if (m_mostRecentPlacement == FlyoutBase.MajorPlacementMode.Right) + { + // m_animationInProgress = m_epRightLandscapeCompletedHandler; + GoToState(bUseTransitions, "RightLandscape"); + } + else if (m_mostRecentPlacement == FlyoutBase.MajorPlacementMode.Top) + { + // m_animationInProgress = m_epTopPortraitCompletedHandler; + GoToState(bUseTransitions, "TopPortrait"); + } + else + { + // m_animationInProgress = m_epBottomPortraitCompletedHandler; + GoToState(bUseTransitions, "BottomPortrait"); + } + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + // Get the ScrollViewer template part + var spScrollViewer = this.GetTemplateChild("MenuFlyoutPresenterScrollViewer") as ScrollViewer; + m_tpScrollViewer = spScrollViewer; + + // Apply a shadow + bool isDefaultShadowEnabled = IsDefaultShadowEnabled; + if (isDefaultShadowEnabled) + { + if (ThemeShadow.IsDropShadowMode) + { + // In drop shadow mode, we apply the shadow to the MenuFlyoutPresenter, otherwise the shadow is clipped. + ApplyElevationEffect(this, GetDepth()); + } + else + { + var child = VisualTreeHelper.GetChild(this, 0); + var spChildAsUIE = child as UIElement; + + if (spChildAsUIE is not null) + { + ApplyElevationEffect(spChildAsUIE, GetDepth()); + } + } + } + } + + internal string GetOwnerName() + { + var parentMenuFlyout = GetParentMenuFlyout(); + + if (parentMenuFlyout is not null) + { + return parentMenuFlyout.GetValue(FrameworkElement.NameProperty) as string; // TODO Uno: Should be DependencyObject.Name + } + + return null; + } + +#if HAS_UNO // Entrance theme animations are not supported in Uno yet +#if false + // UNO TODO + // + //Timeline AttachEntranceAnimationCompleted( + // string pszStateName, + // uint nStateNameLength, + // TimelineCompletedEventCallback pCompletedEvent) + //{ + // bool found = false; + // VisualState spState; + + // VisualStateManager.TryGetState(this, pszStateName, null, &spState, &found); + + // if (found && spState) + // { + // Storyboard spStoryboard; + + // spStoryboard = (spState.Storyboard); + // if (spStoryboard != null) + // { + // Timeline spTimeline; + // (spStoryboard.As(&spTimeline)); + + // (pCompletedEvent.AttachEventHandler(spTimeline, + // std.bind(&OnEntranceAnimationCompleted, this, _1, _2))); + + // (spTimeline.CopyTo(ppTimeline)); + // } + // } + //} + + void DetachEntranceAnimationCompletedHandlers() + { + // Marked as no longer used + // + //if (m_epTopPortraitCompletedHandler) + //{ + // ASSERT(m_tpTopPortraitTimeline); + // (m_epTopPortraitCompletedHandler.DetachEventHandler(m_tpTopPortraitTimeline)); + // m_tpTopPortraitTimeline.Clear(); + //} + + //if (m_epBottomPortraitCompletedHandler) + //{ + // ASSERT(m_tpBottomPortraitTimeline); + // (m_epBottomPortraitCompletedHandler.DetachEventHandler(m_tpBottomPortraitTimeline)); + // m_tpBottomPortraitTimeline.Clear(); + //} + + //if (m_epLeftLandscapeCompletedHandler) + //{ + // ASSERT(m_tpLeftLandscapeTimeline); + // (m_epLeftLandscapeCompletedHandler.DetachEventHandler(m_tpLeftLandscapeTimeline)); + // m_tpLeftLandscapeTimeline.Clear(); + //} + + //if (m_epRightLandscapeCompletedHandler) + //{ + // ASSERT(m_tpRightLandscapeTimeline); + // (m_epRightLandscapeCompletedHandler.DetachEventHandler(m_tpRightLandscapeTimeline)); + // m_tpRightLandscapeTimeline.Clear(); + //} + } + + void OnEntranceAnimationCompleted( + DependencyObject pSender, + DependencyObject pArgs) + { + // UNO TODO + // m_animationInProgress = false; + + Focus(FocusState.Programmatic); + } +#endif +#endif + + protected override void OnPointerExited(PointerRoutedEventArgs pArgs) + { + var handled = pArgs.Handled; + + if (!handled) + { + Pointer spPointer; + spPointer = (pArgs.Pointer); + var pointerDeviceType = (spPointer.PointerDeviceType); + if (PointerDeviceType.Mouse == (PointerDeviceType)pointerDeviceType && !m_isSubPresenter) + { + var isHitVerticalScrollBarOrSubPresenter = false; + IMenuPresenter subPresenter; + + // Hit test the current position for the vertical ScrollBar and the sub presenter. + // Close the existing sub presenter if the current mouse position isn't hit + // the vertical ScrollBar nor sub presenter. + + subPresenter = (this as IMenuPresenter).SubPresenter; + + if (subPresenter != null) + { + PointerPoint spPointerPoint; + UIElement spSubPresenterAsUIE; + + spSubPresenterAsUIE = subPresenter as UIElement; + + spPointerPoint = pArgs.GetCurrentPoint(null /* relativeTo*/); + var clientLogicalPointerPosition = spPointerPoint.Position; + + if (m_tpScrollViewer != null) + { + UIElement spVerticalScrollBarAsUE; + Control spScrollViewerAsControl; + DependencyObject spVerticalScrollBarAsDO; + + spScrollViewerAsControl = m_tpScrollViewer as Control; + spVerticalScrollBarAsDO = spScrollViewerAsControl.GetTemplateChild("VerticalScrollBar"); + spVerticalScrollBarAsUE = spVerticalScrollBarAsDO as UIElement; + + if (spSubPresenterAsUIE != null || spVerticalScrollBarAsUE != null) + { + var spElements = VisualTreeHelper.FindElementsInHostCoordinates(clientLogicalPointerPosition, spVerticalScrollBarAsUE, true /* includeAllElements */); + + foreach (var spElement in spElements) + { + DependencyObject pElementAsCDO = spElement; + + if ((pElementAsCDO is ScrollBar && spVerticalScrollBarAsUE == spElement) || + (spSubPresenterAsUIE == spElement)) + { + isHitVerticalScrollBarOrSubPresenter = true; + break; + } + } + + if (!isHitVerticalScrollBarOrSubPresenter) + { + spElements = VisualTreeHelper.FindElementsInHostCoordinates(clientLogicalPointerPosition, spSubPresenterAsUIE, true /* includeAllElements */); + + foreach (var spElement in spElements) + { + DependencyObject pElementAsCDO = spElement; + + if ((pElementAsCDO is ScrollBar && spVerticalScrollBarAsUE == spElement) || + (spSubPresenterAsUIE == spElement)) + { + isHitVerticalScrollBarOrSubPresenter = true; + break; + } + } + } + } + } + + // The opened MenuFlyoutSubItem won't to be closed if the mouse position is + // on the vertical ScrollBar or sub presenter. + if (!isHitVerticalScrollBarOrSubPresenter) + { + var subPresenterBoundsLogical = GetSubPresenterBounds(spSubPresenterAsUIE); + var containsSubPresenter = subPresenterBoundsLogical.Contains(clientLogicalPointerPosition); + + if (!containsSubPresenter) + { + DelayCloseMenuFlyoutSubItem(); + } + } + } + } + + pArgs.Handled = true; + } + } + + private Rect GetSubPresenterBounds(UIElement pSubPresenterAsUIE) + { +#if HAS_UNO + // TODO Uno: This code is simplified - WinUI uses GetGlobalBoundsLogical + var t = pSubPresenterAsUIE.TransformToVisual(null); + var r = t.TransformBounds(pSubPresenterAsUIE.LayoutSlotWithMarginsAndAlignments); + + return r; +#endif + } + + protected override void OnPointerEntered(PointerRoutedEventArgs pArgs) + { + base.OnPointerEntered(pArgs); + + var handled = pArgs.Handled; + + if (!handled) + { + Pointer spPointer; + spPointer = pArgs.Pointer; + var pointerDeviceType = spPointer.PointerDeviceType; + if (PointerDeviceType.Mouse == pointerDeviceType && m_isSubPresenter) + { + CancelCloseMenuFlyoutSubItem(); + var owner = (this as IMenuPresenter).Owner; + + if (owner != null) + { + // When the mouse enters a MenuFlyoutPresenter that is a sub menu + // we have to tell the parent presenter to cancel any plans it had + // to close this sub + owner.CancelCloseSubMenu(); + } + } + + pArgs.Handled = true; + } + } + + private protected override string GetPlainText() + { + string automationName = null; + + var ownerFlyout = m_wrParentMenuFlyout.Target as DependencyObject; + + if (ownerFlyout != null) + { + // If an automation name is set on the parent flyout, we'll use that as our plain text. + // Otherwise, we'll report the default plain text. + automationName = AutomationProperties.GetName(ownerFlyout); + } + + if (automationName != null) + { + return automationName; + } + else + { +#if HAS_UNO // TODO Uno: We don't support this path yet. + // If we have no title, we'll fall back to the default implementation, + // which retrieves our content as plain text (e.g., if our content is a string, + // it returns that; if our content is a TextBlock, it returns its Text value, etc.) + // MenuFlyoutPresenterGenerated.GetPlainText(strPlainText); + + // If we get the plain text from the content, then we want to truncate it, + // in case the resulting automation name is very long. + // Popup.TruncateAutomationName(strPlainText); +#endif + } + + return automationName; + } + + void IMenuPresenter.CloseSubMenu() + { + var subPresenter = m_wrSubPresenter?.IsAlive == true ? m_wrSubPresenter.Target as IMenuPresenter : null; + + if (subPresenter != null) + { + subPresenter.CloseSubMenu(); + } + + ISubMenuOwner owner; + owner = m_wrOwner?.Target as ISubMenuOwner; + + if (owner != null) + { + owner.CloseSubMenu(); + } + + // Reset the focused index not to cached the previous focused index when + // the sub menu is opened in the next time + m_iFocusedIndex = -1; + } + + void DelayCloseMenuFlyoutSubItem() + { + var subMenuPresenter = m_wrSubPresenter?.IsAlive == true ? m_wrSubPresenter.Target as IMenuPresenter : null; + + if (subMenuPresenter != null) + { + ISubMenuOwner subMenuOwner; + subMenuOwner = (subMenuPresenter.Owner); + + if (subMenuOwner != null) + { + subMenuOwner.DelayCloseSubMenu(); + } + } + } + + void CancelCloseMenuFlyoutSubItem() + { + var subMenuPresenter = m_wrSubPresenter?.IsAlive == true ? m_wrSubPresenter.Target as IMenuPresenter : null; + + if (subMenuPresenter != null) + { + ISubMenuOwner subMenuOwner; + subMenuOwner = (subMenuPresenter.Owner); + + if (subMenuOwner != null) + { + subMenuOwner.CancelCloseSubMenu(); + } + } + } + +#if false // Unused + DependencyObject GetParentMenuFlyoutSubItem(DependencyObject nativeDO) + { + var spThis = nativeDO as MenuFlyoutPresenter; + ISubMenuOwner spResult; + spResult = (spThis as IMenuPresenter).Owner; + + var spResultAsMenuFlyoutSubItem = spResult as MenuFlyoutSubItem; + + if (spResultAsMenuFlyoutSubItem != null) + { + return spResultAsMenuFlyoutSubItem as DependencyObject; + } + else + { + return null; + } + } +#endif + + internal void UpdateTemplateSettings() + { + var templateSettings = TemplateSettings; + var templateSettingsConcrete = templateSettings as MenuFlyoutPresenterTemplateSettings; + + var ownerFlyout = GetParentMenuFlyout(); + + if (ownerFlyout != null && templateSettingsConcrete != null) + { + // Query MenuFlyout Content MinWidth, given the input mode, from resource dictionary. + var flyoutContentMinWidth = ResourceResolver.ResolveTopLevelResourceDouble( + (ownerFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.Touch || ownerFlyout.InputDeviceTypeUsedToOpen == Uno.UI.Xaml.Input.InputDeviceType.GamepadOrRemote) + ? "FlyoutThemeTouchMinWidth" : "FlyoutThemeMinWidth" + ); + var visibleBounds = this.LayoutSlotWithMarginsAndAlignments; + // DXamlCore.GetCurrent().GetVisibleContentBoundsForElement(GetHandle(), &visibleBounds); + + templateSettingsConcrete.FlyoutContentMinWidth = Math.Min(visibleBounds.Width, flyoutContentMinWidth); + } + + double maxItemKeyboardAcceleratorTextWidth = 0; + + IObservableVector menuItems = Items; + + // MenuFlyoutItem's alignment changes based on the layout of other MenuFlyoutItems. + // This check looks through all MenuFlyoutItems in our items source and finds the max + // keyboard accelerator label width, which affects the look of other items. + var nCount = (menuItems as ItemCollection).Size; + for (var i = 0; i < nCount; ++i) + { + MenuFlyoutItemBase item; + MenuFlyoutItem itemAsMenuItem; + + item = Items[i] as MenuFlyoutItemBase; + + itemAsMenuItem = item as MenuFlyoutItem; + if (itemAsMenuItem != null) + { + var desiredSize = (itemAsMenuItem as MenuFlyoutItem).GetKeyboardAcceleratorTextDesiredSize(); + var desiredWidth = desiredSize.Width; + if (desiredWidth > maxItemKeyboardAcceleratorTextWidth) + { + maxItemKeyboardAcceleratorTextWidth = desiredWidth; + } + } + } + + for (var i = 0; i < nCount; ++i) + { + MenuFlyoutItemBase item; + MenuFlyoutItem itemAsMenuItem; + + item = Items[i] as MenuFlyoutItemBase; + + itemAsMenuItem = item as MenuFlyoutItem; + if (itemAsMenuItem != null) + { + (itemAsMenuItem as MenuFlyoutItem).UpdateTemplateSettings(maxItemKeyboardAcceleratorTextWidth); + } + } + } + + private void OnLoaded(object sender, object args) + { + // Sometimes GotFocus can come in before we're loaded, in which case focusing the first item may not have succeeded. + // In that case, we should focus on load. + SetInitialFocus(); + } + + protected override void OnGotFocus(RoutedEventArgs pArgs) + { + base.OnGotFocus(pArgs); + SetInitialFocus(); + } + + private void SetInitialFocus() + { + var focusState = FocusState; + if (m_iFocusedIndex == -1 && + focusState != FocusState.Unfocused) + { + // The MenuFlyoutPresenter gets focused for the first time right after it is opened. + // In this case we want to send focus to the first focusable item. + CycleFocus(true /* shouldCycleDown */, focusState); + } + else if (focusState == FocusState.Unfocused) + { + // A child element got focus, so make sure we keep m_iFocusedIndex in sync + // with it. + FocusManager focusManager = VisualTree.GetFocusManagerForElement(this); + + var focusedElement = focusManager?.FocusedElement; + + // Since GotFocus is an async event, the focused element could be null if we got it + // after the popup closes, which clears focus. + if (focusedElement is not null) + { + var focusedElementIndex = IndexFromContainer(focusedElement); + if (focusedElementIndex != -1) + { + m_iFocusedIndex = focusedElementIndex; + } + } + } + } + + private void EnsureInitialFocusIndex() + { + if (m_iFocusedIndex == -1) + { + var focusedElement = this.GetFocusedElement(); + + if (this != focusedElement) + { + var menuItemsCount = Items.Count; + + for (var i = 0; i < menuItemsCount; ++i) + { + var itemAsDependencyObject = Items[i] as DependencyObject; + MenuFlyoutItem menuItem; + + menuItem = itemAsDependencyObject as MenuFlyoutItem; + + if (menuItem == focusedElement) + { + m_iFocusedIndex = i; + break; + } + } + + global::System.Diagnostics.Debug.Assert(m_iFocusedIndex != -1); + } + } + } + + internal static int GetPositionInSetHelper(MenuFlyoutItemBase item) + { + var returnValue = -1; + + var presenter = item.GetParentMenuFlyoutPresenter(); + + if (presenter != null) + { + var indexOfItem = presenter.Items.IndexOf(item); + + if (indexOfItem != -1) + { + // Iterate through the items preceding this item and subtract the number + // of separaters and collapsed items to get its position in the set. + var positionInSet = (int)(indexOfItem); + + for (var i = 0; i < indexOfItem; ++i) + { + var child = presenter.Items[i] as DependencyObject; + + if (child != null) + { + if (child is MenuFlyoutSeparator) + { + --positionInSet; + } + else + { + if (child is UIElement itemAsUI) + { + var visibility = itemAsUI.Visibility; + + if (Visibility.Visible != visibility) + { + --positionInSet; + } + } + } + } + } + + global::System.Diagnostics.Debug.Assert(positionInSet >= 0); + + returnValue = positionInSet + 1; // Add 1 to convert from a 0-based index to a 1-based index. + } + } + + return returnValue; + } + + internal static int GetSizeOfSetHelper(MenuFlyoutItemBase item) + { + var returnValue = -1; + + var presenter = item.GetParentMenuFlyoutPresenter(); + + if (presenter != null) + { + var itemsCount = presenter.Items.Count; + + // Iterate through the parent presenters items and subtract the + // number of separaters and collapsed items from the total count + // to get the size of the set. + var sizeOfSet = (int)(itemsCount); + + for (var i = 0; i < itemsCount; ++i) + { + var child = presenter.Items[i] as DependencyObject; + + if (child != null) + { + if (child is MenuFlyoutSeparator) + { + --sizeOfSet; + } + else + { + var itemAsUI = child as UIElement; + if (itemAsUI != null) + { + var visibility = itemAsUI.Visibility; + + if (Visibility.Visible != visibility) + { + --sizeOfSet; + } + } + } + } + } + + global::System.Diagnostics.Debug.Assert(sizeOfSet >= 0); + + returnValue = sizeOfSet; + } + + return returnValue; + } + + ISubMenuOwner IMenuPresenter.Owner + { + get => m_wrOwner?.IsAlive == true ? m_wrOwner.Target as ISubMenuOwner : null; + set => m_wrOwner = WeakReferencePool.RentWeakReference(this, value); + } + + IMenu IMenuPresenter.OwningMenu + { + get => m_wrOwningMenu?.IsAlive == true ? m_wrOwningMenu?.Target as IMenu : null; + set => m_wrOwningMenu = WeakReferencePool.RentWeakReference(this, value); + } + + IMenuPresenter IMenuPresenter.SubPresenter + { + get => m_wrSubPresenter?.IsAlive == true ? m_wrSubPresenter.Target as IMenuPresenter : null; + set => m_wrSubPresenter = WeakReferencePool.RentWeakReference(this, value); + } + + internal void SetDepth(int depth) => m_depth = depth; + + internal int GetDepth() => m_depth; + +#if HAS_UNO // TODO Uno: This should be defined above, not directly here + internal bool IsTextScaleFactorEnabledInternal { get; set; } +#endif +} From 3d94bf0d10a9173d99e537836e91558ad3362882 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 12:00:07 +0200 Subject: [PATCH 23/28] chore: Cleanup unused code --- src/Uno.UI/DirectUI/ElevationHelper.cs | 6 ++++- .../MenuFlyout/MenuFlyout.partial.mux.cs | 4 ++++ .../MenuFlyoutPresenter.partial.h.mux.cs | 22 ++++++++++++++----- .../MenuFlyoutPresenter.partial.mux.cs | 8 ++++++- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Uno.UI/DirectUI/ElevationHelper.cs b/src/Uno.UI/DirectUI/ElevationHelper.cs index dc7f7d4547de..f03751cf402c 100644 --- a/src/Uno.UI/DirectUI/ElevationHelper.cs +++ b/src/Uno.UI/DirectUI/ElevationHelper.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\phone\lib\ElevationHelper.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs index 7724fccb7ef9..fef49a31b921 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyout.partial.mux.cs @@ -212,6 +212,7 @@ private void CloseSubMenu() } } +#if false // TODO Uno: Unused for now private void PreparePopupTheme(Popup pPopup, MajorPlacementMode placementMode, FrameworkElement pPlacementTarget) { // UNO TODO @@ -240,6 +241,7 @@ private void PreparePopupTheme(Popup pPopup, MajorPlacementMode placementMode, F //return S_OK; } +#endif internal static Transition PreparePopupThemeTransitionsAndShadows(Popup popup, double closedRatioConstant, int depth) { @@ -299,6 +301,7 @@ internal static Transition PreparePopupThemeTransitionsAndShadows(Popup popup, d //return S_OK; } +#if false // Unused in WinUI private void UpdatePresenterVisualState(MajorPlacementMode placement, bool doForceTransitions) { //base.UpdatePresenterVisualState(placement); @@ -322,6 +325,7 @@ private void AutoAdjustPlacement(MajorPlacementMode pPlacement) } private void UpdatePresenterVisualState(MajorPlacementMode placement) => UpdatePresenterVisualState(placement, true); +#endif //internal override void ShowAt(FrameworkElement placementTarget) //{ diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.h.mux.cs index 8844fe7bb63f..64c5a31a13f7 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.h.mux.cs @@ -1,4 +1,8 @@ -using Microsoft.UI.Xaml.Media.Animation; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutPresenter_Partial.h, tag winui3/release/1.5.4, commit 98a60c8 + +using Microsoft.UI.Xaml.Media.Animation; using Uno.UI.DataBinding; using static Microsoft.UI.Xaml.Controls.Primitives.FlyoutBase; @@ -39,30 +43,38 @@ partial class MenuFlyoutPresenter private MajorPlacementMode m_mostRecentPlacement; +#if false // Unused in WinUI // References the panels in the template. private UIElement m_tpOuterBorder; +#endif +#if false // TODO Uno: Unused for now private Timeline m_tpTopPortraitTimeline; private Timeline m_tpBottomPortraitTimeline; private Timeline m_tpLeftLandscapeTimeline; private Timeline m_tpRightLandscapeTimeline; +#endif private ScrollViewer m_tpScrollViewer; - internal bool IsSubPresenter => m_isSubPresenter; + internal bool IsSubPresenter + { + get => m_isSubPresenter; + set => m_isSubPresenter = value; + } /// /// Returns true if the ItemsSource contains at least one ToggleMenuFlyoutItem; false otherwise. /// - private bool GetContainsToggleItems() => m_containsToggleItems; + internal bool GetContainsToggleItems() => m_containsToggleItems; /// /// Returns true if the ItemsSource contains at least one MenuFlyoutItem with an Icon; false otherwise. /// - private bool GetContainsIconItems() => m_containsIconItems; + internal bool GetContainsIconItems() => m_containsIconItems; /// /// Returns true if the ItemsSource contains at least one MenuFlyoutItem with an keyboard accelerator; false otherwise. /// - private bool GetContainsItemsWithKeyboardAcceleratorText() => m_containsItemsWithKeyboardAcceleratorText; + internal bool GetContainsItemsWithKeyboardAcceleratorText() => m_containsItemsWithKeyboardAcceleratorText; } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs index f87a0df28bb1..a68284c7f2f0 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// MUX Reference dxaml\xcp\dxaml\lib\MenuFlyoutPresenter_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8 + +using System; using System.Collections.Generic; using Uno.UI; using Windows.Foundation; @@ -295,12 +299,14 @@ protected override void OnItemsSourceChanged(DependencyPropertyChangedEventArgs protected override AutomationPeer OnCreateAutomationPeer() => new MenuFlyoutPresenterAutomationPeer(this); +#if false // Unused in WinUI private void UpdateVisualStateForPlacement(FlyoutBase.MajorPlacementMode placement) { m_mostRecentPlacement = placement; UpdateVisualState(false); } +#endif private void ResetVisualState() { From 279dfecc763afaf7c06dabc570b4ba19a62b8323 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 12:00:20 +0200 Subject: [PATCH 24/28] test: Enable additional KA tests --- .../KeyboardAccelerators/KeyboardAcceleratorTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs b/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs index 3a0e7551e48a..6f7ea90d1ebb 100644 --- a/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs +++ b/src/Uno.UI.RuntimeTests/MUX/Input/KeyboardAccelerators/KeyboardAcceleratorTests.cs @@ -2196,9 +2196,11 @@ await TestServices.RunOnUIThread(async () => TestServices.WindowHelper.WindowContent = rootPanel; await TestServices.WindowHelper.WaitForLoaded(rootPanel); - ButtonFlyout = (Flyout)rootPanel.FindName("ButtonFlyout"); - flyoutButton1 = (Button)rootPanel.FindName("flyoutButton1"); - flyoutButton2 = (Button)rootPanel.FindName("flyoutButton2"); + var button1 = (Button)rootPanel.FindName("focusButton1"); + ButtonFlyout = (Flyout)button1.ContextFlyout; + var stackPanel = (StackPanel)ButtonFlyout.Content; + flyoutButton1 = (Button)stackPanel.Children[0]; + flyoutButton2 = (Button)stackPanel.Children[1]; focusButton = (Button)rootPanel.FindName("focusButton"); ctrl1Accelerator = flyoutButton1.KeyboardAccelerators[0]; ctrl1Accelerator.ScopeOwner = ButtonFlyout; @@ -2824,7 +2826,6 @@ await TestServices.RunOnUIThread(async () => [TestMethod] [TestProperty("Description", "Validates that ListView fires the accelerators on its attached ContextFlyout.")] - [Ignore("Requires ContextFlyout support for Keyboard Accelerators #17134")] public async Task VerifyListViewContextFlyoutCanInvokeHiddenAccelerator() { const string rootPanelXaml = @@ -3077,7 +3078,6 @@ await TestServices.RunOnUIThread(() => [TestMethod] [TestProperty("Description", "Validates that ListView does not fire the accelerators on its attached ContextFlyout, as it is scoped to be local.")] - [Ignore("Requires ContextFlyout support for Keyboard Accelerators #17134")] public async Task VerifyListViewContextFlyoutCanNotInvokeHiddenLocallyScopedAccelerator() { const string rootPanelXaml = From 5424ebb9edd19c3684895ced7e496c4ce123db26 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 13:04:57 +0200 Subject: [PATCH 25/28] chore: Leave KACollection on change --- src/Uno.UI/UI/Xaml/UIElement.Properties.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/UIElement.Properties.cs b/src/Uno.UI/UI/Xaml/UIElement.Properties.cs index 82ba9c46d7d0..b26964f51dc9 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.Properties.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.Properties.cs @@ -95,12 +95,16 @@ public IList KeyboardAccelerators private set => SetKeyboardAcceleratorsValue(value); } - private void OnKeyboardAcceleratorsChanged() + private void OnKeyboardAcceleratorsChanged(IList oldValue, IList newValue) { -#if HAS_UNO // TODO: Uno specific - WinUI does analogous action in EnterEffectiveValue - if (KeyboardAccelerators is KeyboardAcceleratorCollection collection) +#if HAS_UNO // TODO: Uno specific - WinUI does analogous action in Enter/LeaveEffectiveValue + if (oldValue is KeyboardAcceleratorCollection oldCollection) { - collection.Enter(null, new EnterParams(IsLoaded) { IsForKeyboardAccelerator = true }); + oldCollection.Leave(null, new LeaveParams(false) { IsForKeyboardAccelerator = true }); + } + if (newValue is KeyboardAcceleratorCollection newCollection) + { + newCollection.Enter(null, new EnterParams(false) { IsForKeyboardAccelerator = true }); } #endif } From db788faf9408b392fe2889fd2f0efcda681ce955 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 13:07:00 +0200 Subject: [PATCH 26/28] chore: Unused members --- src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs | 2 ++ .../Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs | 2 +- .../Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs index 6a008d8fccac..5c789c2e8f03 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.h.mux.cs @@ -62,7 +62,9 @@ partial class MenuFlyoutItem internal virtual bool HasToggle() => false; +#if false // Unused in WinUI private bool GetIsPressed() => m_bIsPressed; private bool GetIsPointerOver() => m_bIsPointerOver; +#endif } diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs index a68284c7f2f0..19cc6987fd35 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutPresenter.partial.mux.cs @@ -306,12 +306,12 @@ private void UpdateVisualStateForPlacement(FlyoutBase.MajorPlacementMode placeme UpdateVisualState(false); } -#endif private void ResetVisualState() { VisualStateManager.GoToState(this, "None", false); } +#endif private protected override void ChangeVisualState( // true to use transitions when updating the visual state, false diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs index 757143395559..d8caa8e194fd 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs @@ -280,6 +280,7 @@ private void ForwardPresenterProperties( private void ForwardSystemBackdropToPopup(MenuFlyout ownerMenuFlyout) { +#if HAS_UNO_WINUI // This only applies to WinUI // Set the popup's SystemBackdrop from the parent flyout's SystemBackdrop. Note that the top-level menu is a // MenuFlyout with a SystemBackdrop property, but submenus are MenyFlyoutSubItems with no SystemBackdrop property, // so we just use the one set on the top-level menu. SystemBackdrop can handle having multiple parents, and will @@ -290,6 +291,7 @@ private void ForwardSystemBackdropToPopup(MenuFlyout ownerMenuFlyout) { m_tpPopup.SystemBackdrop = flyoutSystemBackdrop; } +#endif } // Ensure that any currently open MenuFlyoutSubItems are closed From 2182678d21c04bff79156669130db849f2442e98 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 14:28:39 +0200 Subject: [PATCH 27/28] chore: Adjust build --- .../Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs index d8caa8e194fd..4ee634ed93d2 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutSubItem.partial.mux.cs @@ -14,6 +14,12 @@ using Windows.Foundation.Metadata; using Windows.System; +#if HAS_UNO_WINUI +using MUXDispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue; +#else +using MUXDispatcherQueue = Windows.System.DispatcherQueue; +#endif + namespace Microsoft.UI.Xaml.Controls; public partial class MenuFlyoutSubItem : MenuFlyoutItemBase, ISubMenuOwner @@ -729,7 +735,7 @@ internal void QueueRefreshItemsSource() // into a single event once all of the changes have completed. if (m_tpPresenter is not null && !_itemsSourceRefreshPending) { - var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + var dispatcherQueue = MUXDispatcherQueue.GetForCurrentThread(); var wrThis = WeakReferencePool.RentSelfWeakReference(this); From 83f6063f8e5120fe2cac4c7d99f4c5d2d6114f46 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 25 Jun 2024 14:30:32 +0200 Subject: [PATCH 28/28] chore: New hiding --- .../UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs index 12445282a23a..3553e762ef48 100644 --- a/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs +++ b/src/Uno.UI/UI/Xaml/Controls/MenuFlyout/MenuFlyoutItem.Properties.cs @@ -115,8 +115,10 @@ public string Text internal bool PreventDismissOnPointer { get; set; } +#pragma warning disable CS0108 // Member hides inherited member; missing new keyword /// /// Occurs when a menu item is clicked. /// public event RoutedEventHandler Click; +#pragma warning restore CS0108 }