File: Handlers\Items\Tizen\MauiCollectionView.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
using System.Collections.Specialized;
using System.Linq;
using Microsoft.Maui.Controls.Platform;
using Tizen.UIExtensions.NUI;
using IMeasurable = Tizen.UIExtensions.Common.IMeasurable;
using TCollectionView = Tizen.UIExtensions.NUI.CollectionView;
using TScrollToPosition = Tizen.UIExtensions.Common.ScrollToPosition;
using TSize = Tizen.UIExtensions.Common.Size;
using TSnapPointsAlignment = Tizen.UIExtensions.NUI.SnapPointsAlignment;
using TSnapPointsType = Tizen.UIExtensions.NUI.SnapPointsType;
 
namespace Microsoft.Maui.Controls.Handlers.Items
{
	public abstract class MauiCollectionView<TItemsView> : TCollectionView, IMeasurable where TItemsView : ItemsView
	{
		INotifyCollectionChanged? _observableSource;
		protected TItemsView? ItemsView { get; set; }
		protected IItemsLayout? ItemsLayout { get; private set; }
 
		public virtual void SetupNewElement(TItemsView newElement)
		{
			if (newElement == null)
			{
				ItemsView = null;
				return;
			}
 
			Scrolled += OnScrolled;
 
			ItemsView = newElement;
			ItemsView.ScrollToRequested += OnScrollToRequested;
		}
 
		public virtual void TearDownOldElement(TItemsView oldElement)
		{
			if (ItemsLayout != null)
			{
				ItemsLayout.PropertyChanged -= OnLayoutPropertyChanged;
			}
			oldElement.ScrollToRequested -= OnScrollToRequested;
 
			using var oldAdaptor = Adaptor;
			Adaptor = null;
			LayoutManager = null;
		}
 
		public virtual void UpdateItemsSource()
		{
			if (ItemsView == null)
				return;
 
			if (ItemsView.ItemsSource is INotifyCollectionChanged collectionChanged)
			{
				if (_observableSource != null)
				{
					_observableSource.CollectionChanged -= OnCollectionChanged;
				}
				_observableSource = collectionChanged;
				_observableSource.CollectionChanged += OnCollectionChanged;
			}
			UpdateAdaptor();
		}
 
		public virtual void UpdateAdaptor()
		{
			if (ItemsView == null)
				return;
 
			using var oldAdaptor = Adaptor;
			if (Adaptor is ItemTemplateAdaptor old)
			{
				old.SelectionChanged -= OnItemSelectedFromUI;
			}
 
			if (ItemsView.ItemsSource == null || !ItemsView.ItemsSource.Cast<object>().Any())
			{
				Adaptor = EmptyItemAdaptor.Create(ItemsView);
			}
			else
			{
				Adaptor = CreateItemAdaptor(ItemsView);
			}
 
			if (Adaptor is ItemTemplateAdaptor adaptor)
			{
				adaptor.SelectionChanged += OnItemSelectedFromUI;
			}
		}
 
		public virtual void UpdateLayoutManager()
		{
			if (ItemsLayout != null)
			{
				ItemsLayout.PropertyChanged -= OnLayoutPropertyChanged;
			}
			ItemsLayout = GetItemsLayout();
			if (ItemsLayout != null)
			{
				LayoutManager = ItemsLayout.ToLayoutManager((ItemsView as StructuredItemsView)?.ItemSizingStrategy ?? ItemSizingStrategy.MeasureFirstItem);
				ItemsLayout.PropertyChanged += OnLayoutPropertyChanged;
				if (ItemsLayout is ItemsLayout itemsLayout)
				{
					SnapPointsType = (TSnapPointsType)itemsLayout.SnapPointsType;
					SnapPointsAlignment = (TSnapPointsAlignment)itemsLayout.SnapPointsAlignment;
				}
			}
		}
 
		public void UpdateHorizontalScrollBarVisibility()
		{
			if (ItemsView == null || LayoutManager == null)
				return;
 
			if (LayoutManager.IsHorizontal)
			{
				ScrollView.HideScrollbar = ItemsView.HorizontalScrollBarVisibility == ScrollBarVisibility.Never;
			}
		}
 
		public void UpdateVerticalScrollBarVisibility()
		{
			if (ItemsView == null || LayoutManager == null)
				return;
 
			if (!LayoutManager.IsHorizontal)
			{
				ScrollView.HideScrollbar = ItemsView.VerticalScrollBarVisibility == ScrollBarVisibility.Never;
			}
		}
 
		public abstract IItemsLayout GetItemsLayout();
 
		TSize IMeasurable.Measure(double availableWidth, double availableHeight)
		{
			if (Adaptor == null || LayoutManager == null || AllocatedSize == TSize.Zero)
			{
				var scaled = Devices.DeviceDisplay.MainDisplayInfo.GetScaledScreenSize();
				var size = new TSize(availableWidth, availableHeight);
				if (size.Width == double.PositiveInfinity)
					size.Width = scaled.Width.ToScaledPixel();
				if (size.Height == double.PositiveInfinity)
					size.Height = scaled.Height.ToScaledPixel();
				return size;
			}
 
			var canvasSize = LayoutManager.GetScrollCanvasSize();
			canvasSize.Width = System.Math.Min(canvasSize.Width, availableWidth);
			canvasSize.Height = System.Math.Min(canvasSize.Height, availableHeight);
 
			return canvasSize;
		}
 
		void OnLayoutPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			if (sender == null)
				return;
 
			if (e.PropertyName == nameof(LinearItemsLayout.ItemSpacing)
				|| e.PropertyName == nameof(GridItemsLayout.VerticalItemSpacing)
				|| e.PropertyName == nameof(GridItemsLayout.HorizontalItemSpacing)
				|| e.PropertyName == nameof(Controls.ItemsLayout.SnapPointsType)
				|| e.PropertyName == nameof(Controls.ItemsLayout.SnapPointsAlignment))
			{
				UpdateLayoutManager();
			}
			else if (e.PropertyName == nameof(GridItemsLayout.Span))
			{
				(LayoutManager as GridLayoutManager)?.UpdateSpan(((GridItemsLayout)sender).Span);
			}
		}
 
		void OnScrolled(object? sender, CollectionViewScrolledEventArgs e)
		{
			if (ItemsView == null || Adaptor == null)
				return;
 
			ItemsView.SendScrolled(new ItemsViewScrolledEventArgs
			{
				HorizontalDelta = e.HorizontalDelta.ToScaledDP(),
				HorizontalOffset = e.HorizontalOffset.ToScaledDP(),
				VerticalDelta = e.VerticalDelta.ToScaledDP(),
				VerticalOffset = e.VerticalOffset.ToScaledDP(),
				FirstVisibleItemIndex = e.FirstVisibleItemIndex,
				CenterItemIndex = e.CenterItemIndex,
				LastVisibleItemIndex = e.LastVisibleItemIndex,
			});
 
			if (ItemsView.RemainingItemsThreshold >= 0)
			{
				if (Adaptor.Count - 1 - e.LastVisibleItemIndex <= ItemsView.RemainingItemsThreshold)
					ItemsView.SendRemainingItemsThresholdReached();
			}
		}
 
		protected virtual ItemTemplateAdaptor CreateItemAdaptor(ItemsView view)
		{
			return new ItemTemplateAdaptor(view);
		}
 
		protected virtual void OnItemSelectedFromUI(object? sender, CollectionViewSelectionChangedEventArgs e) { }
 
		protected virtual int GetIndex(ScrollToRequestEventArgs request)
		{
			return request.Index;
		}
 
		void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
		{
			if (ItemsView == null)
				return;
 
			if (ItemsView.ItemsSource == null || !ItemsView.ItemsSource.Cast<object>().Any())
			{
				Adaptor = EmptyItemAdaptor.Create(ItemsView);
			}
			else if (Adaptor is EmptyItemAdaptor)
			{
				UpdateAdaptor();
			}
		}
 
		void OnScrollToRequested(object? sender, ScrollToRequestEventArgs e)
		{
			if (e.Mode == ScrollToMode.Position)
			{
				ScrollTo(GetIndex(e), (TScrollToPosition)e.ScrollToPosition, e.IsAnimated);
			}
			else
			{
				ScrollTo(e.Item, (TScrollToPosition)e.ScrollToPosition, e.IsAnimated);
			}
		}
	}
 
	public class MauiStructuredItemsView<TItemsView> : MauiCollectionView<TItemsView> where TItemsView : StructuredItemsView
	{
		public override IItemsLayout GetItemsLayout()
		{
			return ItemsView!.ItemsLayout;
		}
	}
 
	public class MauiSelectableItemsView<TItemsView> : MauiStructuredItemsView<TItemsView> where TItemsView : SelectableItemsView
	{
		bool _updateSelection;
		bool _updateFromUI;
 
		public override void UpdateAdaptor()
		{
			base.UpdateAdaptor();
			UpdateSelection();
		}
 
		public void UpdateSelection()
		{
			if (ItemsView == null || _updateFromUI)
				return;
 
			_updateSelection = true;
 
			if (SelectionMode != ItemsView.SelectionMode.ToNative())
			{
				SelectionMode = ItemsView.SelectionMode.ToNative();
			}
 
			if (Adaptor == null)
			{
				_updateSelection = false;
				return;
			}
 
			// Sync SelectedItem from Maui to Native
			if (ItemsView.SelectionMode == Controls.SelectionMode.Single)
			{
				var selected = Adaptor.GetItemIndex(ItemsView.SelectedItem);
				foreach (var index in SelectedItems.ToList())
				{
					if (selected != index)
						RequestItemUnselect(index);
				}
				if (selected != -1)
					RequestItemSelect(selected);
 
			}
			else if (ItemsView.SelectionMode == Controls.SelectionMode.Multiple)
			{
				var selectedItemIndexes = ItemsView.SelectedItems.Select(d => Adaptor.GetItemIndex(d)).ToHashSet();
				foreach (var index in SelectedItems.ToList())
				{
					if (index < 0 || Adaptor.Count <= index)
						continue;
 
					if (!selectedItemIndexes.Contains(index))
					{
						RequestItemUnselect(index);
					}
				}
				var alreadySelected = SelectedItems.ToHashSet();
				foreach (var selected in selectedItemIndexes)
				{
					if (!alreadySelected.Contains(selected))
					{
						RequestItemSelect(selected);
					}
				}
			}
 
			_updateSelection = false;
		}
 
		protected override void OnItemSelectedFromUI(object? sender, CollectionViewSelectionChangedEventArgs e)
		{
			if (ItemsView == null || Adaptor == null || _updateSelection)
			{
				return;
			}
 
			_updateFromUI = true;
 
			if (ItemsView.SelectionMode == Controls.SelectionMode.Single)
			{
				ItemsView.SelectedItem = e.SelectedItems?.FirstOrDefault() ?? null;
			}
			else if (ItemsView.SelectionMode == Controls.SelectionMode.Multiple)
			{
				ItemsView.SelectedItems = e.SelectedItems;
			}
 
			_updateFromUI = false;
		}
	}
 
	public class MauiGroupableItemsView<TItemsView> : MauiSelectableItemsView<TItemsView> where TItemsView : GroupableItemsView
	{
		protected override ItemTemplateAdaptor CreateItemAdaptor(ItemsView view)
		{
			if (view is GroupableItemsView groupableItemsView && groupableItemsView.IsGrouped)
			{
				return new GroupItemTemplateAdaptor(groupableItemsView, new GroupItemSource(groupableItemsView));
			}
			return base.CreateItemAdaptor(view);
		}
 
		protected override int GetIndex(ScrollToRequestEventArgs request)
		{
			if (Adaptor is GroupItemTemplateAdaptor groupAdaptor)
			{
				return groupAdaptor.GetAbsoluteIndex(request.GroupIndex, request.Index);
			}
			return base.GetIndex(request);
		}
	}
}