From db71402c23f1ef269bc607342e3421e7ae2bf49a Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 5 Sep 2023 16:36:57 +0200 Subject: [PATCH] feat(NavigationView): Include Delay load Hierarchical NavViewItem elements https://github.com/microsoft/microsoft-ui-xaml/commit/d3fef08fdf2b3e86386928097216fdfbedfda02c --- .../PriorityDefault/NavigationView.xaml | 2 + .../NavigationView_rs1_themeresources.xaml | 15 +- .../NavigationViewItem.Header.cs | 104 +- .../NavigationView/NavigationViewItem.cs | 1809 +++++++++-------- .../NavigationViewItemPresenter.Header.cs | 28 +- .../NavigationViewItemPresenter.cs | 291 +-- 6 files changed, 1165 insertions(+), 1084 deletions(-) diff --git a/src/Uno.UI.FluentTheme.v2/Resources/Version2/PriorityDefault/NavigationView.xaml b/src/Uno.UI.FluentTheme.v2/Resources/Version2/PriorityDefault/NavigationView.xaml index 67bdd6f02b8e..626a5251f757 100644 --- a/src/Uno.UI.FluentTheme.v2/Resources/Version2/PriorityDefault/NavigationView.xaml +++ b/src/Uno.UI.FluentTheme.v2/Resources/Version2/PriorityDefault/NavigationView.xaml @@ -1,4 +1,5 @@  + diff --git a/src/Uno.UI.FluentTheme.v2/Resources/Version2/PriorityDefault/NavigationView_rs1_themeresources.xaml b/src/Uno.UI.FluentTheme.v2/Resources/Version2/PriorityDefault/NavigationView_rs1_themeresources.xaml index dbcd25b36b31..4bd4d2dcc84d 100644 --- a/src/Uno.UI.FluentTheme.v2/Resources/Version2/PriorityDefault/NavigationView_rs1_themeresources.xaml +++ b/src/Uno.UI.FluentTheme.v2/Resources/Version2/PriorityDefault/NavigationView_rs1_themeresources.xaml @@ -1,5 +1,5 @@  - + + + @@ -701,11 +703,13 @@ + + @@ -784,6 +788,7 @@ AutomationProperties.AccessibilityView="Raw"/> + + @@ -1108,11 +1115,13 @@ + + @@ -1169,6 +1178,7 @@ Height="12" RenderTransformOrigin="0.5, 0.5" x:Name="ExpandCollapseChevronIcon" + Foreground="{ThemeResource NavigationViewItemForeground}" HorizontalAlignment="Center" VerticalAlignment="Center" AutomationProperties.AccessibilityView="Raw" @@ -1419,6 +1429,7 @@ VerticalAlignment="Center" AutomationProperties.AccessibilityView="Raw"/> - + \ No newline at end of file diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItem.Header.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItem.Header.cs index 833512fd0433..5b992edc27ad 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItem.Header.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItem.Header.cs @@ -1,60 +1,60 @@ -// MUX reference NavigationViewItem.h, commit fd22d7f +// MUX reference NavigationViewItem.h, commit d3fef08fd using Microsoft.UI.Xaml.Controls.Primitives; using Uno.Disposables; -using Windows.ApplicationModel.Core; -using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial class NavigationViewItem { - public partial class NavigationViewItem - { - internal SerialDisposable EventRevoker { get; } = new SerialDisposable(); - - internal ItemsRepeater GetRepeater() => m_repeater; - - private readonly SerialDisposable m_splitViewIsPaneOpenChangedRevoker = new SerialDisposable(); - private readonly SerialDisposable m_splitViewDisplayModeChangedRevoker = new SerialDisposable(); - private readonly SerialDisposable m_splitViewCompactPaneLengthChangedRevoker = new SerialDisposable(); - - private readonly SerialDisposable m_presenterPointerPressedRevoker = new SerialDisposable(); - private readonly SerialDisposable m_presenterPointerEnteredRevoker = new SerialDisposable(); - private readonly SerialDisposable m_presenterPointerMovedRevoker = new SerialDisposable(); - private readonly SerialDisposable m_presenterPointerReleasedRevoker = new SerialDisposable(); - private readonly SerialDisposable m_presenterPointerExitedRevoker = new SerialDisposable(); - private readonly SerialDisposable m_presenterPointerCanceledRevoker = new SerialDisposable(); - private readonly SerialDisposable m_presenterPointerCaptureLostRevoker = new SerialDisposable(); - - private readonly SerialDisposable m_repeaterElementPreparedRevoker = new SerialDisposable(); - private readonly SerialDisposable m_repeaterElementClearingRevoker = new SerialDisposable(); - private readonly SerialDisposable m_itemsSourceViewCollectionChangedRevoker = new SerialDisposable(); - - private readonly SerialDisposable m_flyoutClosingRevoker = new SerialDisposable(); - private readonly SerialDisposable m_isEnabledChangedRevoker = new SerialDisposable(); - - private ToolTip m_toolTip = null; - private NavigationViewItemHelper backing_m_helper = null; - private NavigationViewItemHelper m_helper => backing_m_helper ??= new NavigationViewItemHelper(this); - - private NavigationViewItemPresenter m_navigationViewItemPresenter = null; - private object m_suggestedToolTipContent = null; - private ItemsRepeater m_repeater = null; - private Grid m_flyoutContentGrid = null; - private Grid m_rootGrid = null; - - private bool m_isClosedCompact = false; - - private bool m_appliedTemplate = false; - private bool m_hasKeyboardFocus = false; - - // Visual state tracking - private Pointer m_capturedPointer = null; - private uint m_trackedPointerId = 0; - private bool m_isPressed = false; - private bool m_isPointerOver = false; - - private bool m_isRepeaterParentedToFlyout = false; - } + internal SerialDisposable EventRevoker { get; } = new(); + + internal ItemsRepeater GetRepeater() => m_repeater; + + private readonly SerialDisposable m_splitViewIsPaneOpenChangedRevoker = new(); + private readonly SerialDisposable m_splitViewDisplayModeChangedRevoker = new(); + private readonly SerialDisposable m_splitViewCompactPaneLengthChangedRevoker = new(); + + private readonly SerialDisposable m_presenterPointerPressedRevoker = new(); + private readonly SerialDisposable m_presenterPointerEnteredRevoker = new(); + private readonly SerialDisposable m_presenterPointerMovedRevoker = new(); + private readonly SerialDisposable m_presenterPointerReleasedRevoker = new(); + private readonly SerialDisposable m_presenterPointerExitedRevoker = new(); + private readonly SerialDisposable m_presenterPointerCanceledRevoker = new(); + private readonly SerialDisposable m_presenterPointerCaptureLostRevoker = new(); + + private readonly SerialDisposable m_repeaterElementPreparedRevoker = new(); + private readonly SerialDisposable m_repeaterElementClearingRevoker = new(); + private readonly SerialDisposable m_itemsSourceViewCollectionChangedRevoker = new(); + private readonly SerialDisposable m_menuItemsVectorChangedRevoker = new(); + + private readonly SerialDisposable m_flyoutClosingRevoker = new(); + private readonly SerialDisposable m_isEnabledChangedRevoker = new(); + + private ToolTip m_toolTip; + private NavigationViewItemHelper backing_m_helper; + private NavigationViewItemHelper m_helper => backing_m_helper ??= new(this); + + private NavigationViewItemPresenter m_navigationViewItemPresenter; + private object m_suggestedToolTipContent; + private ItemsRepeater m_repeater; + private Grid m_flyoutContentGrid; + private Grid m_rootGrid; + + private bool m_isClosedCompact; + + private bool m_appliedTemplate; + private bool m_hasKeyboardFocus; + + // Visual state tracking + private Pointer m_capturedPointer; + private uint m_trackedPointerId; + private bool m_isPressed; + private bool m_isPointerOver; + + private bool m_isRepeaterParentedToFlyout; + // used to bypass all Chevron visual state logic in order to keep it unloaded + private bool m_hasHadChildren; } diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItem.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItem.cs index 5084fe34d9c2..6376b5c8d11f 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItem.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItem.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -// MUX reference NavigationViewItem.cpp, commit 2562ac6 +// MUX reference NavigationViewItem.cpp, commit d3fef08 #if __ANDROID__ // For performance considerations, we prefer to delay pressed and over state in order to avoid @@ -9,14 +9,12 @@ #define UNO_USE_DEFERRED_VISUAL_STATES #endif -using System; using System.Collections.Generic; using System.Collections.Specialized; using Microsoft.UI.Xaml.Controls.Primitives; using Uno.Disposables; using Uno.UI.Helpers.WinUI; -using Windows.Foundation; -using Windows.System; +using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Automation; using Windows.UI.Xaml.Automation.Peers; @@ -31,610 +29,655 @@ #if HAS_UNO_WINUI using Microsoft.UI.Input; #else -using Windows.UI.Input; -using Windows.Devices.Input; #endif -namespace Microsoft.UI.Xaml.Controls +namespace Microsoft.UI.Xaml.Controls; + +public partial class NavigationViewItem : NavigationViewItemBase { - public partial class NavigationViewItem : NavigationViewItemBase - { - private const string c_navigationViewItemPresenterName = "NavigationViewItemPresenter"; - private const string c_repeater = "NavigationViewItemMenuItemsHost"; - private const string c_rootGrid = "NVIRootGrid"; - private const string c_flyoutContentGrid = "FlyoutContentGrid"; - - // Visual States - private const string c_pressedSelected = "PressedSelected"; - private const string c_pointerOverSelected = "PointerOverSelected"; - private const string c_selected = "Selected"; - private const string c_pressed = "Pressed"; - private const string c_pointerOver = "PointerOver"; - private const string c_disabled = "Disabled"; - private const string c_enabled = "Enabled"; - private const string c_normal = "Normal"; - private const string c_chevronHidden = "ChevronHidden"; - private const string c_chevronVisibleOpen = "ChevronVisibleOpen"; - private const string c_chevronVisibleClosed = "ChevronVisibleClosed"; - - private const string c_normalChevronHidden = "NormalChevronHidden"; - private const string c_normalChevronVisibleOpen = "NormalChevronVisibleOpen"; - private const string c_normalChevronVisibleClosed = "NormalChevronVisibleClosed"; - private const string c_pointerOverChevronHidden = "PointerOverChevronHidden"; - private const string c_pointerOverChevronVisibleOpen = "PointerOverChevronVisibleOpen"; - private const string c_pointerOverChevronVisibleClosed = "PointerOverChevronVisibleClosed"; - private const string c_pressedChevronHidden = "PressedChevronHidden"; - private const string c_pressedChevronVisibleOpen = "PressedChevronVisibleOpen"; - private const string c_pressedChevronVisibleClosed = "PressedChevronVisibleClosed"; - - public NavigationViewItem() - { - DefaultStyleKey = typeof(NavigationViewItem); - SetValue(MenuItemsProperty, new List()); - } + private const string c_navigationViewItemPresenterName = "NavigationViewItemPresenter"; + private const string c_repeater = "NavigationViewItemMenuItemsHost"; + private const string c_rootGrid = "NVIRootGrid"; + private const string c_flyoutContentGrid = "FlyoutContentGrid"; + + // Visual States + private const string c_pressedSelected = "PressedSelected"; + private const string c_pointerOverSelected = "PointerOverSelected"; + private const string c_selected = "Selected"; + private const string c_pressed = "Pressed"; + private const string c_pointerOver = "PointerOver"; + private const string c_disabled = "Disabled"; + private const string c_enabled = "Enabled"; + private const string c_normal = "Normal"; + private const string c_chevronHidden = "ChevronHidden"; + private const string c_chevronVisibleOpen = "ChevronVisibleOpen"; + private const string c_chevronVisibleClosed = "ChevronVisibleClosed"; + + private const string c_normalChevronHidden = "NormalChevronHidden"; + private const string c_normalChevronVisibleOpen = "NormalChevronVisibleOpen"; + private const string c_normalChevronVisibleClosed = "NormalChevronVisibleClosed"; + private const string c_pointerOverChevronHidden = "PointerOverChevronHidden"; + private const string c_pointerOverChevronVisibleOpen = "PointerOverChevronVisibleOpen"; + private const string c_pointerOverChevronVisibleClosed = "PointerOverChevronVisibleClosed"; + private const string c_pressedChevronHidden = "PressedChevronHidden"; + private const string c_pressedChevronVisibleOpen = "PressedChevronVisibleOpen"; + private const string c_pressedChevronVisibleClosed = "PressedChevronVisibleClosed"; + + public NavigationViewItem() + { + DefaultStyleKey = typeof(NavigationViewItem); + SetValue(MenuItemsProperty, new List()); + } - internal void UpdateVisualStateNoTransition() - { - UpdateVisualState(false /*useTransition*/); - } + internal void UpdateVisualStateNoTransition() + { + UpdateVisualState(false /*useTransition*/); + } - protected override void OnNavigationViewItemBaseDepthChanged() - { - UpdateItemIndentation(); - PropagateDepthToChildren(Depth + 1); - } + protected override void OnNavigationViewItemBaseDepthChanged() + { + UpdateItemIndentation(); + PropagateDepthToChildren(Depth + 1); + } - protected override void OnNavigationViewItemBaseIsSelectedChanged() - { - UpdateVisualStateForPointer(); - } + protected override void OnNavigationViewItemBaseIsSelectedChanged() + { + UpdateVisualStateForPointer(); + } + + protected override void OnNavigationViewItemBasePositionChanged() + { + UpdateVisualStateNoTransition(); + ReparentRepeater(); + } - protected override void OnNavigationViewItemBasePositionChanged() + protected override void OnApplyTemplate() + { + // TODO: Uno specific: NavigationView may not be set yet, wait for later #4689 + if (GetNavigationView() is null) { - UpdateVisualStateNoTransition(); - ReparentRepeater(); + // Postpone template application for later + return; } - protected override void OnApplyTemplate() - { - // TODO: Uno specific: NavigationView may not be set yet, wait for later #4689 - if (GetNavigationView() is null) - { - // Postpone template application for later - return; - } + // Stop UpdateVisualState before template is applied. Otherwise the visuals may be unexpected + m_appliedTemplate = false; - // Stop UpdateVisualState before template is applied. Otherwise the visuals may be unexpected - m_appliedTemplate = false; + UnhookEventsAndClearFields(); - UnhookEventsAndClearFields(); + base.OnApplyTemplate(); - base.OnApplyTemplate(); + // Find selection indicator + // Retrieve pointers to stable controls + //IControlProtected controlProtected = this; + m_helper.Init(this); - // Find selection indicator - // Retrieve pointers to stable controls - //IControlProtected controlProtected = this; - m_helper.Init(this); + var rootGrid = GetTemplateChild(c_rootGrid) as Grid; + if (rootGrid != null) + { + m_rootGrid = rootGrid; - var rootGrid = GetTemplateChild(c_rootGrid) as Grid; - if (rootGrid != null) + var flyoutBase = FlyoutBase.GetAttachedFlyout(rootGrid); + if (flyoutBase != null) { - m_rootGrid = rootGrid; - - var flyoutBase = FlyoutBase.GetAttachedFlyout(rootGrid); - if (flyoutBase != null) - { - flyoutBase.Closing += OnFlyoutClosing; - m_flyoutClosingRevoker.Disposable = Disposable.Create(() => flyoutBase.Closing -= OnFlyoutClosing); - } + flyoutBase.Closing += OnFlyoutClosing; + m_flyoutClosingRevoker.Disposable = Disposable.Create(() => flyoutBase.Closing -= OnFlyoutClosing); } + } - HookInputEvents(); + HookInputEvents(); - IsEnabledChanged += OnIsEnabledChanged; - m_isEnabledChangedRevoker.Disposable = Disposable.Create(() => IsEnabledChanged -= OnIsEnabledChanged); + IsEnabledChanged += OnIsEnabledChanged; + m_isEnabledChangedRevoker.Disposable = Disposable.Create(() => IsEnabledChanged -= OnIsEnabledChanged); - m_toolTip = (ToolTip)GetTemplateChild("ToolTip"); + m_toolTip = (ToolTip)GetTemplateChild("ToolTip"); - var splitView = GetSplitView(); - if (splitView != null) - { - PrepNavigationViewItem(splitView); - } - else - { - // If the NVI is not prepared in an ItemPresenter, it will not have reference to SplitView. So check OnLoaded - // if it the reference has been manually set in NavigationViewItemBase::OnLoaded(). - Loaded -= OnLoaded; - Loaded += OnLoaded; - } - - // Retrieve reference to NavigationView - var nvImpl = GetNavigationView(); - if (nvImpl != null) - { - var repeater = GetTemplateChild(c_repeater) as ItemsRepeater; - if (repeater != null) - { - m_repeater = repeater; + var splitView = GetSplitView(); + if (splitView != null) + { + PrepNavigationViewItem(splitView); + } + else + { + // If the NVI is not prepared in an ItemPresenter, it will not have reference to SplitView. So check OnLoaded + // if it the reference has been manually set in NavigationViewItemBase::OnLoaded(). + Loaded -= OnLoaded; + Loaded += OnLoaded; + } - // Primary element setup happens in NavigationView - repeater.ElementPrepared += nvImpl.OnRepeaterElementPrepared; - m_repeaterElementPreparedRevoker.Disposable = Disposable.Create(() => repeater.ElementPrepared -= nvImpl.OnRepeaterElementPrepared); - repeater.ElementClearing += nvImpl.OnRepeaterElementClearing; - m_repeaterElementClearingRevoker.Disposable = Disposable.Create(() => repeater.ElementClearing -= nvImpl.OnRepeaterElementClearing); + if (HasPotentialChildren()) + { + LoadMenuItemsHost(); - repeater.ItemTemplate = nvImpl.GetNavigationViewItemsFactory(); - } + UpdateRepeaterItemsSource(); + } - UpdateRepeaterItemsSource(); - } + m_flyoutContentGrid = (Grid)GetTemplateChild(c_flyoutContentGrid); - m_flyoutContentGrid = (Grid)GetTemplateChild(c_flyoutContentGrid); + m_appliedTemplate = true; - m_appliedTemplate = true; + UpdateItemIndentation(); + UpdateVisualStateNoTransition(); + ReparentRepeater(); + // We dont want to update the repeater visibilty during OnApplyTemplate if NavigationView is in a mode when items are shown in a flyout + if (!ShouldRepeaterShowInFlyout()) + { + ShowHideChildren(); + } - UpdateItemIndentation(); - UpdateVisualStateNoTransition(); - ReparentRepeater(); - // We dont want to update the repeater visibilty during OnApplyTemplate if NavigationView is in a mode when items are shown in a flyout - if (!ShouldRepeaterShowInFlyout()) - { - ShowHideChildren(); - } + var visual = ElementCompositionPreview.GetElementVisual(this); + NavigationView.CreateAndAttachHeaderAnimation(visual); - var visual = ElementCompositionPreview.GetElementVisual(this); - NavigationView.CreateAndAttachHeaderAnimation(visual); + _fullyInitialized = true; + } - _fullyInitialized = true; - } + private void LoadElementsForDisplayingChildren() + { + m_hasHadChildren = true; - private void OnLoaded(object sender, RoutedEventArgs args) - { - if (GetSplitView() is { } splitView) - { - PrepNavigationViewItem(splitView); - } - } + LoadMenuItemsHost(); - private void UpdateRepeaterItemsSource() + if (GetPresenter() is { } nvip) { - var repeater = m_repeater; - if (repeater != null) - { - object GetItemsSource() - { - var menuItemsSource = MenuItemsSource; - if (menuItemsSource != null) - { - return menuItemsSource; - } - return MenuItems; - } - var itemsSource = GetItemsSource(); - m_itemsSourceViewCollectionChangedRevoker.Disposable = null; - repeater.ItemsSource = itemsSource; - repeater.ItemsSourceView.CollectionChanged += OnItemsSourceViewChanged; - m_itemsSourceViewCollectionChangedRevoker.Disposable = Disposable.Create(() => repeater.ItemsSourceView.CollectionChanged -= OnItemsSourceViewChanged); - } + nvip.LoadChevron(); + UpdateVisualStateForChevron(); } + } - private void OnItemsSourceViewChanged(object sender, NotifyCollectionChangedEventArgs args) + private void LoadMenuItemsHost() + { + // verify repeater is not already loaded + if (m_repeater != null) { - UpdateVisualStateForChevron(); + return; } - internal UIElement GetSelectionIndicator() + if (GetNavigationView() is { } nvImpl) { - var selectIndicator = m_helper.GetSelectionIndicator(); - var presenter = GetPresenter(); - if (presenter != null) + if (GetTemplateChild(c_repeater) is { } repeater) { - selectIndicator = presenter.GetSelectionIndicator(); + m_repeater = repeater; + + // Primary element setup happens in NavigationView + m_repeaterElementPreparedRevoker = repeater.ElementPrepared(winrt::auto_revoke, { nvImpl, &NavigationView::OnRepeaterElementPrepared }); + m_repeaterElementClearingRevoker = repeater.ElementClearing(winrt::auto_revoke, { nvImpl, &NavigationView::OnRepeaterElementClearing }); + + repeater.ItemTemplate(*(nvImpl->GetNavigationViewItemsFactory())); } - return selectIndicator; } + } - private void OnSplitViewPropertyChanged(DependencyObject sender, DependencyProperty args) + private void OnLoaded(object sender, RoutedEventArgs args) + { + if (GetSplitView() is { } splitView) { - if (args == SplitView.CompactPaneLengthProperty) - { - UpdateCompactPaneLength(); - } - else if (args == SplitView.IsPaneOpenProperty || - args == SplitView.DisplayModeProperty) - { - UpdateIsClosedCompact(); - ReparentRepeater(); - } + PrepNavigationViewItem(splitView); } + } - private void UpdateCompactPaneLength() + private void UpdateRepeaterItemsSource() + { + var repeater = m_repeater; + if (repeater != null) { - var splitView = GetSplitView(); - if (splitView != null) + object GetItemsSource() { - SetValue(CompactPaneLengthProperty, splitView.CompactPaneLength); //PropertyValue.CreateDouble(splitView.CompactPaneLength)); - - // Only update when on left - var presenter = GetPresenter(); - if (presenter != null) + var menuItemsSource = MenuItemsSource; + if (menuItemsSource != null) { - presenter.UpdateCompactPaneLength(splitView.CompactPaneLength, IsOnLeftNav()); + return menuItemsSource; } + return MenuItems; } + var itemsSource = GetItemsSource(); + m_itemsSourceViewCollectionChangedRevoker.Disposable = null; + repeater.ItemsSource = itemsSource; + repeater.ItemsSourceView.CollectionChanged += OnItemsSourceViewChanged; + m_itemsSourceViewCollectionChangedRevoker.Disposable = Disposable.Create(() => repeater.ItemsSourceView.CollectionChanged -= OnItemsSourceViewChanged); } + } + + private void OnItemsSourceViewChanged(object sender, NotifyCollectionChangedEventArgs args) + { + UpdateVisualStateForChevron(); + } - private void UpdateIsClosedCompact() + internal UIElement GetSelectionIndicator() + { + var selectIndicator = m_helper.GetSelectionIndicator(); + var presenter = GetPresenter(); + if (presenter != null) { - var splitView = GetSplitView(); if (splitView != null) - { - // Check if the pane is closed and if the splitview is in either compact mode. - m_isClosedCompact = !splitView.IsPaneOpen - && (splitView.DisplayMode == SplitViewDisplayMode.CompactOverlay || splitView.DisplayMode == SplitViewDisplayMode.CompactInline); + selectIndicator = presenter.GetSelectionIndicator(); + } + return selectIndicator; + } - UpdateVisualState(true /*useTransitions*/); - } + private void OnSplitViewPropertyChanged(DependencyObject sender, DependencyProperty args) + { + if (args == SplitView.CompactPaneLengthProperty) + { + UpdateCompactPaneLength(); + } + else if (args == SplitView.IsPaneOpenProperty || + args == SplitView.DisplayModeProperty) + { + UpdateIsClosedCompact(); + ReparentRepeater(); } + } - private void UpdateNavigationViewItemToolTip() + private void UpdateCompactPaneLength() + { + var splitView = GetSplitView(); + if (splitView != null) { - var toolTipContent = ToolTipService.GetToolTip(this); + SetValue(CompactPaneLengthProperty, splitView.CompactPaneLength); //PropertyValue.CreateDouble(splitView.CompactPaneLength)); - // no custom tooltip, then use suggested tooltip - if (toolTipContent == null || toolTipContent == m_suggestedToolTipContent) + // Only update when on left + var presenter = GetPresenter(); + if (presenter != null) { - if (ShouldEnableToolTip()) - { - // Don't SetToolTip with the same parameter because it close/re-open the ToolTip - if (toolTipContent != m_suggestedToolTipContent) - { - ToolTipService.SetToolTip(this, m_suggestedToolTipContent); - } - } - else - { - ToolTipService.SetToolTip(this, null); - } + presenter.UpdateCompactPaneLength(splitView.CompactPaneLength, IsOnLeftNav()); } } + } - private void SuggestedToolTipChanged(object newContent) + private void UpdateIsClosedCompact() + { + var splitView = GetSplitView(); if (splitView != null) { - var potentialString = newContent; - bool validStringableToolTip = potentialString != null - && potentialString is string stringData - && !string.IsNullOrEmpty(stringData); + // Check if the pane is closed and if the splitview is in either compact mode. + m_isClosedCompact = !splitView.IsPaneOpen + && (splitView.DisplayMode == SplitViewDisplayMode.CompactOverlay || splitView.DisplayMode == SplitViewDisplayMode.CompactInline); - object newToolTipContent = null; - if (validStringableToolTip) - { - newToolTipContent = newContent; - } + UpdateVisualState(true /*useTransitions*/); + } + } - // Both customer and NavigationViewItem can update ToolTipContent by ToolTipService.SetToolTip or XAML - // If the ToolTipContent is not the same as m_suggestedToolTipContent, then it's set by customer. - // Customer's ToolTip take high priority, and we never override Customer's ToolTip. - var toolTipContent = ToolTipService.GetToolTip(this); - var oldToolTipContent = m_suggestedToolTipContent; - if (oldToolTipContent != null) + private void UpdateNavigationViewItemToolTip() + { + var toolTipContent = ToolTipService.GetToolTip(this); + + // no custom tooltip, then use suggested tooltip + if (toolTipContent == null || toolTipContent == m_suggestedToolTipContent) + { + if (ShouldEnableToolTip()) { - if (oldToolTipContent == toolTipContent) + // Don't SetToolTip with the same parameter because it close/re-open the ToolTip + if (toolTipContent != m_suggestedToolTipContent) { - ToolTipService.SetToolTip(this, null); + ToolTipService.SetToolTip(this, m_suggestedToolTipContent); } } - - m_suggestedToolTipContent = newToolTipContent; - } - - private void OnIsExpandedPropertyChanged(DependencyPropertyChangedEventArgs args) - { - AutomationPeer peer = FrameworkElementAutomationPeer.FromElement(this); - if (peer != null) + else { - var navViewItemPeer = (NavigationViewItemAutomationPeer)peer; - navViewItemPeer.RaiseExpandCollapseAutomationEvent( - IsExpanded ? - ExpandCollapseState.Expanded : - ExpandCollapseState.Collapsed - ); + ToolTipService.SetToolTip(this, null); } - UpdateVisualState(true); } + } - private void OnIconPropertyChanged(DependencyPropertyChangedEventArgs args) - { - UpdateVisualStateNoTransition(); - } + private void SuggestedToolTipChanged(object newContent) + { + var potentialString = newContent; + bool validStringableToolTip = potentialString != null + && potentialString is string stringData + && !string.IsNullOrEmpty(stringData); - private void OnInfoBadgePropertyChanged(DependencyPropertyChangedEventArgs args) + object newToolTipContent = null; + if (validStringableToolTip) { - UpdateVisualStateForInfoBadge(); + newToolTipContent = newContent; } - private void OnMenuItemsPropertyChanged(DependencyPropertyChangedEventArgs args) + // Both customer and NavigationViewItem can update ToolTipContent by ToolTipService.SetToolTip or XAML + // If the ToolTipContent is not the same as m_suggestedToolTipContent, then it's set by customer. + // Customer's ToolTip take high priority, and we never override Customer's ToolTip. + var toolTipContent = ToolTipService.GetToolTip(this); + var oldToolTipContent = m_suggestedToolTipContent; + if (oldToolTipContent != null) { - UpdateRepeaterItemsSource(); - UpdateVisualStateForChevron(); + if (oldToolTipContent == toolTipContent) + { + ToolTipService.SetToolTip(this, null); + } } - private void OnMenuItemsSourcePropertyChanged(DependencyPropertyChangedEventArgs args) + m_suggestedToolTipContent = newToolTipContent; + } + + private void OnIsExpandedPropertyChanged(DependencyPropertyChangedEventArgs args) + { + AutomationPeer peer = FrameworkElementAutomationPeer.FromElement(this); + if (peer != null) { - UpdateRepeaterItemsSource(); - UpdateVisualStateForChevron(); + var navViewItemPeer = (NavigationViewItemAutomationPeer)peer; + navViewItemPeer.RaiseExpandCollapseAutomationEvent( + IsExpanded ? + ExpandCollapseState.Expanded : + ExpandCollapseState.Collapsed + ); } + UpdateVisualState(true); + } - private void OnHasUnrealizedChildrenPropertyChanged(DependencyPropertyChangedEventArgs args) + private void OnIconPropertyChanged(DependencyPropertyChangedEventArgs args) + { + UpdateVisualStateNoTransition(); + } + + private void OnInfoBadgePropertyChanged(DependencyPropertyChangedEventArgs args) + { + UpdateVisualStateForInfoBadge(); + } + + private void OnMenuItemsVectorChanged(IObservableVector sender, IVectorChangedEventArgs args) + { + LoadElementsForDisplayingChildren(); + } + + private void OnMenuItemsPropertyChanged(DependencyPropertyChangedEventArgs args) + { + m_menuItemsVectorChangedRevoker.Disposable = null; + if (MenuItems is { } menuItemsVector) { - UpdateVisualStateForChevron(); + if (menuItemsVector is IObservableVector menuItemsObservableVector) + { + menuItemsObservableVector.VectorChanged += OnMenuItemsVectorChanged; + m_menuItemsVectorChangedRevoker.Disposable = Disposable.Create(() => menuItemsObservableVector.VectorChanged -= OnMenuItemsVectorChanged); + } + + if (menuItemsVector.Count > 0) + { + LoadElementsForDisplayingChildren(); + } } + UpdateRepeaterItemsSource(); + UpdateVisualStateForChevron(); + } + + private void OnMenuItemsSourcePropertyChanged(DependencyPropertyChangedEventArgs args) + { + LoadElementsForDisplayingChildren(); + UpdateRepeaterItemsSource(); + UpdateVisualStateForChevron(); + } + + private void OnHasUnrealizedChildrenPropertyChanged(DependencyPropertyChangedEventArgs args) + { + LoadElementsForDisplayingChildren(); + UpdateVisualStateForChevron(); + } #if false - private void ShowSelectionIndicator(bool visible) + private void ShowSelectionIndicator(bool visible) + { + var selectionIndicator = GetSelectionIndicator(); + if (selectionIndicator != null) { - var selectionIndicator = GetSelectionIndicator(); - if (selectionIndicator != null) - { - selectionIndicator.Opacity = visible ? 1.0 : 0.0; - } + selectionIndicator.Opacity = visible ? 1.0 : 0.0; } + } #endif - private void UpdateVisualStateForIconAndContent(bool showIcon, bool showContent) + private void UpdateVisualStateForIconAndContent(bool showIcon, bool showContent) + { + var presenter = m_navigationViewItemPresenter; + if (presenter != null) { - var presenter = m_navigationViewItemPresenter; - if (presenter != null) - { - var stateName = showIcon ? (showContent ? "IconOnLeft" : "IconOnly") : "ContentOnly"; - VisualStateManager.GoToState(presenter, stateName, false /*useTransitions*/); - } + var stateName = showIcon ? (showContent ? "IconOnLeft" : "IconOnly") : "ContentOnly"; + VisualStateManager.GoToState(presenter, stateName, false /*useTransitions*/); } + } - private void UpdateVisualStateForInfoBadge() + private void UpdateVisualStateForInfoBadge() + { + if (m_navigationViewItemPresenter is { } presenter) { - if (m_navigationViewItemPresenter is { } presenter) - { - var stateName = ShouldShowInfoBadge() ? "InfoBadgeVisible" : "InfoBadgeCollapsed"; - VisualStateManager.GoToState(presenter, stateName, false /*useTransitions*/); - } + var stateName = ShouldShowInfoBadge() ? "InfoBadgeVisible" : "InfoBadgeCollapsed"; + VisualStateManager.GoToState(presenter, stateName, false /*useTransitions*/); } + } - private void UpdateVisualStateForClosedCompact() + private void UpdateVisualStateForClosedCompact() + { + if (GetPresenter() is { } presenter) { - if (GetPresenter() is { } presenter) - { - presenter.UpdateClosedCompactVisualState(IsTopLevelItem, m_isClosedCompact); - } + presenter.UpdateClosedCompactVisualState(IsTopLevelItem, m_isClosedCompact); } + } - private void UpdateVisualStateForNavigationViewPositionChange() - { - var position = Position; - var stateName = NavigationViewItemHelper.c_OnLeftNavigation; + private void UpdateVisualStateForNavigationViewPositionChange() + { + var position = Position; + var stateName = NavigationViewItemHelper.c_OnLeftNavigation; - bool handled = false; + bool handled = false; - switch (position) - { - case NavigationViewRepeaterPosition.LeftNav: - case NavigationViewRepeaterPosition.LeftFooter: - if (SharedHelpers.IsRS4OrHigher() && Application.Current.FocusVisualKind == FocusVisualKind.Reveal) + switch (position) + { + case NavigationViewRepeaterPosition.LeftNav: + case NavigationViewRepeaterPosition.LeftFooter: + if (SharedHelpers.IsRS4OrHigher() && Application.Current.FocusVisualKind == FocusVisualKind.Reveal) + { + // OnLeftNavigationReveal is introduced in RS6 and only in the V1 style. + // Fallback to OnLeftNavigation for other styles. + if (VisualStateManager.GoToState(this, NavigationViewItemHelper.c_OnLeftNavigationReveal, false /*useTransitions*/)) { - // OnLeftNavigationReveal is introduced in RS6 and only in the V1 style. - // Fallback to OnLeftNavigation for other styles. - if (VisualStateManager.GoToState(this, NavigationViewItemHelper.c_OnLeftNavigationReveal, false /*useTransitions*/)) - { - handled = true; - } + handled = true; } - break; - case NavigationViewRepeaterPosition.TopPrimary: - case NavigationViewRepeaterPosition.TopFooter: - stateName = NavigationViewItemHelper.c_OnTopNavigationPrimary; - if (SharedHelpers.IsRS4OrHigher() && Application.Current.FocusVisualKind == FocusVisualKind.Reveal) + } + break; + case NavigationViewRepeaterPosition.TopPrimary: + case NavigationViewRepeaterPosition.TopFooter: + stateName = NavigationViewItemHelper.c_OnTopNavigationPrimary; + if (SharedHelpers.IsRS4OrHigher() && Application.Current.FocusVisualKind == FocusVisualKind.Reveal) + { + // OnTopNavigationPrimaryReveal is introduced in RS6 and only in the V1 style. + // Fallback to c_OnTopNavigationPrimary for other styles. + if (VisualStateManager.GoToState(this, NavigationViewItemHelper.c_OnTopNavigationPrimaryReveal, false /*useTransitions*/)) { - // OnTopNavigationPrimaryReveal is introduced in RS6 and only in the V1 style. - // Fallback to c_OnTopNavigationPrimary for other styles. - if (VisualStateManager.GoToState(this, NavigationViewItemHelper.c_OnTopNavigationPrimaryReveal, false /*useTransitions*/)) - { - handled = true; - } + handled = true; } - break; - case NavigationViewRepeaterPosition.TopOverflow: - stateName = NavigationViewItemHelper.c_OnTopNavigationOverflow; - break; - } - - if (!handled) - { - VisualStateManager.GoToState(this, stateName, false /*useTransitions*/); - } + } + break; + case NavigationViewRepeaterPosition.TopOverflow: + stateName = NavigationViewItemHelper.c_OnTopNavigationOverflow; + break; } - private void UpdateVisualStateForKeyboardFocusedState() + if (!handled) { - var focusState = "KeyboardNormal"; - if (m_hasKeyboardFocus) - { - focusState = "KeyboardFocused"; - } + VisualStateManager.GoToState(this, stateName, false /*useTransitions*/); + } + } - VisualStateManager.GoToState(this, focusState, false /*useTransitions*/); + private void UpdateVisualStateForKeyboardFocusedState() + { + var focusState = "KeyboardNormal"; + if (m_hasKeyboardFocus) + { + focusState = "KeyboardFocused"; } - private void UpdateVisualStateForToolTip() + VisualStateManager.GoToState(this, focusState, false /*useTransitions*/); + } + + private void UpdateVisualStateForToolTip() + { + // Since RS5, ToolTip apply to NavigationViewItem directly to make Keyboard focus has tooltip too. + // If ToolTip TemplatePart is detected, fallback to old logic and apply ToolTip on TemplatePart. + var toolTip = m_toolTip; if (toolTip != null) { - // Since RS5, ToolTip apply to NavigationViewItem directly to make Keyboard focus has tooltip too. - // If ToolTip TemplatePart is detected, fallback to old logic and apply ToolTip on TemplatePart. - var toolTip = m_toolTip; if (toolTip != null) + var shouldEnableToolTip = ShouldEnableToolTip(); + var toolTipContent = m_suggestedToolTipContent; + if (shouldEnableToolTip && toolTipContent != null) { - var shouldEnableToolTip = ShouldEnableToolTip(); - var toolTipContent = m_suggestedToolTipContent; - if (shouldEnableToolTip && toolTipContent != null) - { - toolTip.Content = toolTipContent; - toolTip.IsEnabled = true; - } - else - { - toolTip.Content = null; - toolTip.IsEnabled = false; - } + toolTip.Content = toolTipContent; + toolTip.IsEnabled = true; } - else { - UpdateNavigationViewItemToolTip(); + toolTip.Content = null; + toolTip.IsEnabled = false; } } - private void UpdateVisualStateForPointer() + else { - var isEnabled = IsEnabled; - var enabledStateValue = isEnabled ? c_enabled : c_disabled; + UpdateNavigationViewItemToolTip(); + } + } + + private void UpdateVisualStateForPointer() + { + var isEnabled = IsEnabled; + var enabledStateValue = isEnabled ? c_enabled : c_disabled; - string GetSelectedStateValue(bool isEnabled, bool isSelected) + string GetSelectedStateValue(bool isEnabled, bool isSelected) + { + if (isEnabled) { - if (isEnabled) + if (isSelected) { - if (isSelected) + if (m_isPressed && !_uno_isDefferingPressedState) { - if (m_isPressed && !_uno_isDefferingPressedState) - { - return c_pressedSelected; - } - else if (m_isPointerOver && !_uno_isDefferingOverState) - { - return c_pointerOverSelected; - } - else - { - return c_selected; - } + return c_pressedSelected; } else if (m_isPointerOver && !_uno_isDefferingOverState) { - if (m_isPressed && !_uno_isDefferingPressedState) - { - return c_pressed; - } - else - { - return c_pointerOver; - } + return c_pointerOverSelected; } - else if (m_isPressed && !_uno_isDefferingPressedState) + else { - return c_pressed; + return c_selected; } } - else + else if (m_isPointerOver && !_uno_isDefferingOverState) { - if (isSelected) + if (m_isPressed && !_uno_isDefferingPressedState) { - return c_selected; + return c_pressed; + } + else + { + return c_pointerOver; } } - return c_normal; - } - // DisabledStates and CommonStates - var selectedStateValue = GetSelectedStateValue(isEnabled, IsSelected); - - // There are scenarios where the presenter may not exist. - // For example, the top nav settings item. In that case, - // update the states for the item itself. - var presenter = m_navigationViewItemPresenter; - if (presenter != null) - { - VisualStateManager.GoToState(presenter, enabledStateValue, true); - VisualStateManager.GoToState(presenter, selectedStateValue, true); + else if (m_isPressed && !_uno_isDefferingPressedState) + { + return c_pressed; + } } - else { - VisualStateManager.GoToState(this, enabledStateValue, true); - VisualStateManager.GoToState(this, selectedStateValue, true); + if (isSelected) + { + return c_selected; + } } + return c_normal; } + // DisabledStates and CommonStates + var selectedStateValue = GetSelectedStateValue(isEnabled, IsSelected); - internal override void UpdateVisualState(bool useTransitions) + // There are scenarios where the presenter may not exist. + // For example, the top nav settings item. In that case, + // update the states for the item itself. + var presenter = m_navigationViewItemPresenter; + if (presenter != null) { - if (!m_appliedTemplate) - { - return; - } + VisualStateManager.GoToState(presenter, enabledStateValue, true); + VisualStateManager.GoToState(presenter, selectedStateValue, true); + } + + else + { + VisualStateManager.GoToState(this, enabledStateValue, true); + VisualStateManager.GoToState(this, selectedStateValue, true); + } + } - UpdateVisualStateForPointer(); + internal override void UpdateVisualState(bool useTransitions) + { + if (!m_appliedTemplate) + { + return; + } - UpdateVisualStateForNavigationViewPositionChange(); + UpdateVisualStateForPointer(); - bool shouldShowIcon = ShouldShowIcon(); - bool shouldShowContent = ShouldShowContent(); + UpdateVisualStateForNavigationViewPositionChange(); - if (IsOnLeftNav()) - { - var presenter = m_navigationViewItemPresenter; - if (presenter != null) - { - // Backward Compatibility with RS4-, new implementation prefer IconOnLeft/IconOnly/ContentOnly - VisualStateManager.GoToState(presenter, shouldShowIcon ? "IconVisible" : "IconCollapsed", useTransitions); - } + bool shouldShowIcon = ShouldShowIcon(); + bool shouldShowContent = ShouldShowContent(); - UpdateVisualStateForClosedCompact(); + if (IsOnLeftNav()) + { + var presenter = m_navigationViewItemPresenter; + if (presenter != null) + { + // Backward Compatibility with RS4-, new implementation prefer IconOnLeft/IconOnly/ContentOnly + VisualStateManager.GoToState(presenter, shouldShowIcon ? "IconVisible" : "IconCollapsed", useTransitions); } - UpdateVisualStateForToolTip(); + UpdateVisualStateForClosedCompact(); + } - UpdateVisualStateForIconAndContent(shouldShowIcon, shouldShowContent); + UpdateVisualStateForToolTip(); - UpdateVisualStateForInfoBadge(); + UpdateVisualStateForIconAndContent(shouldShowIcon, shouldShowContent); - // visual state for focus state. top navigation use it to provide different visual for selected and selected+focused - UpdateVisualStateForKeyboardFocusedState(); + UpdateVisualStateForInfoBadge(); - UpdateVisualStateForChevron(); - } + // visual state for focus state. top navigation use it to provide different visual for selected and selected+focused + UpdateVisualStateForKeyboardFocusedState(); - private enum PointerStateValue { Normal, PointerOver, Pressed }; + UpdateVisualStateForChevron(); + } + + private enum PointerStateValue { Normal, PointerOver, Pressed }; - private enum ChevronStateValue { ChevronHidden, ChevronVisibleOpen, ChevronVisibleClosed }; + private enum ChevronStateValue { ChevronHidden, ChevronVisibleOpen, ChevronVisibleClosed }; - private void UpdateVisualStateForChevron() + private void UpdateVisualStateForChevron() + { + var presenter = m_navigationViewItemPresenter; + if (presenter != null) { - var presenter = m_navigationViewItemPresenter; - if (presenter != null) + PointerStateValue GetPointerStateValue(bool isEnabled, bool isSelected) { - PointerStateValue GetPointerStateValue(bool isEnabled, bool isSelected) + if (isEnabled) { - if (isEnabled) + if (m_isPointerOver) { - if (m_isPointerOver) + if (m_isPressed) { - if (m_isPressed) - { - return PointerStateValue.Pressed; //Pressed - } - else - { - return PointerStateValue.PointerOver; //PointerOver - } + return PointerStateValue.Pressed; //Pressed } - - else if (m_isPressed) + else { - return PointerStateValue.Pressed; //Pressed + return PointerStateValue.PointerOver; //PointerOver } } - return PointerStateValue.Normal; //Normal + + else if (m_isPressed) + { + return PointerStateValue.Pressed; //Pressed + } } + return PointerStateValue.Normal; //Normal + } - var pointerStateValue = GetPointerStateValue(IsEnabled, IsSelected); + var pointerStateValue = GetPointerStateValue(IsEnabled, IsSelected); - var chevronState = HasChildren() && !(m_isClosedCompact && ShouldRepeaterShowInFlyout()) ? (IsExpanded ? ChevronStateValue.ChevronVisibleOpen : ChevronStateValue.ChevronVisibleClosed) : ChevronStateValue.ChevronHidden; + var chevronState = HasChildren() && !(m_isClosedCompact && ShouldRepeaterShowInFlyout()) ? (IsExpanded ? ChevronStateValue.ChevronVisibleOpen : ChevronStateValue.ChevronVisibleClosed) : ChevronStateValue.ChevronHidden; - static string GetPointerChevronState(PointerStateValue pointerStateValue, ChevronStateValue chevronState) + string GetPointerChevronState(PointerStateValue pointerStateValue, ChevronStateValue chevronState) + { + // This Visual State Group will load the chevron in the PointerOver & Pressed states even if it is hidden. + // In order to avoid loading the chevron when we don't need it, only execute this if we can confirm chevron is needed. + if (m_hasHadChildren) { if (chevronState == ChevronStateValue.ChevronHidden) { @@ -681,593 +724,601 @@ static string GetPointerChevronState(PointerStateValue pointerStateValue, Chevro return c_pressedChevronVisibleClosed; } } - return c_normalChevronHidden; } + return c_normalChevronHidden; + } - var pointerChevronState = GetPointerChevronState(pointerStateValue, chevronState); - // Go to the appropriate pointerChevronState - VisualStateManager.GoToState(presenter, pointerChevronState, true); + var pointerChevronState = GetPointerChevronState(pointerStateValue, chevronState); + // Go to the appropriate pointerChevronState + VisualStateManager.GoToState(presenter, pointerChevronState, true); - // Go to the appropriate chevronState - if (chevronState == ChevronStateValue.ChevronHidden) - { - VisualStateManager.GoToState(presenter, c_chevronHidden, true); - } - else if (chevronState == ChevronStateValue.ChevronVisibleOpen) - { - VisualStateManager.GoToState(presenter, c_chevronVisibleOpen, true); - } - else if (chevronState == ChevronStateValue.ChevronVisibleClosed) - { - VisualStateManager.GoToState(presenter, c_chevronVisibleClosed, true); - } + // Go to the appropriate chevronState + if (chevronState == ChevronStateValue.ChevronHidden) + { + VisualStateManager.GoToState(presenter, c_chevronHidden, true); + } + else if (chevronState == ChevronStateValue.ChevronVisibleOpen) + { + VisualStateManager.GoToState(presenter, c_chevronVisibleOpen, true); + } + else if (chevronState == ChevronStateValue.ChevronVisibleClosed) + { + VisualStateManager.GoToState(presenter, c_chevronVisibleClosed, true); } } + } - internal bool HasChildren() - { - return MenuItems.Count > 0 - || (MenuItemsSource != null && m_repeater != null && m_repeater.ItemsSourceView.Count > 0) - || HasUnrealizedChildren; - } + internal bool HasChildren() => + (MenuItems is not null && MenuItems.Count > 0) || + (MenuItemsSource is not null && m_repeater is not null && m_repeater.ItemsSourceView is not null && m_repeater.ItemsSourceView.Count > 0) || + HasUnrealizedChildren; - private bool ShouldShowIcon() - { - return Icon != null; - } + /// + /// Needed for scenarios where the ItemsRepeater is not loaded (OnApplyTemplate) therefore we cannot guarantee a non-null MenuItemsSource actually contains items. + /// + internal bool HasPotentialChildren() => + (MenuItems is not null && MenuItems.Count > 0) || + MenuItemsSource is not null || + HasUnrealizedChildren; - private bool ShouldShowInfoBadge() - { - return InfoBadge != null; - } + private bool ShouldShowIcon() + { + return Icon != null; + } - private bool ShouldEnableToolTip() - { - // We may enable Tooltip for IconOnly in the future, but not now - return IsOnLeftNav() && m_isClosedCompact; - } + private bool ShouldShowInfoBadge() + { + return InfoBadge != null; + } - private bool ShouldShowContent() - { - return Content != null; - } + private bool ShouldEnableToolTip() + { + // We may enable Tooltip for IconOnly in the future, but not now + return IsOnLeftNav() && m_isClosedCompact; + } - public bool IsOnLeftNav() - { - var position = Position; - return position == NavigationViewRepeaterPosition.LeftNav || position == NavigationViewRepeaterPosition.LeftFooter; - } + private bool ShouldShowContent() + { + return Content != null; + } + + public bool IsOnLeftNav() + { + var position = Position; + return position == NavigationViewRepeaterPosition.LeftNav || position == NavigationViewRepeaterPosition.LeftFooter; + } + + private bool IsOnTopPrimary() + { + return Position == NavigationViewRepeaterPosition.TopPrimary; + } - private bool IsOnTopPrimary() + private UIElement GetPresenterOrItem() + { + var presenter = m_navigationViewItemPresenter; + if (presenter != null) { - return Position == NavigationViewRepeaterPosition.TopPrimary; + return presenter as UIElement; } - private UIElement GetPresenterOrItem() + else { - var presenter = m_navigationViewItemPresenter; - if (presenter != null) - { - return presenter as UIElement; - } - - else - { - return this as UIElement; - } + return this as UIElement; } + } - private NavigationViewItemPresenter GetPresenter() + private NavigationViewItemPresenter GetPresenter() + { + NavigationViewItemPresenter presenter = null; + if (m_navigationViewItemPresenter != null) { - NavigationViewItemPresenter presenter = null; - if (m_navigationViewItemPresenter != null) - { - presenter = m_navigationViewItemPresenter; - } - return presenter; + presenter = m_navigationViewItemPresenter; } + return presenter; + } - internal void ShowHideChildren() + internal void ShowHideChildren() + { + var repeater = m_repeater; + if (repeater != null) { - var repeater = m_repeater; - if (repeater != null) - { - bool shouldShowChildren = IsExpanded; - var visibility = shouldShowChildren ? Visibility.Visible : Visibility.Collapsed; - repeater.Visibility = visibility; + bool shouldShowChildren = IsExpanded; + var visibility = shouldShowChildren ? Visibility.Visible : Visibility.Collapsed; + repeater.Visibility = visibility; - if (ShouldRepeaterShowInFlyout()) + if (ShouldRepeaterShowInFlyout()) + { + if (shouldShowChildren) { - if (shouldShowChildren) + // Verify that repeater is parented correctly + if (!m_isRepeaterParentedToFlyout) { - // Verify that repeater is parented correctly - if (!m_isRepeaterParentedToFlyout) - { - ReparentRepeater(); - } + ReparentRepeater(); + } - // There seems to be a race condition happening which sometimes - // prevents the opening of the flyout. Queue callback as a workaround. + // There seems to be a race condition happening which sometimes + // prevents the opening of the flyout. Queue callback as a workaround. - // TODO: Uno specific - Queue callback for composition rendering is not implemented yet - #4690 - //SharedHelpers.QueueCallbackForCompositionRendering(() => - //{ - FlyoutBase.ShowAttachedFlyout(m_rootGrid); - //}); - } - else + // TODO: Uno specific - Queue callback for composition rendering is not implemented yet - #4690 + //SharedHelpers.QueueCallbackForCompositionRendering(() => + //{ + FlyoutBase.ShowAttachedFlyout(m_rootGrid); + //}); + } + else + { + var flyout = FlyoutBase.GetAttachedFlyout(m_rootGrid); + if (flyout != null) { - var flyout = FlyoutBase.GetAttachedFlyout(m_rootGrid); - if (flyout != null) - { - flyout.Hide(); - } + flyout.Hide(); } } } } + } - private void ReparentRepeater() + private void ReparentRepeater() + { + if (HasChildren()) { - if (HasChildren()) + var repeater = m_repeater; if (repeater != null) { - var repeater = m_repeater; if (repeater != null) + if (ShouldRepeaterShowInFlyout() && !m_isRepeaterParentedToFlyout) { - if (ShouldRepeaterShowInFlyout() && !m_isRepeaterParentedToFlyout) - { - // Reparent repeater to flyout - m_rootGrid.Children.RemoveAt(m_rootGrid.Children.Count - 1); - m_flyoutContentGrid.Children.Add(repeater); - m_isRepeaterParentedToFlyout = true; + // Reparent repeater to flyout + m_rootGrid.Children.RemoveAt(m_rootGrid.Children.Count - 1); + m_flyoutContentGrid.Children.Add(repeater); + m_isRepeaterParentedToFlyout = true; - PropagateDepthToChildren(0); - } - else if (!ShouldRepeaterShowInFlyout() && m_isRepeaterParentedToFlyout) - { - m_flyoutContentGrid.Children.RemoveAt(m_flyoutContentGrid.Children.Count - 1); - m_rootGrid.Children.Add(repeater); - m_isRepeaterParentedToFlyout = false; + PropagateDepthToChildren(0); + } + else if (!ShouldRepeaterShowInFlyout() && m_isRepeaterParentedToFlyout) + { + m_flyoutContentGrid.Children.RemoveAt(m_flyoutContentGrid.Children.Count - 1); + m_rootGrid.Children.Add(repeater); + m_isRepeaterParentedToFlyout = false; - PropagateDepthToChildren(1); - } + PropagateDepthToChildren(1); } } } + } - internal bool ShouldRepeaterShowInFlyout() - { - return (m_isClosedCompact && IsTopLevelItem) || IsOnTopPrimary(); - } + internal bool ShouldRepeaterShowInFlyout() + { + return (m_isClosedCompact && IsTopLevelItem) || IsOnTopPrimary(); + } - internal bool IsRepeaterVisible() + internal bool IsRepeaterVisible() + { + var repeater = m_repeater; if (repeater != null) { - var repeater = m_repeater; if (repeater != null) - { - return repeater.Visibility == Visibility.Visible; - } - return false; + return repeater.Visibility == Visibility.Visible; } + return false; + } - private void UpdateItemIndentation() + private void UpdateItemIndentation() + { + // Update item indentation based on its depth + var presenter = m_navigationViewItemPresenter; + if (presenter != null) { - // Update item indentation based on its depth - var presenter = m_navigationViewItemPresenter; - if (presenter != null) - { - var newLeftMargin = Depth * c_itemIndentation; - presenter.UpdateContentLeftIndentation((double)(newLeftMargin)); - } + var newLeftMargin = Depth * c_itemIndentation; + presenter.UpdateContentLeftIndentation((double)(newLeftMargin)); } + } - internal void PropagateDepthToChildren(int depth) + internal void PropagateDepthToChildren(int depth) + { + var repeater = m_repeater; if (repeater != null) { - var repeater = m_repeater; if (repeater != null) + var itemsCount = repeater.ItemsSourceView.Count; + for (int index = 0; index < itemsCount; index++) { - var itemsCount = repeater.ItemsSourceView.Count; - for (int index = 0; index < itemsCount; index++) + var element = repeater.TryGetElement(index); + if (element != null) { - var element = repeater.TryGetElement(index); - if (element != null) + var nvib = element as NavigationViewItemBase; + if (nvib != null) { - var nvib = element as NavigationViewItemBase; - if (nvib != null) - { - nvib.Depth = depth; - } + nvib.Depth = depth; } } } } + } - // UNO - OnExpandCollapseChevronTapped is not necessary because NavigationViewMenuItem explicitly + internal void OnExpandCollapseChevronTapped(object sender, TappedRoutedEventArgs args) + { + // TODO Uno specific - OnExpandCollapseChevronTapped is not necessary because NavigationViewMenuItem explicitly // captures the pointer, so when the pointer is released, NVMI's Tapped will trigger first, flipping // IsExpanded. So, flipping it here again undoes the change. Now, if the chevron itself it clicked, we will // bubble up to OnNavigationViewItemTapped, which will take care of IsExpanded. - // internal void OnExpandCollapseChevronTapped(object sender, TappedRoutedEventArgs args) - // { - // IsExpanded = !IsExpanded; - // args.Handled = true; - // } - - private void OnFlyoutClosing(object sender, FlyoutBaseClosingEventArgs args) - { - IsExpanded = false; - } +#if !HAS_UNO + IsExpanded = !IsExpanded; + args.Handled = true; +#endif + } - // UIElement / UIElementOverridesHelper - protected override AutomationPeer OnCreateAutomationPeer() - { - return new NavigationViewItemAutomationPeer(this); - } + private void OnFlyoutClosing(object sender, FlyoutBaseClosingEventArgs args) + { + IsExpanded = false; + } - // IContentControlOverrides / IContentControlOverridesHelper - protected override void OnContentChanged(object oldContent, object newContent) - { - base.OnContentChanged(oldContent, newContent); - SuggestedToolTipChanged(newContent); - UpdateVisualStateNoTransition(); + // UIElement / UIElementOverridesHelper + protected override AutomationPeer OnCreateAutomationPeer() + { + return new NavigationViewItemAutomationPeer(this); + } - if (!IsOnLeftNav()) - { - // Content has changed for the item, so we want to trigger a re-measure - var navView = GetNavigationView(); - if (navView != null) - { - navView.TopNavigationViewItemContentChanged(); - } - } - } + // IContentControlOverrides / IContentControlOverridesHelper + protected override void OnContentChanged(object oldContent, object newContent) + { + base.OnContentChanged(oldContent, newContent); + SuggestedToolTipChanged(newContent); + UpdateVisualStateNoTransition(); - protected override void OnGotFocus(RoutedEventArgs e) + if (!IsOnLeftNav()) { - base.OnGotFocus(e); - var originalSource = e.OriginalSource as Control; - if (originalSource != null) + // Content has changed for the item, so we want to trigger a re-measure + var navView = GetNavigationView(); + if (navView != null) { - // It's used to support bluebar have difference appearance between focused and focused+selection. - // For example, we can move the SelectionIndicator 3px up when focused and selected to make sure focus rectange doesn't override SelectionIndicator. - // If it's a pointer or programatic, no focus rectangle, so no action - var focusState = originalSource.FocusState; - if (focusState == FocusState.Keyboard) - { - m_hasKeyboardFocus = true; - UpdateVisualStateNoTransition(); - } + navView.TopNavigationViewItemContentChanged(); } } + } - protected override void OnLostFocus(RoutedEventArgs e) - { - base.OnLostFocus(e); - if (m_hasKeyboardFocus) + protected override void OnGotFocus(RoutedEventArgs e) + { + base.OnGotFocus(e); + var originalSource = e.OriginalSource as Control; + if (originalSource != null) + { + // It's used to support bluebar have difference appearance between focused and focused+selection. + // For example, we can move the SelectionIndicator 3px up when focused and selected to make sure focus rectange doesn't override SelectionIndicator. + // If it's a pointer or programatic, no focus rectangle, so no action + var focusState = originalSource.FocusState; + if (focusState == FocusState.Keyboard) { - m_hasKeyboardFocus = false; + m_hasKeyboardFocus = true; UpdateVisualStateNoTransition(); } } + } - private void ResetTrackedPointerId() + protected override void OnLostFocus(RoutedEventArgs e) + { + base.OnLostFocus(e); + if (m_hasKeyboardFocus) { - m_trackedPointerId = 0; + m_hasKeyboardFocus = false; + UpdateVisualStateNoTransition(); } + } - // Returns False when the provided pointer Id matches the currently tracked Id. - // When there is no currently tracked Id, sets the tracked Id to the provided Id and returns False. - // Returns True when the provided pointer Id does not match the currently tracked Id. - private bool IgnorePointerId(PointerRoutedEventArgs args) - { - uint pointerId = args.Pointer.PointerId; + private void ResetTrackedPointerId() + { + m_trackedPointerId = 0; + } - if (m_trackedPointerId == 0) - { - m_trackedPointerId = pointerId; - } - else if (m_trackedPointerId != pointerId) - { - return true; - } - return false; + // Returns False when the provided pointer Id matches the currently tracked Id. + // When there is no currently tracked Id, sets the tracked Id to the provided Id and returns False. + // Returns True when the provided pointer Id does not match the currently tracked Id. + private bool IgnorePointerId(PointerRoutedEventArgs args) + { + uint pointerId = args.Pointer.PointerId; + + if (m_trackedPointerId == 0) + { + m_trackedPointerId = pointerId; + } + else if (m_trackedPointerId != pointerId) + { + return true; } + return false; + } - private void OnPresenterPointerPressed(object sender, PointerRoutedEventArgs args) + private void OnPresenterPointerPressed(object sender, PointerRoutedEventArgs args) + { + if (IgnorePointerId(args)) { - if (IgnorePointerId(args)) - { - return; - } + return; + } - MUX_ASSERT(!m_isPressed); - MUX_ASSERT(m_capturedPointer == null); + MUX_ASSERT(!m_isPressed); + MUX_ASSERT(m_capturedPointer == null); - // WinUI TODO: Update to look at presenter instead - var pointerProperties = args.GetCurrentPoint(this).Properties; - m_isPressed = pointerProperties.IsLeftButtonPressed || pointerProperties.IsRightButtonPressed; + // WinUI TODO: Update to look at presenter instead + var pointerProperties = args.GetCurrentPoint(this).Properties; + m_isPressed = pointerProperties.IsLeftButtonPressed || pointerProperties.IsRightButtonPressed; - var pointer = args.Pointer; - var presenter = GetPresenterOrItem(); + var pointer = args.Pointer; + var presenter = GetPresenterOrItem(); - MUX_ASSERT(presenter != null); + MUX_ASSERT(presenter != null); - if (presenter.CapturePointer(pointer)) - { - m_capturedPointer = pointer; - } + if (presenter.CapturePointer(pointer)) + { + m_capturedPointer = pointer; + } #if UNO_USE_DEFERRED_VISUAL_STATES - _uno_isDefferingPressedState = true; - DeferUpdateVisualStateForPointer(); + _uno_isDefferingPressedState = true; + DeferUpdateVisualStateForPointer(); #endif - UpdateVisualState(true); + UpdateVisualState(true); + } + + private void OnPresenterPointerReleased(object sender, PointerRoutedEventArgs args) + { + if (IgnorePointerId(args)) + { + return; } - private void OnPresenterPointerReleased(object sender, PointerRoutedEventArgs args) + if (m_isPressed) { - if (IgnorePointerId(args)) - { - return; - } + m_isPressed = false; - if (m_isPressed) + if (m_capturedPointer != null) { - m_isPressed = false; - - if (m_capturedPointer != null) - { - var presenter = GetPresenterOrItem(); + var presenter = GetPresenterOrItem(); - MUX_ASSERT(presenter != null); + MUX_ASSERT(presenter != null); - presenter.ReleasePointerCapture(m_capturedPointer); - } - - UpdateVisualState(true); + presenter.ReleasePointerCapture(m_capturedPointer); } + + UpdateVisualState(true); } + } - private void OnPresenterPointerEntered(object sender, PointerRoutedEventArgs args) - { + private void OnPresenterPointerEntered(object sender, PointerRoutedEventArgs args) + { #if UNO_USE_DEFERRED_VISUAL_STATES - _uno_isDefferingOverState = args.Pointer.PointerDeviceType != PointerDeviceType.Mouse; - DeferUpdateVisualStateForPointer(); + _uno_isDefferingOverState = args.Pointer.PointerDeviceType != PointerDeviceType.Mouse; + DeferUpdateVisualStateForPointer(); #endif - ProcessPointerOver(args); - } + ProcessPointerOver(args); + } + + private void OnPresenterPointerMoved(object sender, PointerRoutedEventArgs args) + { + ProcessPointerOver(args); + } - private void OnPresenterPointerMoved(object sender, PointerRoutedEventArgs args) + private void OnPresenterPointerExited(object sender, PointerRoutedEventArgs args) + { + if (IgnorePointerId(args)) { - ProcessPointerOver(args); + return; } - private void OnPresenterPointerExited(object sender, PointerRoutedEventArgs args) - { - if (IgnorePointerId(args)) - { - return; - } + m_isPointerOver = false; - m_isPointerOver = false; + if (m_capturedPointer == null) + { + ResetTrackedPointerId(); + } - if (m_capturedPointer == null) - { - ResetTrackedPointerId(); - } + UpdateVisualState(true); + } - UpdateVisualState(true); - } + private void OnPresenterPointerCanceled(object sender, PointerRoutedEventArgs args) + { + ProcessPointerCanceled(args); + } - private void OnPresenterPointerCanceled(object sender, PointerRoutedEventArgs args) - { - ProcessPointerCanceled(args); - } + private void OnPresenterPointerCaptureLost(object sender, PointerRoutedEventArgs args) + { + ProcessPointerCanceled(args); + } - private void OnPresenterPointerCaptureLost(object sender, PointerRoutedEventArgs args) + private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs args) + { + if (!IsEnabled) { - ProcessPointerCanceled(args); - } + m_isPressed = false; + m_isPointerOver = false; - private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs args) - { - if (!IsEnabled) + if (m_capturedPointer != null) { - m_isPressed = false; - m_isPointerOver = false; + var presenter = GetPresenterOrItem(); - if (m_capturedPointer != null) - { - var presenter = GetPresenterOrItem(); - - MUX_ASSERT(presenter != null); - - presenter.ReleasePointerCapture(m_capturedPointer); - m_capturedPointer = null; - } + MUX_ASSERT(presenter != null); - ResetTrackedPointerId(); + presenter.ReleasePointerCapture(m_capturedPointer); + m_capturedPointer = null; } - UpdateVisualState(true); + ResetTrackedPointerId(); } - internal void RotateExpandCollapseChevron(bool isExpanded) + UpdateVisualState(true); + } + + internal void RotateExpandCollapseChevron(bool isExpanded) + { + var presenter = GetPresenter(); if (presenter != null) { - var presenter = GetPresenter(); if (presenter != null) - { - presenter.RotateExpandCollapseChevron(isExpanded); - } + presenter.RotateExpandCollapseChevron(isExpanded); } + } - private void ProcessPointerCanceled(PointerRoutedEventArgs args) + private void ProcessPointerCanceled(PointerRoutedEventArgs args) + { + if (IgnorePointerId(args)) { - if (IgnorePointerId(args)) - { - return; - } + return; + } - _uno_isDefferingPressedState = false; - _uno_isDefferingOverState = false; - _uno_pointerDeferring?.Stop(); + _uno_isDefferingPressedState = false; + _uno_isDefferingOverState = false; + _uno_pointerDeferring?.Stop(); - m_isPressed = false; + m_isPressed = false; #if false - // UNO specific: This seems to be only for Animated icons (https://github.com/microsoft/microsoft-ui-xaml/commit/c27a05caa0eeebaacdbd2106aebd12a6fc3dd912) + // UNO specific: This seems to be only for Animated icons (https://github.com/microsoft/microsoft-ui-xaml/commit/c27a05caa0eeebaacdbd2106aebd12a6fc3dd912) - // which are not supported yet and causes some trouble with current pointers and lifecycle implementation when used with a minimal NavView - // (cf. https://github.com/unoplatform/uno/issues/7327 and https://github.com/unoplatform/uno/issues/11610) - // Note: That check has been modified in the WinUI repo to exclude the case where the pointer is touch https://github.com/microsoft/microsoft-ui-xaml/commit/18a981d03ec46763872c9b76bfc2351dc93ab197 + // which are not supported yet and causes some trouble with current pointers and lifecycle implementation when used with a minimal NavView + // (cf. https://github.com/unoplatform/uno/issues/7327 and https://github.com/unoplatform/uno/issues/11610) + // Note: That check has been modified in the WinUI repo to exclude the case where the pointer is touch https://github.com/microsoft/microsoft-ui-xaml/commit/18a981d03ec46763872c9b76bfc2351dc93ab197 - // m_isPointerOver should be true before this event so this doesn't need to be set to true in the else block... - // What this flag tracks is complicated because of the NavigationView sub items and the m_capturedPointers that are being tracked.. - // We do this check because PointerCaptureLost can sometimes take the place of PointerReleased events. - // In these cases we need to test if the pointer is over the item to maintain the proper state. - if (IsOutOfControlBounds(args.GetCurrentPoint(this).Position)) + // m_isPointerOver should be true before this event so this doesn't need to be set to true in the else block... + // What this flag tracks is complicated because of the NavigationView sub items and the m_capturedPointers that are being tracked.. + // We do this check because PointerCaptureLost can sometimes take the place of PointerReleased events. + // In these cases we need to test if the pointer is over the item to maintain the proper state. + if (IsOutOfControlBounds(args.GetCurrentPoint(this).Position)) #endif - { - m_isPointerOver = false; - } - m_capturedPointer = null; - ResetTrackedPointerId(); - UpdateVisualState(true); + { + m_isPointerOver = false; } + m_capturedPointer = null; + ResetTrackedPointerId(); + UpdateVisualState(true); + } #if false // Not used in Uno - private bool IsOutOfControlBounds(Point point) - { - // This is a conservative check. It is okay to say we are - // out of the bounds when close to the edge to account for rounding. - var tolerance = 1.0; - var actualWidth = ActualWidth; - var actualHeight = ActualHeight; - return - point.X < tolerance || - point.X > actualWidth - tolerance || - point.Y < tolerance || - point.Y > actualHeight - tolerance; - } + private bool IsOutOfControlBounds(Point point) + { + // This is a conservative check. It is okay to say we are + // out of the bounds when close to the edge to account for rounding. + var tolerance = 1.0; + var actualWidth = ActualWidth; + var actualHeight = ActualHeight; + return + point.X < tolerance || + point.X > actualWidth - tolerance || + point.Y < tolerance || + point.Y > actualHeight - tolerance; + } #endif - private void ProcessPointerOver(PointerRoutedEventArgs args) - { - if (IgnorePointerId(args)) - { - return; - } - - if (!m_isPointerOver) - { - m_isPointerOver = true; - UpdateVisualState(true); - } - } - - private void HookInputEvents() + private void ProcessPointerOver(PointerRoutedEventArgs args) + { + if (IgnorePointerId(args)) { - UIElement GetPresenter() - { - var presenter = GetTemplateChild(c_navigationViewItemPresenterName) as NavigationViewItemPresenter; - if (presenter != null) - { - m_navigationViewItemPresenter = presenter; - return presenter as UIElement; - } - // We don't have a presenter, so we are our own presenter. - return this as UIElement; - } - UIElement presenter = GetPresenter(); - - MUX_ASSERT(presenter != null); - - // Handlers that set flags are skipped when args.Handled is already True. - presenter.PointerPressed += OnPresenterPointerPressed; - m_presenterPointerPressedRevoker.Disposable = Disposable.Create(() => presenter.PointerPressed -= OnPresenterPointerPressed); - presenter.PointerEntered += OnPresenterPointerEntered; - m_presenterPointerEnteredRevoker.Disposable = Disposable.Create(() => presenter.PointerEntered -= OnPresenterPointerEntered); - presenter.PointerMoved += OnPresenterPointerMoved; - m_presenterPointerMovedRevoker.Disposable = Disposable.Create(() => presenter.PointerMoved -= OnPresenterPointerMoved); - - // Handlers that reset flags are not skipped when args.Handled is already True to avoid broken states. - var pointerReleasedHandler = new PointerEventHandler(OnPresenterPointerReleased); - presenter.AddHandler( - UIElement.PointerReleasedEvent, - pointerReleasedHandler, - true /*handledEventsToo*/); - m_presenterPointerReleasedRevoker.Disposable = Disposable.Create( - () => presenter.RemoveHandler(UIElement.PointerReleasedEvent, pointerReleasedHandler)); - - var pointerExitedHandler = new PointerEventHandler(OnPresenterPointerExited); - presenter.AddHandler( - UIElement.PointerExitedEvent, - pointerExitedHandler, - true /*handledEventsToo*/); - m_presenterPointerExitedRevoker.Disposable = Disposable.Create( - () => presenter.RemoveHandler(UIElement.PointerExitedEvent, pointerExitedHandler)); - - var pointerCanceledHandler = new PointerEventHandler(OnPresenterPointerCanceled); - presenter.AddHandler( - UIElement.PointerCanceledEvent, - pointerCanceledHandler, - true /*handledEventsToo*/); - m_presenterPointerCanceledRevoker.Disposable = Disposable.Create( - () => presenter.RemoveHandler(UIElement.PointerCanceledEvent, pointerCanceledHandler)); - - var pointerCaptureLostHandler = new PointerEventHandler(OnPresenterPointerCaptureLost); - presenter.AddHandler( - UIElement.PointerCaptureLostEvent, - pointerCaptureLostHandler, - true /*handledEventsToo*/); - m_presenterPointerCaptureLostRevoker.Disposable = Disposable.Create( - () => presenter.RemoveHandler(UIElement.PointerCaptureLostEvent, pointerCaptureLostHandler)); + return; } - private void UnhookInputEvents() + if (!m_isPointerOver) { - m_presenterPointerPressedRevoker.Disposable = null; - m_presenterPointerEnteredRevoker.Disposable = null; - m_presenterPointerMovedRevoker.Disposable = null; - m_presenterPointerReleasedRevoker.Disposable = null; - m_presenterPointerExitedRevoker.Disposable = null; - m_presenterPointerCanceledRevoker.Disposable = null; - m_presenterPointerCaptureLostRevoker.Disposable = null; + m_isPointerOver = true; + UpdateVisualState(true); } + } - private void UnhookEventsAndClearFields() + private void HookInputEvents() + { + UIElement GetPresenter() { - UnhookInputEvents(); - - m_flyoutClosingRevoker.Disposable = null; - m_splitViewIsPaneOpenChangedRevoker.Disposable = null; - m_splitViewDisplayModeChangedRevoker.Disposable = null; - m_splitViewCompactPaneLengthChangedRevoker.Disposable = null; - m_repeaterElementPreparedRevoker.Disposable = null; - m_repeaterElementClearingRevoker.Disposable = null; - m_isEnabledChangedRevoker.Disposable = null; - m_itemsSourceViewCollectionChangedRevoker.Disposable = null; + var presenter = GetTemplateChild(c_navigationViewItemPresenterName) as NavigationViewItemPresenter; + if (presenter != null) + { + m_navigationViewItemPresenter = presenter; + return presenter as UIElement; + } + // We don't have a presenter, so we are our own presenter. + return this as UIElement; + } + UIElement presenter = GetPresenter(); + + MUX_ASSERT(presenter != null); + + // Handlers that set flags are skipped when args.Handled is already True. + presenter.PointerPressed += OnPresenterPointerPressed; + m_presenterPointerPressedRevoker.Disposable = Disposable.Create(() => presenter.PointerPressed -= OnPresenterPointerPressed); + presenter.PointerEntered += OnPresenterPointerEntered; + m_presenterPointerEnteredRevoker.Disposable = Disposable.Create(() => presenter.PointerEntered -= OnPresenterPointerEntered); + presenter.PointerMoved += OnPresenterPointerMoved; + m_presenterPointerMovedRevoker.Disposable = Disposable.Create(() => presenter.PointerMoved -= OnPresenterPointerMoved); + + // Handlers that reset flags are not skipped when args.Handled is already True to avoid broken states. + var pointerReleasedHandler = new PointerEventHandler(OnPresenterPointerReleased); + presenter.AddHandler( + UIElement.PointerReleasedEvent, + pointerReleasedHandler, + true /*handledEventsToo*/); + m_presenterPointerReleasedRevoker.Disposable = Disposable.Create( + () => presenter.RemoveHandler(UIElement.PointerReleasedEvent, pointerReleasedHandler)); + + var pointerExitedHandler = new PointerEventHandler(OnPresenterPointerExited); + presenter.AddHandler( + UIElement.PointerExitedEvent, + pointerExitedHandler, + true /*handledEventsToo*/); + m_presenterPointerExitedRevoker.Disposable = Disposable.Create( + () => presenter.RemoveHandler(UIElement.PointerExitedEvent, pointerExitedHandler)); + + var pointerCanceledHandler = new PointerEventHandler(OnPresenterPointerCanceled); + presenter.AddHandler( + UIElement.PointerCanceledEvent, + pointerCanceledHandler, + true /*handledEventsToo*/); + m_presenterPointerCanceledRevoker.Disposable = Disposable.Create( + () => presenter.RemoveHandler(UIElement.PointerCanceledEvent, pointerCanceledHandler)); + + var pointerCaptureLostHandler = new PointerEventHandler(OnPresenterPointerCaptureLost); + presenter.AddHandler( + UIElement.PointerCaptureLostEvent, + pointerCaptureLostHandler, + true /*handledEventsToo*/); + m_presenterPointerCaptureLostRevoker.Disposable = Disposable.Create( + () => presenter.RemoveHandler(UIElement.PointerCaptureLostEvent, pointerCaptureLostHandler)); + } - m_rootGrid = null; - m_navigationViewItemPresenter = null; - m_toolTip = null; - m_repeater = null; - m_flyoutContentGrid = null; - } + private void UnhookInputEvents() + { + m_presenterPointerPressedRevoker.Disposable = null; + m_presenterPointerEnteredRevoker.Disposable = null; + m_presenterPointerMovedRevoker.Disposable = null; + m_presenterPointerReleasedRevoker.Disposable = null; + m_presenterPointerExitedRevoker.Disposable = null; + m_presenterPointerCanceledRevoker.Disposable = null; + m_presenterPointerCaptureLostRevoker.Disposable = null; + } - private void PrepNavigationViewItem(SplitView splitView) - { - var splitViewIsPaneOpenChangedSubscription = splitView.RegisterPropertyChangedCallback( - SplitView.IsPaneOpenProperty, OnSplitViewPropertyChanged); - m_splitViewIsPaneOpenChangedRevoker.Disposable = Disposable.Create( - () => splitView.UnregisterPropertyChangedCallback(SplitView.IsPaneOpenProperty, splitViewIsPaneOpenChangedSubscription)); - var splitViewDisplayModeChangedSubscription = splitView.RegisterPropertyChangedCallback( - SplitView.DisplayModeProperty, OnSplitViewPropertyChanged); - m_splitViewDisplayModeChangedRevoker.Disposable = Disposable.Create( - () => splitView.UnregisterPropertyChangedCallback(SplitView.DisplayModeProperty, splitViewDisplayModeChangedSubscription)); - var splitViewCompactPaneLengthSubsctiption = splitView.RegisterPropertyChangedCallback( - SplitView.CompactPaneLengthProperty, OnSplitViewPropertyChanged); - m_splitViewCompactPaneLengthChangedRevoker.Disposable = Disposable.Create( - () => splitView.UnregisterPropertyChangedCallback(SplitView.CompactPaneLengthProperty, splitViewCompactPaneLengthSubsctiption)); + private void UnhookEventsAndClearFields() + { + UnhookInputEvents(); + + m_flyoutClosingRevoker.Disposable = null; + m_splitViewIsPaneOpenChangedRevoker.Disposable = null; + m_splitViewDisplayModeChangedRevoker.Disposable = null; + m_splitViewCompactPaneLengthChangedRevoker.Disposable = null; + m_repeaterElementPreparedRevoker.Disposable = null; + m_repeaterElementClearingRevoker.Disposable = null; + m_isEnabledChangedRevoker.Disposable = null; + m_itemsSourceViewCollectionChangedRevoker.Disposable = null; + + m_rootGrid = null; + m_navigationViewItemPresenter = null; + m_toolTip = null; + m_repeater = null; + m_flyoutContentGrid = null; + } - UpdateCompactPaneLength(); - UpdateIsClosedCompact(); - } + private void PrepNavigationViewItem(SplitView splitView) + { + var splitViewIsPaneOpenChangedSubscription = splitView.RegisterPropertyChangedCallback( + SplitView.IsPaneOpenProperty, OnSplitViewPropertyChanged); + m_splitViewIsPaneOpenChangedRevoker.Disposable = Disposable.Create( + () => splitView.UnregisterPropertyChangedCallback(SplitView.IsPaneOpenProperty, splitViewIsPaneOpenChangedSubscription)); + var splitViewDisplayModeChangedSubscription = splitView.RegisterPropertyChangedCallback( + SplitView.DisplayModeProperty, OnSplitViewPropertyChanged); + m_splitViewDisplayModeChangedRevoker.Disposable = Disposable.Create( + () => splitView.UnregisterPropertyChangedCallback(SplitView.DisplayModeProperty, splitViewDisplayModeChangedSubscription)); + var splitViewCompactPaneLengthSubsctiption = splitView.RegisterPropertyChangedCallback( + SplitView.CompactPaneLengthProperty, OnSplitViewPropertyChanged); + m_splitViewCompactPaneLengthChangedRevoker.Disposable = Disposable.Create( + () => splitView.UnregisterPropertyChangedCallback(SplitView.CompactPaneLengthProperty, splitViewCompactPaneLengthSubsctiption)); + + UpdateCompactPaneLength(); + UpdateIsClosedCompact(); } } diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItemPresenter.Header.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItemPresenter.Header.cs index d2ae0c37c723..498abf2e1f1e 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItemPresenter.Header.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItemPresenter.Header.cs @@ -1,25 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -// MUX Reference NavigationViewItemPresenter.h, commit fd22d7f +// MUX Reference NavigationViewItemPresenter.h, commit d3fef08 +using Uno.Disposables; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media.Animation; -namespace Microsoft.UI.Xaml.Controls.Primitives +namespace Microsoft.UI.Xaml.Controls.Primitives; + +public partial class NavigationViewItemPresenter { - public partial class NavigationViewItemPresenter - { - private double m_compactPaneLengthValue = 40; + private double m_compactPaneLengthValue = 40; + + private NavigationViewItemHelper m_helper; - private NavigationViewItemHelper m_helper; + private Grid m_contentGrid; + private ContentPresenter m_infoBadgePresenter; + private Grid m_expandCollapseChevron; - private Grid m_contentGrid = null; - private ContentPresenter m_infoBadgePresenter = null; - private Grid m_expandCollapseChevron = null; + private SerialDisposable m_expandCollapseChevronTappedToken = new(); - private double m_leftIndentation = 0; + private double m_leftIndentation; - private Storyboard m_chevronExpandedStoryboard = null; - private Storyboard m_chevronCollapsedStoryboard = null; - } + private Storyboard m_chevronExpandedStoryboard; + private Storyboard m_chevronCollapsedStoryboard; } diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItemPresenter.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItemPresenter.cs index d1468367d270..0fb956693104 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItemPresenter.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NavigationView/NavigationViewItemPresenter.cs @@ -1,202 +1,217 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -// MUX Reference NavigationViewItemPresenter.cpp, commit 3f6310d +// MUX Reference NavigationViewItemPresenter.cpp, commit d3fef08 using System; +using Uno.Disposables; using Uno.UI.Helpers.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; -namespace Microsoft.UI.Xaml.Controls.Primitives +namespace Microsoft.UI.Xaml.Controls.Primitives; + +/// +/// Represents the visual elements of a NavigationViewItem. +/// +public partial class NavigationViewItemPresenter : ContentControl { - /// - /// Represents the visual elements of a NavigationViewItem. - /// - public partial class NavigationViewItemPresenter : ContentControl + private const string c_contentGrid = "PresenterContentRootGrid"; + private const string c_infoBadgePresenter = "InfoBadgePresenter"; + private const string c_expandCollapseChevron = "ExpandCollapseChevron"; + private const string c_expandCollapseRotateExpandedStoryboard = "ExpandCollapseRotateExpandedStoryboard"; + private const string c_expandCollapseRotateCollapsedStoryboard = "ExpandCollapseRotateCollapsedStoryboard"; + + //private const string c_iconBoxColumnDefinitionName = "IconColumn"; + + public NavigationViewItemPresenter() { - private const string c_contentGrid = "PresenterContentRootGrid"; - private const string c_infoBadgePresenter = "InfoBadgePresenter"; - private const string c_expandCollapseChevron = "ExpandCollapseChevron"; - private const string c_expandCollapseRotateExpandedStoryboard = "ExpandCollapseRotateExpandedStoryboard"; - private const string c_expandCollapseRotateCollapsedStoryboard = "ExpandCollapseRotateCollapsedStoryboard"; + SetValue(TemplateSettingsProperty, new NavigationViewItemPresenterTemplateSettings()); + DefaultStyleKey = typeof(NavigationViewItemPresenter); + } - //private const string c_iconBoxColumnDefinitionName = "IconColumn"; + protected override void OnApplyTemplate() + { + //IControlProtected controlProtected = this; - public NavigationViewItemPresenter() - { - SetValue(TemplateSettingsProperty, new NavigationViewItemPresenterTemplateSettings()); - DefaultStyleKey = typeof(NavigationViewItemPresenter); - } + // Retrieve pointers to stable controls + m_helper = new NavigationViewItemHelper(this); + m_helper.Init(this); - protected override void OnApplyTemplate() + var contentGrid = GetTemplateChild(c_contentGrid) as Grid; + if (contentGrid != null) { - //IControlProtected controlProtected = this; + m_contentGrid = contentGrid; + } - // Retrieve pointers to stable controls - m_helper = new NavigationViewItemHelper(this); - m_helper.Init(this); + m_infoBadgePresenter = GetTemplateChild(c_infoBadgePresenter) as ContentPresenter; - var contentGrid = GetTemplateChild(c_contentGrid) as Grid; - if (contentGrid != null) + var navigationViewItem = GetNavigationViewItem(); + if (navigationViewItem != null) + { +#if IS_UNO + // TODO: Uno specific: We may be reapplying the template, in which case + // we need to unsubscribe the previous Tapped event handler. + // Can be removed when #4689. + if (m_expandCollapseChevron != null) { - m_contentGrid = contentGrid; + m_expandCollapseChevronTappedToken.Disposable = null; } - - m_infoBadgePresenter = GetTemplateChild(c_infoBadgePresenter) as ContentPresenter; - - var navigationViewItem = GetNavigationViewItem(); - if (navigationViewItem != null) - { -#if IS_UNO - // TODO: Uno specific: We may be reapplying the template, in which case - // we need to unsubscribe the previous Tapped event handler. - // Can be removed when #4689. - if (m_expandCollapseChevron != null) - { - // m_expandCollapseChevron.Tapped -= navigationViewItem.OnExpandCollapseChevronTapped; - } #endif + if (navigationViewItem.HasPotentialChildren()) + { + LoadChevron(); + } - var expandCollapseChevron = GetTemplateChild(c_expandCollapseChevron) as Grid; - if (expandCollapseChevron != null) - { - m_expandCollapseChevron = expandCollapseChevron; - // expandCollapseChevron.Tapped += navigationViewItem.OnExpandCollapseChevronTapped; - } - navigationViewItem.UpdateVisualStateNoTransition(); + navigationViewItem.UpdateVisualStateNoTransition(); - // We probably switched displaymode, so restore width now, otherwise the next time we will restore is when the CompactPaneLength changes - var navigationView = navigationViewItem.GetNavigationView(); - if (navigationView != null) + // We probably switched displaymode, so restore width now, otherwise the next time we will restore is when the CompactPaneLength changes + var navigationView = navigationViewItem.GetNavigationView(); + if (navigationView != null) + { + if (navigationView.PaneDisplayMode != NavigationViewPaneDisplayMode.Top) { - if (navigationView.PaneDisplayMode != NavigationViewPaneDisplayMode.Top) - { - UpdateCompactPaneLength(m_compactPaneLengthValue, true); - } + UpdateCompactPaneLength(m_compactPaneLengthValue, true); } } + } - m_chevronExpandedStoryboard = (Storyboard)GetTemplateChild(c_expandCollapseRotateExpandedStoryboard); - m_chevronCollapsedStoryboard = (Storyboard)GetTemplateChild(c_expandCollapseRotateCollapsedStoryboard); + m_chevronExpandedStoryboard = (Storyboard)GetTemplateChild(c_expandCollapseRotateExpandedStoryboard); + m_chevronCollapsedStoryboard = (Storyboard)GetTemplateChild(c_expandCollapseRotateCollapsedStoryboard); - UpdateMargin(); - } + UpdateMargin(); + } - internal void RotateExpandCollapseChevron(bool isExpanded) + private void LoadChevron() + { + if (m_expandCollapseChevron is null) { - if (isExpanded) - { - var openStoryboard = m_chevronExpandedStoryboard; - if (openStoryboard != null) - { - openStoryboard.Begin(); - } - } - else + if (GetNavigationViewItem() is { } navigationViewItem) { - var closedStoryboard = m_chevronCollapsedStoryboard; - if (closedStoryboard != null) + if (GetTemplateChild(c_expandCollapseChevron) is { } expandCollapseChevron) { - closedStoryboard.Begin(); + m_expandCollapseChevronTappedToken.Disposable = null; + m_expandCollapseChevron = expandCollapseChevron; + expandCollapseChevron.Tapped += navigationViewItem.OnExpandCollapseChevronTapped; + m_expandCollapseChevronTappedToken.Disposable = Disposable.Create(() => expandCollapseChevron.Tapped -= navigationViewItem.OnExpandCollapseChevronTapped); } } } + } - internal UIElement GetSelectionIndicator() + internal void RotateExpandCollapseChevron(bool isExpanded) + { + if (isExpanded) { -#if IS_UNO - // TODO: Uno specific: This is done to ensure that the presenter - // was initialized properly - if helper is not null, but content grid - // is null, it means the presenter was not initialized correctly. - // Can be removed when #4809 is fixed. - if (m_contentGrid == null && m_helper != null) + var openStoryboard = m_chevronExpandedStoryboard; + if (openStoryboard != null) { - // Reinitialize - OnApplyTemplate(); + openStoryboard.Begin(); } -#endif - // m_helper could be null here, if template was not yet applied - return m_helper?.GetSelectionIndicator(); } - - protected override bool GoToElementStateCore(string state, bool useTransitions) + else { - // GoToElementStateCore: Update visualstate for itself. - // VisualStateManager.GoToState: update visualstate for it's first child. - - // If NavigationViewItemPresenter is used, two sets of VisualStateGroups are supported. One set is help to switch the style and it's NavigationViewItemPresenter itself and defined in NavigationViewItem - // Another set is defined in style for NavigationViewItemPresenter. - // OnLeftNavigation, OnTopNavigationPrimary, OnTopNavigationOverflow only apply to itself. - if (state == NavigationViewItemHelper.c_OnLeftNavigation || state == NavigationViewItemHelper.c_OnLeftNavigationReveal || state == NavigationViewItemHelper.c_OnTopNavigationPrimary - || state == NavigationViewItemHelper.c_OnTopNavigationPrimaryReveal || state == NavigationViewItemHelper.c_OnTopNavigationOverflow) + var closedStoryboard = m_chevronCollapsedStoryboard; + if (closedStoryboard != null) { - if (m_infoBadgePresenter is { } infoBadgePresenter) - { - infoBadgePresenter.Content = null; - } - return base.GoToElementStateCore(state, useTransitions); + closedStoryboard.Begin(); } - return VisualStateManager.GoToState(this, state, useTransitions); } + } - private NavigationViewItem GetNavigationViewItem() + internal UIElement GetSelectionIndicator() + { +#if IS_UNO + // TODO: Uno specific: This is done to ensure that the presenter + // was initialized properly - if helper is not null, but content grid + // is null, it means the presenter was not initialized correctly. + // Can be removed when #4809 is fixed. + if (m_contentGrid == null && m_helper != null) { - NavigationViewItem navigationViewItem = null; - - DependencyObject obj = this; + // Reinitialize + OnApplyTemplate(); + } +#endif + // m_helper could be null here, if template was not yet applied + return m_helper?.GetSelectionIndicator(); + } - var item = SharedHelpers.GetAncestorOfType(VisualTreeHelper.GetParent(obj)); - if (item != null) + protected override bool GoToElementStateCore(string state, bool useTransitions) + { + // GoToElementStateCore: Update visualstate for itself. + // VisualStateManager.GoToState: update visualstate for it's first child. + + // If NavigationViewItemPresenter is used, two sets of VisualStateGroups are supported. One set is help to switch the style and it's NavigationViewItemPresenter itself and defined in NavigationViewItem + // Another set is defined in style for NavigationViewItemPresenter. + // OnLeftNavigation, OnTopNavigationPrimary, OnTopNavigationOverflow only apply to itself. + if (state == NavigationViewItemHelper.c_OnLeftNavigation || state == NavigationViewItemHelper.c_OnLeftNavigationReveal || state == NavigationViewItemHelper.c_OnTopNavigationPrimary + || state == NavigationViewItemHelper.c_OnTopNavigationPrimaryReveal || state == NavigationViewItemHelper.c_OnTopNavigationOverflow) + { + if (m_infoBadgePresenter is { } infoBadgePresenter) { - navigationViewItem = item; + infoBadgePresenter.Content = null; } - return navigationViewItem; + return base.GoToElementStateCore(state, useTransitions); } + return VisualStateManager.GoToState(this, state, useTransitions); + } + + private NavigationViewItem GetNavigationViewItem() + { + NavigationViewItem navigationViewItem = null; - internal void UpdateContentLeftIndentation(double leftIndentation) + DependencyObject obj = this; + + var item = SharedHelpers.GetAncestorOfType(VisualTreeHelper.GetParent(obj)); + if (item != null) { - m_leftIndentation = leftIndentation; - UpdateMargin(); + navigationViewItem = item; } + return navigationViewItem; + } + + internal void UpdateContentLeftIndentation(double leftIndentation) + { + m_leftIndentation = leftIndentation; + UpdateMargin(); + } - private void UpdateMargin() + private void UpdateMargin() + { + var grid = m_contentGrid; + if (grid != null) { - var grid = m_contentGrid; - if (grid != null) - { - var oldGridMargin = grid.Margin; - grid.Margin = new Thickness(m_leftIndentation, oldGridMargin.Top, oldGridMargin.Right, oldGridMargin.Bottom); - } + var oldGridMargin = grid.Margin; + grid.Margin = new Thickness(m_leftIndentation, oldGridMargin.Top, oldGridMargin.Right, oldGridMargin.Bottom); } + } - internal void UpdateCompactPaneLength(double compactPaneLength, bool shouldUpdate) + internal void UpdateCompactPaneLength(double compactPaneLength, bool shouldUpdate) + { + m_compactPaneLengthValue = compactPaneLength; + if (shouldUpdate) { - m_compactPaneLengthValue = compactPaneLength; - if (shouldUpdate) - { - var templateSettings = TemplateSettings; - var gridLength = compactPaneLength; + var templateSettings = TemplateSettings; + var gridLength = compactPaneLength; - templateSettings.IconWidth = gridLength; - templateSettings.SmallerIconWidth = Math.Max(0.0, gridLength - 8); - } + templateSettings.IconWidth = gridLength; + templateSettings.SmallerIconWidth = Math.Max(0.0, gridLength - 8); } + } - internal void UpdateClosedCompactVisualState(bool isTopLevelItem, bool isClosedCompact) - { - // We increased the ContentPresenter margin to align it visually with the expand/collapse chevron. This updated margin is even applied when the - // NavigationView is in a visual state where no expand/collapse chevrons are shown, leading to more content being cut off than necessary. - // This is the case for top-level items when the NavigationView is in a compact mode and the NavigationView pane is closed. To keep the original - // cutoff visual experience intact, we restore the original ContentPresenter margin for such top-level items only (children shown in a flyout - // will use the updated margin). - var stateName = isClosedCompact && isTopLevelItem - ? "ClosedCompactAndTopLevelItem" - : "NotClosedCompactAndTopLevelItem"; - - VisualStateManager.GoToState(this, stateName, false /*useTransitions*/); - } + internal void UpdateClosedCompactVisualState(bool isTopLevelItem, bool isClosedCompact) + { + // We increased the ContentPresenter margin to align it visually with the expand/collapse chevron. This updated margin is even applied when the + // NavigationView is in a visual state where no expand/collapse chevrons are shown, leading to more content being cut off than necessary. + // This is the case for top-level items when the NavigationView is in a compact mode and the NavigationView pane is closed. To keep the original + // cutoff visual experience intact, we restore the original ContentPresenter margin for such top-level items only (children shown in a flyout + // will use the updated margin). + var stateName = isClosedCompact && isTopLevelItem + ? "ClosedCompactAndTopLevelItem" + : "NotClosedCompactAndTopLevelItem"; + + VisualStateManager.GoToState(this, stateName, false /*useTransitions*/); } }