diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/ItemContainerAutomationPeer.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/ItemContainerAutomationPeer.cs index 11390c2ea2dc..45e4a03f2e76 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/ItemContainerAutomationPeer.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/ItemContainerAutomationPeer.cs @@ -3,69 +3,20 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Automation.Peers { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false || false || false || false || false || false || false [global::Uno.NotImplemented] #endif public partial class ItemContainerAutomationPeer : global::Microsoft.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer, global::Microsoft.UI.Xaml.Automation.Provider.ISelectionItemProvider, global::Microsoft.UI.Xaml.Automation.Provider.IInvokeProvider { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public bool IsSelected - { - get - { - throw new global::System.NotImplementedException("The member bool ItemContainerAutomationPeer.IsSelected is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=bool%20ItemContainerAutomationPeer.IsSelected"); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.Automation.Provider.IRawElementProviderSimple SelectionContainer - { - get - { - throw new global::System.NotImplementedException("The member IRawElementProviderSimple ItemContainerAutomationPeer.SelectionContainer is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=IRawElementProviderSimple%20ItemContainerAutomationPeer.SelectionContainer"); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public ItemContainerAutomationPeer(global::Microsoft.UI.Xaml.Controls.ItemContainer owner) : base(owner) - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer", "ItemContainerAutomationPeer.ItemContainerAutomationPeer(ItemContainer owner)"); - } -#endif + // Skipping already declared property IsSelected + // Skipping already declared property SelectionContainer // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer.ItemContainerAutomationPeer(Microsoft.UI.Xaml.Controls.ItemContainer) // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer.IsSelected.get // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer.SelectionContainer.get -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void AddToSelection() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer", "void ItemContainerAutomationPeer.AddToSelection()"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void RemoveFromSelection() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer", "void ItemContainerAutomationPeer.RemoveFromSelection()"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void Select() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer", "void ItemContainerAutomationPeer.Select()"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void Invoke() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer", "void ItemContainerAutomationPeer.Invoke()"); - } -#endif + // Skipping already declared method Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer.AddToSelection() + // Skipping already declared method Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer.RemoveFromSelection() + // Skipping already declared method Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer.Select() + // Skipping already declared method Microsoft.UI.Xaml.Automation.Peers.ItemContainerAutomationPeer.Invoke() // Processing: Microsoft.UI.Xaml.Automation.Provider.ISelectionItemProvider // Processing: Microsoft.UI.Xaml.Automation.Provider.IInvokeProvider } diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/ItemsViewAutomationPeer.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/ItemsViewAutomationPeer.cs index 60a9829aed2d..59d1080e3e9a 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/ItemsViewAutomationPeer.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Automation.Peers/ItemsViewAutomationPeer.cs @@ -3,48 +3,18 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Automation.Peers { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false || false || false || false || false || false || false [global::Uno.NotImplemented] #endif public partial class ItemsViewAutomationPeer : global::Microsoft.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer, global::Microsoft.UI.Xaml.Automation.Provider.ISelectionProvider { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public bool CanSelectMultiple - { - get - { - throw new global::System.NotImplementedException("The member bool ItemsViewAutomationPeer.CanSelectMultiple is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=bool%20ItemsViewAutomationPeer.CanSelectMultiple"); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public bool IsSelectionRequired - { - get - { - throw new global::System.NotImplementedException("The member bool ItemsViewAutomationPeer.IsSelectionRequired is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=bool%20ItemsViewAutomationPeer.IsSelectionRequired"); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public ItemsViewAutomationPeer(global::Microsoft.UI.Xaml.Controls.ItemsView owner) : base(owner) - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Automation.Peers.ItemsViewAutomationPeer", "ItemsViewAutomationPeer.ItemsViewAutomationPeer(ItemsView owner)"); - } -#endif + // Skipping already declared property CanSelectMultiple + // Skipping already declared property IsSelectionRequired + // Skipping already declared method Microsoft.UI.Xaml.Automation.Peers.ItemsViewAutomationPeer.ItemsViewAutomationPeer(Microsoft.UI.Xaml.Controls.ItemsView) // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.ItemsViewAutomationPeer.ItemsViewAutomationPeer(Microsoft.UI.Xaml.Controls.ItemsView) // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.ItemsViewAutomationPeer.CanSelectMultiple.get // Forced skipping of method Microsoft.UI.Xaml.Automation.Peers.ItemsViewAutomationPeer.IsSelectionRequired.get -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.Automation.Provider.IRawElementProviderSimple[] GetSelection() - { - throw new global::System.NotImplementedException("The member IRawElementProviderSimple[] ItemsViewAutomationPeer.GetSelection() is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=IRawElementProviderSimple%5B%5D%20ItemsViewAutomationPeer.GetSelection%28%29"); - } -#endif + // Skipping already declared method Microsoft.UI.Xaml.Automation.Peers.ItemsViewAutomationPeer.GetSelection() // Processing: Microsoft.UI.Xaml.Automation.Provider.ISelectionProvider } } diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemContainer.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemContainer.cs index 12383da5dad0..22d7e2225a09 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemContainer.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemContainer.cs @@ -3,63 +3,16 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Controls { - [global::Microsoft.UI.Xaml.Markup.ContentPropertyAttribute(Name = "Child")] -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false || false || false || false || false || false || false [global::Uno.NotImplemented] #endif public partial class ItemContainer : global::Microsoft.UI.Xaml.Controls.Control { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public bool IsSelected - { - get - { - return (bool)this.GetValue(IsSelectedProperty); - } - set - { - this.SetValue(IsSelectedProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.UIElement Child - { - get - { - return (global::Microsoft.UI.Xaml.UIElement)this.GetValue(ChildProperty); - } - set - { - this.SetValue(ChildProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty ChildProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(Child), typeof(global::Microsoft.UI.Xaml.UIElement), - typeof(global::Microsoft.UI.Xaml.Controls.ItemContainer), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.UIElement))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty IsSelectedProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(IsSelected), typeof(bool), - typeof(global::Microsoft.UI.Xaml.Controls.ItemContainer), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(bool))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public ItemContainer() : base() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemContainer", "ItemContainer.ItemContainer()"); - } -#endif + // Skipping already declared property IsSelected + // Skipping already declared property Child + // Skipping already declared property ChildProperty + // Skipping already declared property IsSelectedProperty + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemContainer.ItemContainer() // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemContainer.ItemContainer() // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemContainer.Child.get // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemContainer.Child.set diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemsView.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemsView.cs index 0b491bb5c40d..22a6a8fdda2e 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemsView.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemsView.cs @@ -3,236 +3,33 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Controls { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false || false || false || false || false || false || false [global::Uno.NotImplemented] #endif public partial class ItemsView : global::Microsoft.UI.Xaml.Controls.Control { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.Controls.Primitives.IScrollController VerticalScrollController - { - get - { - return (global::Microsoft.UI.Xaml.Controls.Primitives.IScrollController)this.GetValue(VerticalScrollControllerProperty); - } - set - { - this.SetValue(VerticalScrollControllerProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.Controls.ItemsViewSelectionMode SelectionMode - { - get - { - return (global::Microsoft.UI.Xaml.Controls.ItemsViewSelectionMode)this.GetValue(SelectionModeProperty); - } - set - { - this.SetValue(SelectionModeProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.Controls.Layout Layout - { - get - { - return (global::Microsoft.UI.Xaml.Controls.Layout)this.GetValue(LayoutProperty); - } - set - { - this.SetValue(LayoutProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public object ItemsSource - { - get - { - return (object)this.GetValue(ItemsSourceProperty); - } - set - { - this.SetValue(ItemsSourceProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.Controls.ItemCollectionTransitionProvider ItemTransitionProvider - { - get - { - return (global::Microsoft.UI.Xaml.Controls.ItemCollectionTransitionProvider)this.GetValue(ItemTransitionProviderProperty); - } - set - { - this.SetValue(ItemTransitionProviderProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.IElementFactory ItemTemplate - { - get - { - return (global::Microsoft.UI.Xaml.IElementFactory)this.GetValue(ItemTemplateProperty); - } - set - { - this.SetValue(ItemTemplateProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public bool IsItemInvokedEnabled - { - get - { - return (bool)this.GetValue(IsItemInvokedEnabledProperty); - } - set - { - this.SetValue(IsItemInvokedEnabledProperty, value); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public int CurrentItemIndex - { - get - { - return (int)this.GetValue(CurrentItemIndexProperty); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.Controls.ScrollView ScrollView - { - get - { - return (global::Microsoft.UI.Xaml.Controls.ScrollView)this.GetValue(ScrollViewProperty); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public object SelectedItem - { - get - { - return (object)this.GetValue(SelectedItemProperty); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::System.Collections.Generic.IReadOnlyList SelectedItems - { - get - { - throw new global::System.NotImplementedException("The member IReadOnlyList ItemsView.SelectedItems is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=IReadOnlyList%3Cobject%3E%20ItemsView.SelectedItems"); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty CurrentItemIndexProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(CurrentItemIndex), typeof(int), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(int))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty IsItemInvokedEnabledProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(IsItemInvokedEnabled), typeof(bool), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(bool))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty ItemTemplateProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(ItemTemplate), typeof(global::Microsoft.UI.Xaml.IElementFactory), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.IElementFactory))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty ItemTransitionProviderProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(ItemTransitionProvider), typeof(global::Microsoft.UI.Xaml.Controls.ItemCollectionTransitionProvider), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.Controls.ItemCollectionTransitionProvider))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty ItemsSourceProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(ItemsSource), typeof(object), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(object))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty LayoutProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(Layout), typeof(global::Microsoft.UI.Xaml.Controls.Layout), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.Controls.Layout))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty ScrollViewProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(ScrollView), typeof(global::Microsoft.UI.Xaml.Controls.ScrollView), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.Controls.ScrollView))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty SelectedItemProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(SelectedItem), typeof(object), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(object))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty SelectionModeProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(SelectionMode), typeof(global::Microsoft.UI.Xaml.Controls.ItemsViewSelectionMode), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.Controls.ItemsViewSelectionMode))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static global::Microsoft.UI.Xaml.DependencyProperty VerticalScrollControllerProperty { get; } = - Microsoft.UI.Xaml.DependencyProperty.Register( - nameof(VerticalScrollController), typeof(global::Microsoft.UI.Xaml.Controls.Primitives.IScrollController), - typeof(global::Microsoft.UI.Xaml.Controls.ItemsView), - new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.Controls.Primitives.IScrollController))); -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public ItemsView() : base() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "ItemsView.ItemsView()"); - } -#endif + // Skipping already declared property VerticalScrollController + // Skipping already declared property SelectionMode + // Skipping already declared property Layout + // Skipping already declared property ItemsSource + // Skipping already declared property ItemTransitionProvider + // Skipping already declared property ItemTemplate + // Skipping already declared property IsItemInvokedEnabled + // Skipping already declared property CurrentItemIndex + // Skipping already declared property ScrollView + // Skipping already declared property SelectedItem + // Skipping already declared property SelectedItems + // Skipping already declared property CurrentItemIndexProperty + // Skipping already declared property IsItemInvokedEnabledProperty + // Skipping already declared property ItemTemplateProperty + // Skipping already declared property ItemTransitionProviderProperty + // Skipping already declared property ItemsSourceProperty + // Skipping already declared property LayoutProperty + // Skipping already declared property ScrollViewProperty + // Skipping already declared property SelectedItemProperty + // Skipping already declared property SelectionModeProperty + // Skipping already declared property VerticalScrollControllerProperty + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.ItemsView() // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.ItemsView() // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.ItemsSource.get // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.ItemsSource.set @@ -252,62 +49,14 @@ public ItemsView() : base() // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.CurrentItemIndex.get // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.SelectedItem.get // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.SelectedItems.get -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public bool TryGetItemIndex(double horizontalViewportRatio, double verticalViewportRatio, out int index) - { - throw new global::System.NotImplementedException("The member bool ItemsView.TryGetItemIndex(double horizontalViewportRatio, double verticalViewportRatio, out int index) is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=bool%20ItemsView.TryGetItemIndex%28double%20horizontalViewportRatio%2C%20double%20verticalViewportRatio%2C%20out%20int%20index%29"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void StartBringItemIntoView(int index, global::Microsoft.UI.Xaml.BringIntoViewOptions options) - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "void ItemsView.StartBringItemIntoView(int index, BringIntoViewOptions options)"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void Select(int itemIndex) - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "void ItemsView.Select(int itemIndex)"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void Deselect(int itemIndex) - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "void ItemsView.Deselect(int itemIndex)"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public bool IsSelected(int itemIndex) - { - throw new global::System.NotImplementedException("The member bool ItemsView.IsSelected(int itemIndex) is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=bool%20ItemsView.IsSelected%28int%20itemIndex%29"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void SelectAll() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "void ItemsView.SelectAll()"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void DeselectAll() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "void ItemsView.DeselectAll()"); - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public void InvertSelection() - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "void ItemsView.InvertSelection()"); - } -#endif + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.TryGetItemIndex(double, double, out int) + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.StartBringItemIntoView(int, Microsoft.UI.Xaml.BringIntoViewOptions) + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.Select(int) + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.Deselect(int) + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.IsSelected(int) + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.SelectAll() + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.DeselectAll() + // Skipping already declared method Microsoft.UI.Xaml.Controls.ItemsView.InvertSelection() // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.ItemInvoked.add // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.ItemInvoked.remove // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.SelectionChanged.add @@ -322,37 +71,7 @@ public void InvertSelection() // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.SelectedItemProperty.get // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.ScrollViewProperty.get // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsView.VerticalScrollControllerProperty.get -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public event global::Windows.Foundation.TypedEventHandler ItemInvoked - { - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - add - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "event TypedEventHandler ItemsView.ItemInvoked"); - } - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - remove - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "event TypedEventHandler ItemsView.ItemInvoked"); - } - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public event global::Windows.Foundation.TypedEventHandler SelectionChanged - { - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - add - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "event TypedEventHandler ItemsView.SelectionChanged"); - } - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - remove - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Controls.ItemsView", "event TypedEventHandler ItemsView.SelectionChanged"); - } - } -#endif + // Skipping already declared event Microsoft.UI.Xaml.Controls.ItemsView.ItemInvoked + // Skipping already declared event Microsoft.UI.Xaml.Controls.ItemsView.SelectionChanged } } diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemsViewItemInvokedEventArgs.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemsViewItemInvokedEventArgs.cs index 3762db1a3442..ea22ba14d2b5 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemsViewItemInvokedEventArgs.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/ItemsViewItemInvokedEventArgs.cs @@ -3,26 +3,12 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Controls { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false || false || false || false || false || false || false [global::Uno.NotImplemented] #endif public partial class ItemsViewItemInvokedEventArgs { -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - internal ItemsViewItemInvokedEventArgs() - { - } -#endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public object InvokedItem - { - get - { - throw new global::System.NotImplementedException("The member object ItemsViewItemInvokedEventArgs.InvokedItem is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=object%20ItemsViewItemInvokedEventArgs.InvokedItem"); - } - } -#endif + // Skipping already declared property InvokedItem // Forced skipping of method Microsoft.UI.Xaml.Controls.ItemsViewItemInvokedEventArgs.InvokedItem.get } } diff --git a/src/Uno.UI/Helpers/WinUI/ResourceAccessor.ResourceKeys.cs b/src/Uno.UI/Helpers/WinUI/ResourceAccessor.ResourceKeys.cs index dabc710f14ee..39fa15d84121 100644 --- a/src/Uno.UI/Helpers/WinUI/ResourceAccessor.ResourceKeys.cs +++ b/src/Uno.UI/Helpers/WinUI/ResourceAccessor.ResourceKeys.cs @@ -146,6 +146,8 @@ internal partial class ResourceAccessor public const string SR_NumberBoxDownSpinButtonName = "NumberBoxDownSpinButtonName"; public const string SR_NumberBoxMaximumValueStatus = "NumberBoxMaximumValueStatus"; public const string SR_NumberBoxMinimumValueStatus = "NumberBoxMinimumValueStatus"; + public const string SR_ItemContainerDefaultControlName = "ItemContainerDefaultControlName"; + public const string SR_InfoBarCloseButtonName = "InfoBarCloseButtonName"; public const string SR_InfoBarOpenedNotification = "InfoBarOpenedNotification"; public const string SR_InfoBarClosedNotification = "InfoBarClosedNotification"; diff --git a/src/Uno.UI/Helpers/WinUI/SharedHelpers.cs b/src/Uno.UI/Helpers/WinUI/SharedHelpers.cs index be79131f70bc..56cc3f3772a9 100644 --- a/src/Uno.UI/Helpers/WinUI/SharedHelpers.cs +++ b/src/Uno.UI/Helpers/WinUI/SharedHelpers.cs @@ -647,6 +647,19 @@ public static bool IsAncestor( return false; } + public static bool IsFocusableElement( + UIElement element) + { + if (element.Visibility == Visibility.Collapsed) + { + return false; + } + + var control = element as Control; + + return control is not null && (control.IsEnabled || control.AllowFocusWhenDisabled) && control.IsTabStop; + } + public static IconElement MakeIconElementFrom(Microsoft/* UWP don't rename */.UI.Xaml.Controls.IconSource iconSource) { if (iconSource is Microsoft/* UWP don't rename */.UI.Xaml.Controls.FontIconSource fontIconSource) diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.cs new file mode 100644 index 000000000000..a315f7e14fa0 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.cs @@ -0,0 +1,695 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using Microsoft.UI.Input; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Markup; +using Uno; +using Uno.UI.Helpers.WinUI; +using Windows.Foundation; +using Windows.System; + +namespace Microsoft.UI.Xaml.Controls; + +[ContentProperty(Name = nameof(Child))] +partial class ItemContainer : Control +{ + // Change to 'true' to turn on debugging outputs in Output window + //bool ItemContainerTrace::s_IsDebugOutputEnabled{ false }; + //bool ItemContainerTrace::s_IsVerboseDebugOutputEnabled{ false }; + + // Keeps track of the one ItemContainer with the mouse pointer over, if any. + // The OnPointerExited method does not get invoked when the ItemContainer is scrolled away from the mouse pointer. + // This static instance allows to clear the mouse over state when any other ItemContainer gets the mouse over state. + [ThreadStatic] + static WeakReference s_mousePointerOverInstance; + + public ItemContainer() + { + //ITEMCONTAINER_TRACE_INFO(null, TRACE_MSG_METH, METH_NAME, this); + + //__RP_Marker_ClassById(RuntimeProfiler::ProfId_ItemContainer); + + //EnsureProperties(); + SetDefaultStyleKey(this); + } + + ~ItemContainer() + { + //ITEMCONTAINER_TRACE_INFO(null, TRACE_MSG_METH, METH_NAME, this); + + if (s_mousePointerOverInstance?.TryGetTarget(out var mousePointerOverInstance) == true) + { + if (mousePointerOverInstance == this) + { + s_mousePointerOverInstance = null; + } + } + } + + protected override void OnApplyTemplate() + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + // Unload events from previous template when ItemContainer gets a new template. + if (m_selectionCheckbox is { } selectionCheckbox) + { + m_checked_revoker.Disposable = null; + m_unchecked_revoker.Disposable = null; + } + + m_rootPanel = GetTemplateChild(s_containerRootName); + + // If the Child property is already set, add it to the tree. After this point, the + // property changed event for Child property will add it to the tree. + if (Child is { } child) + { + if (m_rootPanel is { } rootPanel) + { + rootPanel.Children.Insert(0, child); + } + } + + m_pointerInfo = new PointerInfo(); + // Uno docs: We override OnIsEnabledChanged instead of subscribing to IsEnabledChanged event. + //m_isEnabledChangedRevoker.revoke(); + //m_isEnabledChangedRevoker = IsEnabledChanged(auto_revoke, { this, &OnIsEnabledChanged }); + + base.OnApplyTemplate(); + + UpdateVisualState(true); + UpdateMultiSelectState(true); + } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new ItemContainerAutomationPeer(this); + } + + // Uno docs: We use OnIsEnabledChanged override instead of subscribing to IsEnabledChanged event. + private protected override void OnIsEnabledChanged(IsEnabledChangedEventArgs e) + { + //ITEMCONTAINER_TRACE_INFO(*this, TRACE_MSG_METH_INT, METH_NAME, this, IsEnabled()); + + if (!IsEnabled) + { + ProcessPointerCanceled(null); + } + else + { + UpdateVisualState(true); + } + } + + void OnPropertyChanged(DependencyPropertyChangedEventArgs args) + { + var dependencyProperty = args.Property; + +#if DEBUG + if (dependencyProperty == IsSelectedProperty) + { + //bool oldValue = (bool)args.OldValue; + //bool newValue = (bool)args.NewValue; + + //ITEMCONTAINER_TRACE_VERBOSE(null, TRACE_MSG_METH_STR_INT_INT, METH_NAME, this, L"IsSelected", oldValue, newValue); + } + else if (dependencyProperty == MultiSelectModeProperty) + { + //ItemContainerMultiSelectMode oldValue = (ItemContainerMultiSelectMode)args.OldValue; + //ItemContainerMultiSelectMode newValue = (ItemContainerMultiSelectMode)args.NewValue; + + //ITEMCONTAINER_TRACE_VERBOSE(null, TRACE_MSG_METH_STR_STR, METH_NAME, this, L"MultiSelectMode", TypeLogging::ItemContainerMultiSelectModeToString(newValue).c_str()); + } + else + { + //ITEMCONTAINER_TRACE_VERBOSE(null, L"%s[%p](property: %s)\n", METH_NAME, this, DependencyPropertyToString(dependencyProperty).c_str()); + } +#endif + + if (dependencyProperty == IsSelectedProperty) + { +#if MUX_PRERELEASE + bool isMultipleOrExtended = (MultiSelectMode() & (ItemContainerMultiSelectMode.Multiple | ItemContainerMultiSelectMode.Extended)) != 0; +#else + bool isMultipleOrExtended = (MultiSelectModeInternal() & (ItemContainerMultiSelectMode.Multiple | ItemContainerMultiSelectMode.Extended)) != 0; +#endif + + AutomationEvents eventToRaise = + IsSelected ? + (isMultipleOrExtended ? AutomationEvents.SelectionItemPatternOnElementAddedToSelection : AutomationEvents.SelectionItemPatternOnElementSelected) : + (isMultipleOrExtended ? AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection : AutomationEvents.PropertyChanged); + + if (eventToRaise != AutomationEvents.PropertyChanged && AutomationPeer.ListenerExists(eventToRaise)) + { + if (FrameworkElementAutomationPeer.CreatePeerForElement(this) is { } peer) + { + peer.RaiseAutomationEvent(eventToRaise); + } + } + } + else if (dependencyProperty == ChildProperty) + { + if (m_rootPanel is { } rootPanel) + { + if (args.OldValue != null && rootPanel.Children.IndexOf(args.OldValue as UIElement) is int oldChildIndex) + { + rootPanel.Children.RemoveAt(oldChildIndex); + } + + rootPanel.Children.Insert(0, args.NewValue as UIElement); + } + } + + if (m_pointerInfo != null) + { + if (dependencyProperty == IsSelectedProperty || + dependencyProperty == MultiSelectModeProperty) + { + UpdateVisualState(true); + UpdateMultiSelectState(true); + } + } + } + + void GoToState(string stateName, bool useTransitions) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, stateName.data(), useTransitions); + + VisualStateManager.GoToState(this, stateName, useTransitions); + } + + internal override void UpdateVisualState(bool useTransitions) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, useTransitions); + + // DisabledStates + if (!IsEnabled) + { + GoToState(s_disabledStateName, useTransitions); + GoToState(IsSelected ? s_selectedNormalStateName : s_unselectedNormalStateName, useTransitions); + } + else + { + GoToState(s_enabledStateName, useTransitions); + + // CombinedStates + if (m_pointerInfo == null) + { + return; + } + + if (m_pointerInfo.IsPressed() && IsSelected) + { + GoToState(s_selectedPressedStateName, useTransitions); + } + else if (m_pointerInfo.IsPressed()) + { + GoToState(s_unselectedPressedStateName, useTransitions); + } + else if (m_pointerInfo.IsPointerOver() && IsSelected) + { + GoToState(s_selectedPointerOverStateName, useTransitions); + } + else if (m_pointerInfo.IsPointerOver()) + { + GoToState(s_unselectedPointerOverStateName, useTransitions); + } + else if (IsSelected) + { + GoToState(s_selectedNormalStateName, useTransitions); + } + else + { + GoToState(s_unselectedNormalStateName, useTransitions); + } + } + } + + void UpdateMultiSelectState(bool useTransitions) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, useTransitions); + +#if MUX_PRERELEASE + ItemContainerMultiSelectMode multiSelectMode = MultiSelectMode(); +#else + ItemContainerMultiSelectMode multiSelectMode = MultiSelectModeInternal(); +#endif + + // MultiSelectStates + if ((multiSelectMode & ItemContainerMultiSelectMode.Multiple) != 0) + { + GoToState(s_multipleStateName, useTransitions); + + if (m_selectionCheckbox is null) + { + LoadSelectionCheckbox(); + } + + UpdateCheckboxState(); + } + else + { + GoToState(s_singleStateName, useTransitions); + } + } + + void ProcessPointerOver(PointerRoutedEventArgs args, bool isPointerOver) + { + if (m_pointerInfo is not null) + { + bool oldIsPointerOver = m_pointerInfo.IsPointerOver(); + var pointerDeviceType = args.Pointer.PointerDeviceType; + + if (pointerDeviceType == PointerDeviceType.Touch) + { + if (isPointerOver) + { + m_pointerInfo.SetIsTouchPointerOver(); + } + else + { + // Once a touch pointer leaves the ItemContainer it is no longer tracked because this + // ItemContainer may never receive a PointerReleased, PointerCanceled or PointerCaptureLost + // for that PointerId. + m_pointerInfo.ResetAll(); + } + } + else if (pointerDeviceType == PointerDeviceType.Pen) + { + if (isPointerOver) + { + m_pointerInfo.SetIsPenPointerOver(); + } + else + { + // Once a pen pointer leaves the ItemContainer it is no longer tracked because this + // ItemContainer may never receive a PointerReleased, PointerCanceled or PointerCaptureLost + // for that PointerId. + m_pointerInfo.ResetAll(); + } + } + else + { + if (isPointerOver) + { + m_pointerInfo.SetIsMousePointerOver(); + } + else + { + m_pointerInfo.ResetIsMousePointerOver(); + } + UpdateMousePointerOverInstance(isPointerOver); + } + + if (m_pointerInfo.IsPointerOver() != oldIsPointerOver) + { + UpdateVisualState(true); + } + } + } + + void ProcessPointerCanceled(PointerRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_INFO(*this, TRACE_MSG_METH_INT, METH_NAME, this, args == null ? -1 : args.Pointer().PointerId()); + + if (m_pointerInfo is not null) + { + if (args == null) + { + m_pointerInfo.ResetAll(); + } + else + { + var pointer = args.Pointer; + + if (m_pointerInfo.IsPointerIdTracked(pointer.PointerId)) + { + m_pointerInfo.ResetTrackedPointerId(); + } + + var pointerDeviceType = pointer.PointerDeviceType; + + switch (pointerDeviceType) + { + case PointerDeviceType.Touch: + m_pointerInfo.ResetPointerPressed(); + m_pointerInfo.ResetIsTouchPointerOver(); + break; + case PointerDeviceType.Pen: + m_pointerInfo.ResetPointerPressed(); + m_pointerInfo.ResetIsPenPointerOver(); + break; + case PointerDeviceType.Mouse: + m_pointerInfo.ResetIsMouseButtonPressed(true /*isForLeftMouseButton*/); + m_pointerInfo.ResetIsMouseButtonPressed(false /*isForLeftMouseButton*/); + m_pointerInfo.ResetIsMousePointerOver(); + UpdateMousePointerOverInstance(false /*isPointerOver*/); + break; + } + } + } + + UpdateVisualState(true); + } + + protected override void OnPointerEntered(PointerRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, args.Pointer().PointerId()); + + base.OnPointerEntered(args); + + ProcessPointerOver(args, true /*isPointerOver*/); + } + + protected override void OnPointerMoved(PointerRoutedEventArgs args) + { + base.OnPointerMoved(args); + + ProcessPointerOver(args, true /*isPointerOver*/); + } + + protected override void OnPointerExited(PointerRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, args.Pointer().PointerId()); + + base.OnPointerExited(args); + + ProcessPointerOver(args, false /*isPointerOver*/); + } + + protected override void OnPointerPressed(PointerRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, args.Pointer().PointerId()); + + base.OnPointerPressed(args); + + if (m_pointerInfo == null || args.Handled) + { + return; + } + + var pointer = args.Pointer; + var pointerDeviceType = pointer.PointerDeviceType; + PointerUpdateKind pointerUpdateKind = PointerUpdateKind.Other; + + if (pointerDeviceType == PointerDeviceType.Mouse) + { + var pointerProperties = args.GetCurrentPoint(this).Properties; + + pointerUpdateKind = pointerProperties.PointerUpdateKind; + + if (pointerUpdateKind != PointerUpdateKind.LeftButtonPressed && + pointerUpdateKind != PointerUpdateKind.RightButtonPressed) + { + return; + } + } + + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_STR, METH_NAME, this, TypeLogging::PointerDeviceTypeToString(pointerDeviceType).c_str(), TypeLogging::PointerUpdateKindToString(pointerUpdateKind).c_str()); + + var pointerId = pointer.PointerId; + + if (!m_pointerInfo.IsTrackingPointer()) + { + m_pointerInfo.TrackPointerId(pointerId); + } + else if (!m_pointerInfo.IsPointerIdTracked(pointerId)) + { + return; + } + + bool canRaiseItemInvoked = CanRaiseItemInvoked(); + + switch (pointerDeviceType) + { + case PointerDeviceType.Touch: + m_pointerInfo.SetPointerPressed(); + m_pointerInfo.SetIsTouchPointerOver(); + break; + case PointerDeviceType.Pen: + m_pointerInfo.SetPointerPressed(); + m_pointerInfo.SetIsPenPointerOver(); + break; + case PointerDeviceType.Mouse: + m_pointerInfo.SetIsMouseButtonPressed(pointerUpdateKind == PointerUpdateKind.LeftButtonPressed /*isForLeftMouseButton*/); + canRaiseItemInvoked &= pointerUpdateKind == PointerUpdateKind.LeftButtonPressed; + m_pointerInfo.SetIsMousePointerOver(); + UpdateMousePointerOverInstance(true /*isPointerOver*/); + break; + } + + if (canRaiseItemInvoked) + { + args.Handled = RaiseItemInvoked(ItemContainerInteractionTrigger.PointerPressed, args.OriginalSource); + } + + UpdateVisualState(true); + } + + protected override void OnPointerReleased(PointerRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, args.Pointer().PointerId()); + + base.OnPointerReleased(args); + + if (m_pointerInfo == null || + !m_pointerInfo.IsPointerIdTracked(args.Pointer.PointerId) || + !m_pointerInfo.IsPressed()) + { + return; + } + + m_pointerInfo.ResetTrackedPointerId(); + + bool canRaiseItemInvoked = CanRaiseItemInvoked(); + var pointerDeviceType = args.Pointer.PointerDeviceType; + + if (pointerDeviceType == PointerDeviceType.Mouse) + { + var pointerProperties = args.GetCurrentPoint(this).Properties; + var pointerUpdateKind = pointerProperties.PointerUpdateKind; + + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_STR, METH_NAME, this, TypeLogging::PointerDeviceTypeToString(pointerDeviceType).c_str(), TypeLogging::PointerUpdateKindToString(pointerUpdateKind).c_str()); + + if (pointerUpdateKind == PointerUpdateKind.LeftButtonReleased || + pointerUpdateKind == PointerUpdateKind.RightButtonReleased) + { + m_pointerInfo.ResetIsMouseButtonPressed(pointerUpdateKind == PointerUpdateKind.LeftButtonReleased /*isForLeftMouseButton*/); + } + canRaiseItemInvoked &= pointerUpdateKind == PointerUpdateKind.LeftButtonReleased; + } + else + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR, METH_NAME, this, TypeLogging::PointerDeviceTypeToString(pointerDeviceType).c_str()); + + m_pointerInfo.ResetPointerPressed(); + } + + if (canRaiseItemInvoked && + !args.Handled && + !m_pointerInfo.IsPressed()) + { + args.Handled = RaiseItemInvoked(ItemContainerInteractionTrigger.PointerReleased, args.OriginalSource); + } + + UpdateVisualState(true); + } + + protected override void OnPointerCanceled(PointerRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, args.Pointer().PointerId()); + + base.OnPointerCanceled(args); + + ProcessPointerCanceled(args); + } + + protected override void OnPointerCaptureLost(PointerRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, args.Pointer().PointerId()); + + base.OnPointerCaptureLost(args); + + ProcessPointerCanceled(args); + } + + protected override void OnTapped(TappedRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + base.OnTapped(args); + + if (CanRaiseItemInvoked() && !args.Handled) + { + args.Handled = RaiseItemInvoked(ItemContainerInteractionTrigger.Tap, args.OriginalSource); + } + } + + protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + base.OnDoubleTapped(args); + + if (CanRaiseItemInvoked() && !args.Handled) + { + args.Handled = RaiseItemInvoked(ItemContainerInteractionTrigger.DoubleTap, args.OriginalSource); + } + } + + protected override void OnKeyDown(KeyRoutedEventArgs args) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR, METH_NAME, this, TypeLogging::KeyRoutedEventArgsToString(args).c_str()); + + base.OnKeyDown(args); + + if (!args.Handled && CanRaiseItemInvoked()) + { + if (args.Key == VirtualKey.Enter) + { + args.Handled = RaiseItemInvoked(ItemContainerInteractionTrigger.EnterKey, args.OriginalSource); + } + else if (args.Key == VirtualKey.Space) + { + args.Handled = RaiseItemInvoked(ItemContainerInteractionTrigger.SpaceKey, args.OriginalSource); + } + } + } + + bool CanRaiseItemInvoked() + { +#if MUX_PRERELEASE + return static_cast(CanUserInvoke() & ItemContainerUserInvokeMode::UserCanInvoke) || + static_cast(CanUserSelect() & (ItemContainerUserSelectMode::Auto | ItemContainerUserSelectMode::UserCanSelect)); +#else + return (CanUserInvokeInternal() & ItemContainerUserInvokeMode.UserCanInvoke) != 0 || + (CanUserSelectInternal() & (ItemContainerUserSelectMode.Auto | ItemContainerUserSelectMode.UserCanSelect)) != 0; +#endif + } + + internal bool RaiseItemInvoked(ItemContainerInteractionTrigger interactionTrigger, object originalSource) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR, METH_NAME, this, TypeLogging::ItemContainerInteractionTriggerToString(interactionTrigger).c_str()); + + if (ItemInvoked is not null) + { + var itemInvokedEventArgs = new ItemContainerInvokedEventArgs(interactionTrigger, originalSource); + ItemInvoked.Invoke(this, itemInvokedEventArgs); + + return itemInvokedEventArgs.Handled; + } + + return false; + } + + void LoadSelectionCheckbox() + { + m_selectionCheckbox = GetTemplateChild(s_selectionCheckboxName); + + if (m_selectionCheckbox is { } selectionCheckbox) + { + selectionCheckbox.Checked += OnCheckToggle; + m_checked_revoker.Disposable = new DisposableAction(() => selectionCheckbox.Checked -= OnCheckToggle); + + selectionCheckbox.Unchecked += OnCheckToggle; + m_unchecked_revoker.Disposable = new DisposableAction(() => selectionCheckbox.Unchecked -= OnCheckToggle); + } + } + + void OnCheckToggle(object sender, RoutedEventArgs args) + { + UpdateCheckboxState(); + } + + void UpdateCheckboxState() + { + if (m_selectionCheckbox is { } selectionCheckbox) + { + bool isChecked = SharedHelpers.IsTrue(selectionCheckbox.IsChecked); + bool isSelected = IsSelected; + + if (isChecked != isSelected) + { + selectionCheckbox.IsChecked = isSelected; + } + } + } + + void UpdateMousePointerOverInstance(bool isPointerOver) + { + s_mousePointerOverInstance.TryGetTarget(out var mousePointerOverInstance); + + if (isPointerOver) + { + if (mousePointerOverInstance == null || mousePointerOverInstance != this) + { + if (mousePointerOverInstance != null && mousePointerOverInstance.m_pointerInfo != null) + { + mousePointerOverInstance.m_pointerInfo.ResetIsMousePointerOver(); + } + + s_mousePointerOverInstance = new WeakReference(this); + } + } + else + { + if (mousePointerOverInstance != null && mousePointerOverInstance == this) + { + s_mousePointerOverInstance = null; + } + } + } + +#if false + + string DependencyPropertyToString( + DependencyProperty dependencyProperty) + { + if (dependencyProperty == IsSelectedProperty) + { + return "IsSelected"; + } + else if (dependencyProperty == CanUserSelectProperty) + { + return "CanUserSelect"; + } + else if (dependencyProperty == CanUserInvokeProperty) + { + return "CanUserInvoke"; + } + else if (dependencyProperty == MultiSelectModeProperty) + { + return "MultiSelectMode"; + } + else + { + return "UNKNOWN"; + } + } + + protected override Size MeasureOverride(Size availableSize) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_FLT_FLT, METH_NAME, this, L"availableSize", availableSize.Width, availableSize.Height); + + Size returnedSize = base.MeasureOverride(availableSize); + + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_FLT_FLT, METH_NAME, this, L"returnedSize", returnedSize.Width, returnedSize.Height); + + return returnedSize; + } + + protected override Size ArrangeOverride(Size finalSize) + { + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_FLT_FLT, METH_NAME, this, L"finalSize", finalSize.Width, finalSize.Height); + + Size returnedSize = base.ArrangeOverride(finalSize); + + //ITEMCONTAINER_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_FLT_FLT, METH_NAME, this, L"returnedSize", returnedSize.Width, returnedSize.Height); + + return returnedSize; + } + +#endif +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.h.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.h.cs new file mode 100644 index 000000000000..76d75b893949 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.h.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Uno.Disposables; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ItemContainer +{ +#if !MUX_PRERELEASE + internal ItemContainerUserSelectMode CanUserSelectInternal() + { + return m_canUserSelectInternal; + } + + internal ItemContainerUserInvokeMode CanUserInvokeInternal() + { + return m_canUserInvokeInternal; + } + + internal ItemContainerMultiSelectMode MultiSelectModeInternal() + { + return m_multiSelectModeInternal; + } + + internal void CanUserSelectInternal(ItemContainerUserSelectMode value) + { + if (m_canUserSelectInternal != value) + { + m_canUserSelectInternal = value; + } + } + + internal void CanUserInvokeInternal(ItemContainerUserInvokeMode value) + { + if (m_canUserInvokeInternal != value) + { + m_canUserInvokeInternal = value; + } + } + + internal void MultiSelectModeInternal(ItemContainerMultiSelectMode value) + { + if (m_multiSelectModeInternal != value) + { + m_multiSelectModeInternal = value; + + // Same code as in ItemContainer::OnPropertyChanged, when MultiSelectMode changed. + if (m_pointerInfo != null) + { + UpdateVisualState(true); + UpdateMultiSelectState(true); + } + } + } +#endif + + // Uno docs: We use IsEnabledChanged override instead. + //SerialDisposable m_isEnabledChangedRevoker = new(); + SerialDisposable m_checked_revoker = new(); + SerialDisposable m_unchecked_revoker = new(); + PointerInfo m_pointerInfo; + + CheckBox m_selectionCheckbox; + Panel m_rootPanel; + +#if !MUX_PRERELEASE + ItemContainerUserSelectMode m_canUserSelectInternal = ItemContainerUserSelectMode.Auto; + ItemContainerUserInvokeMode m_canUserInvokeInternal = ItemContainerUserInvokeMode.Auto; + ItemContainerMultiSelectMode m_multiSelectModeInternal = ItemContainerMultiSelectMode.Auto; +#endif + + private const string s_disabledStateName = "Disabled"; + private const string s_enabledStateName = "Enabled"; + private const string s_selectedPressedStateName = "SelectedPressed"; + private const string s_unselectedPressedStateName = "UnselectedPressed"; + private const string s_selectedPointerOverStateName = "SelectedPointerOver"; + private const string s_unselectedPointerOverStateName = "UnselectedPointerOver"; + private const string s_selectedNormalStateName = "SelectedNormal"; + private const string s_unselectedNormalStateName = "UnselectedNormal"; + private const string s_multipleStateName = "Multiple"; + private const string s_singleStateName = "Single"; + private const string s_selectionCheckboxName = "PART_SelectionCheckbox"; + private const string s_containerRootName = "PART_ContainerRoot"; +}; diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.idl.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.idl.cs new file mode 100644 index 000000000000..24fb6d7938de --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.idl.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.UI.Xaml.Markup; +using Windows.Foundation; + +namespace Microsoft.UI.Xaml.Controls; + +[Flags] +internal enum ItemContainerMultiSelectMode +{ + Auto = 1, + Single = 2, + Extended = 4, + Multiple = 8 +} + +internal enum ItemContainerInteractionTrigger +{ + PointerPressed, + PointerReleased, + Tap, + DoubleTap, + EnterKey, + SpaceKey, + AutomationInvoke +} + +[Flags] +internal enum ItemContainerUserInvokeMode +{ + Auto = 1, + UserCanInvoke = 2, + UserCannotInvoke = 4, +} + +[Flags] +internal enum ItemContainerUserSelectMode +{ + Auto = 1, + UserCanSelect = 2, + UserCannotSelect = 4, +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.properties.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.properties.cs new file mode 100644 index 000000000000..54569357745e --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.properties.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Windows.Foundation; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ItemContainer +{ + internal static DependencyProperty CanUserInvokeProperty { get; } = DependencyProperty.Register( + nameof(CanUserInvoke), + typeof(ItemContainerUserInvokeMode), + typeof(ItemContainer), + new FrameworkPropertyMetadata(defaultValue: ItemContainerUserInvokeMode.Auto, propertyChangedCallback: OnCanUserInvokePropertyChanged)); + + internal static DependencyProperty CanUserSelectProperty { get; } = DependencyProperty.Register( + nameof(CanUserSelect), + typeof(ItemContainerUserSelectMode), + typeof(ItemContainer), + new FrameworkPropertyMetadata(defaultValue: ItemContainerUserSelectMode.Auto, propertyChangedCallback: OnCanUserSelectPropertyChanged)); + + public static DependencyProperty ChildProperty { get; } = DependencyProperty.Register( + nameof(Child), + typeof(UIElement), + typeof(ItemContainer), + new FrameworkPropertyMetadata(defaultValue: null, propertyChangedCallback: OnChildPropertyChanged)); + + public static DependencyProperty IsSelectedProperty { get; } = DependencyProperty.Register( + nameof(IsSelected), + typeof(bool), + typeof(ItemContainer), + new FrameworkPropertyMetadata(defaultValue: false, propertyChangedCallback: OnIsSelectedPropertyChanged)); + + internal static DependencyProperty MultiSelectModeProperty { get; } = DependencyProperty.Register( + nameof(MultiSelectMode), + typeof(ItemContainerMultiSelectMode), + typeof(ItemContainer), + new FrameworkPropertyMetadata(defaultValue: ItemContainerMultiSelectMode.Auto, propertyChangedCallback: OnMultiSelectModePropertyChanged)); + + private static void OnCanUserInvokePropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemContainer)sender; + owner.OnPropertyChanged(args); + } + + private static void OnCanUserSelectPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemContainer)sender; + owner.OnPropertyChanged(args); + } + + private static void OnChildPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemContainer)sender; + owner.OnPropertyChanged(args); + } + + private static void OnIsSelectedPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemContainer)sender; + owner.OnPropertyChanged(args); + } + + private static void OnMultiSelectModePropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemContainer)sender; + owner.OnPropertyChanged(args); + } + + + internal ItemContainerUserInvokeMode CanUserInvoke + { + get => (ItemContainerUserInvokeMode)GetValue(CanUserInvokeProperty); + set => SetValue(CanUserInvokeProperty, value); + } + + internal ItemContainerUserSelectMode CanUserSelect + { + get => (ItemContainerUserSelectMode)GetValue(CanUserSelectProperty); + set => SetValue(CanUserSelectProperty, value); + } + + public UIElement Child + { + get => (UIElement)GetValue(ChildProperty); + set => SetValue(ChildProperty, value); + } + + public bool IsSelected + { + get => (bool)GetValue(IsSelectedProperty); + set => SetValue(IsSelectedProperty, value); + } + + internal ItemContainerMultiSelectMode MultiSelectMode + { + get => (ItemContainerMultiSelectMode)GetValue(MultiSelectModeProperty); + set => SetValue(MultiSelectModeProperty, value); + } + + internal event TypedEventHandler ItemInvoked; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.xaml b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.xaml new file mode 100644 index 000000000000..369db4d08583 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer.xaml @@ -0,0 +1,150 @@ + + + + diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerAutomationPeer.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerAutomationPeer.cs new file mode 100644 index 000000000000..62966d5fd970 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerAutomationPeer.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Microsoft.UI.Xaml.Automation.Provider; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Uno.UI.Helpers.WinUI; + +namespace Microsoft.UI.Xaml.Automation.Peers; + +partial class ItemContainerAutomationPeer : FrameworkElementAutomationPeer, ISelectionItemProvider, IInvokeProvider +{ + public ItemContainerAutomationPeer(ItemContainer owner) + : base(owner) + { + } + + // IAutomationPeerOverrides + protected override string GetNameCore() + { + string returnHString = base.GetNameCore(); + + // If a name hasn't been provided by AutomationProperties.Name in markup: + if (returnHString.Length == 0) + { + if (Owner is ItemContainer itemContainer) + { + returnHString = SharedHelpers.TryGetStringRepresentationFromObject(itemContainer.Child); + } + } + + if (returnHString.Length == 0) + { + returnHString = ResourceAccessor.GetLocalizedStringResource(ResourceAccessor.SR_ItemContainerDefaultControlName); + } + + return returnHString; + } + + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (Owner is ItemContainer itemContainer) + { + if (patternInterface == PatternInterface.SelectionItem) + { +#if MUX_PRERELEASE + bool canUserSelect = (itemContainer.CanUserSelect() & (ItemContainerUserSelectMode.Auto | ItemContainerUserSelectMode.UserCanSelect)) != 0; +#else + bool canUserSelect = (GetImpl().CanUserSelectInternal() & (ItemContainerUserSelectMode.Auto | ItemContainerUserSelectMode.UserCanSelect)) != 0; +#endif + + if (canUserSelect) + { + return this; + } + + } + else if (patternInterface == PatternInterface.Invoke) + { +#if MUX_PRERELEASE + bool canUserInvoke = (itemContainer.CanUserInvoke() & ItemContainerUserInvokeMode.UserCanInvoke) != 0; +#else + bool canUserInvoke = (GetImpl().CanUserInvokeInternal() & ItemContainerUserInvokeMode.UserCanInvoke) != 0; +#endif + + if (canUserInvoke) + { + return this; + } + } + } + + return base.GetPatternCore(patternInterface); + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.ListItem; + } + + protected override string GetLocalizedControlTypeCore() + { + return ResourceAccessor.GetLocalizedStringResource(ResourceAccessor.SR_ItemContainerDefaultControlName); + } + + protected override string GetClassNameCore() + { + return nameof(ItemContainer); + } + + // IInvokeProvider + public void Invoke() + { + if (GetImpl() is { } itemContainer) + { +#if MUX_PRERELEASE + bool canUserInvoke = (itemContainer.CanUserInvoke() & ItemContainerUserInvokeMode.UserCanInvoke) != 0; +#else + bool canUserInvoke = (itemContainer.CanUserInvokeInternal() & ItemContainerUserInvokeMode.UserCanInvoke) != 0; +#endif + + if (canUserInvoke) + { + itemContainer.RaiseItemInvoked(ItemContainerInteractionTrigger.AutomationInvoke, null); + } + } + } + + //ISelectionItemProvider + public bool IsSelected + { + get + { + if (GetImpl() is { } itemContainer) + { + return itemContainer.IsSelected; + } + return false; + } + } + + public IRawElementProviderSimple SelectionContainer + { + get + { + if (GetImpl() is { } itemContainer) + { + UIElement GetSelectionContainer() + { + var parent = VisualTreeHelper.GetParent(itemContainer); + + while (parent is not null) + { + if (parent is UIElement parentAsUIElement) + { + if (FrameworkElementAutomationPeer.CreatePeerForElement(parentAsUIElement) is { } peer) + { + if (peer is ISelectionProvider selectionProvider) + { + return parentAsUIElement; + } + } + } + + parent = VisualTreeHelper.GetParent(parent); + } + + return parent as UIElement; + }; + + if (GetSelectionContainer() is { } selectionContainer) + { + if (FrameworkElementAutomationPeer.CreatePeerForElement(selectionContainer) is { } peer) + { + return ProviderFromPeer(peer); + } + } + } + + return null; + } + } + + public void AddToSelection() + { + UpdateSelection(true); + } + + public void RemoveFromSelection() + { + UpdateSelection(false); + } + + public void Select() + { + UpdateSelection(true); + } + + private ItemContainer GetImpl() + { + ItemContainer impl = null; + + if (Owner is ItemContainer itemContainer) + { + impl = itemContainer; + } + + return impl; + } + + private void UpdateSelection(bool isSelected) + { + if (GetImpl() is { } itemContainer) + { + if (isSelected) + { +#if MUX_PRERELEASE + bool canUserSelect = (itemContainer.CanUserSelect() & (ItemContainerUserSelectMode.Auto | ItemContainerUserSelectMode.UserCanSelect)) != 0; +#else + bool canUserSelect = (itemContainer.CanUserSelectInternal() & (ItemContainerUserSelectMode.Auto | ItemContainerUserSelectMode.UserCanSelect)) != 0; +#endif + + if (canUserSelect) + { + itemContainer.IsSelected = true; + } + } + else + { + itemContainer.IsSelected = false; + } + } + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerInvokedEventArgs.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerInvokedEventArgs.cs new file mode 100644 index 000000000000..e9282c444705 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerInvokedEventArgs.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class ItemContainerInvokedEventArgs +{ + public ItemContainerInvokedEventArgs(ItemContainerInteractionTrigger interactionTrigger, object originalSource) + { + InteractionTrigger = interactionTrigger; + OriginalSource = originalSource; + } + + public object OriginalSource { get; } + + public ItemContainerInteractionTrigger InteractionTrigger { get; } + + public bool Handled { get; set; } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerRevokers.h.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerRevokers.h.cs new file mode 100644 index 000000000000..b2dbb1775f96 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainerRevokers.h.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Uno.Disposables; + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class ItemContainerRevokers +{ + public void RevokeAll(ItemContainer itemContainer) + { + if (m_isSelectedPropertyChangedRevoker.Disposable is not null) + { + m_isSelectedPropertyChangedRevoker.Disposable = null; + } + + if (m_gettingFocusRevoker.Disposable is not null) + { + m_gettingFocusRevoker.Disposable = null; + } + + if (m_losingFocusRevoker.Disposable is not null) + { + m_losingFocusRevoker.Disposable = null; + } + + if (m_keyDownRevoker.Disposable is not null) + { + m_keyDownRevoker.Disposable = null; + } + + if (m_itemInvokedRevoker.Disposable is not null) + { + m_itemInvokedRevoker.Disposable = null; + } + +#if DEBUG + if (m_sizeChangedRevokerDbg.Disposable is not null) + { + m_sizeChangedRevokerDbg.Disposable = null; + } +#endif + } + + internal SerialDisposable m_itemInvokedRevoker = new(); + internal SerialDisposable m_keyDownRevoker = new(); + internal SerialDisposable m_gettingFocusRevoker = new(); + internal SerialDisposable m_losingFocusRevoker = new(); + + internal SerialDisposable m_isSelectedPropertyChangedRevoker = new(); + +#if DEBUG + internal SerialDisposable m_sizeChangedRevokerDbg = new(); +#endif +}; diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer_themeresources.xaml b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer_themeresources.xaml new file mode 100644 index 000000000000..b56c1a0b4abb --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemContainer/ItemContainer_themeresources.xaml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.3 + 0 + 4,-2 + 2 + 1 + Right + Top + + + + diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ExtendedSelector.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ExtendedSelector.cs new file mode 100644 index 000000000000..447050199ea0 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ExtendedSelector.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +// MUX Reference ExtendedSelector.cpp, tag winui3/release/1.5.0 + +#if !HAS_UNO_WINUI +using Microsoft/* UWP don't rename */.UI.Xaml.Controls; +#endif + +namespace Microsoft.UI.Xaml.Controls; + +internal class ExtendedSelector : SelectorBase +{ + // ExtendedSelector::ExtendedSelector() + // { + // ITEMSVIEW_TRACE_VERBOSE(nullptr, TRACE_MSG_METH, METH_NAME, this); + // } + + // ExtendedSelector::~ExtendedSelector() + // { + // ITEMSVIEW_TRACE_VERBOSE(nullptr, TRACE_MSG_METH, METH_NAME, this); + // } + + public override void OnInteractedAction(IndexPath index, bool ctrl, bool shift) + { + // ITEMSVIEW_TRACE_VERBOSE(nullptr, TRACE_MSG_METH_INT_INT, METH_NAME, this, ctrl, shift); + + var selectionModel = GetSelectionModel(); + if (shift) + { + var anchorIndex = selectionModel.AnchorIndex; + if (anchorIndex is not null) + { + selectionModel.ClearSelection(); + selectionModel.AnchorIndex = anchorIndex; + selectionModel.SelectRangeFromAnchorTo(index); + } + } + else if (ctrl) + { + if (IsSelected(index)) + { + selectionModel.DeselectAt(index); + } + else + { + selectionModel.SelectAt(index); + } + } + else + { + // Only clear selection if interacting with a different item. + if (!IsSelected(index)) + { + selectionModel.ClearSelection(); + selectionModel.SelectAt(index); + } + } + } + + public override void OnFocusedAction(IndexPath index, bool ctrl, bool shift) + { + // ITEMSVIEW_TRACE_VERBOSE(nullptr, TRACE_MSG_METH_INT_INT, METH_NAME, this, ctrl, shift); + + var selectionModel = GetSelectionModel(); + + if (shift && ctrl) + { + if (selectionModel.AnchorIndex is not null) + { + selectionModel.SelectRangeFromAnchorTo(index); + } + } + else if (shift) + { + var anchorIndex = selectionModel.AnchorIndex; + if (anchorIndex is not null) + { + selectionModel.ClearSelection(); + selectionModel.AnchorIndex = anchorIndex; + selectionModel.SelectRangeFromAnchorTo(index); + } + } + else if (!ctrl) + { + selectionModel.ClearSelection(); + selectionModel.SelectAt(index); + } + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.cs new file mode 100644 index 000000000000..e3addf59ac35 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.cs @@ -0,0 +1,2228 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +// MUX Reference ItemsView.cpp, tag winui3/release/1.5.0 + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using DirectUI; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Markup; +using Microsoft.UI.Xaml.Media; +using Uno; +using Uno.UI.Helpers.WinUI; +using Windows.Foundation; +using Windows.System; +using static Microsoft/* UWP don't rename */.UI.Xaml.Controls._Tracing; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ItemsView : Control +{ + // Change to 'true' to turn on debugging outputs in Output window + //bool ItemsViewTrace.s_IsDebugOutputEnabled{ false }; + //bool ItemsViewTrace.s_IsVerboseDebugOutputEnabled{ false }; + + // Number of CompositionTarget.Rendering event occurrences after bring-into-view completion before resetting m_bringIntoViewElement as the scroll anchoring element. + const byte c_renderingEventsPostBringIntoView = 4; + + public ItemsView() + { + //ITEMSVIEW_TRACE_INFO(null, TRACE_MSG_METH, METH_NAME, this); + + //__RP_Marker_ClassById(RuntimeProfiler.ProfId_ItemsView); + + //static var s_ItemsViewItemContainerRevokersPropertyInit = []() + //{ + // s_ItemsViewItemContainerRevokersProperty = + // InitializeDependencyProperty( + // "ItemsViewItemContainerRevokers", + // name_of(), + // name_of(), + // true /* isAttached */, + // null /* defaultValue */); + // return false; + //}(); + + //EnsureProperties(); + SetDefaultStyleKey(this); + + // Uno docs: We use OnLoaded and OnUnloaded overrides instead of event subscriptions. + //m_loadedRevoker = Loaded(auto_revoke, { this, &OnLoaded }); + //m_unloadedRevoker = Unloaded(auto_revoke, { this, &OnUnloaded }); + m_selectionModel.SelectionChanged += OnSelectionModelSelectionChanged; + m_selectionModelSelectionChangedRevoker.Disposable = new DisposableAction(() => m_selectionModel.SelectionChanged -= OnSelectionModelSelectionChanged); + + m_currentElementSelectionModel.SelectionChanged += OnCurrentElementSelectionModelSelectionChanged; + m_currentElementSelectionModelSelectionChangedRevoker.Disposable = new DisposableAction(() => m_currentElementSelectionModel.SelectionChanged -= OnCurrentElementSelectionModelSelectionChanged); + + // m_currentElementSelectionModel tracks the single current element. + m_currentElementSelectionModel.SingleSelect = true; + + UpdateSelector(); + } + + ~ItemsView() + { + //ITEMSVIEW_TRACE_INFO(null, TRACE_MSG_METH, METH_NAME, this); + + //m_loadedRevoker.revoke(); + //m_unloadedRevoker.revoke(); + m_selectionModelSelectionChangedRevoker.Disposable = null; + m_currentElementSelectionModelSelectionChangedRevoker.Disposable = null; + + UnhookCompositionTargetRendering(); + UnhookItemsRepeaterEvents(true /*isForDestructor*/); + UnhookScrollViewEvents(true /*isForDestructor*/); + } + + public ScrollView ScrollView => m_scrollView; + + // Returns the index of the closest realized item to a point specified by the two viewport ratio numbers. + // See GetItemInternal for details. + public bool TryGetItemIndex( + double horizontalViewportRatio, + double verticalViewportRatio, + out int index) + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_DBL_DBL, METH_NAME, this, horizontalViewportRatio, verticalViewportRatio); + + IndexBasedLayoutOrientation indexBasedLayoutOrientation = GetLayoutIndexBasedLayoutOrientation(); + bool isHorizontalDistanceFavored = indexBasedLayoutOrientation == IndexBasedLayoutOrientation.TopToBottom; + bool isVerticalDistanceFavored = indexBasedLayoutOrientation == IndexBasedLayoutOrientation.LeftToRight; + + index = GetItemInternal( + horizontalViewportRatio, + verticalViewportRatio, + isHorizontalDistanceFavored, + isVerticalDistanceFavored, + false /*useKeyboardNavigationReferenceHorizontalOffset*/, + false /*useKeyboardNavigationReferenceVerticalOffset*/, + false /*capItemEdgesToViewportRatioEdges*/, + false /*forFocusableItemsOnly*/); + return index != -1; + } + + // Invokes UIElement.StartBringIntoView with the provided BringIntoViewOptions instance, if any, + // for the element associated with the given data index. + // If that element is currently virtualized, it is realized and synchronously layed out, before that StartBringIntoView call. + // Note that because of lines 111-112 of ViewportManagerWithPlatformFeatures.GetLayoutVisibleWindow() and line 295 of + // ViewportManagerWithPlatformFeatures.OnBringIntoViewRequested(...), animated bring-into-view operations are not supported. + // ViewportManagerWithPlatformFeatures.GetLayoutVisibleWindow snaps the RealizationWindow to 0,0 while + // ViewportManagerWithPlatformFeatures.OnBringIntoViewRequested turns off BringIntoViewRequestedEventArgs.AnimationDesired. + public void StartBringItemIntoView( + int index, + BringIntoViewOptions options) + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH, METH_NAME, this); + + bool success = StartBringItemIntoViewInternal(true /*throwOutOfBounds*/, true /* throwOnAnyFailure */, index, options); + MUX_ASSERT(success); + } + + public IReadOnlyList SelectedItems => m_selectionModel.SelectedItems; + + public void Select(int itemIndex) + { + m_selectionModel.Select(itemIndex); + } + + public void Deselect(int itemIndex) + { + m_selectionModel.Deselect(itemIndex); + } + + public bool IsSelected(int itemIndex) + { + bool? isItemIndexSelected = m_selectionModel.IsSelected(itemIndex); + return isItemIndexSelected != null && isItemIndexSelected.Value; + } + + public void SelectAll() + { + // TODO: Update once ItemsView has grouping. + // This function assumes a flat list data source. + m_selectionModel.SelectAllFlat(); + } + + public void DeselectAll() + { + m_selectionModel.ClearSelection(); + } + + public void InvertSelection() + { + if (m_itemsRepeater is { } itemsRepeater) + { + if (itemsRepeater.ItemsSourceView is { } itemsSourceView) + { + var selectedIndices = m_selectionModel.SelectedIndices; + int indexEnd = itemsSourceView.Count - 1; + + // We loop backwards through the selected indices so we can deselect as we go + for (int i = selectedIndices.Count - 1; i >= 0; i--) + { + var indexPath = selectedIndices[i]; + // TODO: Update once ItemsView has grouping. + int index = indexPath.GetAt(0); + + // Select all the unselected items + if (index < indexEnd) + { + var startIndex = IndexPath.CreateFrom(index + 1); + var endIndex = IndexPath.CreateFrom(indexEnd); + m_selectionModel.SelectRange(startIndex, endIndex); + } + + m_selectionModel.DeselectAt(indexPath); + indexEnd = index - 1; + } + + // Select the remaining unselected items at the beginning of the collection + if (indexEnd >= 0) + { + var startIndex = IndexPath.CreateFrom(0); + var endIndex = IndexPath.CreateFrom(indexEnd); + m_selectionModel.SelectRange(startIndex, endIndex); + } + } + } + } + + #region IControlOverrides + +#if DEBUG + protected override void OnGotFocus( + RoutedEventArgs args) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + base.OnGotFocus(args); + } +#endif + + #endregion + + #region IFrameworkElementOverrides + + protected override void OnApplyTemplate() + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH, METH_NAME, this); + + RestoreOriginalVerticalScrollControllerAndVisibility(); + + base.OnApplyTemplate(); + + ScrollView scrollView = GetTemplateChild(s_scrollViewPartName); + + UpdateScrollView(scrollView); + + ItemsRepeater itemsRepeater = GetTemplateChild(s_itemsRepeaterPartName); + + UpdateItemsRepeater(itemsRepeater); + + m_setVerticalScrollControllerOnLoaded = true; + } + + #endregion + + #region IUIElementOverrides + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new ItemsViewAutomationPeer(this); + } + + #endregion + + // Invoked when a dependency property of this ItemsView has changed. + void OnPropertyChanged(DependencyPropertyChangedEventArgs args) + { + var dependencyProperty = args.Property; + + //ITEMSVIEW_TRACE_VERBOSE_DBG(null, "%s(property: %s)\n", METH_NAME, DependencyPropertyToStringDbg(dependencyProperty).c_str()); + + if (dependencyProperty == IsItemInvokedEnabledProperty) + { + OnIsItemInvokedEnabledChanged(); + } + else if (dependencyProperty == ItemTemplateProperty) + { + OnItemTemplateChanged(); + } + else if (dependencyProperty == SelectionModeProperty) + { + OnSelectionModeChanged(); + } + else if (dependencyProperty == ItemsSourceProperty) + { + OnItemsSourceChanged(); + } + else if (dependencyProperty == VerticalScrollControllerProperty) + { + OnVerticalScrollControllerChanged(); + } +#if DEBUG + else if (dependencyProperty == LayoutProperty) + { + OnLayoutChangedDbg(); + } +#endif + } + + // Make sure the default ItemTemplate is used when the ItemsSource is non-null and the ItemTemplate is null. + // Prevents ViewManager.GetElementFromElementFactory from setting the ItemsRepeater.ItemTemplate property + // which would clear the template-binding between the ItemsView & ItemsRepeater ItemTemplate properties. + // This is not needed though when the ItemsSource points to UIElements instead of data items. In that case, + // ViewManager.GetElementFromElementFactory will not set a default ItemTemplate. It must remain null. + void EnsureItemTemplate() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + if (ItemsSource == null || ItemTemplate != null) + { + return; + } + + if (m_itemsRepeater is { } itemsRepeater) + { + if (itemsRepeater.ItemsSourceView is { } itemsSourceView) + { + var itemCount = itemsSourceView.Count; + + if (itemCount == 0) + { + return; + } + + var data = itemsSourceView.GetAt(0); + + if (data is UIElement) + { + // No need to set a default ItemTemplate with an ItemContainer when the ItemsSource is a list of UIElements. + return; + } + + // The default ItemTemplate uses an ItemContainer for its root element since this is an ItemsView requirement for now. + var defaultItemTemplate = XamlReader.Load("") as DataTemplate; + + ItemTemplate = defaultItemTemplate; + } + } + } + + void UpdateItemsRepeater( + ItemsRepeater itemsRepeater) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + m_keyboardNavigationReferenceResetPending = false; + + UnhookItemsRepeaterEvents(false /*isForDestructor*/); + + // Reset inner ItemsRepeater dependency properties + if (m_itemsRepeater is { } oldItemsRepeater) + { + oldItemsRepeater.ClearValue(ItemsRepeater.ItemsSourceProperty); + oldItemsRepeater.ClearValue(ItemsRepeater.ItemTemplateProperty); + oldItemsRepeater.ClearValue(ItemsRepeater.LayoutProperty); + } + + m_itemsRepeater = itemsRepeater; + + HookItemsRepeaterEvents(); + HookItemsSourceViewEvents(); + } + + void UpdateScrollView( + ScrollView scrollView) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + UnhookScrollViewEvents(false /*isForDestructor*/); + + m_scrollView = scrollView; + + SetValue(ScrollViewProperty, scrollView); + + HookScrollViewEvents(); + } + + void UpdateSelector() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, SelectionMode()); + + m_selectionModel.SingleSelect = false; + + switch (SelectionMode) + { + case ItemsViewSelectionMode.None: + { + m_selectionModel.ClearSelection(); + m_selector = new NullSelector(); + break; + } + + case ItemsViewSelectionMode.Single: + { + m_selectionModel.SingleSelect = true; + + m_selector = new SingleSelector(); + m_selector.SetSelectionModel(m_selectionModel); + break; + } + + case ItemsViewSelectionMode.Multiple: + { + m_selector = new MultipleSelector(); + m_selector.SetSelectionModel(m_selectionModel); + break; + } + + case ItemsViewSelectionMode.Extended: + { + m_selector = new ExtendedSelector(); + m_selector.SetSelectionModel(m_selectionModel); + break; + } + } + } + + bool CanRaiseItemInvoked( + ItemContainerInteractionTrigger interactionTrigger, + ItemContainer itemContainer) + { + MUX_ASSERT(itemContainer != null); + +#if MUX_PRERELEASE + ItemContainerUserInvokeMode canUserInvoke = itemContainer.CanUserInvoke(); +#else + ItemContainerUserInvokeMode canUserInvoke = itemContainer.CanUserInvokeInternal(); +#endif + + if ((canUserInvoke & (ItemContainerUserInvokeMode.UserCannotInvoke | ItemContainerUserInvokeMode.UserCanInvoke)) != (ItemContainerUserInvokeMode.UserCanInvoke)) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, "Returns false based on ItemContainer.CanUserInvoke."); + + return false; + } + + bool cannotRaiseItemInvoked = + (!IsItemInvokedEnabled || + (SelectionMode == ItemsViewSelectionMode.None && interactionTrigger == ItemContainerInteractionTrigger.DoubleTap) || + (SelectionMode != ItemsViewSelectionMode.None && (interactionTrigger == ItemContainerInteractionTrigger.Tap || interactionTrigger == ItemContainerInteractionTrigger.SpaceKey))); + + bool canRaiseItemInvoked = !cannotRaiseItemInvoked; + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "Returns based on ItemsView.IsItemInvokedEnabled.", canRaiseItemInvoked); + + return canRaiseItemInvoked; + } + + void RaiseItemInvoked( + UIElement element) + { + if (ItemInvoked is not null) + { + bool itemInvokedFound = false; + + var itemInvoked = GetElementItem(element, ref itemInvokedFound); + + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_PTR_INT, METH_NAME, this, itemInvoked, itemInvokedFound); + + if (itemInvokedFound) + { + var itemsViewItemInvokedEventArgs = new ItemsViewItemInvokedEventArgs(itemInvoked); + + ItemInvoked.Invoke(this, itemsViewItemInvokedEventArgs); + } + } + } + + void RaiseSelectionChanged() + { + if (SelectionChanged is not null) + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH, METH_NAME, this); + + var itemsViewSelectionChangedEventArgs = new ItemsViewSelectionChangedEventArgs(); + + SelectionChanged.Invoke(this, itemsViewSelectionChangedEventArgs); + } + } + + void HookItemsSourceViewEvents() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + m_itemsSourceViewChangedRevoker.Disposable = null; + + if (m_itemsRepeater is { } itemsRepeater) + { + if (itemsRepeater.ItemsSourceView is { } itemsSourceView) + { + itemsSourceView.CollectionChanged += OnSourceListChanged; + m_itemsSourceViewChangedRevoker.Disposable = new DisposableAction(() => itemsSourceView.CollectionChanged -= OnSourceListChanged); + + // Make sure the default ItemTemplate is used when the ItemsSource is non-null and the ItemTemplate is null. + EnsureItemTemplate(); + } + } + } + + void HookItemsRepeaterEvents() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + if (m_itemsRepeater is { } itemsRepeater) + { + itemsRepeater.ElementPrepared += OnItemsRepeaterElementPrepared; + m_itemsRepeaterElementPreparedRevoker.Disposable = new DisposableAction(() => itemsRepeater.ElementPrepared -= OnItemsRepeaterElementPrepared); + + itemsRepeater.ElementClearing += OnItemsRepeaterElementClearing; + m_itemsRepeaterElementClearingRevoker.Disposable = new DisposableAction(() => itemsRepeater.ElementClearing -= OnItemsRepeaterElementClearing); + + itemsRepeater.ElementIndexChanged += OnItemsRepeaterElementIndexChanged; + m_itemsRepeaterElementIndexChangedRevoker.Disposable = new DisposableAction(() => itemsRepeater.ElementIndexChanged -= OnItemsRepeaterElementIndexChanged); + + itemsRepeater.LayoutUpdated += OnItemsRepeaterLayoutUpdated; + m_itemsRepeaterLayoutUpdatedRevoker.Disposable = new DisposableAction(() => itemsRepeater.LayoutUpdated -= OnItemsRepeaterLayoutUpdated); + + itemsRepeater.SizeChanged += OnItemsRepeaterSizeChanged; + m_itemsRepeaterSizeChangedRevoker.Disposable = new DisposableAction(() => itemsRepeater.SizeChanged -= OnItemsRepeaterSizeChanged); + + var token = itemsRepeater.RegisterPropertyChangedCallback(ItemsRepeater.ItemsSourceProperty, OnItemsRepeaterItemsSourceChanged); + m_itemsRepeaterItemsSourcePropertyChangedRevoker.Disposable = new DisposableAction(() => itemsRepeater.UnregisterPropertyChangedCallback(ItemsRepeater.ItemsSourceProperty, token)); + } + } + + void UnhookItemsRepeaterEvents( + bool isForDestructor) + { + if (isForDestructor) + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + } + else + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + } + + //if (var itemRepeater = isForDestructor ? m_itemsRepeater.safe_get() : m_itemsRepeater.get()) + { + m_itemsRepeaterElementPreparedRevoker.Disposable = null; + m_itemsRepeaterElementClearingRevoker.Disposable = null; + m_itemsRepeaterElementIndexChangedRevoker.Disposable = null; + m_itemsRepeaterItemsSourcePropertyChangedRevoker.Disposable = null; + m_itemsRepeaterLayoutUpdatedRevoker.Disposable = null; + m_itemsRepeaterSizeChangedRevoker.Disposable = null; + + if (isForDestructor) + { + ClearAllItemsViewItemContainerRevokers(); + } + } + } + + void HookScrollViewEvents() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + if (m_scrollView is { } scrollView) + { + scrollView.AnchorRequested += OnScrollViewAnchorRequested; + m_scrollViewAnchorRequestedRevoker.Disposable = new DisposableAction(() => scrollView.AnchorRequested -= OnScrollViewAnchorRequested); + + scrollView.BringingIntoView += OnScrollViewBringingIntoView; + m_scrollViewBringingIntoViewRevoker.Disposable = new DisposableAction(() => scrollView.BringingIntoView -= OnScrollViewBringingIntoView); +#if DEBUG + scrollView.ExtentChanged += OnScrollViewExtentChangedDbg; + m_scrollViewExtentChangedRevokerDbg.Disposable = new DisposableAction(() => scrollView.ExtentChanged -= OnScrollViewExtentChangedDbg); +#endif + + scrollView.ScrollCompleted += OnScrollViewScrollCompleted; + m_scrollViewScrollCompletedRevoker.Disposable = new DisposableAction(() => scrollView.ScrollCompleted -= OnScrollViewScrollCompleted); + } + } + + void UnhookScrollViewEvents( + bool isForDestructor) + { + if (isForDestructor) + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + } + else + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + } + + //if (var scrollView = isForDestructor ? m_scrollView.safe_get() : m_scrollView.get()) + { + m_scrollViewAnchorRequestedRevoker.Disposable = null; + m_scrollViewBringingIntoViewRevoker.Disposable = null; +#if DEBUG + m_scrollViewExtentChangedRevokerDbg.Disposable = null; +#endif + m_scrollViewScrollCompletedRevoker.Disposable = null; + } + } + + void HookCompositionTargetRendering() + { + if (m_renderingRevoker.Disposable is null) + { + CompositionTarget.Rendering += OnCompositionTargetRendering; + m_renderingRevoker.Disposable = new DisposableAction(() => CompositionTarget.Rendering -= OnCompositionTargetRendering); + } + } + + void UnhookCompositionTargetRendering() + { + m_renderingRevoker.Disposable = null; + } + + void CacheOriginalVerticalScrollControllerAndVisibility() + { + if (m_scrollView is { } scrollView) + { + if (scrollView.ScrollPresenter is { } scrollPresenter) + { + m_originalVerticalScrollController = scrollPresenter.VerticalScrollController; + } + + m_originalVerticalScrollBarVisibility = scrollView.VerticalScrollBarVisibility; + } + + //ITEMSVIEW_TRACE_VERBOSE_DBG(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, m_originalVerticalScrollController, "m_originalVerticalScrollController"); + //ITEMSVIEW_TRACE_VERBOSE_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_originalVerticalScrollBarVisibility", m_originalVerticalScrollBarVisibility); + } + + // Restore the original ScrollView and ScrollController properties when + // the ItemsView gets re-templated. + void RestoreOriginalVerticalScrollControllerAndVisibility() + { + //ITEMSVIEW_TRACE_VERBOSE_DBG(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, m_originalVerticalScrollController, "m_originalVerticalScrollController"); + //ITEMSVIEW_TRACE_VERBOSE_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_originalVerticalScrollBarVisibility", m_originalVerticalScrollBarVisibility); + + if (m_scrollView is { } scrollView) + { + if (scrollView.ScrollPresenter is { } scrollPresenter) + { + scrollPresenter.VerticalScrollController = m_originalVerticalScrollController; + } + + scrollView.VerticalScrollBarVisibility = m_originalVerticalScrollBarVisibility; + } + + m_originalVerticalScrollController = null; + m_originalVerticalScrollBarVisibility = ScrollingScrollBarVisibility.Auto; + } + + void SetVerticalScrollControllerOnLoaded() + { + if (VerticalScrollController is not null) + { + // Apply the VerticalScrollController property value that was set + // before the Loaded event. + ApplyVerticalScrollController(); + } + else + { + // The VerticalScrollController property was left null prior to the + // Loaded event. Use the value from the inner ScrollPresenter. + // This may invoke OnVerticalScrollControllerChanged and + // ApplyVerticalScrollController but will be a no-op. + VerticalScrollController = m_originalVerticalScrollController; + } + } + + // Propagates the ItemsView.VerticalScrollController property value to the + // inner ScrollPresenter.VerticalScrollController property. + // If the value is other than original value read in the Loaded event, + // the inner ScrollView's VerticalScrollBarVisibility is set to Hidden. + // Else, the original visibility is restored. + void ApplyVerticalScrollController() + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, VerticalScrollController(), "VerticalScrollController"); + + if (m_scrollView is { } scrollView) + { + var verticalScrollController = VerticalScrollController; + + if (verticalScrollController == m_originalVerticalScrollController) + { + scrollView.VerticalScrollBarVisibility = m_originalVerticalScrollBarVisibility; + } + else + { + scrollView.VerticalScrollBarVisibility = ScrollingScrollBarVisibility.Hidden; + } + + var scrollViewImpl = scrollView; + + if (scrollViewImpl.ScrollPresenter is { } scrollPresenter) + { + scrollPresenter.VerticalScrollController = verticalScrollController; + } + } + } + + // Invoked at the beginning of a StartBringItemIntoView call to abort any potential bring-into-view operation, and a few ticks after a bring-into-view + // operation completed, giving time for the new layout to settle and scroll anchoring to do its job of freezing its target element. + void CompleteStartBringItemIntoView() + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, m_bringIntoViewElement.get(), "m_bringIntoViewElement reset."); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_bringIntoViewCorrelationId reset", m_bringIntoViewCorrelationId); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_bringIntoViewElementRetentionCountdown reset", m_bringIntoViewElementRetentionCountdown); + + m_bringIntoViewElement = null; + m_bringIntoViewElementRetentionCountdown = 0; + m_bringIntoViewCorrelationId = -1; + + if (m_scrollViewHorizontalAnchorRatio != -1) + { + MUX_ASSERT(m_scrollViewVerticalAnchorRatio != -1); + + // Restore the pre-operation anchor settings. + if (m_scrollView is { } scrollView) + { + scrollView.HorizontalAnchorRatio = m_scrollViewHorizontalAnchorRatio; + scrollView.VerticalAnchorRatio = m_scrollViewVerticalAnchorRatio; + } + + m_scrollViewHorizontalAnchorRatio = -1; + m_scrollViewVerticalAnchorRatio = -1; + } + + if (m_navigationKeyProcessingCountdown == 0) + { + UnhookCompositionTargetRendering(); + } + } + + void OnItemsRepeaterElementPrepared( + ItemsRepeater itemsRepeater, + ItemsRepeaterElementPreparedEventArgs args) + { + if (args.Element is { } element) + { + var index = args.Index; + +#if DEBUG_VERBOSE + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, index); +#endif + + MUX_ASSERT(index == GetElementIndex(element)); + + var itemContainer = element as ItemContainer; + + if (itemContainer != null) + { +#if MUX_PRERELEASE + if (static_cast(itemContainer.CanUserInvoke() & ItemContainerUserInvokeMode.Auto)) + { + ItemContainerUserInvokeMode canUserInvoke = ItemContainerUserInvokeMode.Auto; + + canUserInvoke |= IsItemInvokedEnabled() ? ItemContainerUserInvokeMode.UserCanInvoke : ItemContainerUserInvokeMode.UserCannotInvoke; + + itemContainer.CanUserInvoke(canUserInvoke); + } + + if (static_cast(itemContainer.MultiSelectMode() & ItemContainerMultiSelectMode.Auto)) + { + ItemContainerMultiSelectMode multiSelectMode = ItemContainerMultiSelectMode.Auto; + + switch (SelectionMode()) + { + case ItemsViewSelectionMode.None: + case ItemsViewSelectionMode.Single: + multiSelectMode |= ItemContainerMultiSelectMode.Single; + break; + case ItemsViewSelectionMode.Extended: + multiSelectMode |= ItemContainerMultiSelectMode.Extended; + break; + case ItemsViewSelectionMode.Multiple: + multiSelectMode |= ItemContainerMultiSelectMode.Multiple; + break; + } + + itemContainer.MultiSelectMode(multiSelectMode); + } + + if ((itemContainer.CanUserSelect() & ItemContainerUserSelectMode.Auto) != 0) + { + ItemContainerUserSelectMode canUserSelect = ItemContainerUserSelectMode.Auto; + + canUserSelect |= SelectionMode() == ItemsViewSelectionMode.None ? ItemContainerUserSelectMode.UserCannotSelect : ItemContainerUserSelectMode.UserCanSelect; + + itemContainer.CanUserSelect(canUserSelect); + } +#else + if (itemContainer is ItemContainer itemContainerImpl) + { + if ((itemContainerImpl.CanUserInvokeInternal() & ItemContainerUserInvokeMode.Auto) != 0) + { + ItemContainerUserInvokeMode canUserInvoke = ItemContainerUserInvokeMode.Auto; + + canUserInvoke |= IsItemInvokedEnabled ? ItemContainerUserInvokeMode.UserCanInvoke : ItemContainerUserInvokeMode.UserCannotInvoke; + + itemContainerImpl.CanUserInvokeInternal(canUserInvoke); + } + + if ((itemContainerImpl.MultiSelectModeInternal() & ItemContainerMultiSelectMode.Auto) != 0) + { + ItemContainerMultiSelectMode multiSelectMode = ItemContainerMultiSelectMode.Auto; + + switch (SelectionMode) + { + case ItemsViewSelectionMode.None: + case ItemsViewSelectionMode.Single: + multiSelectMode |= ItemContainerMultiSelectMode.Single; + break; + case ItemsViewSelectionMode.Extended: + multiSelectMode |= ItemContainerMultiSelectMode.Extended; + break; + case ItemsViewSelectionMode.Multiple: + multiSelectMode |= ItemContainerMultiSelectMode.Multiple; + break; + } + + itemContainerImpl.MultiSelectModeInternal(multiSelectMode); + } + + if ((itemContainerImpl.CanUserSelectInternal() & ItemContainerUserSelectMode.Auto) != 0) + { + ItemContainerUserSelectMode canUserSelect = ItemContainerUserSelectMode.Auto; + + canUserSelect |= SelectionMode == ItemsViewSelectionMode.None ? ItemContainerUserSelectMode.UserCannotSelect : ItemContainerUserSelectMode.UserCanSelect; + + itemContainerImpl.CanUserSelectInternal(canUserSelect); + } + } +#endif + + bool? isSelectionModelSelectedAsNullable = m_selectionModel.IsSelected(index); + bool isSelectionModelSelected = isSelectionModelSelectedAsNullable != null && isSelectionModelSelectedAsNullable.Value; + + if (itemContainer.IsSelected) + { + // The ItemsSource may be a list of ItemContainers, some of them having IsSelected==True. Account for this situation + // by updating the selection model accordingly. Only selected containers are pushed into the selection model to avoid + // clearing any potential selections already present in that model, which are pushed into the ItemContainers next. + if (!isSelectionModelSelected && SelectionMode != ItemsViewSelectionMode.None) + { + // When SelectionMode is None, ItemContainer.IsSelected will be reset below. + // For all other selection modes, simply select the item. + // No need to go through the SingleSelector, MultipleSelector or ExtendedSelector policy. + m_selectionModel.Select(index); + + // Access the new selection status for the same ItemContainer so it can be updated accordingly below. + isSelectionModelSelectedAsNullable = m_selectionModel.IsSelected(index); + isSelectionModelSelected = isSelectionModelSelectedAsNullable != null && isSelectionModelSelectedAsNullable.Value; + } + } + + itemContainer.IsSelected = isSelectionModelSelected; + + SetItemsViewItemContainerRevokers(itemContainer); + } + else + { + throw new ArgumentException(s_invalidItemTemplateRoot); + } + + if (itemsRepeater.ItemsSourceView is { } itemsSourceView) + { + element.SetValue(AutomationProperties.PositionInSetProperty, index + 1); + element.SetValue(AutomationProperties.SizeOfSetProperty, itemsSourceView.Count); + } + } + } + + void OnItemsRepeaterElementClearing( + ItemsRepeater itemsRepeater, + ItemsRepeaterElementClearingEventArgs args) + { + if (args.Element is { } element) + { +#if DEBUG_VERBOSE + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, GetElementIndex(element)); +#endif + + var itemContainer = element as ItemContainer; + + if (itemContainer != null) + { + // Clear all the revokers first before touching ItemContainer properties to avoid side effects. + // For example, if you clear IsSelected before clearing revokers, we will listen to that change and + // update SelectionModel which is incorrect. + ClearItemsViewItemContainerRevokers(itemContainer); + +#if MUX_PRERELEASE + if ((itemContainer.CanUserInvoke() & ItemContainerUserInvokeMode.Auto) != 0) + { + itemContainer.CanUserInvoke(ItemContainerUserInvokeMode.Auto); + } + + if ((itemContainer.MultiSelectMode() & ItemContainerMultiSelectMode.Auto) != 0) + { + itemContainer.MultiSelectMode(ItemContainerMultiSelectMode.Auto); + } + + if ((itemContainer.CanUserSelect() & ItemContainerUserSelectMode.Auto) != 0) + { + itemContainer.CanUserSelect(ItemContainerUserSelectMode.Auto); + } +#else + if (itemContainer is ItemContainer itemContainerImpl) + { + if ((itemContainerImpl.CanUserInvokeInternal() & ItemContainerUserInvokeMode.Auto) != 0) + { + itemContainerImpl.CanUserInvokeInternal(ItemContainerUserInvokeMode.Auto); + } + + if ((itemContainerImpl.MultiSelectModeInternal() & ItemContainerMultiSelectMode.Auto) != 0) + { + itemContainerImpl.MultiSelectModeInternal(ItemContainerMultiSelectMode.Auto); + } + + if ((itemContainerImpl.CanUserSelectInternal() & ItemContainerUserSelectMode.Auto) != 0) + { + itemContainerImpl.CanUserSelectInternal(ItemContainerUserSelectMode.Auto); + } + } +#endif + + itemContainer.IsSelected = false; + } + + element.ClearValue(AutomationProperties.PositionInSetProperty); + element.ClearValue(AutomationProperties.SizeOfSetProperty); + } + } + + void OnItemsRepeaterElementIndexChanged( + ItemsRepeater itemsRepeater, + ItemsRepeaterElementIndexChangedEventArgs args) + { + if (args.Element is { } element) + { + var newIndex = args.NewIndex; + + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, newIndex); + + element.SetValue(AutomationProperties.PositionInSetProperty, newIndex + 1); + } + } + + void OnItemsRepeaterItemsSourceChanged( + DependencyObject sender, + DependencyProperty args) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + HookItemsSourceViewEvents(); + + var itemsSource = ItemsSource; + + // Updating the selection model's ItemsSource here rather than earlier in OnPropertyChanged/OnItemsSourceChanged so that + // Layout.OnItemsChangedCore is executed before OnSelectionModelSelectionChanged. Otherwise OnSelectionModelSelectionChanged + // would operate on out-of-date ItemsRepeater children. + m_selectionModel.Source = itemsSource; + + m_currentElementSelectionModel.Source = itemsSource; + } + + void OnItemsRepeaterLayoutUpdated( + object sender, + object args) + { +#if DEBUG_VERBOSE + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); +#endif + + if (m_keyboardNavigationReferenceResetPending) + { + m_keyboardNavigationReferenceResetPending = false; + UpdateKeyboardNavigationReference(); + } + } + + void OnItemsRepeaterSizeChanged( + object sender, + SizeChangedEventArgs args) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_STR, METH_NAME, this, TypeLogging.SizeToString(args.PreviousSize()).c_str(), TypeLogging.SizeToString(args.NewSize()).c_str()); + + UpdateKeyboardNavigationReference(); + } + + void OnScrollViewAnchorRequested( + ScrollView scrollView, + ScrollingAnchorRequestedEventArgs args) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "ScrollingAnchorRequestedEventArgs.AnchorCandidates.Size", args.AnchorCandidates().Size()); + + if (m_bringIntoViewElement != null) + { + // During a StartBringItemIntoView operation, its target element is used as the scroll anchor so that any potential shuffling of the Layout does not disturb the final visual. + // This anchor is used until the new layout has a chance to settle. + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, m_bringIntoViewElement.get(), "ScrollingAnchorRequestedEventArgs.AnchorElement set to m_bringIntoViewElement."); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "at index", GetElementIndex(m_bringIntoViewElement.get())); + + args.AnchorElement = m_bringIntoViewElement; + } +#if DEBUG + else + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, "ScrollingAnchorRequestedEventArgs.AnchorElement unset. m_bringIntoViewElement null."); + } +#endif + } + + void OnScrollViewBringingIntoView( + ScrollView scrollView, + ScrollingBringingIntoViewEventArgs args) + { +#if DEBUG + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, + // "ScrollingBringingIntoViewEventArgs.CorrelationId", args.CorrelationId); + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR_DBL, METH_NAME, this, + // "ScrollingBringingIntoViewEventArgs.TargetHorizontalOffset", args.TargetHorizontalOffset()); + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR_DBL, METH_NAME, this, + // "ScrollingBringingIntoViewEventArgs.TargetVerticalOffset", args.TargetVerticalOffset()); + + if (args.RequestEventArgs is { } requestEventArgs) + { + //ITEMSVIEW_TRACE_INFO(*this, "%s[0x%p](ScrollingBringingIntoViewEventArgs.RequestEventArgs: AnimationDesired:%d, H/V AlignmentRatio:%lf,%lf, H/V Offset:%f,%f, TargetRect:%s, TargetElement:0x%p)\n", + // METH_NAME, this, + // requestEventArgs.AnimationDesired(), + // requestEventArgs.HorizontalAlignmentRatio(), requestEventArgs.VerticalAlignmentRatio(), + // requestEventArgs.HorizontalOffset(), requestEventArgs.VerticalOffset(), + // TypeLogging.RectToString(requestEventArgs.TargetRect()).c_str(), + // requestEventArgs.TargetElement()); + + if (requestEventArgs.AnimationDesired) + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, + // "ScrollingBringingIntoViewEventArgs.RequestEventArgs.AnimationDesired unexpectedly True"); + } + } + + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, + // m_bringIntoViewElement.get(), "m_bringIntoViewElement"); +#endif + + if (m_bringIntoViewCorrelationId == -1 && + m_bringIntoViewElement != null && + args.RequestEventArgs != null && + m_bringIntoViewElement == args.RequestEventArgs.TargetElement) + { + // Record the CorrelationId for the bring-into-view operation so OnScrollViewScrollCompleted can trigger the countdown to a stable layout. + m_bringIntoViewCorrelationId = args.CorrelationId; + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "for m_bringIntoViewElement index", GetElementIndex(m_bringIntoViewElement.get())); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_bringIntoViewCorrelationId set", m_bringIntoViewCorrelationId); + } + + if (m_navigationKeyBringIntoViewPendingCount > 0) + { + m_navigationKeyBringIntoViewPendingCount--; + // Record the CorrelationId for the navigation-key-triggered bring-into-view operation so OnScrollViewScrollCompleted can trigger the countdown + // to a stable layout for large offset changes or immediately process queued navigation keys. + m_navigationKeyBringIntoViewCorrelationId = args.CorrelationId; + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_navigationKeyBringIntoViewPendingCount decremented", m_navigationKeyBringIntoViewPendingCount); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_navigationKeyBringIntoViewCorrelationId set", m_navigationKeyBringIntoViewCorrelationId); + } + } + +#if DEBUG + void OnScrollViewExtentChangedDbg( + ScrollView scrollView, + object args) + { + //if (m_scrollView is { } scrollView) + //{ + // ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR_DBL, METH_NAME, this, "ScrollView.ExtentWidth", scrollView.ExtentWidth()); + // ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR_DBL, METH_NAME, this, "ScrollView.ExtentHeight", scrollView.ExtentHeight()); + //} + } +#endif + + void OnScrollViewScrollCompleted( + ScrollView scrollView, + ScrollingScrollCompletedEventArgs args) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "ScrollingScrollCompletedEventArgs.CorrelationId", args.CorrelationId); + + if (args.CorrelationId == m_bringIntoViewCorrelationId) + { + MUX_ASSERT(m_bringIntoViewElement != null); + + m_bringIntoViewCorrelationId = -1; + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, "m_bringIntoViewCorrelationId reset & m_bringIntoViewElementRetentionCountdown initialized to 4."); + + m_bringIntoViewElementRetentionCountdown = c_renderingEventsPostBringIntoView; + + // OnCompositionTargetRendering will decrement m_bringIntoViewElementRetentionCountdown until it reaches 0, + // at which point m_bringIntoViewElement is reset to null and the ScrollView anchor alignments are restored. + HookCompositionTargetRendering(); + } + + if (args.CorrelationId == m_navigationKeyBringIntoViewCorrelationId) + { + if (m_lastNavigationKeyProcessed == VirtualKey.Left || + m_lastNavigationKeyProcessed == VirtualKey.Right || + m_lastNavigationKeyProcessed == VirtualKey.Up || + m_lastNavigationKeyProcessed == VirtualKey.Down) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, "m_navigationKeyBringIntoViewCorrelationId reset."); + + m_navigationKeyBringIntoViewCorrelationId = -1; + + if (m_navigationKeyBringIntoViewPendingCount == 0) + { + // After a small offset change, for better perf, immediately process the remaining queued navigation keys as no item re-layout is likely. + ProcessNavigationKeys(); + } + } + else + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, "m_navigationKeyProcessingCountdown initialized to 4."); + + // After a large offset change for PageDown/PageUp/Home/End, wait a few ticks for the UI to settle before processing the remaining queued + // navigation keys, so the content is properly layed out. + m_navigationKeyProcessingCountdown = c_renderingEventsPostBringIntoView; + + HookCompositionTargetRendering(); + } + } + } + + void OnItemsViewItemContainerIsSelectedChanged( + DependencyObject sender, + DependencyProperty args) + { + var element = sender as UIElement; + + MUX_ASSERT(element != null); + + if (element != null) + { + int itemIndex = GetElementIndex(element); + + if (itemIndex != -1) + { + var itemContainer = element as ItemContainer; + + MUX_ASSERT(itemContainer != null); + + if (itemContainer != null) + { + bool? isSelectionModelSelectedAsNullable = m_selectionModel.IsSelected(itemIndex); + bool isSelectionModelSelected = isSelectionModelSelectedAsNullable != null && isSelectionModelSelectedAsNullable.Value; + + if (itemContainer.IsSelected) + { + if (!isSelectionModelSelected) + { + if (SelectionMode == ItemsViewSelectionMode.None) + { + // Permission denied. + itemContainer.IsSelected = false; + } + else + { + // For all other selection modes, simply select the item. + // No need to go through the SingleSelector, MultipleSelector or ExtendedSelector policy. + m_selectionModel.Select(itemIndex); + } + } + } + else + { + if (isSelectionModelSelected) + { + // For all selection modes, simply deselect the item & preserve the anchor if any. + m_selector.DeselectWithAnchorPreservation(itemIndex); + } + } + } + } + } + } + +#if DEBUG + void OnLayoutMeasureInvalidatedDbg( + Layout sender, + object args) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + } + + void OnLayoutArrangeInvalidatedDbg( + Layout sender, + object args) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + } +#endif + + void OnCompositionTargetRendering( + object sender, + object args) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_bringIntoViewElementRetentionCountdown", m_bringIntoViewElementRetentionCountdown); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "m_navigationKeyProcessingCountdown", m_navigationKeyProcessingCountdown); + + MUX_ASSERT(m_bringIntoViewElementRetentionCountdown > 0 || m_navigationKeyProcessingCountdown > 0); + + if (m_bringIntoViewElementRetentionCountdown > 0) + { + // Waiting for the new layout to settle before discarding the bring-into-view target element and no longer using it as a scroll anchor. + m_bringIntoViewElementRetentionCountdown--; + + if (m_bringIntoViewElementRetentionCountdown == 0) + { + CompleteStartBringItemIntoView(); + } + } + + if (m_navigationKeyProcessingCountdown > 0) + { + // Waiting for the new layout to settle before processing remaining queued navigation keys. + m_navigationKeyProcessingCountdown--; + + if (m_navigationKeyProcessingCountdown == 0) + { + if (m_bringIntoViewElementRetentionCountdown == 0) + { + UnhookCompositionTargetRendering(); + } + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, "m_navigationKeyBringIntoViewCorrelationId reset."); + + m_navigationKeyBringIntoViewCorrelationId = -1; + + if (m_navigationKeyBringIntoViewPendingCount == 0) + { + // With no pending OnScrollViewBringingIntoView calls, it is time to process the remaining queue navigation keys. + ProcessNavigationKeys(); + } + } + } + } + + void OnItemsSourceChanged() + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH, METH_NAME, this); + + // When the inner ItemsRepeater has not been loaded yet, set the selection models' Source + // right away as OnItemsRepeaterItemsSourceChanged will not be invoked. + // There is no reason to delay the updates to OnItemsRepeaterItemsSourceChanged + // in this case since ItemsRepeater and its children do not exist yet. + var itemsRepeater = m_itemsRepeater; + + if (itemsRepeater == null) + { + var itemsSource = ItemsSource; + + m_selectionModel.Source = itemsSource; + m_currentElementSelectionModel.Source = itemsSource; + } + } + + void OnVerticalScrollControllerChanged() + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, VerticalScrollController(), "VerticalScrollController"); + + ApplyVerticalScrollController(); + } + +#if DEBUG + void OnLayoutChangedDbg() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + m_layoutMeasureInvalidatedDbg.Disposable = null; + m_layoutArrangeInvalidatedDbg.Disposable = null; + + if (Layout is { } layout) + { + layout.MeasureInvalidated += OnLayoutMeasureInvalidatedDbg; + m_layoutMeasureInvalidatedDbg.Disposable = new DisposableAction(() => layout.MeasureInvalidated -= OnLayoutMeasureInvalidatedDbg); + + layout.ArrangeInvalidated += OnLayoutArrangeInvalidatedDbg; + m_layoutArrangeInvalidatedDbg.Disposable = new DisposableAction(() => layout.ArrangeInvalidated -= OnLayoutArrangeInvalidatedDbg); + } + } +#endif + + void OnIsItemInvokedEnabledChanged() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + // For all ItemContainer children, update the CanUserInvoke property according to the new ItemsView.IsItemInvokedEnabled property. + if (m_itemsRepeater is { } itemsRepeater) + { + var count = VisualTreeHelper.GetChildrenCount(itemsRepeater); + ItemContainerUserInvokeMode canUserInvoke = IsItemInvokedEnabled ? + ItemContainerUserInvokeMode.Auto | ItemContainerUserInvokeMode.UserCanInvoke : + ItemContainerUserInvokeMode.Auto | ItemContainerUserInvokeMode.UserCannotInvoke; + + for (int childIndex = 0; childIndex < count; childIndex++) + { + var elementAsDO = VisualTreeHelper.GetChild(itemsRepeater, childIndex); + var itemContainer = elementAsDO as ItemContainer; + + if (itemContainer != null) + { +#if MUX_PRERELEASE + if ((itemContainer.CanUserInvoke() & ItemContainerUserInvokeMode.Auto) != 0) + { + itemContainer.CanUserInvoke(canUserInvoke); + } +#else + if (itemContainer is ItemContainer itemContainerImpl) + { + if ((itemContainerImpl.CanUserInvokeInternal() & ItemContainerUserInvokeMode.Auto) != 0) + { + itemContainerImpl.CanUserInvokeInternal(canUserInvoke); + } + } +#endif + } + } + } + } + + void OnItemTemplateChanged() + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH, METH_NAME, this); + + // Make sure the default ItemTemplate is used when the ItemsSource is non-null and the ItemTemplate is null. + EnsureItemTemplate(); + } + + void OnSelectionModeChanged() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + UpdateSelector(); + + // For all ItemContainer children, update the MultiSelectMode and CanUserSelect properties according to the new ItemsView.SelectionMode property. + if (m_itemsRepeater is { } itemsRepeater) + { + var count = VisualTreeHelper.GetChildrenCount(itemsRepeater); + var selectionMode = SelectionMode; + ItemContainerMultiSelectMode multiSelectMode = ItemContainerMultiSelectMode.Auto; + + switch (selectionMode) + { + case ItemsViewSelectionMode.None: + case ItemsViewSelectionMode.Single: + multiSelectMode |= ItemContainerMultiSelectMode.Single; + break; + case ItemsViewSelectionMode.Extended: + multiSelectMode |= ItemContainerMultiSelectMode.Extended; + break; + case ItemsViewSelectionMode.Multiple: + multiSelectMode |= ItemContainerMultiSelectMode.Multiple; + break; + } + + ItemContainerUserSelectMode canUserSelect = selectionMode == ItemsViewSelectionMode.None ? + ItemContainerUserSelectMode.Auto | ItemContainerUserSelectMode.UserCannotSelect : + ItemContainerUserSelectMode.Auto | ItemContainerUserSelectMode.UserCanSelect; + + for (int childIndex = 0; childIndex < count; childIndex++) + { + var elementAsDO = VisualTreeHelper.GetChild(itemsRepeater, childIndex); + var itemContainer = elementAsDO as ItemContainer; + + if (itemContainer != null) + { +#if MUX_PRERELEASE + if ((itemContainer.MultiSelectMode() & ItemContainerMultiSelectMode.Auto) != 0) + { + itemContainer.MultiSelectMode(multiSelectMode); + } + + if ((itemContainer.CanUserSelect() & ItemContainerUserSelectMode.Auto) != 0) + { + itemContainer.CanUserSelect(canUserSelect); + } +#else + if (itemContainer is ItemContainer itemContainerImpl) + { + if ((itemContainerImpl.MultiSelectModeInternal() & ItemContainerMultiSelectMode.Auto) != 0) + { + itemContainerImpl.MultiSelectModeInternal(multiSelectMode); + } + + if ((itemContainerImpl.CanUserSelectInternal() & ItemContainerUserSelectMode.Auto) != 0) + { + itemContainerImpl.CanUserSelectInternal(canUserSelect); + } + } +#endif + } + } + } + } + + void OnSelectionModelSelectionChanged( + SelectionModel selectionModel, + SelectionModelSelectionChangedEventArgs args) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + // Unfortunately using an internal hook to see whether this notification orginated from a collection change or not. + bool selectionInvalidatedDueToCollectionChange = + selectionModel.SelectionInvalidatedDueToCollectionChange(); + + /* + Another option, besides a public API on SelectionModel, would have been to apply the changes + asynchronously like below. But that seems fragile compared to delaying the application until + the synchronous call to OnSourceListChanged that is about to occur. + DispatcherQueue().TryEnqueue( + DispatcherQueuePriority.Low, + DispatcherQueueHandler([weakThis{ get_weak() }]() + { + if (var strongThis = weakThis.get()) + { + strongThis->ApplySelectionModelSelectionChange(); + } + })); + */ + + if (selectionInvalidatedDueToCollectionChange) + { + // Delay the SelectionModel's selection changes until the upcoming OnSourceListChanged + // call because neither m_itemsRepeater's Children nor m_itemsRepeater's ItemsSourceView have been updated yet. + // ApplySelectionModelSelectionChange which uses both is thus delayed, but is still going to be called synchronously. + m_applySelectionChangeOnSourceListChanged = true; + } + else + { + ApplySelectionModelSelectionChange(); + } + } + + void ApplySelectionModelSelectionChange() + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + // Update ItemsView properties + SelectedItem = m_selectionModel.SelectedItem; + + // For all ItemContainer children, update the IsSelected property according to the new ItemsView.SelectionModel property. + if (m_itemsRepeater is { } itemsRepeater) + { + var count = VisualTreeHelper.GetChildrenCount(itemsRepeater); + +#if DEBUG + if (itemsRepeater.ItemsSourceView is { } itemsSourceView) + { + var itemsSourceViewCount = itemsSourceView.Count; + } +#endif + + for (int childIndex = 0; childIndex < count; childIndex++) + { + var elementAsDO = VisualTreeHelper.GetChild(itemsRepeater, childIndex); + var itemContainer = elementAsDO as ItemContainer; + + if (itemContainer != null) + { + int itemIndex = itemsRepeater.GetElementIndex(itemContainer); + + if (itemIndex >= 0) + { + bool? isItemContainerSelected = m_selectionModel.IsSelected(itemIndex); + bool isSelected = isItemContainerSelected != null && isItemContainerSelected.Value; + + if (isSelected) + { +#if MUX_PRERELEASE + ItemContainerUserSelectMode canUserSelect = itemContainer.CanUserSelect(); +#else + ItemContainerUserSelectMode canUserSelect = itemContainer.CanUserSelectInternal(); +#endif + + if (!m_isProcessingInteraction || (canUserSelect & ItemContainerUserSelectMode.UserCannotSelect) == 0) + { + itemContainer.IsSelected = true; + } + else + { + // Processing a user interaction while ItemContainer.CanUserSelect is ItemContainerUserSelectMode.UserCannotSelect. Deselect that item + // in the selection model without touching the potential anchor. + m_selector.DeselectWithAnchorPreservation(itemIndex); + } + } + else + { + itemContainer.IsSelected = false; + } + } + } + } + } + + RaiseSelectionChanged(); + } + + // Raised when the current element index changed. + void OnCurrentElementSelectionModelSelectionChanged( + SelectionModel selectionModel, + SelectionModelSelectionChangedEventArgs args) + { + int currentElementIndex = GetCurrentElementIndex(); + + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, currentElementIndex); + + CurrentItemIndex = currentElementIndex; + } + + // Raised when the ItemsSource collection changed. + void OnSourceListChanged( + object dataSource, + NotifyCollectionChangedEventArgs args) + { + if (m_applySelectionChangeOnSourceListChanged) + { + m_applySelectionChangeOnSourceListChanged = false; + + // Finally apply the SelectionModel's changes notified in OnSelectionModelSelectionChanged + // now that both m_itemsRepeater's Children & m_itemsRepeater's ItemsSourceView are up-to-date. + ApplySelectionModelSelectionChange(); + } + + // When the item count goes from 0 to strictly positive, the ItemTemplate property may + // have to be set to a default template which includes an ItemContainer. + EnsureItemTemplate(); + + m_keyboardNavigationReferenceResetPending = true; + + if (m_itemsRepeater is { } itemsRepeater) + { + if (itemsRepeater.ItemsSourceView is { } itemsSourceView) + { + var count = itemsSourceView.Count; + + for (var index = 0; index < count; index++) + { + if (itemsRepeater.TryGetElement(index) is { } element) + { + element.SetValue(AutomationProperties.SizeOfSetProperty, count); + } + } + } + } + } + + private protected override void OnLoaded( + //object sender, + //RoutedEventArgs args + ) + { + if (m_setVerticalScrollControllerOnLoaded) + { + // First occurrence of the Loaded event after template + // application. Now that the inner ScrollView and ScrollPresenter + // are loaded, cache their original vertical scroll + // controller visibility and value for potential restoration. + m_setVerticalScrollControllerOnLoaded = false; + + CacheOriginalVerticalScrollControllerAndVisibility(); + + // Either push the VerticalScrollController value already set + // to the inner control or set VerticalScrollController to the + // inner control's value. + SetVerticalScrollControllerOnLoaded(); + } + } + + private protected override void OnUnloaded( + //object sender, + //RoutedEventArgs args + ) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + + if (!IsLoaded) + { + UnhookCompositionTargetRendering(); + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, "Keyboard navigation fields reset."); + + m_navigationKeyBringIntoViewPendingCount = 0; + m_navigationKeyBringIntoViewCorrelationId = -1; + m_navigationKeyProcessingCountdown = 0; + m_navigationKeysToProcess.Clear(); + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, "Bring-into-view fields reset."); + + m_bringIntoViewElement = null; + m_bringIntoViewCorrelationId = -1; + m_bringIntoViewElementRetentionCountdown = 0; + } + } + + // When isForTopLeftItem is True, returns the top/left focusable item in the viewport. Returns the bottom/right item instead. + // Fully displayed items are favored over partially displayed ones. + int GetCornerFocusableItem( + bool isForTopLeftItem) + { + // When FlowDirection is FlowDirection.RightToLeft, the top/right item is returned instead of top/left (and bottom/left instead of bottom/right). + // GetItemInternal's input is unchanged as it handles FlowDirection itself. + + IndexBasedLayoutOrientation indexBasedLayoutOrientation = GetLayoutIndexBasedLayoutOrientation(); + int itemIndex = GetItemInternal( + isForTopLeftItem ? 0.0 : 1.0 /*horizontalViewportRatio*/, + isForTopLeftItem ? 0.0 : 1.0 /*verticalViewportRatio*/, + indexBasedLayoutOrientation == IndexBasedLayoutOrientation.TopToBottom /*isHorizontalDistanceFavored*/, + indexBasedLayoutOrientation == IndexBasedLayoutOrientation.LeftToRight /*isVerticalDistanceFavored*/, + false /*useKeyboardNavigationReferenceHorizontalOffset*/, + false /*useKeyboardNavigationReferenceVerticalOffset*/, + true /*capItemEdgesToViewportRatioEdges*/, + true /*forFocusableItemsOnly*/); + + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_INT, METH_NAME, this, itemIndex); + + return itemIndex; + } + + int GetElementIndex( + UIElement element) + { + MUX_ASSERT(element != null); + + if (m_itemsRepeater is { } itemsRepeater) + { + return itemsRepeater.GetElementIndex(element); + } + + return -1; + } + + IndexPath GetElementIndexPath( + UIElement element, + ref bool isValid) + { + MUX_ASSERT(element != null); + + int index = GetElementIndex(element); + + isValid = index >= 0; + + return IndexPath.CreateFrom(index); + } + + object GetElementItem( + UIElement element, + ref bool valueReturned) + { + if (m_itemsRepeater is { } itemsRepeater) + { + if (itemsRepeater.ItemsSourceView is { } itemsSourceView) + { + int itemIndex = GetElementIndex(element); + + if (itemIndex >= 0 && itemsSourceView.Count > itemIndex) + { + valueReturned = true; + return itemsSourceView.GetAt(itemIndex); + } + } + } + + return null; + } + + // isHorizontalDistanceFavored==True: + // - Means distance on the horizontal axis supercedes the one on the vertical axis. i.e. the vertical distance is only considered when the horizontal distance is identical. + // isVerticalDistanceFavored==True: + // - Means distance on the vertical axis supercedes the one on the horizontal axis. i.e. the horizontal distance is only considered when the vertical distance is identical. + // useKeyboardNavigationReferenceHorizontalOffset: + // - Instead of using horizontalViewportRatio, m_keyboardNavigationReferenceIndex/m_keyboardNavigationReferenceRect define a target vertical line. The distance from the middle + // of the items to that line is considered. + // useKeyboardNavigationReferenceVerticalOffset: + // - Instead of using verticalViewportRatio, m_keyboardNavigationReferenceIndex/m_keyboardNavigationReferenceRect define a target horizontal line. The distance from the middle + // of the items to that line is considered. + // capItemEdgesToViewportRatioEdges==False: + // - Means + // - horizontalViewportRatio <= 0.5: find item with left edge closest to viewport ratio edge + // - horizontalViewportRatio > 0.5: find item with right edge closest to viewport ratio edge + // - verticalViewportRatio <= 0.5: find item with top edge closest to viewport ratio edge + // - verticalViewportRatio > 0.5: find item with bottom edge closest to viewport ratio edge + // capItemEdgesToViewportRatioEdges==True: + // - Means that the two item edges used for distance measurements must be closer to the center of the viewport than viewport ratio edges. + // - Used for PageUp/PageDown operations which respectively look for items with their near/far edge on the viewport center side of the viewport ratio edges. + // - Additional restrictions compared to capItemEdgesToViewportRatioEdges==False above: + // - horizontalViewportRatio <= 0.5: item left edge larger than viewport ratio edge + // - horizontalViewportRatio > 0.5: item right edge smaller than viewport ratio edge + // - verticalViewportRatio <= 0.5: item top edge larger than viewport ratio edge + // - verticalViewportRatio > 0.5: item bottom edge smaller than viewport ratio edge + // Returns -1 ItemsRepeater or ScrollView part are null, or the data source is empty, or when no item fulfills the restrictions imposed by capItemEdgesToViewportRatioEdges and/or forFocusableItemsOnly. + int GetItemInternal( + double horizontalViewportRatio, + double verticalViewportRatio, + bool isHorizontalDistanceFavored, + bool isVerticalDistanceFavored, + bool useKeyboardNavigationReferenceHorizontalOffset, + bool useKeyboardNavigationReferenceVerticalOffset, + bool capItemEdgesToViewportRatioEdges, + bool forFocusableItemsOnly) + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_DBL_DBL, METH_NAME, this, horizontalViewportRatio, verticalViewportRatio); + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_INT_INT, METH_NAME, this, isHorizontalDistanceFavored, isVerticalDistanceFavored); + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_INT_INT, METH_NAME, this, capItemEdgesToViewportRatioEdges, forFocusableItemsOnly); + + MUX_ASSERT(!isHorizontalDistanceFavored || !isVerticalDistanceFavored); + MUX_ASSERT(!useKeyboardNavigationReferenceHorizontalOffset || horizontalViewportRatio == 0.0); + MUX_ASSERT(!useKeyboardNavigationReferenceVerticalOffset || verticalViewportRatio == 0.0); + + if (m_itemsRepeater is { } itemsRepeater) + { + if (m_scrollView is { } scrollView) + { + float zoomFactor = scrollView.ZoomFactor; + bool useHorizontalItemNearEdge = false; + bool useVerticalItemNearEdge = false; + double horizontalTarget = default; + double verticalTarget = default; + + if (!useKeyboardNavigationReferenceHorizontalOffset) + { + double horizontalScrollOffset = scrollView.HorizontalOffset; + double viewportWidth = scrollView.ViewportWidth; + double horizontalViewportOffset = horizontalViewportRatio * viewportWidth; + double horizontalExtent = scrollView.ExtentWidth * (double)zoomFactor; + + horizontalTarget = horizontalScrollOffset + horizontalViewportOffset; + horizontalTarget = Math.Max(0.0, horizontalTarget); + horizontalTarget = Math.Min(horizontalExtent, horizontalTarget); + + useHorizontalItemNearEdge = horizontalViewportRatio <= 0.5; + } + + if (!useKeyboardNavigationReferenceVerticalOffset) + { + double verticalScrollOffset = scrollView.VerticalOffset; + double viewportHeight = scrollView.ViewportHeight; + double verticalViewportOffset = verticalViewportRatio * viewportHeight; + double verticalExtent = scrollView.ExtentHeight * (double)zoomFactor; + + verticalTarget = verticalScrollOffset + verticalViewportOffset; + verticalTarget = Math.Max(0.0, verticalTarget); + verticalTarget = Math.Min(verticalExtent, verticalTarget); + + useVerticalItemNearEdge = verticalViewportRatio <= 0.5; + } + + float keyboardNavigationReferenceOffset = -1.0f; + + if (useKeyboardNavigationReferenceHorizontalOffset || useKeyboardNavigationReferenceVerticalOffset) + { + Point keyboardNavigationReferenceOffsetPoint = GetUpdatedKeyboardNavigationReferenceOffset(); + keyboardNavigationReferenceOffset = useKeyboardNavigationReferenceHorizontalOffset ? (float)keyboardNavigationReferenceOffsetPoint.X : (float)keyboardNavigationReferenceOffsetPoint.Y; + + MUX_ASSERT(keyboardNavigationReferenceOffset != -1.0f); + } + + double roundingScaleFactor = GetRoundingScaleFactor(scrollView); + int childrenCount = VisualTreeHelper.GetChildrenCount(itemsRepeater); + UIElement closestElement = null; + double smallestFavoredDistance = double.MaxValue; + double smallestUnfavoredDistance = double.MaxValue; + + for (int childIndex = 0; childIndex < childrenCount; childIndex++) + { + var elementAsDO = VisualTreeHelper.GetChild(itemsRepeater, childIndex); + var element = elementAsDO as UIElement; + + if (element != null) + { + if (forFocusableItemsOnly && !SharedHelpers.IsFocusableElement(element)) + { + continue; + } + + Rect elementRect = GetElementRect(element, itemsRepeater); + Rect elementZoomedRect = new Rect(elementRect.X * zoomFactor, elementRect.Y * zoomFactor, elementRect.Width * zoomFactor, elementRect.Height * zoomFactor); + double signedHorizontalDistance = default; + double signedVerticalDistance = default; + + if (useKeyboardNavigationReferenceHorizontalOffset) + { + signedHorizontalDistance = elementZoomedRect.X + elementZoomedRect.Width / 2.0f - keyboardNavigationReferenceOffset * zoomFactor; + } + else + { + signedHorizontalDistance = useHorizontalItemNearEdge ? elementZoomedRect.X - horizontalTarget : horizontalTarget - elementZoomedRect.X - elementZoomedRect.Width; + + if (capItemEdgesToViewportRatioEdges && signedHorizontalDistance < -1.0 / roundingScaleFactor) + { + continue; + } + } + + if (useKeyboardNavigationReferenceVerticalOffset) + { + signedVerticalDistance = elementZoomedRect.Y + elementZoomedRect.Height / 2.0f - keyboardNavigationReferenceOffset * zoomFactor; + } + else + { + signedVerticalDistance = useVerticalItemNearEdge ? elementZoomedRect.Y - verticalTarget : verticalTarget - elementZoomedRect.Y - elementZoomedRect.Height; + + if (capItemEdgesToViewportRatioEdges && signedVerticalDistance < -1.0 / roundingScaleFactor) + { + continue; + } + } + + double horizontalDistance = Math.Abs(signedHorizontalDistance); + double verticalDistance = Math.Abs(signedVerticalDistance); + + if (!isHorizontalDistanceFavored && !isVerticalDistanceFavored) + { + horizontalDistance = Math.Pow(horizontalDistance, 2.0); + verticalDistance = Math.Pow(verticalDistance, 2.0); + + smallestUnfavoredDistance = 0.0; + + if (horizontalDistance + verticalDistance < smallestFavoredDistance) + { + smallestFavoredDistance = horizontalDistance + verticalDistance; + closestElement = element; + } + } + else if (isHorizontalDistanceFavored) + { + if (horizontalDistance < smallestFavoredDistance) + { + smallestFavoredDistance = horizontalDistance; + smallestUnfavoredDistance = verticalDistance; + closestElement = element; + } + else if (horizontalDistance == smallestFavoredDistance && verticalDistance < smallestUnfavoredDistance) + { + smallestUnfavoredDistance = verticalDistance; + closestElement = element; + } + } + else + { + MUX_ASSERT(isVerticalDistanceFavored); + + if (verticalDistance < smallestFavoredDistance) + { + smallestFavoredDistance = verticalDistance; + smallestUnfavoredDistance = horizontalDistance; + closestElement = element; + } + else if (verticalDistance == smallestFavoredDistance && horizontalDistance < smallestUnfavoredDistance) + { + smallestUnfavoredDistance = horizontalDistance; + closestElement = element; + } + } + + if (smallestFavoredDistance == 0.0 && smallestUnfavoredDistance == 0.0) + { + break; + } + } + } + + return closestElement == null ? -1 : itemsRepeater.GetElementIndex(closestElement); + } + } + + return -1; + } + + double GetRoundingScaleFactor( + UIElement xamlRootReference) + { + if (xamlRootReference.XamlRoot is { } xamlRoot) + { + return (float)xamlRoot.RasterizationScale; + } + return 1.0; // Assuming a 1.0 scale factor when no XamlRoot is available at the moment. + } + + bool SetCurrentElementIndex( + int index, + FocusState focusState, + bool forceKeyboardNavigationReferenceReset, + bool startBringIntoView = false, + bool expectBringIntoView = false) + { + int currentElementIndex = GetCurrentElementIndex(); + + if (index != currentElementIndex) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "current element index", currentElementIndex); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "index", index); + + if (index == -1) + { + m_currentElementSelectionModel.ClearSelection(); + } + else + { + m_currentElementSelectionModel.Select(index); + } + + if (index == -1 || m_keyboardNavigationReferenceRect.X == -1.0 || forceKeyboardNavigationReferenceReset) + { + UpdateKeyboardNavigationReference(); + } + } + + return SetFocusElementIndex(index, focusState, startBringIntoView, expectBringIntoView); + } + + // Returns True when a bring-into-view operation was initiated, and False otherwise. + bool StartBringItemIntoViewInternal( + bool throwOutOfBounds, + bool throwOnAnyFailure, + int index, + BringIntoViewOptions options) + { +#if DEBUG + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, "index", index); + + if (options != null) + { + //ITEMSVIEW_TRACE_INFO(*this, "%s[0x%p](AnimationDesired:%d, H/V AlignmentRatio:%lf,%lf, H/V Offset:%f,%f, TargetRect:%s)\n", + // METH_NAME, this, + // options.AnimationDesired(), + // options.HorizontalAlignmentRatio(), options.VerticalAlignmentRatio(), + // options.HorizontalOffset(), options.VerticalOffset(), + // options.TargetRect() == null ? "nul" : TypeLogging.RectToString(options.TargetRect().Value()).c_str()); + } +#endif + + var itemsRepeater = m_itemsRepeater; + + if (itemsRepeater == null) + { + // StartBringIntoView operation cannot be initiated without ItemsRepeater part. + if (throwOnAnyFailure) + { + throw new InvalidOperationException(s_missingItemsRepeaterPart); + } + + return false; + } + + bool isItemIndexValid = ValidateItemIndex(throwOutOfBounds || throwOnAnyFailure, index); + + if (!isItemIndexValid) + { + MUX_ASSERT(!throwOutOfBounds && !throwOnAnyFailure); + + return false; + } + + // Reset the fields changed by any potential bring-into-view operation still in progress. + CompleteStartBringItemIntoView(); + + MUX_ASSERT(m_bringIntoViewElement == null); + MUX_ASSERT(m_bringIntoViewElementRetentionCountdown == 0); + MUX_ASSERT(m_bringIntoViewCorrelationId == -1); + MUX_ASSERT(m_scrollViewHorizontalAnchorRatio == -1); + MUX_ASSERT(m_scrollViewVerticalAnchorRatio == -1); + + // Access or create the target element so its position within the ItemsRepeater can be evaluated. + UIElement element = itemsRepeater.GetOrCreateElement(index); + + MUX_ASSERT(element != null); + + var scrollView = m_scrollView; + + // During the initial position evaluation, scroll anchoring is turned off to avoid shifting offsets + // which may result in an incorrect final scroll offset. + if (scrollView is not null) + { + // Current anchoring settings are recorded for post-operation restoration. + m_scrollViewHorizontalAnchorRatio = scrollView.HorizontalAnchorRatio; + m_scrollViewVerticalAnchorRatio = scrollView.VerticalAnchorRatio; + + scrollView.HorizontalAnchorRatio = DoubleUtil.NaN; + scrollView.VerticalAnchorRatio = DoubleUtil.NaN; + } + + //ITEMSVIEW_TRACE_VERBOSE_DBG(*this, TRACE_MSG_METH_METH, METH_NAME, this, "UIElement.UpdateLayout"); + + // Ensure the item is given a valid position within the ItemsRepeater. It will determine the target scroll offset. + element.UpdateLayout(); + + // Make sure that the target index is still valid after the UpdateLayout call. + isItemIndexValid = ValidateItemIndex(false /*throwIfInvalid*/, index); + + if (!isItemIndexValid) + { + // Restore scrollView.HorizontalAnchorRatio/VerticalAnchorRatio properties with m_scrollViewHorizontalAnchorRatio/m_scrollViewVerticalAnchorRatio cached values. + CompleteStartBringItemIntoView(); + + // StartBringIntoView operation cannot be initiated without ItemsRepeater part or in-bounds item index. + if (throwOnAnyFailure) + { + throw new InvalidOperationException(s_indexOutOfBounds); + } + return false; + } + + if (scrollView is not null) + { + // Turn on scroll anchoring during and after the scroll operation so target repositionings have no effect on the final visual. + // The value 0.5 is used rather than 0.0 or 1.0 to avoid near and far edge anchoring which only take effect when the content + // hits the viewport boundary. With any value between 0 and 1, anchoring is also effective in those extreme cases. + const double anchorRatio = 0.5; + + scrollView.HorizontalAnchorRatio = anchorRatio; + scrollView.VerticalAnchorRatio = anchorRatio; + } + + // Access the element's index to account for the rare event where it was recycled during the layout. + if (index != GetElementIndex(element)) + { + // Access the target element which was created during the layout pass. Its position is already set. + element = itemsRepeater.GetOrCreateElement(index); + + if (GetElementIndex(element) == -1) + { + // This situation arises when the ItemsView is not parented. + // Restore the ScrollView's HorizontalAnchorRatio/VerticalAnchorRatio properties. + CompleteStartBringItemIntoView(); + + if (throwOnAnyFailure) + { + throw new InvalidOperationException(s_itemsViewNotParented); + } + + return false; + } + + MUX_ASSERT(index == GetElementIndex(element)); + + // Make sure animation is turned off in this rare case. + if (options != null) + { + options.AnimationDesired = false; + } + } + + m_bringIntoViewElement = element; + + // Trigger the actual bring-into-view scroll - it'll cause OnScrollViewBringingIntoView and OnScrollViewScrollCompleted calls. + if (options == null) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, m_bringIntoViewElement.get(), "m_bringIntoViewElement set. Invoking UIElement.StartBringIntoView without options."); + + element.StartBringIntoView(); + } + else + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_PTR_STR, METH_NAME, this, m_bringIntoViewElement.get(), "m_bringIntoViewElement set. Invoking UIElement.StartBringIntoView with options."); + + element.StartBringIntoView(options); + } + + return true; + } + + UIElement TryGetElement(int index) + { + if (m_itemsRepeater is { } itemsRepeater) + { + return itemsRepeater.TryGetElement(index); + } + + return null; + } + + void SetItemsViewItemContainerRevokers( + ItemContainer itemContainer) + { + var itemContainerRevokers = new ItemContainerRevokers(); + + itemContainer.KeyDown += OnItemsViewElementKeyDown; + itemContainerRevokers.m_keyDownRevoker.Disposable = new DisposableAction(() => itemContainer.KeyDown -= OnItemsViewElementKeyDown); + + itemContainer.GettingFocus += OnItemsViewElementGettingFocus; + itemContainerRevokers.m_gettingFocusRevoker.Disposable = new DisposableAction(() => itemContainer.GettingFocus -= OnItemsViewElementGettingFocus); + +#if DEBUG + itemContainer.LosingFocus += OnItemsViewElementLosingFocusDbg; + itemContainerRevokers.m_losingFocusRevoker.Disposable = new DisposableAction(() => itemContainer.LosingFocus -= OnItemsViewElementLosingFocusDbg); +#endif + + itemContainer.ItemInvoked += OnItemsViewItemContainerItemInvoked; + itemContainerRevokers.m_itemInvokedRevoker.Disposable = new DisposableAction(() => itemContainer.ItemInvoked -= OnItemsViewItemContainerItemInvoked); + + var token = itemContainer.RegisterPropertyChangedCallback(ItemContainer.IsSelectedProperty, OnItemsViewItemContainerIsSelectedChanged); + itemContainerRevokers.m_isSelectedPropertyChangedRevoker.Disposable = new DisposableAction(() => itemContainer.UnregisterPropertyChangedCallback(ItemContainer.IsSelectedProperty, token)); + +#if DEBUG + itemContainer.SizeChanged += OnItemsViewItemContainerSizeChangedDbg; + itemContainerRevokers.m_sizeChangedRevokerDbg.Disposable = new DisposableAction(() => itemContainer.SizeChanged -= OnItemsViewItemContainerSizeChangedDbg); +#endif + + itemContainer.SetValue(ItemsViewItemContainerRevokersProperty, itemContainerRevokers); + + m_itemContainersWithRevokers.Add(itemContainer); + } + + void ClearItemsViewItemContainerRevokers( + ItemContainer itemContainer) + { + RevokeItemsViewItemContainerRevokers(itemContainer); + itemContainer.SetValue(ItemsViewItemContainerRevokersProperty, null); + m_itemContainersWithRevokers.Remove(itemContainer); + } + + void ClearAllItemsViewItemContainerRevokers() + { + foreach (var itemContainer in m_itemContainersWithRevokers) + { + // ClearAllItemsViewItemContainerRevokers is only called in the destructor, where exceptions cannot be thrown. + // If the associated ItemsView items have not yet been cleaned up, we must detach these revokers or risk a call into freed + // memory being made. However if they have been cleaned up these calls will throw. In this case we can ignore + // those exceptions. + try + { + RevokeItemsViewItemContainerRevokers(itemContainer); + itemContainer.SetValue(ItemsViewItemContainerRevokersProperty, null); + } + catch + { + } + } + m_itemContainersWithRevokers.Clear(); + } + + void RevokeItemsViewItemContainerRevokers( + ItemContainer itemContainer) + { + if (itemContainer.GetValue(ItemsViewItemContainerRevokersProperty) is { } revokers) + { + if (revokers is ItemContainerRevokers itemContainerRevokers) + { + itemContainerRevokers.RevokeAll(itemContainer); + } + } + } + + bool ValidateItemIndex( + bool throwIfInvalid, + int index) + { + if (m_itemsRepeater is { } itemsRepeater) + { + if (itemsRepeater.ItemsSourceView == null) + { + if (throwIfInvalid) + { + throw new InvalidOperationException(s_itemsSourceNull); + } + return false; + } + + if (index < 0 || index >= itemsRepeater.ItemsSourceView.Count) + { + if (throwIfInvalid) + { + throw new IndexOutOfRangeException(s_indexOutOfBounds); + } + return false; + } + + return true; + } + + return false; + } + + // Invoked by ItemsViewTestHooks + internal Point GetKeyboardNavigationReferenceOffset() + { + if (m_keyboardNavigationReferenceRect.X == -1.0f) + { + return new Point(-1.0f, -1.0f); + } + + return new Point( + m_keyboardNavigationReferenceRect.X + m_keyboardNavigationReferenceRect.Width / 2.0f, + m_keyboardNavigationReferenceRect.Y + m_keyboardNavigationReferenceRect.Height / 2.0f); + } + + int GetCurrentElementIndex() + { + IndexPath currentElementIndexPath = m_currentElementSelectionModel.SelectedIndex; + + MUX_ASSERT(currentElementIndexPath == null || currentElementIndexPath.GetSize() == 1); + + int currentElementIndex = currentElementIndexPath == null ? -1 : currentElementIndexPath.GetAt(0); + + return currentElementIndex; + } + + internal ScrollView GetScrollViewPart() + { + return m_scrollView; + } + + internal ItemsRepeater GetItemsRepeaterPart() + { + return m_itemsRepeater; + } + + internal SelectionModel GetSelectionModel() + { + return m_selectionModel; + } + +#if false + + string DependencyPropertyToStringDbg( + DependencyProperty dependencyProperty) + { + if (dependencyProperty == ItemsSourceProperty) + { + return "ItemsSource"; + } + else if (dependencyProperty == ItemTemplateProperty) + { + return "ItemTemplate"; + } + else if (dependencyProperty == LayoutProperty) + { + return "Layout"; + } + else if (dependencyProperty == SelectionModeProperty) + { + return "SelectionMode"; + } + else if (dependencyProperty == ScrollViewProperty) + { + return "ScrollView"; + } + else + { + return "UNKNOWN"; + } + } + +#endif +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.h.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.h.cs new file mode 100644 index 000000000000..67d79c6169b0 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.h.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Collections.Generic; +using Microsoft.UI.Xaml.Controls.Primitives; +using Uno.Disposables; +using Windows.Foundation; +using Windows.System; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ItemsView +{ + // Properties' default values. + const ItemsViewSelectionMode s_defaultSelectionMode = ItemsViewSelectionMode.Single; + + private static DependencyProperty ItemsViewItemContainerRevokersProperty { get; } = DependencyProperty.RegisterAttached( + "ItemsViewItemContainerRevokers", + typeof(object), + typeof(UIElement), + new FrameworkPropertyMetadata(defaultValue: null)); + + private const string s_itemsRepeaterPartName = "PART_ItemsRepeater"; + private const string s_scrollViewPartName = "PART_ScrollView"; + private const string s_indexOutOfBounds = "Index is out of bounds."; + private const string s_invalidItemTemplateRoot = "ItemTemplate's root element must be an ItemContainer."; + private const string s_itemsSourceNull = "ItemsSource does not have a value."; + private const string s_itemsViewNotParented = "ItemsView is not parented."; + private const string s_missingItemsRepeaterPart = "ItemsRepeater part is not available."; + + // CorrelationId of the bring-into-view scroll resulting from a navigation key processing. + private int m_navigationKeyBringIntoViewCorrelationId = -1; + // Ticks count left before the next queued navigation key is processed, + // after a navigation-key-induced bring-into-view scroll completed a large offset change. + private byte m_navigationKeyProcessingCountdown = 0; + // Incremented in SetFocusElementIndex when a navigation key processing causes a new item to get focus. + // This will trigger an OnScrollViewBringingIntoView call where it is decremented. + // Used to delay a navigation key processing until the content has settled on a new viewport. + private byte m_navigationKeyBringIntoViewPendingCount = 0; + // Caches the most recent navigation key processed. + private VirtualKey m_lastNavigationKeyProcessed; + + // CorrelationId of the bring-into-view scroll resulting from a StartBringItemIntoView call. + private int m_bringIntoViewCorrelationId = -1; + // Ticks count left after bring-into-view scroll completed before the m_bringIntoViewElement + // field is reset and no longer returned in OnScrollViewAnchorRequested. + private byte m_bringIntoViewElementRetentionCountdown = 0; + // ScrollView anchor ratios to restore after a StartBringItemIntoView operation completes. + private double m_scrollViewHorizontalAnchorRatio = -1.0; + private double m_scrollViewVerticalAnchorRatio = -1.0; + + // Set to True when a ResetKeyboardNavigationReference() call is delayed until the next ItemsView::OnItemsRepeaterLayoutUpdated occurrence. + private bool m_keyboardNavigationReferenceResetPending = false; + // Caches the element index used to define m_keyboardNavigationReferenceRect. + private int m_keyboardNavigationReferenceIndex = -1; + // Bounds of the reference element for directional keyboard navigations. + private Rect m_keyboardNavigationReferenceRect = new Rect(-1.0f, -1.0f, -1.0f, -1.0f); + + // Set to True during a user action processing which updates selection. + private bool m_isProcessingInteraction = false; + + private bool m_setVerticalScrollControllerOnLoaded = false; + + // Set to True in ItemsView::OnSelectionModelSelectionChanged to delay the application + // of the selection changes until the imminent ItemsView::OnSourceListChanged call. + private bool m_applySelectionChangeOnSourceListChanged = false; + + private SelectorBase m_selector; + + SerialDisposable m_renderingRevoker = new(); + SerialDisposable m_selectionModelSelectionChangedRevoker = new(); + SerialDisposable m_currentElementSelectionModelSelectionChangedRevoker = new(); + SerialDisposable m_itemsRepeaterElementPreparedRevoker = new(); + SerialDisposable m_itemsRepeaterElementClearingRevoker = new(); + SerialDisposable m_itemsRepeaterElementIndexChangedRevoker = new(); + SerialDisposable m_itemsRepeaterLayoutUpdatedRevoker = new(); + SerialDisposable m_itemsRepeaterSizeChangedRevoker = new(); + //SerialDisposable m_unloadedRevoker; + //SerialDisposable m_loadedRevoker; + SerialDisposable m_itemsSourceViewChangedRevoker = new(); +#if DEBUG + SerialDisposable m_layoutMeasureInvalidatedDbg = new(); + SerialDisposable m_layoutArrangeInvalidatedDbg = new(); +#endif + SerialDisposable m_scrollViewAnchorRequestedRevoker = new(); + SerialDisposable m_scrollViewBringingIntoViewRevoker = new(); + SerialDisposable m_scrollViewScrollCompletedRevoker = new(); +#if DEBUG + SerialDisposable m_scrollViewExtentChangedRevokerDbg = new(); +#endif + SerialDisposable m_itemsRepeaterItemsSourcePropertyChangedRevoker = new(); + + // Tracks selected elements. + SelectionModel m_selectionModel = new(); + // Tracks current element. + SelectionModel m_currentElementSelectionModel = new(); + + // ScrollView's vertical scrollbar visibility to restore in the event VerticalScrollController gets assigned back to + // its original value (m_originalVerticalScrollController read in OnApplyTemplate) after being set to a custom value. + ScrollingScrollBarVisibility m_originalVerticalScrollBarVisibility = ScrollingScrollBarVisibility.Auto; + // Original VerticalScrollController read in OnApplyTemplate, which by default is the ScrollView's ScrollBarController. + IScrollController m_originalVerticalScrollController; + + ItemsRepeater m_itemsRepeater; + ScrollView m_scrollView; + UIElement m_bringIntoViewElement; + + List m_navigationKeysToProcess = new(); + HashSet m_itemContainersWithRevokers = new(); + //Dictionary> m_itemContainersPointerInfos; // Uno docs: This is unused in WinUI +}; diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.properties.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.properties.cs new file mode 100644 index 000000000000..43f25c5ddb14 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.properties.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Microsoft.UI.Xaml.Controls.Primitives; +using Windows.Foundation; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ItemsView +{ + public static DependencyProperty CurrentItemIndexProperty { get; } = DependencyProperty.Register( + nameof(CurrentItemIndex), + typeof(int), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: -1, propertyChangedCallback: OnCurrentItemIndexPropertyChanged)); + + public static DependencyProperty IsItemInvokedEnabledProperty { get; } = DependencyProperty.Register( + nameof(IsItemInvokedEnabled), + typeof(bool), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: false, propertyChangedCallback: OnIsItemInvokedEnabledPropertyChanged)); + + public static DependencyProperty ItemsSourceProperty { get; } = DependencyProperty.Register( + nameof(ItemsSource), + typeof(object), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: null, OnItemsSourcePropertyChanged)); + + public static DependencyProperty ItemTemplateProperty { get; } = DependencyProperty.Register( + nameof(ItemTemplate), + typeof(IElementFactory), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: null, propertyChangedCallback: OnItemTemplatePropertyChanged)); + + public static DependencyProperty ItemTransitionProviderProperty { get; } = DependencyProperty.Register( + nameof(ItemTransitionProvider), + typeof(ItemCollectionTransitionProvider), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: null, propertyChangedCallback: OnItemTransitionProviderPropertyChanged)); + + public static DependencyProperty LayoutProperty { get; } = DependencyProperty.Register( + nameof(Layout), + typeof(Layout), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: null, propertyChangedCallback: OnLayoutPropertyChanged)); + + public static DependencyProperty ScrollViewProperty { get; } = DependencyProperty.Register( + nameof(ScrollView), + typeof(ScrollView), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: null, propertyChangedCallback: OnScrollViewPropertyChanged)); + + public static DependencyProperty SelectedItemProperty { get; } = DependencyProperty.Register( + nameof(SelectedItem), + typeof(object), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: null, propertyChangedCallback: OnSelectedItemPropertyChanged)); + + public static DependencyProperty SelectionModeProperty { get; } = DependencyProperty.Register( + nameof(SelectionMode), + typeof(ItemsViewSelectionMode), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: s_defaultSelectionMode, propertyChangedCallback: OnSelectionModePropertyChanged)); + + public static DependencyProperty VerticalScrollControllerProperty { get; } = DependencyProperty.Register( + nameof(VerticalScrollController), + typeof(IScrollController), + typeof(ItemsView), + new FrameworkPropertyMetadata(defaultValue: null, propertyChangedCallback: OnVerticalScrollControllerPropertyChanged)); + + private static void OnCurrentItemIndexPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnIsItemInvokedEnabledPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnItemsSourcePropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnItemTemplatePropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnItemTransitionProviderPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnLayoutPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnScrollViewPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnSelectedItemPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnSelectionModePropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + private static void OnVerticalScrollControllerPropertyChanged( + DependencyObject sender, + DependencyPropertyChangedEventArgs args) + { + var owner = (ItemsView)sender; + owner.OnPropertyChanged(args); + } + + public int CurrentItemIndex + { + get => (int)GetValue(CurrentItemIndexProperty); + private set => SetValue(CurrentItemIndexProperty, value); + } + + public bool IsItemInvokedEnabled + { + get => (bool)GetValue(IsItemInvokedEnabledProperty); + set => SetValue(IsItemInvokedEnabledProperty, value); + } + + public object ItemsSource + { + get => (object)GetValue(ItemsSourceProperty); + set => SetValue(ItemsSourceProperty, value); + } + + public IElementFactory ItemTemplate + { + get => (IElementFactory)GetValue(ItemTemplateProperty); + set => SetValue(ItemTemplateProperty, value); + } + + public ItemCollectionTransitionProvider ItemTransitionProvider + { + get => (ItemCollectionTransitionProvider)GetValue(ItemTransitionProviderProperty); + set => SetValue(ItemTransitionProviderProperty, value); + } + + public +#if __ANDROID__ || __MACOS__ + new +#endif + Layout Layout + { + get => (Layout)GetValue(LayoutProperty); + set => SetValue(LayoutProperty, value); + } + + internal ScrollView ScrollViewInternal + { + get => (ScrollView)GetValue(ScrollViewProperty); + set => SetValue(ScrollViewProperty, value); + } + + public object SelectedItem + { + get => GetValue(SelectedItemProperty); + private set => SetValue(SelectedItemProperty, value); + } + + public ItemsViewSelectionMode SelectionMode + { + get => (ItemsViewSelectionMode)GetValue(SelectionModeProperty); + set => SetValue(SelectionModeProperty, value); + } + + public IScrollController VerticalScrollController + { + get => (IScrollController)GetValue(VerticalScrollControllerProperty); + set => SetValue(VerticalScrollControllerProperty, value); + } + + public event TypedEventHandler ItemInvoked; + public event TypedEventHandler SelectionChanged; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.xaml b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.xaml new file mode 100644 index 000000000000..f8ac105313c6 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView.xaml @@ -0,0 +1,43 @@ + + + + diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewAutomationPeer.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewAutomationPeer.cs new file mode 100644 index 000000000000..916757f41213 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewAutomationPeer.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Collections.Generic; +using Microsoft.UI.Private.Controls; +using Microsoft.UI.Xaml.Automation.Provider; +using Microsoft.UI.Xaml.Controls; + +namespace Microsoft.UI.Xaml.Automation.Peers; + +partial class ItemsViewAutomationPeer : FrameworkElementAutomationPeer, ISelectionProvider +{ + + public ItemsViewAutomationPeer(ItemsView owner) : base(owner) + { + } + + // IAutomationPeerOverrides + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (Owner is ItemsView itemsView) + { + if (patternInterface == PatternInterface.Selection && itemsView.SelectionMode != ItemsViewSelectionMode.None) + { + return this; + } + } + + return base.GetPatternCore(patternInterface); + } + + protected override string GetClassNameCore() + { + return nameof(ItemsView); + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.List; + } + + // ISelectionProvider + public bool CanSelectMultiple + { + get + { + if (GetImpl() is { } itemsView) + + { + var selectionMode = itemsView.SelectionMode; + if (selectionMode == ItemsViewSelectionMode.Multiple || + selectionMode == ItemsViewSelectionMode.Extended) + { + return true; + } + } + + return false; + } + } + + public IRawElementProviderSimple[] GetSelection() + { + List selectionList = new(); + + if (GetImpl() is ItemsView itemsView) + + { + if (itemsView.GetSelectionModel() is { } selectionModel) + + { + if (selectionModel.SelectedIndices is { } selectedIndices) + + { + if (selectedIndices.Count > 0) + { + if (ItemsViewTestHooks.GetItemsRepeaterPart(itemsView) is { } repeater) + + { + foreach (var indexPath in selectedIndices) + { + // TODO: Update once ItemsView has grouping. + var index = indexPath.GetAt(0); + + if (repeater.TryGetElement(index) is { } itemElement) + { + if (FrameworkElementAutomationPeer.CreatePeerForElement(itemElement) is { } peer) + + { + selectionList.Add(ProviderFromPeer(peer)); + } + } + } + } + } + } + } + } + + return selectionList.ToArray(); + } + + void RaiseSelectionChanged(double oldIndex, double newIndex) + { + if (AutomationPeer.ListenerExists(AutomationEvents.SelectionPatternOnInvalidated)) + { + if (Owner is ItemsView itemsView) + { + if (FrameworkElementAutomationPeer.CreatePeerForElement(itemsView) is { } peer) + { + peer.RaiseAutomationEvent(AutomationEvents.SelectionPatternOnInvalidated); + } + } + + } + } + + ItemsView GetImpl() + { + return Owner as ItemsView; + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewAutomationPeer.h.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewAutomationPeer.h.cs new file mode 100644 index 000000000000..6ef268509fe6 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewAutomationPeer.h.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Microsoft.UI.Xaml.Automation.Peers; + +partial class ItemsViewAutomationPeer +{ + public bool IsSelectionRequired => false; +}; + diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewInteractions.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewInteractions.cs new file mode 100644 index 000000000000..6752255e66b3 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewInteractions.cs @@ -0,0 +1,1434 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using Microsoft.UI.Input; +using Microsoft.UI.Private.Controls; +using Microsoft.UI.Xaml.Input; +using Uno.UI.Helpers.WinUI; +using Windows.Foundation; +using Windows.System; +using Windows.UI.Core; + +using static Microsoft/* UWP don't rename */.UI.Xaml.Controls._Tracing; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ItemsView +{ + #region IControlOverrides + + protected override void OnKeyDown( + KeyRoutedEventArgs args) + { + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR, METH_NAME, this, TypeLogging::KeyRoutedEventArgsToString(args).c_str()); + + base.OnKeyDown(args); + + if (args.Handled) + { + return; + } + + switch (args.Key) + { + case VirtualKey.A: + { + if (m_selector != null) + { + ItemsViewSelectionMode selectionMode = SelectionMode; + + if (selectionMode != ItemsViewSelectionMode.None && + selectionMode != ItemsViewSelectionMode.Single && + (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control) & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down) + { + m_selector.SelectAll(); + args.Handled = true; + } + } + break; + } + } + } + + #endregion + + // Returns True when the provided virtual key and navigation key are canceling each other. + bool AreNavigationKeysOpposite( + VirtualKey key1, + VirtualKey key2) + { + MUX_ASSERT(IsNavigationKey(key1)); + MUX_ASSERT(IsNavigationKey(key2)); + + return (key1 == VirtualKey.Left && key2 == VirtualKey.Right) || + (key1 == VirtualKey.Right && key2 == VirtualKey.Left) || + (key1 == VirtualKey.Up && key2 == VirtualKey.Down) || + (key1 == VirtualKey.Down && key2 == VirtualKey.Up); + } + + // Returns True when ScrollView.ComputedVerticalScrollMode is Enabled. + bool CanScrollVertically() + { + if (m_scrollView is { } scrollView) + { + return scrollView.ComputedVerticalScrollMode == ScrollingScrollMode.Enabled; + } + return false; + } + + // Returns the index of the closest focusable element to the current element following the provided direction, or -1 when no element was found. + // hasIndexBasedLayoutOrientation indicates whether the Layout has one orientation that has an index-based layout. + int GetAdjacentFocusableElementByDirection( + FocusNavigationDirection focusNavigationDirection, + bool hasIndexBasedLayoutOrientation) + { + int currentElementIndex = GetCurrentElementIndex(); + + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT_INT, METH_NAME, this, focusNavigationDirection, currentElementIndex); + + MUX_ASSERT( + focusNavigationDirection == FocusNavigationDirection.Up || + focusNavigationDirection == FocusNavigationDirection.Down || + focusNavigationDirection == FocusNavigationDirection.Left || + focusNavigationDirection == FocusNavigationDirection.Right); + + if (currentElementIndex == -1) + { + return -1; + } + + var currentElement = TryGetElement(currentElementIndex); + + if (currentElement == null) + { + bool startBringItemIntoViewSuccess = StartBringItemIntoViewInternal(true /*throwOutOfBounds*/, false /* throwOnAnyFailure */, currentElementIndex, null /* options */); + + currentElementIndex = GetCurrentElementIndex(); + + if (!startBringItemIntoViewSuccess || currentElementIndex == -1) + { + return -1; + } + + currentElement = TryGetElement(currentElementIndex); + + if (currentElement == null) + { + return -1; + } + } + + var itemsRepeater = m_itemsRepeater; + + MUX_ASSERT(itemsRepeater != null); + + var itemsSourceView = itemsRepeater.ItemsSourceView; + + MUX_ASSERT(itemsSourceView != null); + + int itemsCount = itemsSourceView.Count; + + MUX_ASSERT(itemsCount > 0); + + bool useKeyboardNavigationReferenceHorizontalOffset = + focusNavigationDirection == FocusNavigationDirection.Up || + focusNavigationDirection == FocusNavigationDirection.Down; + + MUX_ASSERT((useKeyboardNavigationReferenceHorizontalOffset && m_keyboardNavigationReferenceRect.X != -1.0f) || + (!useKeyboardNavigationReferenceHorizontalOffset && m_keyboardNavigationReferenceRect.Y != -1.0f)); + + Rect currentElementRect = GetElementRect(currentElement, itemsRepeater); + Point keyboardNavigationReferenceOffsetPoint = GetUpdatedKeyboardNavigationReferenceOffset(); + float keyboardNavigationReferenceOffset = useKeyboardNavigationReferenceHorizontalOffset ? (float)keyboardNavigationReferenceOffsetPoint.X : (float)keyboardNavigationReferenceOffsetPoint.Y; + + MUX_ASSERT(keyboardNavigationReferenceOffset != -1.0f); + + bool getPreviousFocusableElement = focusNavigationDirection == FocusNavigationDirection.Up || focusNavigationDirection == FocusNavigationDirection.Left; + bool traversalDirectionChanged = false; + int closestElementIndex = -1; + int itemIndex = getPreviousFocusableElement ? currentElementIndex - 1 : currentElementIndex + 1; + float smallestDistance = float.MaxValue; + float smallestNavigationDirectionDistance = float.MaxValue; + float smallestNoneNavigationDirectionDistance = float.MaxValue; + double roundingScaleFactor = GetRoundingScaleFactor(itemsRepeater); + + while ((getPreviousFocusableElement && itemIndex >= 0) || (!getPreviousFocusableElement && itemIndex < itemsCount)) + { + var element = itemsRepeater.TryGetElement(itemIndex); + + if (element == null) + { + if (hasIndexBasedLayoutOrientation) + { + if (smallestNoneNavigationDirectionDistance == float.MaxValue) + { + // Ran out of realized items and smallestNoneNavigationDirectionDistance still has its initial float::max value. + // Assuming no qualifying item will be found, return -1 instead of realizing more items and bringing them into view. + return -1; + } + + // Bring the still unrealized item into view to realize it. + bool startBringItemIntoViewSuccess = StartBringItemIntoViewInternal(true /* throwOutOfBounds */, false /* throwOnAnyFailure */, itemIndex, null /*options*/); + + if (!startBringItemIntoViewSuccess) + { + return -1; + } + + element = itemsRepeater.TryGetElement(itemIndex); + } + else + { + // When the Layout has no index-based orientation, all items are traversed to find the closest one. + // First a traversal from the current item to one end, and then from the current item to the other end. + if (traversalDirectionChanged) + { + // Both traversals were performed. Return the resulting closest index. + return closestElementIndex; + } + else + { + // Do the second traversal in the opposite direction. + traversalDirectionChanged = true; + getPreviousFocusableElement = !getPreviousFocusableElement; + itemIndex = getPreviousFocusableElement ? currentElementIndex - 1 : currentElementIndex + 1; + continue; + } + } + } + + MUX_ASSERT(element != null); + + if (SharedHelpers.IsFocusableElement(element)) + { + float navigationDirectionDistance = float.MaxValue; + float noneNavigationDirectionDistance = float.MaxValue; + + GetDistanceToKeyboardNavigationReferenceOffset( + focusNavigationDirection, + currentElementRect, + element, + itemsRepeater, + keyboardNavigationReferenceOffset, + roundingScaleFactor, + ref navigationDirectionDistance, + ref noneNavigationDirectionDistance); + + MUX_ASSERT(navigationDirectionDistance >= 0.0f); + MUX_ASSERT(noneNavigationDirectionDistance >= 0.0f); + + if (navigationDirectionDistance <= 1.0f / (float)roundingScaleFactor && noneNavigationDirectionDistance <= 1.0f / (float)roundingScaleFactor) + { + // Stop the search right away since an element at the target point was found. + return itemIndex; + } + else if (hasIndexBasedLayoutOrientation) + { + // When the Layout has an index-based orientation, its orthogonal orientation defines the primary (favored) distance. The index-based orientation defines a secondary distance. + if (noneNavigationDirectionDistance < smallestNoneNavigationDirectionDistance || + (noneNavigationDirectionDistance == smallestNoneNavigationDirectionDistance && navigationDirectionDistance < smallestNavigationDirectionDistance)) + { + smallestNoneNavigationDirectionDistance = noneNavigationDirectionDistance; + smallestNavigationDirectionDistance = navigationDirectionDistance; + closestElementIndex = itemIndex; + } + else if (noneNavigationDirectionDistance > smallestNoneNavigationDirectionDistance) + { + return closestElementIndex; + } + } + else + { + // When the Layout has no index-based orientation, the typical Euclidean distance is used. + float distance = MathF.Pow(navigationDirectionDistance, 2.0f) + MathF.Pow(noneNavigationDirectionDistance, 2.0f); + + if (distance < smallestDistance) + { + smallestDistance = distance; + closestElementIndex = itemIndex; + } + } + } + + itemIndex = getPreviousFocusableElement ? itemIndex - 1 : itemIndex + 1; + } + + return closestElementIndex; + } + + // Returns the index of the previous or next focusable element from the current element, or -1 when no element was found. + int GetAdjacentFocusableElementByIndex( + bool getPreviousFocusableElement) + { + int currentElementIndex = GetCurrentElementIndex(); + + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT_INT, METH_NAME, this, getPreviousFocusableElement, currentElementIndex); + + if (currentElementIndex == -1) + { + return -1; + } + + var currentElement = TryGetElement(currentElementIndex); + + if (currentElement == null) + { + // Realize the current element so its neighbors are available for evaluation. + bool startBringItemIntoViewSuccess = StartBringItemIntoViewInternal(false /*throwOutOfBounds*/, false /* throwOnAnyFailure */, currentElementIndex, null /*options*/); + + currentElementIndex = GetCurrentElementIndex(); + + if (!startBringItemIntoViewSuccess || currentElementIndex == -1 || TryGetElement(currentElementIndex) == null) + { + return -1; + } + } + + var itemsRepeater = m_itemsRepeater; + + MUX_ASSERT(itemsRepeater != null); + + var itemsSourceView = itemsRepeater.ItemsSourceView; + + MUX_ASSERT(itemsSourceView != null); + + int itemsCount = itemsSourceView.Count; + + MUX_ASSERT(itemsCount > 0); + + // Because we are dealing with an index-based layout, the search is only done in one direction. + int itemIndex = getPreviousFocusableElement ? currentElementIndex - 1 : currentElementIndex + 1; + + while ((getPreviousFocusableElement && itemIndex >= 0) || (!getPreviousFocusableElement && itemIndex < itemsCount)) + { + var element = itemsRepeater.TryGetElement(itemIndex); + + if (element == null) + { + bool startBringItemIntoViewSuccess = StartBringItemIntoViewInternal(false /*throwOutOfBounds*/, false /* throwOnAnyFailure */, itemIndex, null /*options*/); + + if (!startBringItemIntoViewSuccess) + { + return -1; + } + + element = itemsRepeater.TryGetElement(itemIndex); + } + + MUX_ASSERT(element != null); + + if (SharedHelpers.IsFocusableElement(element)) + { + return itemIndex; + } + + itemIndex = getPreviousFocusableElement ? itemIndex - 1 : itemIndex + 1; + } + + return -1; + } + + // When focusNavigationDirection is FocusNavigationDirection.Up or FocusNavigationDirection.Down, keyboardNavigationReferenceOffset indicates a vertical line + // to get the distance from 'element'. Otherwise keyboardNavigationReferenceOffset indicates a horizontal line. + void GetDistanceToKeyboardNavigationReferenceOffset( + FocusNavigationDirection focusNavigationDirection, + Rect currentElementRect, + UIElement element, + ItemsRepeater itemsRepeater, + float keyboardNavigationReferenceOffset, + double roundingScaleFactor, + ref float navigationDirectionDistance, + ref float noneNavigationDirectionDistance) + { + MUX_ASSERT(focusNavigationDirection == FocusNavigationDirection.Up || + focusNavigationDirection == FocusNavigationDirection.Down || + focusNavigationDirection == FocusNavigationDirection.Left || + focusNavigationDirection == FocusNavigationDirection.Right); + MUX_ASSERT(element != null); + MUX_ASSERT(itemsRepeater != null); + //MUX_ASSERT(navigationDirectionDistance != null); + //MUX_ASSERT(noneNavigationDirectionDistance != null); + MUX_ASSERT(keyboardNavigationReferenceOffset != -1.0f); + MUX_ASSERT(roundingScaleFactor > 0.0); + + noneNavigationDirectionDistance = navigationDirectionDistance = float.MaxValue; + + Rect elementRect = GetElementRect(element, itemsRepeater); + double roundingMargin = roundingScaleFactor <= 1.0 ? 0.5 : 2.0 / roundingScaleFactor; + + if (focusNavigationDirection == FocusNavigationDirection.Up && + elementRect.Y + elementRect.Height > currentElementRect.Y + roundingMargin) + { + // This element is disqualified because it is not placed at the top of currentElement. + return; + } + + if (focusNavigationDirection == FocusNavigationDirection.Down && + elementRect.Y + roundingMargin < currentElementRect.Y + currentElementRect.Height) + { + // This element is disqualified because it is not placed at the bottom of currentElement. + return; + } + + if (focusNavigationDirection == FocusNavigationDirection.Left && + elementRect.X + elementRect.Width > currentElementRect.X + roundingMargin) + { + // This element is disqualified because it is not placed at the left of currentElement. + return; + } + + if (focusNavigationDirection == FocusNavigationDirection.Right && + elementRect.X + roundingMargin < currentElementRect.X + currentElementRect.Width) + { + // This element is disqualified because it is not placed at the right of currentElement. + return; + } + + switch (focusNavigationDirection) + { + case FocusNavigationDirection.Up: + noneNavigationDirectionDistance = (float)(currentElementRect.Y - elementRect.Y - elementRect.Height); + break; + case FocusNavigationDirection.Down: + noneNavigationDirectionDistance = (float)(elementRect.Y - currentElementRect.Y - currentElementRect.Height); + break; + case FocusNavigationDirection.Left: + noneNavigationDirectionDistance = (float)(currentElementRect.X - elementRect.X - elementRect.Width); + break; + case FocusNavigationDirection.Right: + noneNavigationDirectionDistance = (float)(elementRect.X - currentElementRect.X - currentElementRect.Width); + break; + } + + MUX_ASSERT(noneNavigationDirectionDistance >= -roundingMargin); + + noneNavigationDirectionDistance = Math.Max(0.0f, noneNavigationDirectionDistance); + + if (focusNavigationDirection == FocusNavigationDirection.Up || focusNavigationDirection == FocusNavigationDirection.Down) + { + navigationDirectionDistance = (float)Math.Abs(elementRect.X + elementRect.Width / 2.0f - keyboardNavigationReferenceOffset); + } + else + { + navigationDirectionDistance = (float)Math.Abs(elementRect.Y + elementRect.Height / 2.0f - keyboardNavigationReferenceOffset); + } + } + + // Returns the position within the ItemsRepeater + size of the provided element as a Rect. + // The potential element.Margin is not included in the returned rectangle. + Rect GetElementRect( + UIElement element, + ItemsRepeater itemsRepeater) + { + MUX_ASSERT(element != null); + MUX_ASSERT(itemsRepeater != null); + + var generalTransform = element.TransformToVisual(itemsRepeater); + var elementOffset = generalTransform.TransformPoint(new Point(0, 0)); + + if (element is FrameworkElement elementAsFE) + { + var farSideOffset = generalTransform.TransformPoint(new Point( + (float)elementAsFE.ActualWidth, + (float)elementAsFE.ActualHeight)); + + return new Rect( + elementOffset.X, + elementOffset.Y, + farSideOffset.X - elementOffset.X, + farSideOffset.Y - elementOffset.Y); + } + else + { + return new Rect( + elementOffset.X, + elementOffset.Y, + 0.0f, + 0.0f); + } + } + + IndexBasedLayoutOrientation GetLayoutIndexBasedLayoutOrientation() + { + if (Layout is { } layout) + { + return layout.IndexBasedLayoutOrientation; + } + return IndexBasedLayoutOrientation.None; + } + + // Returns the number of most recently queued navigation keys of the same kind. + int GetTrailingNavigationKeyCount() + { + MUX_ASSERT(m_navigationKeysToProcess.Count > 0); + + VirtualKey lastNavigationKey = m_navigationKeysToProcess[m_navigationKeysToProcess.Count - 1]; + int count = 0; + for (int i = m_navigationKeysToProcess.Count - 1; i >= 0; i--) + { + if (m_navigationKeysToProcess[i] == lastNavigationKey) + { + count++; + } + else + { + break; + } + } + + MUX_ASSERT(count > 0); + + return count; + } + + // Returns the Point within the ItemsRepeater representing the reference for non-index-based orientation keyboard navigation. + // Both X and Y are relevant for Layouts that have no index-based orientation. + Point GetUpdatedKeyboardNavigationReferenceOffset() + { + if (m_keyboardNavigationReferenceIndex != -1) + { + MUX_ASSERT(m_keyboardNavigationReferenceRect.X != -1.0f); + MUX_ASSERT(m_keyboardNavigationReferenceRect.Y != -1.0f); + + if (m_itemsRepeater is { } itemsRepeater) + { + if (itemsRepeater.TryGetElement(m_keyboardNavigationReferenceIndex) is { } keyboardNavigationReferenceElement) + { + Rect keyboardNavigationReferenceRect = GetElementRect(keyboardNavigationReferenceElement, itemsRepeater); + + if (keyboardNavigationReferenceRect.X + keyboardNavigationReferenceRect.Width >= 0 && keyboardNavigationReferenceRect.Y + keyboardNavigationReferenceRect.Height >= 0) + { + if (keyboardNavigationReferenceRect != m_keyboardNavigationReferenceRect) + { + UpdateKeyboardNavigationReference(); + } + } + // Else the keyboard navigation reference element was pinned and placed out of bounds. Use the cached m_keyboardNavigationReferenceRect. + } + // Else the keyboard navigation reference element was unrealized. Use the cached m_keyboardNavigationReferenceRect. + } + } + + MUX_ASSERT((m_keyboardNavigationReferenceRect.X == -1.0f && m_keyboardNavigationReferenceIndex == -1) || (m_keyboardNavigationReferenceRect.X != -1.0f && m_keyboardNavigationReferenceIndex != -1)); + + return GetKeyboardNavigationReferenceOffset(); + } + + // Returns True when the provided incoming navigation key is canceled + // - because of input throttling + // - because it is the opposite of the last queued key. + // This method also clears all queued up keys when the incoming key is Home or End. + bool IsCancelingNavigationKey( + VirtualKey key, + bool isRepeatKey) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"key", key); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"isRepeatKey", isRepeatKey); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"queued keys size", m_navigationKeysToProcess.size()); + + MUX_ASSERT(IsNavigationKey(key)); + + if (m_navigationKeysToProcess.Count == 0) + { + return false; + } + + // Maximum number of unprocessed keystrokes of the same kind being queued in m_navigationKeysToProcess. + const int maxRepeatQueuedNavigationKeys = 3; // Do not queue more than 3 identical navigation keys with a repeat count > 1. + const int maxNonRepeatQueuedNavigationKeys = 6; // Do not queue more than 6 identical navigation keys with a repeat count = 1. + // Keystrokes without holding down the key are more likely to have a repeat count of 1 and are more liberally queued up because more intentional. + + VirtualKey lastNavigationKey = m_navigationKeysToProcess[m_navigationKeysToProcess.Count - 1]; + + if (lastNavigationKey == key) + { + // Incoming key is identical to the last queued up. + int trailingNavigationKeyCount = GetTrailingNavigationKeyCount(); + + if ((trailingNavigationKeyCount >= maxRepeatQueuedNavigationKeys && isRepeatKey) || + (trailingNavigationKeyCount >= maxNonRepeatQueuedNavigationKeys && !isRepeatKey)) + { + ///ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Navigation keys throttling."); + + // Incoming key is canceled to avoid lenghty processing after keyboard input stops. + return true; + } + } + + switch (key) + { + case VirtualKey.Home: + case VirtualKey.End: + { + // Any navigation key queued before a Home or End key can be canceled. + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Navigation keys list cleared for Home/End."); + + m_navigationKeysToProcess.Clear(); + break; + } + default: + { + if (AreNavigationKeysOpposite(key, lastNavigationKey)) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Incoming key and last queued navigation key canceled."); + + // The incoming key and last queued navigation key are canceling each other and neither need to be processed. + m_navigationKeysToProcess.RemoveAt(m_navigationKeysToProcess.Count - 1); + return true; + } + break; + } + } + + return false; + } + + bool IsLayoutOrientationIndexBased(bool horizontal) + { + IndexBasedLayoutOrientation indexBasedLayoutOrientation = GetLayoutIndexBasedLayoutOrientation(); + + return (horizontal && indexBasedLayoutOrientation == IndexBasedLayoutOrientation.LeftToRight) || + (!horizontal && indexBasedLayoutOrientation == IndexBasedLayoutOrientation.TopToBottom); + } + + bool IsNavigationKey( + VirtualKey key) + { + switch (key) + { + case VirtualKey.Home: + case VirtualKey.End: + case VirtualKey.Left: + case VirtualKey.Right: + case VirtualKey.Up: + case VirtualKey.Down: + case VirtualKey.PageUp: + case VirtualKey.PageDown: + return true; + } + + return false; + } + + void OnItemsViewElementGettingFocus( + UIElement element, + GettingFocusEventArgs args) + { + var focusState = args.FocusState; + + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, TypeLogging::FocusStateToString(focusState).c_str()); + + if (focusState == FocusState.Keyboard) + { + int elementIndex = GetElementIndex(element); + int currentElementIndex = GetCurrentElementIndex(); + bool focusMovingIntoItemsRepeater = true; + + // Check if the focus comes from an element outside the scope of the inner ItemsRepeater. + if (m_itemsRepeater is { } itemsRepeater) + { + var oldFocusedElement = args.OldFocusedElement; + + if (oldFocusedElement is not null && SharedHelpers.IsAncestor(oldFocusedElement, itemsRepeater, false /*checkVisibility*/)) + { + focusMovingIntoItemsRepeater = false; + } + } + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"ElementIndex", elementIndex); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"CurrentElementIndex", currentElementIndex); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"FocusMovingIntoItemsRepeater", focusMovingIntoItemsRepeater); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"Direction", static_cast(args.Direction())); + + if (currentElementIndex != elementIndex && focusMovingIntoItemsRepeater) + { + if (currentElementIndex == -1) + { + var direction = args.Direction; + + if (direction == FocusNavigationDirection.Previous || direction == FocusNavigationDirection.Next) + { + // Tabbing (direction == FocusNavigationDirection.Next) or Shift-Tabbing (direction == FocusNavigationDirection.Previous) into + // the ItemsRepeater while there is no current element. + + int focusableItemIndex = -1; + + // When these conditions are fulfilled, set the selected item as the current one. + // - TabNavigation is Once + // - SelectionMode is Single + // - an item is selected and is focusable + if (TabNavigation == KeyboardNavigationMode.Once && + SelectionMode == ItemsViewSelectionMode.Single) + { + var selectedItem = m_selectionModel.SelectedItem as UIElement; + + if (selectedItem is not null && SharedHelpers.IsFocusableElement(selectedItem)) + { + IndexPath selectedItemIndexPath = m_selectionModel.SelectedIndex; + MUX_ASSERT(selectedItemIndexPath != null && selectedItemIndexPath.GetSize() == 1); + + focusableItemIndex = selectedItemIndexPath.GetAt(0); + MUX_ASSERT(focusableItemIndex != -1); + } + } + + if (focusableItemIndex == -1) + { + // Retrieve the focusable index on the top/left corner for Tabbing, or bottom/right corner for Shift-Tabbing. + focusableItemIndex = GetCornerFocusableItem(direction == FocusNavigationDirection.Next /*isForTopLeftItem*/); + MUX_ASSERT(focusableItemIndex != -1); + } + + // Set that index as the current one. + SetCurrentElementIndex(focusableItemIndex, FocusState.Unfocused, false /*forceKeyboardNavigationReferenceReset*/); + MUX_ASSERT(focusableItemIndex == GetCurrentElementIndex()); + + // Allow TrySetNewFocusedElement to be called below for that new current element. + currentElementIndex = focusableItemIndex; + } + } + + if (currentElementIndex != -1) + { + // The ItemsView has a current element other than the one receiving focus, and focus is moving into the ItemsRepeater. + var currentElement = TryGetElement(currentElementIndex); + + if (currentElement == null) + { + // Realize the current element. + bool startBringItemIntoViewSuccess = StartBringItemIntoViewInternal(true /* throwOutOfBounds*/, false /* throwOnAnyFailure */, currentElementIndex, null /*options*/); + + currentElementIndex = GetCurrentElementIndex(); + + if (startBringItemIntoViewSuccess && currentElementIndex != -1) + { + currentElement = TryGetElement(currentElementIndex); + } + } + + if (currentElement != null) + { + // Redirect focus to the current element. + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"Redirecting focus to CurrentElementIndex", currentElementIndex); + + bool success = args.TrySetNewFocusedElement(currentElement); + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"TrySetNewFocusedElement result", success); + + return; + } + } + } + + if (currentElementIndex != elementIndex) + { + SetCurrentElementIndex(elementIndex, FocusState.Unfocused, false /*forceKeyboardNavigationReferenceReset*/); + } + + // Selection is not updated when focus moves into the ItemsRepeater. + if (m_selector != null && !focusMovingIntoItemsRepeater) + { + bool isCtrlDown = (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control) & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down; + bool isShiftDown = (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift) & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down; + + try + { + m_isProcessingInteraction = true; + + bool isIndexPathValid = false; + IndexPath indexPath = GetElementIndexPath(element, ref isIndexPathValid); + + if (isIndexPathValid) + { + m_selector.OnFocusedAction(indexPath, isCtrlDown, isShiftDown); + } + } + finally + { + m_isProcessingInteraction = false; + } + } + } + } + + // Process the Home/End, arrows and page navigation keystrokes before the inner ScrollView gets a chance to do it by simply scrolling. + void OnItemsViewElementKeyDown( + + object sender, + KeyRoutedEventArgs args) + { + var element = sender as UIElement; + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"sender element index", element? GetElementIndex(element) : -1); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"current element index", GetCurrentElementIndex()); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"key", args.Key()); + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"key repeat count", args.KeyStatus().RepeatCount); + + MUX_ASSERT(!args.Handled); + + var key = args.Key; + + if (!IsNavigationKey(key)) + { + return; + } + + if (IsCancelingNavigationKey(key, args.KeyStatus.RepeatCount > 1 /*isRepeatKey*/)) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Key canceled."); + + // Mark the event as handled to prevent the outer ScrollView from processing it. + args.Handled = true; + return; + } + + QueueNavigationKey(key); + + if (m_navigationKeyBringIntoViewPendingCount == 0 && m_navigationKeyBringIntoViewCorrelationId == -1 && m_navigationKeyProcessingCountdown == 0) + { + // No OnScrollViewBringingIntoView call is pending and there is no count down to a stable layout, so process the key right away. + if (ProcessNavigationKeys()) + { + args.Handled = true; + } + } + + else + { +#if DEBUG + if (m_navigationKeyBringIntoViewPendingCount > 0) + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Key processing delayed until bring-into-view started & completed."); + } + else + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Key processing delayed until bring-into-view completed."); + } +#endif + // Even though the key will be processed asynchronously, it is marked as handled to prevent the parents, like the ScrollView, from processing it. + args.Handled = true; + } + } + +#if DEBUG + void OnItemsViewElementLosingFocusDbg( + UIElement element, + LosingFocusEventArgs args) + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_INT, METH_NAME, this, GetElementIndex(element)); + } +#endif + + void OnItemsViewItemContainerItemInvoked( + ItemContainer itemContainer, + ItemContainerInvokedEventArgs args) + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, TypeLogging::ItemContainerInteractionTriggerToString(args.InteractionTrigger()).c_str(), GetElementIndex(itemContainer)); + + var interactionTrigger = args.InteractionTrigger; + bool handled = args.Handled; + + switch (interactionTrigger) + { + case ItemContainerInteractionTrigger.PointerReleased: + { + handled |= ProcessInteraction(itemContainer, FocusState.Pointer); + break; + } + + case ItemContainerInteractionTrigger.EnterKey: + case ItemContainerInteractionTrigger.SpaceKey: + { + handled |= ProcessInteraction(itemContainer, FocusState.Keyboard); + break; + } + + case ItemContainerInteractionTrigger.Tap: + case ItemContainerInteractionTrigger.DoubleTap: + case ItemContainerInteractionTrigger.AutomationInvoke: + { + break; + } + + default: + { + return; + } + } + + if (!args.Handled && + interactionTrigger != ItemContainerInteractionTrigger.PointerReleased && + CanRaiseItemInvoked(interactionTrigger, itemContainer)) + { + MUX_ASSERT(GetElementIndex(itemContainer) != -1); + + RaiseItemInvoked(itemContainer); + } + + args.Handled = handled; + } + +#if DEBUG + void OnItemsViewItemContainerSizeChangedDbg( + object sender, + SizeChangedEventArgs args) + { + var element = sender as UIElement; + + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"ItemContainer index:", GetElementIndex(element)); + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_FLT_FLT, METH_NAME, this, args.PreviousSize().Width, args.PreviousSize().Height); + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_FLT_FLT, METH_NAME, this, args.NewSize().Width, args.NewSize().Height); + } +#endif + + bool ProcessInteraction( + UIElement element, + FocusState focusState) + { + MUX_ASSERT(element != null); + + int elementIndex = GetElementIndex(element); + + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, TypeLogging::FocusStateToString(focusState).c_str(), elementIndex); + + MUX_ASSERT(elementIndex >= 0); + + // When the focusState is Pointer, the element not only gets focus but is also brought into view by SetFocusElementIndex's StartBringIntoView call. + bool handled = SetCurrentElementIndex(elementIndex, focusState, true /*forceKeyboardNavigationReferenceReset*/, focusState == FocusState.Pointer /*startBringIntoView*/); + + if (m_selector != null) + { + bool isCtrlDown = (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control) & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down; + bool isShiftDown = (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift) & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down; + + try + { + m_isProcessingInteraction = true; + + bool isIndexPathValid = false; + IndexPath indexPath = GetElementIndexPath(element, ref isIndexPathValid); + + if (isIndexPathValid) + { + m_selector.OnInteractedAction(indexPath, isCtrlDown, isShiftDown); + } + } + finally + { + m_isProcessingInteraction = false; + } + } + + return handled; + } + + // Processes the queued up navigation keys while there is no pending bring-into-view operation which needs to settle + // before any subsequent processing, so items are realized, layed out and ready for identification of the target item. + bool ProcessNavigationKeys() + { + MUX_ASSERT(m_navigationKeyBringIntoViewPendingCount == 0); + MUX_ASSERT(m_navigationKeyBringIntoViewCorrelationId == -1); + MUX_ASSERT(m_navigationKeyProcessingCountdown == 0); + + while (m_navigationKeysToProcess.Count > 0 && + m_navigationKeyBringIntoViewPendingCount == 0 && + m_navigationKeyBringIntoViewCorrelationId == -1 && + m_navigationKeyProcessingCountdown == 0) + { + VirtualKey navigationKey = m_navigationKeysToProcess[0]; + + m_lastNavigationKeyProcessed = navigationKey; + m_navigationKeysToProcess.RemoveAt(0); + +#if DEBUG + switch (navigationKey) + { + case VirtualKey.Left: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Left key dequeued."); + break; + } + case VirtualKey.Right: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Right key dequeued."); + break; + } + case VirtualKey.Up: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Up key dequeued."); + break; + } + case VirtualKey.Down: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Down key dequeued."); + break; + } + case VirtualKey.PageUp: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"PageUp key dequeued."); + break; + } + case VirtualKey.PageDown: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"PageDown key dequeued."); + break; + } + case VirtualKey.Home: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Home key dequeued."); + break; + } + case VirtualKey.End: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"End key dequeued."); + break; + } + } +#endif + + bool forceKeyboardNavigationReferenceReset = false; + int newCurrentElementIndexToFocus = -1; + + switch (navigationKey) + { + case VirtualKey.Home: + case VirtualKey.End: + { + if (m_itemsRepeater is { } itemsRepeater) + { + var itemsSourceView = itemsRepeater.ItemsSourceView; + + if (itemsSourceView == null) + { + return false; + } + + int itemsCount = itemsSourceView.Count; + + if (itemsCount == 0) + { + return false; + } + + bool targetIsFirstItem = navigationKey == VirtualKey.Home; + int itemIndex = targetIsFirstItem ? 0 : itemsCount - 1; + BringIntoViewOptions options = new(); + + // When processing the Home key, the top/left corner of the first focusable element is aligned to the top/left corner of the viewport. + // When processing the End key, the bottom/right corner of the last focusable element is aligned to the bottom/right corner of the viewport. + options.HorizontalAlignmentRatio = targetIsFirstItem ? 0.0 : 1.0; + options.VerticalAlignmentRatio = targetIsFirstItem ? 0.0 : 1.0; + + bool startBringItemIntoViewSuccess = StartBringItemIntoViewInternal(false /*throwOutOfBounds*/, false /* throwOnAnyFailure */, itemIndex, options); + + if (!startBringItemIntoViewSuccess) + { + return false; + } + + // Now that the target item is realized and moving into view, check if it needs to take keyboard focus. + if (Layout is { } layout) + { + var focusableObject = targetIsFirstItem ? FocusManager.FindFirstFocusableElement(itemsRepeater) : FocusManager.FindLastFocusableElement(itemsRepeater); + + if (focusableObject is UIElement focusableElement) + { + int index = GetElementIndex(focusableElement); + + MUX_ASSERT(index != -1); + + if (index != GetCurrentElementIndex()) + { + forceKeyboardNavigationReferenceReset = true; + newCurrentElementIndexToFocus = index; + } + } + } + } + break; + } + case VirtualKey.Left: + case VirtualKey.Right: + case VirtualKey.Up: + case VirtualKey.Down: + { + bool isLayoutOrientationIndexBased = IsLayoutOrientationIndexBased(navigationKey == VirtualKey.Left || navigationKey == VirtualKey.Right /*horizontal*/); + bool isRightToLeftDirection = FlowDirection == FlowDirection.RightToLeft; + + if (isLayoutOrientationIndexBased) + { + bool getPreviousFocusableElement = + (navigationKey == VirtualKey.Left && !isRightToLeftDirection) || + (navigationKey == VirtualKey.Right && isRightToLeftDirection) || + navigationKey == VirtualKey.Up; + int index = GetAdjacentFocusableElementByIndex(getPreviousFocusableElement); + + if (index != -1 && index != GetCurrentElementIndex()) + { + forceKeyboardNavigationReferenceReset = true; + newCurrentElementIndexToFocus = index; + } + } + else + { + bool hasIndexBasedLayoutOrientation = GetLayoutIndexBasedLayoutOrientation() != IndexBasedLayoutOrientation.None; + FocusNavigationDirection focusNavigationDirection = default; + + switch (navigationKey) + { + case VirtualKey.Left: + focusNavigationDirection = isRightToLeftDirection ? FocusNavigationDirection.Right : FocusNavigationDirection.Left; + break; + case VirtualKey.Right: + focusNavigationDirection = isRightToLeftDirection ? FocusNavigationDirection.Left : FocusNavigationDirection.Right; + break; + case VirtualKey.Up: + focusNavigationDirection = FocusNavigationDirection.Up; + break; + case VirtualKey.Down: + focusNavigationDirection = FocusNavigationDirection.Down; + break; + } + + int index = GetAdjacentFocusableElementByDirection(focusNavigationDirection, hasIndexBasedLayoutOrientation); + + if (index != -1 && index != GetCurrentElementIndex()) + { + if (!hasIndexBasedLayoutOrientation) + { + forceKeyboardNavigationReferenceReset = true; + } + + newCurrentElementIndexToFocus = index; + } + } + break; + } + case VirtualKey.PageUp: + case VirtualKey.PageDown: + { + IndexBasedLayoutOrientation indexBasedLayoutOrientation = GetLayoutIndexBasedLayoutOrientation(); + bool isHorizontalDistanceFavored = indexBasedLayoutOrientation == IndexBasedLayoutOrientation.TopToBottom; + bool isVerticalDistanceFavored = indexBasedLayoutOrientation == IndexBasedLayoutOrientation.LeftToRight; + bool isForPageUp = navigationKey == VirtualKey.PageUp; + bool isPageNavigationRailed = true; // Keeping this variable for now. It could be set to an ItemsView.IsPageNavigationRailed() property to opt out of railing. + bool useKeyboardNavigationReferenceHorizontalOffset = false; + bool useKeyboardNavigationReferenceVerticalOffset = false; + double horizontalViewportRatio = default, verticalViewportRatio = default; + + // First phase: Check if target element is on the current page. + if (isVerticalDistanceFavored || CanScrollVertically()) + { + if (isPageNavigationRailed) + { + useKeyboardNavigationReferenceHorizontalOffset = true; + } + else + { + horizontalViewportRatio = isForPageUp ? -double.MaxValue : double.MaxValue; + } + + verticalViewportRatio = isForPageUp ? 0.0 : 1.0; + } + else if (isHorizontalDistanceFavored) + { + horizontalViewportRatio = isForPageUp ? 0.0 : 1.0; + + if (isPageNavigationRailed) + { + useKeyboardNavigationReferenceVerticalOffset = true; + } + else + { + verticalViewportRatio = isForPageUp ? -double.MaxValue : double.MaxValue; + } + } + + MUX_ASSERT(!isPageNavigationRailed || horizontalViewportRatio == 0.0 || verticalViewportRatio == 0.0); + MUX_ASSERT(!useKeyboardNavigationReferenceHorizontalOffset || !useKeyboardNavigationReferenceVerticalOffset); + + int index = GetItemInternal( + horizontalViewportRatio, + verticalViewportRatio, + isHorizontalDistanceFavored, + isVerticalDistanceFavored, + useKeyboardNavigationReferenceHorizontalOffset, + useKeyboardNavigationReferenceVerticalOffset, + true /*capItemEdgesToViewportRatioEdges*/, + true /*forFocusableItemsOnly*/); + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, isForPageUp ? L"PageUp - phase 1" : L"PageDown - phase 1", index); + + if (index != -1) + { + if (!isPageNavigationRailed || indexBasedLayoutOrientation == IndexBasedLayoutOrientation.None) + { + forceKeyboardNavigationReferenceReset = true; + } + + if (index != GetCurrentElementIndex()) + { + // Target element is on the current page. + newCurrentElementIndexToFocus = index; + } + else + { + // Find target on the neighboring page. + if (isVerticalDistanceFavored || CanScrollVertically()) + { + verticalViewportRatio = isForPageUp ? -1.0 : 2.0; + } + else if (isHorizontalDistanceFavored) + { + horizontalViewportRatio = isForPageUp ? -1.0 : 2.0; + } + + MUX_ASSERT(!isPageNavigationRailed || horizontalViewportRatio == 0.0 || verticalViewportRatio == 0.0); + + index = GetItemInternal( + horizontalViewportRatio, + verticalViewportRatio, + isHorizontalDistanceFavored, + isVerticalDistanceFavored, + useKeyboardNavigationReferenceHorizontalOffset, + useKeyboardNavigationReferenceVerticalOffset, + true /*capItemEdgesToViewportRatioEdges*/, + true /*forFocusableItemsOnly*/); + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, isForPageUp ? L"PageUp - phase 2" : L"PageDown - phase 2", index); + + if (index != -1) + { + if (index != GetCurrentElementIndex()) + { + // Found target element on neighboring page. + newCurrentElementIndexToFocus = index; + } + else if (isPageNavigationRailed) + { + MUX_ASSERT(useKeyboardNavigationReferenceHorizontalOffset || useKeyboardNavigationReferenceVerticalOffset); + + // Beginning or end of items reached while railing is turned on. Turn it off and try again. + if (isVerticalDistanceFavored || CanScrollVertically()) + { + horizontalViewportRatio = isForPageUp ? -double.MaxValue : double.MaxValue; + verticalViewportRatio = isForPageUp ? 0.0 : 1.0; + } + else if (isHorizontalDistanceFavored) + { + horizontalViewportRatio = isForPageUp ? 0.0 : 1.0; + verticalViewportRatio = isForPageUp ? -double.MaxValue : double.MaxValue; + } + + index = GetItemInternal( + horizontalViewportRatio, + verticalViewportRatio, + isHorizontalDistanceFavored, + isVerticalDistanceFavored, + false /*useKeyboardNavigationReferenceHorizontalOffset*/, + false /*useKeyboardNavigationReferenceVerticalOffset*/, + true /*capItemEdgesToViewportRatioEdges*/, + true /*forFocusableItemsOnly*/); + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, isForPageUp ? L"PageUp - phase 3" : L"PageDown - phase 3", index); + + if (index != -1 && index != GetCurrentElementIndex()) + { + // Target element is first or last focusable element. + forceKeyboardNavigationReferenceReset = true; + newCurrentElementIndexToFocus = index; + } + } + } + } + } + break; + } + } + + if (newCurrentElementIndexToFocus != -1) + { + SetCurrentElementIndex(newCurrentElementIndexToFocus, FocusState.Keyboard, forceKeyboardNavigationReferenceReset, false /*startBringIntoView*/, true /*expectBringIntoView*/); + + if (m_navigationKeysToProcess.Count == 0) + { + return true; + } + } + } + + return false; + } + + // Queues the incoming navigation key for future processing in ProcessNavigationKeys. + void QueueNavigationKey( + + VirtualKey key) + { + MUX_ASSERT(IsNavigationKey(key)); + +#if DEBUG + switch (key) + { + case VirtualKey.Home: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Home key queued."); + break; + } + case VirtualKey.End: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"End key queued."); + break; + } + case VirtualKey.Left: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Left key queued."); + break; + } + case VirtualKey.Right: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Right key queued."); + break; + } + case VirtualKey.Up: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Up key queued."); + break; + } + case VirtualKey.Down: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"Down key queued."); + break; + } + case VirtualKey.PageUp: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"PageUp key queued."); + break; + } + case VirtualKey.PageDown: + { + //ITEMSVIEW_TRACE_INFO(*this, TRACE_MSG_METH_STR, METH_NAME, this, L"PageDown key queued."); + break; + } + } +#endif + + m_navigationKeysToProcess.Add(key); + } + + bool SetFocusElementIndex( + int index, + FocusState focusState, + bool startBringIntoView = false, + bool expectBringIntoView = false) + { + MUX_ASSERT(!startBringIntoView || !expectBringIntoView); + + if (index != -1 && focusState != FocusState.Unfocused) + { + if (TryGetElement(index) is { } element) + { + bool success = CppWinRTHelpers.SetFocus(element, focusState); + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT_INT, METH_NAME, this, L"index", index, success); + + if (success) + { + if (m_scrollView is { } scrollView) + { + if (expectBringIntoView) + { + m_navigationKeyBringIntoViewPendingCount++; + + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, L"m_navigationKeyBringIntoViewPendingCount incremented", m_navigationKeyBringIntoViewPendingCount); + } + else if (startBringIntoView) + { + //ITEMSVIEW_TRACE_INFO_DBG(*this, TRACE_MSG_METH_METH, METH_NAME, this, L"UIElement::StartBringIntoView"); + + element.StartBringIntoView(); + } + } + } + + return success; + } + } + + return false; + } + + // Updates the values of m_keyboardNavigationReferenceIndex and m_keyboardNavigationReferenceRect based on the current element. + void UpdateKeyboardNavigationReference() + { + int currentElementIndex = GetCurrentElementIndex(); + + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, currentElementIndex); + + Point oldKeyboardNavigationReferenceOffset = GetKeyboardNavigationReferenceOffset(); + + m_keyboardNavigationReferenceIndex = currentElementIndex; + + if (currentElementIndex != -1) + { + if (m_itemsRepeater is { } itemsRepeater) + { + if (TryGetElement(currentElementIndex) is { } currentElement) + { + var generalTransform = currentElement.TransformToVisual(itemsRepeater); + var currentElementOffset = generalTransform.TransformPoint(new Point(0, 0)); + + if (currentElement is FrameworkElement currentElementAsFE) + { + m_keyboardNavigationReferenceRect = new Rect( + currentElementOffset.X, + currentElementOffset.Y, + (float)currentElementAsFE.ActualWidth, + (float)currentElementAsFE.ActualHeight); + } + + else + { + m_keyboardNavigationReferenceRect = new Rect( + currentElementOffset.X, + currentElementOffset.Y, + 0.0f, + 0.0f); + } + } + else + { + m_keyboardNavigationReferenceIndex = -1; + m_keyboardNavigationReferenceRect = new Rect(-1.0f, -1.0f, -1.0f, -1.0f); + } + } + + else + { + m_keyboardNavigationReferenceIndex = -1; + m_keyboardNavigationReferenceRect = new Rect(-1.0f, -1.0f, -1.0f, -1.0f); + } + } + else + { + m_keyboardNavigationReferenceRect = new Rect(-1.0f, -1.0f, -1.0f, -1.0f); + } + + //ITEMSVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_STR_INT, METH_NAME, this, TypeLogging::RectToString(m_keyboardNavigationReferenceRect).c_str(), m_keyboardNavigationReferenceIndex); + + ItemsViewTestHooks globalTestHooks = ItemsViewTestHooks.GetGlobalTestHooks(); + + if (globalTestHooks != null) + { + Point newKeyboardNavigationReferenceOffset = GetKeyboardNavigationReferenceOffset(); + IndexBasedLayoutOrientation indexBasedLayoutOrientation = GetLayoutIndexBasedLayoutOrientation(); + + if ((oldKeyboardNavigationReferenceOffset.X != newKeyboardNavigationReferenceOffset.X && (indexBasedLayoutOrientation == IndexBasedLayoutOrientation.LeftToRight || indexBasedLayoutOrientation == IndexBasedLayoutOrientation.None)) || + (oldKeyboardNavigationReferenceOffset.Y != newKeyboardNavigationReferenceOffset.Y && (indexBasedLayoutOrientation == IndexBasedLayoutOrientation.TopToBottom || indexBasedLayoutOrientation == IndexBasedLayoutOrientation.None))) + { + ItemsViewTestHooks.NotifyKeyboardNavigationReferenceOffsetChanged(this); + } + } + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewItemInvokedEventArgs.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewItemInvokedEventArgs.cs new file mode 100644 index 000000000000..16f5d00650be --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewItemInvokedEventArgs.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Microsoft.UI.Xaml.Controls; + +partial class ItemsViewItemInvokedEventArgs +{ + internal ItemsViewItemInvokedEventArgs(object invokedItem) + { + //ITEMSVIEW_TRACE_VERBOSE(nullptr, TRACE_MSG_METH_PTR, METH_NAME, this, invokedItem); + + InvokedItem = invokedItem; + } + + #region IItemsViewItemInvokedEventArgs + object InvokedItem { get; } + #endregion +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewItemInvokedEventArgs.h.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewItemInvokedEventArgs.h.cs new file mode 100644 index 000000000000..c4edaacd03ce --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewItemInvokedEventArgs.h.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Microsoft.UI.Xaml.Controls; + +partial class ItemsViewItemInvokedEventArgs +{ + //~ItemsViewItemInvokedEventArgs() + //{ + // ITEMSVIEW_TRACE_VERBOSE(nullptr, TRACE_MSG_METH, METH_NAME, this); + //} +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewTestHooks.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewTestHooks.cs new file mode 100644 index 000000000000..76aa31a04af3 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsViewTestHooks.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Microsoft/* UWP don't rename */.UI.Xaml.Controls; +using Windows.Foundation; +#if !HAS_UNO_WINUI +using Windows.UI.Xaml.Controls; +#endif + +namespace Microsoft.UI.Private.Controls; + +partial class ItemsViewTestHooks +{ + private static ItemsViewTestHooks s_testHooks; + + public static ItemsViewTestHooks GetGlobalTestHooks() + { + return s_testHooks; + } + + static ItemsViewTestHooks EnsureGlobalTestHooks() + { + s_testHooks ??= new(); + return s_testHooks; + } + + internal static Point GetKeyboardNavigationReferenceOffset(ItemsView itemsView) + { + if (itemsView is not null) + { + return itemsView.GetKeyboardNavigationReferenceOffset(); + } + else + { + return new Point(-1.0f, -1.0f); + } + } + + internal static void NotifyKeyboardNavigationReferenceOffsetChanged(ItemsView itemsView) + { + var hooks = EnsureGlobalTestHooks(); + KeyboardNavigationReferenceOffsetChanged?.Invoke(itemsView, null); + } + + static event TypedEventHandler KeyboardNavigationReferenceOffsetChanged; + + internal static ScrollView GetScrollViewPart(ItemsView itemsView) + { + if (itemsView is not null) + { + return itemsView.GetScrollViewPart(); + } + + return null; + } + + internal static ItemsRepeater GetItemsRepeaterPart(ItemsView itemsView) + { + if (itemsView is not null) + { + return itemsView.GetItemsRepeaterPart(); + } + + return null; + } + + internal static SelectionModel GetSelectionModel(ItemsView itemsView) + { + if (itemsView is not null) + { + return itemsView.GetSelectionModel(); + } + + return null; + } + +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView_themeresources.xaml b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView_themeresources.xaml new file mode 100644 index 000000000000..a9381d8d76a4 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/ItemsView_themeresources.xaml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/MultipleSelector.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/MultipleSelector.cs new file mode 100644 index 000000000000..b9b77942ccfa --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/MultipleSelector.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class MultipleSelector : SelectorBase +{ + public MultipleSelector() + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + } + + //~MultipleSelector() + //{ + // ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + //} + + public override void OnInteractedAction(IndexPath index, bool ctrl, bool shift) + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH_INT_INT, METH_NAME, this, ctrl, shift); + + var selectionModel = GetSelectionModel(); + if (shift) + { + var anchorIndex = selectionModel.AnchorIndex; + if (anchorIndex is not null) + { + bool? isAnchorSelectedNullable = selectionModel.IsSelectedAt(anchorIndex); + bool isAnchorSelected = false; + if (isAnchorSelectedNullable != null) + { + isAnchorSelected = isAnchorSelectedNullable.Value; + } + + bool? isIndexSelectedNullable = selectionModel.IsSelectedAt(index); + bool isIndexSelected = false; + if (isIndexSelectedNullable != null) + { + isIndexSelected = isIndexSelectedNullable.Value; + } + + if (isAnchorSelected != isIndexSelected) + { + if (isAnchorSelected) + { + selectionModel.SelectRangeFromAnchorTo(index); + } + else + { + selectionModel.DeselectRangeFromAnchorTo(index); + } + } + } + } + else if (IsSelected(index)) + { + selectionModel.DeselectAt(index); + } + else + { + selectionModel.SelectAt(index); + } + } + + public override void OnFocusedAction(IndexPath index, bool ctrl, bool shift) + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH_INT_INT, METH_NAME, this, ctrl, shift); + + if (shift) + { + var selectionModel = GetSelectionModel(); + var anchorIndex = selectionModel.AnchorIndex; + if (anchorIndex is not null) + { + bool? isAnchorSelectedNullable = selectionModel.IsSelectedAt(anchorIndex); + bool isAnchorSelected = false; + if (isAnchorSelectedNullable != null) + { + isAnchorSelected = isAnchorSelectedNullable.Value; + } + + if (isAnchorSelected) + { + selectionModel.SelectRangeFromAnchorTo(index); + } + else + { + selectionModel.DeselectRangeFromAnchorTo(index); + } + } + } + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/NullSelector.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/NullSelector.cs new file mode 100644 index 000000000000..bca132d5766e --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/NullSelector.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class NullSelector : SelectorBase +{ + +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SelectorBase.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SelectorBase.cs new file mode 100644 index 000000000000..6450712e003b --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SelectorBase.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using static Microsoft/* UWP don't rename */.UI.Xaml.Controls._Tracing; + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class SelectorBase +{ + public SelectorBase() + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + } + + //~SelectorBase() + //{ + // ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + //} + + public void SetSelectionModel(SelectionModel selectionModel) + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH_PTR, METH_NAME, this, selectionModel); + m_selectionModel = selectionModel; + } + + public void DeselectWithAnchorPreservation(int index) + { + MUX_ASSERT(index != -1); + + if (m_selectionModel != null) + { + var anchorIndexPath = m_selectionModel.AnchorIndex; + + MUX_ASSERT(anchorIndexPath == null || anchorIndexPath.GetSize() == 1); + + int anchorIndex = anchorIndexPath == null ? -1 : anchorIndexPath.GetAt(0); + + m_selectionModel.Deselect(index); + + if (anchorIndex != -1) + { + m_selectionModel.SetAnchorIndex(anchorIndex); + } + } + } + + protected bool IsSelected(IndexPath index) + { + bool isSelected = false; + + if (m_selectionModel != null) + { + bool? isSelectedNullable = m_selectionModel.IsSelectedAt(index); + + if (isSelectedNullable != null) + { + isSelected = isSelectedNullable.Value; + } + } + + return isSelected; + } + + protected virtual bool CanSelect(IndexPath index) + { + return m_selectionModel != null; + } + + public virtual void SelectAll() + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + + if (m_selectionModel != null) + { + m_selectionModel.SelectAll(); + } + } + + public virtual void Clear() + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + + if (m_selectionModel != null) + { + m_selectionModel.ClearSelection(); + } + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SelectorBase.h.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SelectorBase.h.cs new file mode 100644 index 000000000000..8e00c38cf402 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SelectorBase.h.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#if !HAS_UNO_WINUI +using Microsoft/* UWP don't rename */.UI.Xaml.Controls; +#endif + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class SelectorBase +{ + public virtual void OnInteractedAction(IndexPath index, bool ctrl, bool shift) { } + public virtual void OnFocusedAction(IndexPath index, bool ctrl, bool shift) { } + + protected SelectionModel GetSelectionModel() + { + return m_selectionModel; + } + + private SelectionModel m_selectionModel; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SingleSelector.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SingleSelector.cs new file mode 100644 index 000000000000..9f9730d1dbd0 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SingleSelector.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#if !HAS_UNO_WINUI +using Microsoft/* UWP don't rename */.UI.Xaml.Controls; +#endif + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class SingleSelector : SelectorBase +{ + public SingleSelector() + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + } + + //~SingleSelector() + //{ + // ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH, METH_NAME, this); + //} + + public void FollowFocus(bool followFocus) + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH_INT, METH_NAME, this, followFocus); + + m_followFocus = followFocus; + } + + public override void OnInteractedAction(IndexPath index, bool ctrl, bool shift) + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH_INT_INT, METH_NAME, this, ctrl, shift); + + var selectionModel = GetSelectionModel(); + selectionModel.SingleSelect = true; + + if (!ctrl) + { + selectionModel.SelectAt(index); + } + else if (!IsSelected(index)) + { + selectionModel.SelectAt(index); + } + else + { + selectionModel.DeselectAt(index); + } + } + + public override void OnFocusedAction(IndexPath index, bool ctrl, bool shift) + { + //ITEMSVIEW_TRACE_VERBOSE(null, TRACE_MSG_METH_INT_INT, METH_NAME, this, ctrl, shift); + + var selectionModel = GetSelectionModel(); + selectionModel.SingleSelect = true; + + if (!ctrl && m_followFocus) + { + selectionModel.SelectAt(index); + } + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SingleSelector.h.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SingleSelector.h.cs new file mode 100644 index 000000000000..7001e9cdab6b --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ItemsView/SingleSelector.h.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Microsoft.UI.Xaml.Controls; + +internal partial class SingleSelector : SelectorBase +{ + private bool m_followFocus = true; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/PointerInfo.h.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/PointerInfo.h.cs new file mode 100644 index 000000000000..acbdffa48b63 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/PointerInfo.h.cs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using static Microsoft/* UWP don't rename */.UI.Xaml.Controls._Tracing; + +namespace Microsoft.UI.Xaml.Controls; + +internal enum PointerOverStatus +{ + None = 0x00, + Touch = 0x01, + Pen = 0x02, + Mouse = 0x04 +} + +internal enum PointerPressedStatus +{ + None = 0x00, + LeftMouseButton = 0x01, + RightMouseButton = 0x02, + Pointer = 0x04 +} + + +internal class PointerInfo +{ + /* Use this constructor if pointer capturing is added through + CapturedPointer/SetCapturedPointer/m_capturedPointer members below. + public PointerInfo(const ITrackerHandleManager* owner) : m_owner(owner) + { + } + */ + + public PointerInfo() + { + } + + //~PointerInfo() + //{ + //} + + public bool IsPointerOver() + { + return m_pointerOverStatus != PointerOverStatus.None; + } + + public bool IsTouchPointerOver() + { + return (m_pointerOverStatus & PointerOverStatus.Touch) != 0; + } + + public bool IsPenPointerOver() + { + return (m_pointerOverStatus & PointerOverStatus.Pen) != 0; + } + + public bool IsMousePointerOver() + { + return (m_pointerOverStatus & PointerOverStatus.Mouse) != 0; + } + + public void SetIsTouchPointerOver() + { + m_pointerOverStatus |= PointerOverStatus.Touch; + } + + public void ResetIsTouchPointerOver() + { + m_pointerOverStatus &= ~PointerOverStatus.Touch; + } + + public void SetIsPenPointerOver() + { + m_pointerOverStatus |= PointerOverStatus.Pen; + } + + public void ResetIsPenPointerOver() + { + m_pointerOverStatus &= ~PointerOverStatus.Pen; + } + + public void SetIsMousePointerOver() + { + m_pointerOverStatus |= PointerOverStatus.Mouse; + } + + public void ResetIsMousePointerOver() + { + m_pointerOverStatus &= ~PointerOverStatus.Mouse; + } + + public bool IsPressed() + { + return m_pointerPressedStatus != PointerPressedStatus.None; + } + + public bool IsMouseButtonPressed(bool isForLeftMouseButton) + { + return isForLeftMouseButton ? (m_pointerPressedStatus & PointerPressedStatus.LeftMouseButton) != 0 : (m_pointerPressedStatus & PointerPressedStatus.RightMouseButton) != 0; + } + + public void SetIsMouseButtonPressed(bool isForLeftMouseButton) + { + if (isForLeftMouseButton) + { + m_pointerPressedStatus |= PointerPressedStatus.LeftMouseButton; + } + else + { + m_pointerPressedStatus |= PointerPressedStatus.RightMouseButton; + } + } + + public void ResetIsMouseButtonPressed(bool isForLeftMouseButton) + { + if (isForLeftMouseButton) + { + m_pointerPressedStatus &= ~PointerPressedStatus.LeftMouseButton; + } + else + { + m_pointerPressedStatus &= ~PointerPressedStatus.RightMouseButton; + } + } + + public void SetPointerPressed() + { + m_pointerPressedStatus |= PointerPressedStatus.Pointer; + } + + public void ResetPointerPressed() + { + m_pointerPressedStatus &= ~PointerPressedStatus.Pointer; + } + + public bool IsTrackingPointer() + { + return m_trackedPointerId != 0; + } + + public bool IsPointerIdTracked(uint pointerId) + { + return m_trackedPointerId == pointerId; + } + + public void TrackPointerId(uint pointerId) + { + MUX_ASSERT(m_trackedPointerId == 0); + + m_trackedPointerId = pointerId; + } + + public void ResetTrackedPointerId() + { + m_trackedPointerId = 0; + } + + public void ResetAll() + { + m_trackedPointerId = 0; + m_pointerOverStatus = PointerOverStatus.None; + m_pointerPressedStatus = PointerPressedStatus.None; + } + + /* Uncomment when pointer capturing becomes necessary. + Pointer CapturedPointer() const + { + return m_capturedPointer; + } + + void SetCapturedPointer(Pointer pointer) + { + m_capturedPointer = pointer; + } + */ + + //const ITrackerHandleManager* m_owner; + //tracker_ref m_capturedPointer{ m_owner }; + private uint m_trackedPointerId; + private PointerOverStatus m_pointerOverStatus = PointerOverStatus.None; + private PointerPressedStatus m_pointerPressedStatus = PointerPressedStatus.None; +}; diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/SelectionModel.Header.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/SelectionModel.Header.cs index d6b0ee36e904..2f6e7fe00b7f 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/SelectionModel.Header.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/SelectionModel.Header.cs @@ -11,11 +11,18 @@ internal struct SelectedItemInfo public partial class SelectionModel { + internal bool SelectionInvalidatedDueToCollectionChange() + { + return m_selectionInvalidatedDueToCollectionChange; + } + internal SelectionNode SharedLeafNode => m_leafNode; private SelectionNode m_rootNode = null; private bool m_singleSelect = false; + private bool m_selectionInvalidatedDueToCollectionChange = false; + private IReadOnlyList m_selectedIndicesCached = null; private IReadOnlyList m_selectedItemsCached = null; diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/SelectionModel.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/SelectionModel.cs index ea299e51e692..649a66931273 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/SelectionModel.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/SelectionModel.cs @@ -432,6 +432,15 @@ public void SelectAll() OnSelectionChanged(); } + // This function assumes a flat list data source. + // This allows us to avoid devirtualizing all the items. + internal void SelectAllFlat() + { + m_rootNode.SelectAll(); + OnSelectionChanged(); + } + + public void ClearSelection() { ClearSelection(true /*resetAnchor*/, true /* raiseSelectionChanged */); @@ -490,7 +499,15 @@ private void RaisePropertyChanged(string name) internal void OnSelectionInvalidatedDueToCollectionChange() { - OnSelectionChanged(); + m_selectionInvalidatedDueToCollectionChange = true; + try + { + OnSelectionChanged(); + } + finally + { + m_selectionInvalidatedDueToCollectionChange = false; + } } internal object ResolvePath(object data, IndexPath dataIndexPath)