diff --git a/Handlers/CarouselViewHandler.cs b/Handlers/CarouselViewHandler.cs new file mode 100644 index 0000000..0ddaa08 --- /dev/null +++ b/Handlers/CarouselViewHandler.cs @@ -0,0 +1,255 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Platform; +using Microsoft.Maui.Platform.Linux.Hosting; +using SkiaSharp; + +namespace Microsoft.Maui.Platform.Linux.Handlers; + +/// +/// Handler for CarouselView on Linux using Skia rendering. +/// Maps CarouselView to SkiaCarouselView platform view. +/// +public partial class CarouselViewHandler : ViewHandler +{ + private bool _isUpdatingPosition; + + public static IPropertyMapper Mapper = + new PropertyMapper(ViewHandler.ViewMapper) + { + // ItemsView properties + [nameof(ItemsView.ItemsSource)] = MapItemsSource, + [nameof(ItemsView.ItemTemplate)] = MapItemTemplate, + [nameof(ItemsView.EmptyView)] = MapEmptyView, + + // CarouselView specific properties + [nameof(CarouselView.Position)] = MapPosition, + [nameof(CarouselView.CurrentItem)] = MapCurrentItem, + [nameof(CarouselView.IsBounceEnabled)] = MapIsBounceEnabled, + [nameof(CarouselView.IsSwipeEnabled)] = MapIsSwipeEnabled, + [nameof(CarouselView.Loop)] = MapLoop, + [nameof(CarouselView.PeekAreaInsets)] = MapPeekAreaInsets, + + [nameof(IView.Background)] = MapBackground, + }; + + public static CommandMapper CommandMapper = + new(ViewHandler.ViewCommandMapper) + { + ["ScrollTo"] = MapScrollTo, + }; + + public CarouselViewHandler() : base(Mapper, CommandMapper) + { + } + + public CarouselViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null) + : base(mapper ?? Mapper, commandMapper ?? CommandMapper) + { + } + + protected override SkiaCarouselView CreatePlatformView() + { + return new SkiaCarouselView(); + } + + protected override void ConnectHandler(SkiaCarouselView platformView) + { + base.ConnectHandler(platformView); + platformView.PositionChanged += OnPositionChanged; + platformView.Scrolled += OnScrolled; + } + + protected override void DisconnectHandler(SkiaCarouselView platformView) + { + platformView.PositionChanged -= OnPositionChanged; + platformView.Scrolled -= OnScrolled; + base.DisconnectHandler(platformView); + } + + private void OnPositionChanged(object? sender, PositionChangedEventArgs e) + { + if (VirtualView is null || _isUpdatingPosition) return; + + try + { + _isUpdatingPosition = true; + + if (VirtualView.Position != e.CurrentPosition) + { + VirtualView.Position = e.CurrentPosition; + } + + // Update CurrentItem + if (VirtualView.ItemsSource is System.Collections.IList list && + e.CurrentPosition >= 0 && e.CurrentPosition < list.Count) + { + VirtualView.CurrentItem = list[e.CurrentPosition]; + } + } + finally + { + _isUpdatingPosition = false; + } + } + + private void OnScrolled(object? sender, EventArgs e) + { + // CarouselView doesn't have a direct Scrolled event in MAUI + // but we can use this for internal state tracking + } + + public static void MapItemsSource(CarouselViewHandler handler, CarouselView carouselView) + { + if (handler.PlatformView is null || handler.MauiContext is null) return; + + handler.PlatformView.ClearItems(); + + var itemsSource = carouselView.ItemsSource; + if (itemsSource == null) return; + + var template = carouselView.ItemTemplate; + + foreach (var item in itemsSource) + { + SkiaView? skiaView = null; + + if (template != null) + { + try + { + var content = template.CreateContent(); + if (content is View view) + { + view.BindingContext = item; + + if (view.Handler == null) + { + view.Handler = view.ToViewHandler(handler.MauiContext); + } + + if (view.Handler?.PlatformView is SkiaView sv) + { + skiaView = sv; + } + } + } + catch + { + // Ignore template errors + } + } + + if (skiaView == null) + { + // Create a simple label for the item + skiaView = new SkiaLabel { Text = item?.ToString() ?? "" }; + } + + handler.PlatformView.AddItem(skiaView); + } + + handler.PlatformView.Invalidate(); + } + + public static void MapItemTemplate(CarouselViewHandler handler, CarouselView carouselView) + { + // Re-map items when template changes + MapItemsSource(handler, carouselView); + } + + public static void MapEmptyView(CarouselViewHandler handler, CarouselView carouselView) + { + // CarouselView doesn't typically show empty view - handled by ItemsSource + } + + public static void MapPosition(CarouselViewHandler handler, CarouselView carouselView) + { + if (handler.PlatformView is null || handler._isUpdatingPosition) return; + + try + { + handler._isUpdatingPosition = true; + if (handler.PlatformView.Position != carouselView.Position) + { + handler.PlatformView.Position = carouselView.Position; + } + } + finally + { + handler._isUpdatingPosition = false; + } + } + + public static void MapCurrentItem(CarouselViewHandler handler, CarouselView carouselView) + { + if (handler.PlatformView is null || handler._isUpdatingPosition) return; + + // Find position of current item + if (carouselView.ItemsSource is System.Collections.IList list && carouselView.CurrentItem != null) + { + int index = list.IndexOf(carouselView.CurrentItem); + if (index >= 0 && index != handler.PlatformView.Position) + { + try + { + handler._isUpdatingPosition = true; + handler.PlatformView.Position = index; + } + finally + { + handler._isUpdatingPosition = false; + } + } + } + } + + public static void MapIsBounceEnabled(CarouselViewHandler handler, CarouselView carouselView) + { + // SkiaCarouselView handles bounce internally + // Could add IsBounceEnabled property if needed + } + + public static void MapIsSwipeEnabled(CarouselViewHandler handler, CarouselView carouselView) + { + if (handler.PlatformView is null) return; + handler.PlatformView.IsSwipeEnabled = carouselView.IsSwipeEnabled; + } + + public static void MapLoop(CarouselViewHandler handler, CarouselView carouselView) + { + if (handler.PlatformView is null) return; + handler.PlatformView.Loop = carouselView.Loop; + } + + public static void MapPeekAreaInsets(CarouselViewHandler handler, CarouselView carouselView) + { + if (handler.PlatformView is null) return; + // PeekAreaInsets is a Thickness in MAUI, use Left for horizontal peek + handler.PlatformView.PeekAreaInsets = (float)carouselView.PeekAreaInsets.Left; + } + + public static void MapBackground(CarouselViewHandler handler, CarouselView carouselView) + { + if (handler.PlatformView is null) return; + + if (carouselView.Background is SolidColorBrush solidBrush) + { + handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor(); + } + } + + public static void MapScrollTo(CarouselViewHandler handler, CarouselView carouselView, object? args) + { + if (handler.PlatformView is null) return; + + if (args is ScrollToRequestEventArgs scrollArgs) + { + handler.PlatformView.ScrollTo(scrollArgs.Index, scrollArgs.IsAnimated); + } + } +} diff --git a/Handlers/IndicatorViewHandler.cs b/Handlers/IndicatorViewHandler.cs new file mode 100644 index 0000000..2bb513c --- /dev/null +++ b/Handlers/IndicatorViewHandler.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Platform; +using Microsoft.Maui.Platform.Linux.Hosting; +using SkiaSharp; + +namespace Microsoft.Maui.Platform.Linux.Handlers; + +/// +/// Handler for IndicatorView on Linux using Skia rendering. +/// Maps IndicatorView to SkiaIndicatorView platform view. +/// +public partial class IndicatorViewHandler : ViewHandler +{ + private bool _isUpdatingPosition; + + public static IPropertyMapper Mapper = + new PropertyMapper(ViewHandler.ViewMapper) + { + [nameof(IndicatorView.Count)] = MapCount, + [nameof(IndicatorView.Position)] = MapPosition, + [nameof(IndicatorView.IndicatorColor)] = MapIndicatorColor, + [nameof(IndicatorView.SelectedIndicatorColor)] = MapSelectedIndicatorColor, + [nameof(IndicatorView.IndicatorSize)] = MapIndicatorSize, + [nameof(IndicatorView.IndicatorsShape)] = MapIndicatorsShape, + [nameof(IndicatorView.MaximumVisible)] = MapMaximumVisible, + [nameof(IndicatorView.HideSingle)] = MapHideSingle, + [nameof(IndicatorView.ItemsSource)] = MapItemsSource, + }; + + public static CommandMapper CommandMapper = + new(ViewHandler.ViewCommandMapper) + { + }; + + public IndicatorViewHandler() : base(Mapper, CommandMapper) + { + } + + public IndicatorViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null) + : base(mapper ?? Mapper, commandMapper ?? CommandMapper) + { + } + + protected override SkiaIndicatorView CreatePlatformView() + { + return new SkiaIndicatorView(); + } + + protected override void ConnectHandler(SkiaIndicatorView platformView) + { + base.ConnectHandler(platformView); + // SkiaIndicatorView doesn't have position changed event, but we can add one if needed + } + + protected override void DisconnectHandler(SkiaIndicatorView platformView) + { + base.DisconnectHandler(platformView); + } + + public static void MapCount(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null) return; + handler.PlatformView.Count = indicatorView.Count; + } + + public static void MapPosition(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null || handler._isUpdatingPosition) return; + + try + { + handler._isUpdatingPosition = true; + handler.PlatformView.Position = indicatorView.Position; + } + finally + { + handler._isUpdatingPosition = false; + } + } + + public static void MapIndicatorColor(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null) return; + + if (indicatorView.IndicatorColor is not null) + { + handler.PlatformView.IndicatorColor = indicatorView.IndicatorColor.ToSKColor(); + } + } + + public static void MapSelectedIndicatorColor(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null) return; + + if (indicatorView.SelectedIndicatorColor is not null) + { + handler.PlatformView.SelectedIndicatorColor = indicatorView.SelectedIndicatorColor.ToSKColor(); + } + } + + public static void MapIndicatorSize(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null) return; + handler.PlatformView.IndicatorSize = (float)indicatorView.IndicatorSize; + handler.PlatformView.SelectedIndicatorSize = (float)indicatorView.IndicatorSize; + } + + public static void MapIndicatorsShape(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null) return; + + handler.PlatformView.IndicatorShape = indicatorView.IndicatorsShape switch + { + Controls.IndicatorShape.Circle => Platform.IndicatorShape.Circle, + Controls.IndicatorShape.Square => Platform.IndicatorShape.Square, + _ => Platform.IndicatorShape.Circle + }; + } + + public static void MapMaximumVisible(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null) return; + handler.PlatformView.MaximumVisible = indicatorView.MaximumVisible; + } + + public static void MapHideSingle(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null) return; + handler.PlatformView.HideSingle = indicatorView.HideSingle; + } + + public static void MapItemsSource(IndicatorViewHandler handler, IndicatorView indicatorView) + { + if (handler.PlatformView is null) return; + + // Count items from ItemsSource + int count = 0; + if (indicatorView.ItemsSource is System.Collections.ICollection collection) + { + count = collection.Count; + } + else if (indicatorView.ItemsSource is System.Collections.IEnumerable enumerable) + { + foreach (var _ in enumerable) + { + count++; + } + } + + handler.PlatformView.Count = count; + } +} diff --git a/Handlers/RefreshViewHandler.cs b/Handlers/RefreshViewHandler.cs new file mode 100644 index 0000000..3b3c498 --- /dev/null +++ b/Handlers/RefreshViewHandler.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Platform; +using Microsoft.Maui.Platform.Linux.Hosting; +using SkiaSharp; + +namespace Microsoft.Maui.Platform.Linux.Handlers; + +/// +/// Handler for RefreshView on Linux using Skia rendering. +/// Maps RefreshView to SkiaRefreshView platform view. +/// +public partial class RefreshViewHandler : ViewHandler +{ + private bool _isUpdatingRefreshing; + + public static IPropertyMapper Mapper = + new PropertyMapper(ViewHandler.ViewMapper) + { + [nameof(RefreshView.Content)] = MapContent, + [nameof(RefreshView.IsRefreshing)] = MapIsRefreshing, + [nameof(RefreshView.RefreshColor)] = MapRefreshColor, + [nameof(RefreshView.Command)] = MapCommand, + [nameof(RefreshView.CommandParameter)] = MapCommandParameter, + [nameof(IView.Background)] = MapBackground, + }; + + public static CommandMapper CommandMapper = + new(ViewHandler.ViewCommandMapper) + { + }; + + public RefreshViewHandler() : base(Mapper, CommandMapper) + { + } + + public RefreshViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null) + : base(mapper ?? Mapper, commandMapper ?? CommandMapper) + { + } + + protected override SkiaRefreshView CreatePlatformView() + { + return new SkiaRefreshView(); + } + + protected override void ConnectHandler(SkiaRefreshView platformView) + { + base.ConnectHandler(platformView); + platformView.Refreshing += OnRefreshing; + } + + protected override void DisconnectHandler(SkiaRefreshView platformView) + { + platformView.Refreshing -= OnRefreshing; + base.DisconnectHandler(platformView); + } + + private void OnRefreshing(object? sender, EventArgs e) + { + if (VirtualView is null || _isUpdatingRefreshing) return; + + try + { + _isUpdatingRefreshing = true; + + // Notify the virtual view that refreshing has started + VirtualView.IsRefreshing = true; + + // The command will be executed by the platform view + } + finally + { + _isUpdatingRefreshing = false; + } + } + + public static void MapContent(RefreshViewHandler handler, RefreshView refreshView) + { + if (handler.PlatformView is null || handler.MauiContext is null) return; + + var content = refreshView.Content; + if (content == null) + { + handler.PlatformView.Content = null; + return; + } + + // Create handler for content + if (content.Handler == null) + { + content.Handler = content.ToViewHandler(handler.MauiContext); + } + + if (content.Handler?.PlatformView is SkiaView skiaContent) + { + handler.PlatformView.Content = skiaContent; + } + } + + public static void MapIsRefreshing(RefreshViewHandler handler, RefreshView refreshView) + { + if (handler.PlatformView is null || handler._isUpdatingRefreshing) return; + + try + { + handler._isUpdatingRefreshing = true; + handler.PlatformView.IsRefreshing = refreshView.IsRefreshing; + } + finally + { + handler._isUpdatingRefreshing = false; + } + } + + public static void MapRefreshColor(RefreshViewHandler handler, RefreshView refreshView) + { + if (handler.PlatformView is null) return; + + if (refreshView.RefreshColor is not null) + { + handler.PlatformView.RefreshColor = refreshView.RefreshColor.ToSKColor(); + } + } + + public static void MapCommand(RefreshViewHandler handler, RefreshView refreshView) + { + if (handler.PlatformView is null) return; + handler.PlatformView.Command = refreshView.Command; + } + + public static void MapCommandParameter(RefreshViewHandler handler, RefreshView refreshView) + { + if (handler.PlatformView is null) return; + handler.PlatformView.CommandParameter = refreshView.CommandParameter; + } + + public static void MapBackground(RefreshViewHandler handler, RefreshView refreshView) + { + if (handler.PlatformView is null) return; + + if (refreshView.Background is SolidColorBrush solidBrush) + { + handler.PlatformView.RefreshBackgroundColor = solidBrush.Color.ToSKColor(); + } + } +} diff --git a/Handlers/SwipeViewHandler.cs b/Handlers/SwipeViewHandler.cs new file mode 100644 index 0000000..a141c0d --- /dev/null +++ b/Handlers/SwipeViewHandler.cs @@ -0,0 +1,226 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Platform; +using Microsoft.Maui.Platform.Linux.Hosting; +using SkiaSharp; + +namespace Microsoft.Maui.Platform.Linux.Handlers; + +/// +/// Handler for SwipeView on Linux using Skia rendering. +/// Maps SwipeView to SkiaSwipeView platform view. +/// +public partial class SwipeViewHandler : ViewHandler +{ + public static IPropertyMapper Mapper = + new PropertyMapper(ViewHandler.ViewMapper) + { + [nameof(SwipeView.Content)] = MapContent, + [nameof(SwipeView.LeftItems)] = MapLeftItems, + [nameof(SwipeView.RightItems)] = MapRightItems, + [nameof(SwipeView.TopItems)] = MapTopItems, + [nameof(SwipeView.BottomItems)] = MapBottomItems, + [nameof(SwipeView.Threshold)] = MapThreshold, + [nameof(IView.Background)] = MapBackground, + }; + + public static CommandMapper CommandMapper = + new(ViewHandler.ViewCommandMapper) + { + ["RequestOpen"] = MapRequestOpen, + ["RequestClose"] = MapRequestClose, + }; + + public SwipeViewHandler() : base(Mapper, CommandMapper) + { + } + + public SwipeViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null) + : base(mapper ?? Mapper, commandMapper ?? CommandMapper) + { + } + + protected override SkiaSwipeView CreatePlatformView() + { + return new SkiaSwipeView(); + } + + protected override void ConnectHandler(SkiaSwipeView platformView) + { + base.ConnectHandler(platformView); + platformView.SwipeStarted += OnSwipeStarted; + platformView.SwipeEnded += OnSwipeEnded; + } + + protected override void DisconnectHandler(SkiaSwipeView platformView) + { + platformView.SwipeStarted -= OnSwipeStarted; + platformView.SwipeEnded -= OnSwipeEnded; + base.DisconnectHandler(platformView); + } + + private void OnSwipeStarted(object? sender, Platform.SwipeStartedEventArgs e) + { + // SwipeView events are handled internally by the platform view + } + + private void OnSwipeEnded(object? sender, Platform.SwipeEndedEventArgs e) + { + // SwipeView events are handled internally by the platform view + } + + public static void MapContent(SwipeViewHandler handler, SwipeView swipeView) + { + if (handler.PlatformView is null || handler.MauiContext is null) return; + + var content = swipeView.Content; + if (content == null) + { + handler.PlatformView.Content = null; + return; + } + + // Create handler for content + if (content.Handler == null) + { + content.Handler = content.ToViewHandler(handler.MauiContext); + } + + if (content.Handler?.PlatformView is SkiaView skiaContent) + { + handler.PlatformView.Content = skiaContent; + } + } + + public static void MapLeftItems(SwipeViewHandler handler, SwipeView swipeView) + { + if (handler.PlatformView is null) return; + + handler.PlatformView.LeftItems.Clear(); + + if (swipeView.LeftItems != null) + { + foreach (var item in swipeView.LeftItems) + { + handler.PlatformView.LeftItems.Add(CreatePlatformSwipeItem(item)); + } + } + } + + public static void MapRightItems(SwipeViewHandler handler, SwipeView swipeView) + { + if (handler.PlatformView is null) return; + + handler.PlatformView.RightItems.Clear(); + + if (swipeView.RightItems != null) + { + foreach (var item in swipeView.RightItems) + { + handler.PlatformView.RightItems.Add(CreatePlatformSwipeItem(item)); + } + } + } + + public static void MapTopItems(SwipeViewHandler handler, SwipeView swipeView) + { + if (handler.PlatformView is null) return; + + handler.PlatformView.TopItems.Clear(); + + if (swipeView.TopItems != null) + { + foreach (var item in swipeView.TopItems) + { + handler.PlatformView.TopItems.Add(CreatePlatformSwipeItem(item)); + } + } + } + + public static void MapBottomItems(SwipeViewHandler handler, SwipeView swipeView) + { + if (handler.PlatformView is null) return; + + handler.PlatformView.BottomItems.Clear(); + + if (swipeView.BottomItems != null) + { + foreach (var item in swipeView.BottomItems) + { + handler.PlatformView.BottomItems.Add(CreatePlatformSwipeItem(item)); + } + } + } + + public static void MapThreshold(SwipeViewHandler handler, SwipeView swipeView) + { + if (handler.PlatformView is null) return; + + handler.PlatformView.LeftSwipeThreshold = (float)swipeView.Threshold; + handler.PlatformView.RightSwipeThreshold = (float)swipeView.Threshold; + } + + public static void MapBackground(SwipeViewHandler handler, SwipeView swipeView) + { + if (handler.PlatformView is null) return; + + if (swipeView.Background is SolidColorBrush solidBrush) + { + handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor(); + } + } + + public static void MapRequestOpen(SwipeViewHandler handler, SwipeView swipeView, object? args) + { + if (handler.PlatformView is null) return; + + if (args is SwipeViewOpenRequest request) + { + var direction = request.OpenSwipeItem switch + { + OpenSwipeItem.LeftItems => Platform.SwipeDirection.Right, + OpenSwipeItem.RightItems => Platform.SwipeDirection.Left, + OpenSwipeItem.TopItems => Platform.SwipeDirection.Down, + OpenSwipeItem.BottomItems => Platform.SwipeDirection.Up, + _ => Platform.SwipeDirection.Right + }; + + handler.PlatformView.Open(direction); + } + } + + public static void MapRequestClose(SwipeViewHandler handler, SwipeView swipeView, object? args) + { + if (handler.PlatformView is null) return; + handler.PlatformView.Close(); + } + + private static Platform.SwipeItem CreatePlatformSwipeItem(ISwipeItem item) + { + var platformItem = new Platform.SwipeItem(); + + if (item is Controls.SwipeItem swipeItem) + { + platformItem.Text = swipeItem.Text ?? ""; + + // Get background color + var bgColor = swipeItem.BackgroundColor; + if (bgColor is not null) + { + platformItem.BackgroundColor = bgColor.ToSKColor(); + } + } + else if (item is Controls.SwipeItemView swipeItemView) + { + // SwipeItemView uses custom content - use a simple representation + platformItem.Text = "Action"; + platformItem.BackgroundColor = new SKColor(100, 100, 100); + } + + return platformItem; + } +}