// 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 SkiaSharp; namespace Microsoft.Maui.Platform; /// /// Linux handler for Layout controls. /// public partial class LayoutHandler : ViewHandler { /// /// Maps the property mapper for the handler. /// public static IPropertyMapper Mapper = new PropertyMapper(ViewHandler.ViewMapper) { [nameof(ILayout.Background)] = MapBackground, [nameof(ILayout.ClipsToBounds)] = MapClipsToBounds, }; /// /// Maps the command mapper for the handler. /// public static CommandMapper CommandMapper = new(ViewHandler.ViewCommandMapper) { ["Add"] = MapAdd, ["Remove"] = MapRemove, ["Clear"] = MapClear, ["Insert"] = MapInsert, ["Update"] = MapUpdate, ["UpdateZIndex"] = MapUpdateZIndex, }; public LayoutHandler() : base(Mapper, CommandMapper) { } public LayoutHandler(IPropertyMapper? mapper) : base(mapper ?? Mapper, CommandMapper) { } public LayoutHandler(IPropertyMapper? mapper, CommandMapper? commandMapper) : base(mapper ?? Mapper, commandMapper ?? CommandMapper) { } protected override SkiaLayoutView CreatePlatformView() { // Return a concrete SkiaStackLayout as the default layout return new SkiaStackLayout(); } public static void MapBackground(LayoutHandler handler, ILayout layout) { var background = layout.Background; if (background is SolidColorBrush solidBrush && solidBrush.Color != null) { handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor(); } handler.PlatformView.Invalidate(); } public static void MapClipsToBounds(LayoutHandler handler, ILayout layout) { handler.PlatformView.ClipToBounds = layout.ClipsToBounds; handler.PlatformView.Invalidate(); } public static void MapAdd(LayoutHandler handler, ILayout layout, object? arg) { if (arg is LayoutHandlerUpdate update) { var childHandler = update.View.Handler; if (childHandler?.PlatformView is SkiaView skiaView) { handler.PlatformView.InsertChild(update.Index, skiaView); } } } public static void MapRemove(LayoutHandler handler, ILayout layout, object? arg) { if (arg is LayoutHandlerUpdate update) { handler.PlatformView.RemoveChildAt(update.Index); } } public static void MapClear(LayoutHandler handler, ILayout layout, object? arg) { handler.PlatformView.ClearChildren(); } public static void MapInsert(LayoutHandler handler, ILayout layout, object? arg) { if (arg is LayoutHandlerUpdate update) { var childHandler = update.View.Handler; if (childHandler?.PlatformView is SkiaView skiaView) { handler.PlatformView.InsertChild(update.Index, skiaView); } } } public static void MapUpdate(LayoutHandler handler, ILayout layout, object? arg) { handler.PlatformView.InvalidateMeasure(); handler.PlatformView.Invalidate(); } public static void MapUpdateZIndex(LayoutHandler handler, ILayout layout, object? arg) { // Z-index is handled by child order for now handler.PlatformView.Invalidate(); } } /// /// Update information for layout operations. /// public class LayoutHandlerUpdate { public int Index { get; } public IView View { get; } public LayoutHandlerUpdate(int index, IView view) { Index = index; View = view; } } /// /// Linux handler for StackLayout. /// public partial class StackLayoutHandler : LayoutHandler { public static new IPropertyMapper Mapper = new PropertyMapper(LayoutHandler.Mapper) { [nameof(IStackLayout.Spacing)] = MapSpacing, }; public StackLayoutHandler() : base(Mapper) { } protected override SkiaLayoutView CreatePlatformView() { return new SkiaStackLayout(); } public static void MapSpacing(StackLayoutHandler handler, IStackLayout layout) { if (handler.PlatformView is SkiaStackLayout stackLayout) { stackLayout.Spacing = (float)layout.Spacing; stackLayout.Invalidate(); } } } /// /// Linux handler for HorizontalStackLayout. /// public class HorizontalStackLayoutHandler : StackLayoutHandler { protected override SkiaLayoutView CreatePlatformView() { return new SkiaStackLayout { Orientation = StackOrientation.Horizontal }; } } /// /// Linux handler for VerticalStackLayout. /// public class VerticalStackLayoutHandler : StackLayoutHandler { protected override SkiaLayoutView CreatePlatformView() { return new SkiaStackLayout { Orientation = StackOrientation.Vertical }; } } /// /// Linux handler for Grid. /// public partial class GridHandler : LayoutHandler { public static new IPropertyMapper Mapper = new PropertyMapper(LayoutHandler.Mapper) { [nameof(IGridLayout.ColumnSpacing)] = MapColumnSpacing, [nameof(IGridLayout.RowSpacing)] = MapRowSpacing, }; public GridHandler() : base(Mapper) { } protected override SkiaLayoutView CreatePlatformView() { return new SkiaGrid(); } public static void MapColumnSpacing(GridHandler handler, IGridLayout layout) { if (handler.PlatformView is SkiaGrid grid) { grid.ColumnSpacing = (float)layout.ColumnSpacing; grid.Invalidate(); } } public static void MapRowSpacing(GridHandler handler, IGridLayout layout) { if (handler.PlatformView is SkiaGrid grid) { grid.RowSpacing = (float)layout.RowSpacing; grid.Invalidate(); } } } /// /// Linux handler for AbsoluteLayout. /// public partial class AbsoluteLayoutHandler : LayoutHandler { public AbsoluteLayoutHandler() : base(Mapper) { } protected override SkiaLayoutView CreatePlatformView() { return new SkiaAbsoluteLayout(); } } /// /// Linux handler for ScrollView. /// public partial class ScrollViewHandler : ViewHandler { public static IPropertyMapper Mapper = new PropertyMapper(ViewHandler.ViewMapper) { [nameof(IScrollView.Content)] = MapContent, [nameof(IScrollView.HorizontalScrollBarVisibility)] = MapHorizontalScrollBarVisibility, [nameof(IScrollView.VerticalScrollBarVisibility)] = MapVerticalScrollBarVisibility, [nameof(IScrollView.Orientation)] = MapOrientation, }; public static CommandMapper CommandMapper = new(ViewHandler.ViewCommandMapper) { [nameof(IScrollView.RequestScrollTo)] = MapRequestScrollTo, }; public ScrollViewHandler() : base(Mapper, CommandMapper) { } protected override SkiaScrollView CreatePlatformView() { return new SkiaScrollView(); } protected override void ConnectHandler(SkiaScrollView platformView) { base.ConnectHandler(platformView); platformView.Scrolled += OnScrolled; } protected override void DisconnectHandler(SkiaScrollView platformView) { platformView.Scrolled -= OnScrolled; base.DisconnectHandler(platformView); } private void OnScrolled(object? sender, ScrolledEventArgs e) { VirtualView?.ScrollFinished(); } public static void MapContent(ScrollViewHandler handler, IScrollView scrollView) { if (scrollView.PresentedContent?.Handler?.PlatformView is SkiaView content) { handler.PlatformView.Content = content; } else { handler.PlatformView.Content = null; } } public static void MapHorizontalScrollBarVisibility(ScrollViewHandler handler, IScrollView scrollView) { handler.PlatformView.HorizontalScrollBarVisibility = scrollView.HorizontalScrollBarVisibility switch { Microsoft.Maui.ScrollBarVisibility.Always => ScrollBarVisibility.Always, Microsoft.Maui.ScrollBarVisibility.Never => ScrollBarVisibility.Never, _ => ScrollBarVisibility.Auto }; } public static void MapVerticalScrollBarVisibility(ScrollViewHandler handler, IScrollView scrollView) { handler.PlatformView.VerticalScrollBarVisibility = scrollView.VerticalScrollBarVisibility switch { Microsoft.Maui.ScrollBarVisibility.Always => ScrollBarVisibility.Always, Microsoft.Maui.ScrollBarVisibility.Never => ScrollBarVisibility.Never, _ => ScrollBarVisibility.Auto }; } public static void MapOrientation(ScrollViewHandler handler, IScrollView scrollView) { handler.PlatformView.Orientation = scrollView.Orientation switch { Microsoft.Maui.ScrollOrientation.Horizontal => ScrollOrientation.Horizontal, Microsoft.Maui.ScrollOrientation.Both => ScrollOrientation.Both, Microsoft.Maui.ScrollOrientation.Neither => ScrollOrientation.Neither, _ => ScrollOrientation.Vertical }; } public static void MapRequestScrollTo(ScrollViewHandler handler, IScrollView scrollView, object? arg) { if (arg is ScrollToRequest request) { handler.PlatformView.ScrollTo( (float)request.HorizontalOffset, (float)request.VerticalOffset, request.Instant == false); } } } /// /// Scroll to request. /// public class ScrollToRequest { public double HorizontalOffset { get; set; } public double VerticalOffset { get; set; } public bool Instant { get; set; } }