Preview 3: Complete control implementation with XAML data binding
Major milestone adding full control functionality: Controls Enhanced: - Entry/Editor: Full keyboard input, cursor navigation, selection, clipboard - CollectionView: Data binding, selection highlighting, scrolling - CheckBox/Switch/Slider: Interactive state management - Picker/DatePicker/TimePicker: Dropdown selection with popup overlays - ProgressBar/ActivityIndicator: Animated progress display - Button: Press/release visual states - Border/Frame: Rounded corners, stroke styling - Label: Text wrapping, alignment, decorations - Grid/StackLayout: Margin and padding support Features Added: - DisplayAlert dialogs with button actions - NavigationPage with toolbar and back navigation - Shell with flyout menu navigation - XAML value converters for data binding - Margin support in all layout containers - Popup overlay system for pickers New Samples: - TodoApp: Full CRUD task manager with NavigationPage - ShellDemo: Comprehensive control showcase Removed: - ControlGallery (replaced by ShellDemo) - LinuxDemo (replaced by TodoApp) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,8 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
||||
[nameof(IActivityIndicator.IsRunning)] = MapIsRunning,
|
||||
[nameof(IActivityIndicator.Color)] = MapColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<IActivityIndicator, ActivityIndicatorHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
@@ -40,4 +42,22 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
||||
handler.PlatformView.IsEnabled = activityIndicator.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (activityIndicator.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (activityIndicator is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
137
Handlers/ApplicationHandler.cs
Normal file
137
Handlers/ApplicationHandler.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
// 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.Controls;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for MAUI Application on Linux.
|
||||
/// Bridges the MAUI Application lifecycle with LinuxApplication.
|
||||
/// </summary>
|
||||
public partial class ApplicationHandler : ElementHandler<IApplication, LinuxApplicationContext>
|
||||
{
|
||||
public static IPropertyMapper<IApplication, ApplicationHandler> Mapper =
|
||||
new PropertyMapper<IApplication, ApplicationHandler>(ElementHandler.ElementMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public static CommandMapper<IApplication, ApplicationHandler> CommandMapper =
|
||||
new(ElementHandler.ElementCommandMapper)
|
||||
{
|
||||
[nameof(IApplication.OpenWindow)] = MapOpenWindow,
|
||||
[nameof(IApplication.CloseWindow)] = MapCloseWindow,
|
||||
};
|
||||
|
||||
public ApplicationHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public ApplicationHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override LinuxApplicationContext CreatePlatformElement()
|
||||
{
|
||||
return new LinuxApplicationContext();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(LinuxApplicationContext platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.Application = VirtualView;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(LinuxApplicationContext platformView)
|
||||
{
|
||||
platformView.Application = null;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
public static void MapOpenWindow(ApplicationHandler handler, IApplication application, object? args)
|
||||
{
|
||||
if (args is IWindow window)
|
||||
{
|
||||
handler.PlatformView?.OpenWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCloseWindow(ApplicationHandler handler, IApplication application, object? args)
|
||||
{
|
||||
if (args is IWindow window)
|
||||
{
|
||||
handler.PlatformView?.CloseWindow(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Platform context for the MAUI Application on Linux.
|
||||
/// Manages windows and the application lifecycle.
|
||||
/// </summary>
|
||||
public class LinuxApplicationContext
|
||||
{
|
||||
private readonly List<IWindow> _windows = new();
|
||||
private IApplication? _application;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MAUI Application.
|
||||
/// </summary>
|
||||
public IApplication? Application
|
||||
{
|
||||
get => _application;
|
||||
set
|
||||
{
|
||||
_application = value;
|
||||
if (_application != null)
|
||||
{
|
||||
// Initialize windows from the application
|
||||
foreach (var window in _application.Windows)
|
||||
{
|
||||
if (!_windows.Contains(window))
|
||||
{
|
||||
_windows.Add(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of open windows.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IWindow> Windows => _windows;
|
||||
|
||||
/// <summary>
|
||||
/// Opens a window and creates its handler.
|
||||
/// </summary>
|
||||
public void OpenWindow(IWindow window)
|
||||
{
|
||||
if (!_windows.Contains(window))
|
||||
{
|
||||
_windows.Add(window);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a window and cleans up its handler.
|
||||
/// </summary>
|
||||
public void CloseWindow(IWindow window)
|
||||
{
|
||||
_windows.Remove(window);
|
||||
|
||||
if (_windows.Count == 0)
|
||||
{
|
||||
// Last window closed, stop the application
|
||||
LinuxApplication.Current?.MainWindow?.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main window of the application.
|
||||
/// </summary>
|
||||
public IWindow? MainWindow => _windows.Count > 0 ? _windows[0] : null;
|
||||
}
|
||||
@@ -20,7 +20,9 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
[nameof(IBorderView.Content)] = MapContent,
|
||||
[nameof(IBorderStroke.Stroke)] = MapStroke,
|
||||
[nameof(IBorderStroke.StrokeThickness)] = MapStrokeThickness,
|
||||
["StrokeShape"] = MapStrokeShape, // StrokeShape is on Border, not IBorderStroke
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
};
|
||||
|
||||
@@ -55,13 +57,25 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
|
||||
public static void MapContent(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
handler.PlatformView.ClearChildren();
|
||||
|
||||
if (border.PresentedContent?.Handler?.PlatformView is SkiaView skiaContent)
|
||||
var content = border.PresentedContent;
|
||||
if (content != null)
|
||||
{
|
||||
handler.PlatformView.AddChild(skiaContent);
|
||||
// Create handler for content if it doesn't exist
|
||||
if (content.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[BorderHandler] Creating handler for content: {content.GetType().Name}");
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
Console.WriteLine($"[BorderHandler] Adding content: {skiaContent.GetType().Name}");
|
||||
handler.PlatformView.AddChild(skiaContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +105,17 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPadding(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
@@ -101,4 +126,33 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
handler.PlatformView.PaddingRight = (float)padding.Right;
|
||||
handler.PlatformView.PaddingBottom = (float)padding.Bottom;
|
||||
}
|
||||
|
||||
public static void MapStrokeShape(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// StrokeShape is on the Border control class, not IBorderView interface
|
||||
if (border is not Border borderControl) return;
|
||||
|
||||
var shape = borderControl.StrokeShape;
|
||||
if (shape is Microsoft.Maui.Controls.Shapes.RoundRectangle roundRect)
|
||||
{
|
||||
// RoundRectangle can have different corner radii, but we use a uniform one
|
||||
// Take the top-left corner as the uniform radius
|
||||
var cornerRadius = roundRect.CornerRadius;
|
||||
handler.PlatformView.CornerRadius = (float)cornerRadius.TopLeft;
|
||||
}
|
||||
else if (shape is Microsoft.Maui.Controls.Shapes.Rectangle)
|
||||
{
|
||||
handler.PlatformView.CornerRadius = 0;
|
||||
}
|
||||
else if (shape is Microsoft.Maui.Controls.Shapes.Ellipse)
|
||||
{
|
||||
// For ellipse, use half the min dimension as corner radius
|
||||
// This will be applied during rendering when bounds are known
|
||||
handler.PlatformView.CornerRadius = float.MaxValue; // Marker for "fully rounded"
|
||||
}
|
||||
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
67
Handlers/BoxViewHandler.cs
Normal file
67
Handlers/BoxViewHandler.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for BoxView on Linux.
|
||||
/// </summary>
|
||||
public partial class BoxViewHandler : ViewHandler<BoxView, SkiaBoxView>
|
||||
{
|
||||
public static IPropertyMapper<BoxView, BoxViewHandler> Mapper =
|
||||
new PropertyMapper<BoxView, BoxViewHandler>(ViewMapper)
|
||||
{
|
||||
[nameof(BoxView.Color)] = MapColor,
|
||||
[nameof(BoxView.CornerRadius)] = MapCornerRadius,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public BoxViewHandler() : base(Mapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaBoxView CreatePlatformView()
|
||||
{
|
||||
return new SkiaBoxView();
|
||||
}
|
||||
|
||||
public static void MapColor(BoxViewHandler handler, BoxView boxView)
|
||||
{
|
||||
if (boxView.Color != null)
|
||||
{
|
||||
handler.PlatformView.Color = new SKColor(
|
||||
(byte)(boxView.Color.Red * 255),
|
||||
(byte)(boxView.Color.Green * 255),
|
||||
(byte)(boxView.Color.Blue * 255),
|
||||
(byte)(boxView.Color.Alpha * 255));
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCornerRadius(BoxViewHandler handler, BoxView boxView)
|
||||
{
|
||||
handler.PlatformView.CornerRadius = (float)boxView.CornerRadius.TopLeft;
|
||||
}
|
||||
|
||||
public static void MapBackground(BoxViewHandler handler, BoxView boxView)
|
||||
{
|
||||
if (boxView.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(BoxViewHandler handler, BoxView boxView)
|
||||
{
|
||||
if (boxView.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = boxView.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,21 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
platformView.Clicked += OnClicked;
|
||||
platformView.Pressed += OnPressed;
|
||||
platformView.Released += OnReleased;
|
||||
|
||||
// Manually map all properties on connect since MAUI may not trigger updates
|
||||
// for properties that were set before handler connection
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapText(this, VirtualView);
|
||||
MapTextColor(this, VirtualView);
|
||||
MapBackground(this, VirtualView);
|
||||
MapFont(this, VirtualView);
|
||||
MapPadding(this, VirtualView);
|
||||
MapCornerRadius(this, VirtualView);
|
||||
MapBorderColor(this, VirtualView);
|
||||
MapBorderWidth(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaButton platformView)
|
||||
@@ -105,7 +120,8 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
var background = button.Background;
|
||||
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
// Use ButtonBackgroundColor which is used for rendering, not base BackgroundColor
|
||||
handler.PlatformView.ButtonBackgroundColor = solidBrush.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
@@ -156,6 +172,7 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
|
||||
public static void MapIsEnabled(ButtonHandler handler, IButton button)
|
||||
{
|
||||
Console.WriteLine($"[ButtonHandler] MapIsEnabled called - Text='{handler.PlatformView.Text}', IsEnabled={button.IsEnabled}");
|
||||
handler.PlatformView.IsEnabled = button.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
[nameof(IButtonStroke.CornerRadius)] = MapCornerRadius,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
public static CommandMapper<IButton, ButtonHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -47,6 +48,18 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
platformView.Clicked += OnClicked;
|
||||
platformView.Pressed += OnPressed;
|
||||
platformView.Released += OnReleased;
|
||||
|
||||
// Manually map all properties on connect since MAUI may not trigger updates
|
||||
// for properties that were set before handler connection
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapStrokeColor(this, VirtualView);
|
||||
MapStrokeThickness(this, VirtualView);
|
||||
MapCornerRadius(this, VirtualView);
|
||||
MapBackground(this, VirtualView);
|
||||
MapPadding(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaButton platformView)
|
||||
@@ -88,7 +101,8 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
|
||||
if (button.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
// Set ButtonBackgroundColor (used for rendering) not base BackgroundColor
|
||||
handler.PlatformView.ButtonBackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +117,14 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
Console.WriteLine($"[ButtonHandler] MapIsEnabled - Text='{handler.PlatformView.Text}', IsEnabled={button.IsEnabled}");
|
||||
handler.PlatformView.IsEnabled = button.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,6 +146,21 @@ public partial class TextButtonHandler : ButtonHandler
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaButton platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Manually map text properties on connect since MAUI may not trigger updates
|
||||
// for properties that were set before handler connection
|
||||
if (VirtualView is ITextButton textButton)
|
||||
{
|
||||
MapText(this, textButton);
|
||||
MapTextColor(this, textButton);
|
||||
MapFont(this, textButton);
|
||||
MapCharacterSpacing(this, textButton);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapText(TextButtonHandler handler, ITextButton button)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
@@ -19,6 +19,8 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
[nameof(ICheckBox.IsChecked)] = MapIsChecked,
|
||||
[nameof(ICheckBox.Foreground)] = MapForeground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -90,4 +92,22 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
handler.PlatformView.IsEnabled = checkBox.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (checkBox.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (checkBox is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
[nameof(ICheckBox.IsChecked)] = MapIsChecked,
|
||||
[nameof(ICheckBox.Foreground)] = MapForeground,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
||||
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
||||
};
|
||||
|
||||
public static CommandMapper<ICheckBox, CheckBoxHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -83,4 +85,32 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapVerticalLayoutAlignment(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.VerticalOptions = checkBox.VerticalLayoutAlignment switch
|
||||
{
|
||||
Primitives.LayoutAlignment.Start => LayoutOptions.Start,
|
||||
Primitives.LayoutAlignment.Center => LayoutOptions.Center,
|
||||
Primitives.LayoutAlignment.End => LayoutOptions.End,
|
||||
Primitives.LayoutAlignment.Fill => LayoutOptions.Fill,
|
||||
_ => LayoutOptions.Fill
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapHorizontalLayoutAlignment(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.HorizontalOptions = checkBox.HorizontalLayoutAlignment switch
|
||||
{
|
||||
Primitives.LayoutAlignment.Start => LayoutOptions.Start,
|
||||
Primitives.LayoutAlignment.Center => LayoutOptions.Center,
|
||||
Primitives.LayoutAlignment.End => LayoutOptions.End,
|
||||
Primitives.LayoutAlignment.Fill => LayoutOptions.Fill,
|
||||
_ => LayoutOptions.Start
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
/// </summary>
|
||||
public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCollectionView>
|
||||
{
|
||||
private bool _isUpdatingSelection;
|
||||
|
||||
public static IPropertyMapper<CollectionView, CollectionViewHandler> Mapper =
|
||||
new PropertyMapper<CollectionView, CollectionViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
@@ -36,6 +38,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
[nameof(StructuredItemsView.ItemsLayout)] = MapItemsLayout,
|
||||
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(CollectionView.BackgroundColor)] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<CollectionView, CollectionViewHandler> CommandMapper =
|
||||
@@ -76,21 +79,34 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
|
||||
private void OnSelectionChanged(object? sender, CollectionSelectionChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is null) return;
|
||||
if (VirtualView is null || _isUpdatingSelection) return;
|
||||
|
||||
// Update virtual view selection
|
||||
if (VirtualView.SelectionMode == SelectionMode.Single)
|
||||
try
|
||||
{
|
||||
VirtualView.SelectedItem = e.CurrentSelection.FirstOrDefault();
|
||||
}
|
||||
else if (VirtualView.SelectionMode == SelectionMode.Multiple)
|
||||
{
|
||||
// Clear and update selected items
|
||||
VirtualView.SelectedItems.Clear();
|
||||
foreach (var item in e.CurrentSelection)
|
||||
_isUpdatingSelection = true;
|
||||
|
||||
// Update virtual view selection
|
||||
if (VirtualView.SelectionMode == SelectionMode.Single)
|
||||
{
|
||||
VirtualView.SelectedItems.Add(item);
|
||||
var newItem = e.CurrentSelection.FirstOrDefault();
|
||||
if (!Equals(VirtualView.SelectedItem, newItem))
|
||||
{
|
||||
VirtualView.SelectedItem = newItem;
|
||||
}
|
||||
}
|
||||
else if (VirtualView.SelectionMode == SelectionMode.Multiple)
|
||||
{
|
||||
// Clear and update selected items
|
||||
VirtualView.SelectedItems.Clear();
|
||||
foreach (var item in e.CurrentSelection)
|
||||
{
|
||||
VirtualView.SelectedItems.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingSelection = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +134,65 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
|
||||
public static void MapItemTemplate(CollectionViewHandler handler, CollectionView collectionView)
|
||||
{
|
||||
handler.PlatformView?.Invalidate();
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
var template = collectionView.ItemTemplate;
|
||||
if (template != null)
|
||||
{
|
||||
// Set up a renderer that creates views from the DataTemplate
|
||||
handler.PlatformView.ItemViewCreator = (item) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create view from template
|
||||
var content = template.CreateContent();
|
||||
if (content is View view)
|
||||
{
|
||||
// Set binding context FIRST so bindings evaluate
|
||||
view.BindingContext = item;
|
||||
|
||||
// Force binding evaluation by accessing the visual tree
|
||||
// This ensures child bindings are evaluated before handler creation
|
||||
PropagateBindingContext(view, item);
|
||||
|
||||
// Create handler for the view
|
||||
if (view.Handler == null && handler.MauiContext != null)
|
||||
{
|
||||
view.Handler = view.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (view.Handler?.PlatformView is SkiaView skiaView)
|
||||
{
|
||||
return skiaView;
|
||||
}
|
||||
}
|
||||
else if (content is ViewCell cell)
|
||||
{
|
||||
cell.BindingContext = item;
|
||||
var cellView = cell.View;
|
||||
if (cellView != null)
|
||||
{
|
||||
if (cellView.Handler == null && handler.MauiContext != null)
|
||||
{
|
||||
cellView.Handler = cellView.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (cellView.Handler?.PlatformView is SkiaView skiaView)
|
||||
{
|
||||
return skiaView;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore template creation errors
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapEmptyView(CollectionViewHandler handler, CollectionView collectionView)
|
||||
@@ -146,19 +220,40 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
|
||||
public static void MapSelectedItem(CollectionViewHandler handler, CollectionView collectionView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.SelectedItem = collectionView.SelectedItem;
|
||||
if (handler.PlatformView is null || handler._isUpdatingSelection) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler._isUpdatingSelection = true;
|
||||
if (!Equals(handler.PlatformView.SelectedItem, collectionView.SelectedItem))
|
||||
{
|
||||
handler.PlatformView.SelectedItem = collectionView.SelectedItem;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler._isUpdatingSelection = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSelectedItems(CollectionViewHandler handler, CollectionView collectionView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
if (handler.PlatformView is null || handler._isUpdatingSelection) return;
|
||||
|
||||
// Sync selected items
|
||||
var selectedItems = collectionView.SelectedItems;
|
||||
if (selectedItems != null && selectedItems.Count > 0)
|
||||
try
|
||||
{
|
||||
handler.PlatformView.SelectedItem = selectedItems.First();
|
||||
handler._isUpdatingSelection = true;
|
||||
|
||||
// Sync selected items
|
||||
var selectedItems = collectionView.SelectedItems;
|
||||
if (selectedItems != null && selectedItems.Count > 0)
|
||||
{
|
||||
handler.PlatformView.SelectedItem = selectedItems.First();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler._isUpdatingSelection = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,12 +309,26 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// Don't override if BackgroundColor is explicitly set
|
||||
if (collectionView.BackgroundColor is not null)
|
||||
return;
|
||||
|
||||
if (collectionView.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(CollectionViewHandler handler, CollectionView collectionView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (collectionView.BackgroundColor is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = collectionView.BackgroundColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapScrollTo(CollectionViewHandler handler, CollectionView collectionView, object? args)
|
||||
{
|
||||
if (handler.PlatformView is null || args is not ScrollToRequestEventArgs scrollArgs)
|
||||
@@ -234,4 +343,32 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
handler.PlatformView.ScrollToItem(scrollArgs.Item, scrollArgs.IsAnimated);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively propagates binding context to all child views to force binding evaluation.
|
||||
/// </summary>
|
||||
private static void PropagateBindingContext(View view, object? bindingContext)
|
||||
{
|
||||
view.BindingContext = bindingContext;
|
||||
|
||||
// Propagate to children
|
||||
if (view is Layout layout)
|
||||
{
|
||||
foreach (var child in layout.Children)
|
||||
{
|
||||
if (child is View childView)
|
||||
{
|
||||
PropagateBindingContext(childView, bindingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (view is ContentView contentView && contentView.Content != null)
|
||||
{
|
||||
PropagateBindingContext(contentView.Content, bindingContext);
|
||||
}
|
||||
else if (view is Border border && border.Content is View borderContent)
|
||||
{
|
||||
PropagateBindingContext(borderContent, bindingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
[nameof(IEditor.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(IEditor.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<IEditor, EditorHandler> CommandMapper =
|
||||
@@ -82,6 +83,7 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.Text = editor.Text ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPlaceholder(EditorHandler handler, IEditor editor)
|
||||
@@ -165,4 +167,15 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (editor is VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
[nameof(IEntry.ReturnType)] = MapReturnType,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IEntry.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -186,4 +187,13 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (entry is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,10 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (handler.PlatformView.Text != entry.Text)
|
||||
{
|
||||
handler.PlatformView.Text = entry.Text ?? string.Empty;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextColor(EntryHandler handler, IEntry entry)
|
||||
|
||||
104
Handlers/FrameHandler.cs
Normal file
104
Handlers/FrameHandler.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
// 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.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for Frame on Linux using SkiaFrame.
|
||||
/// </summary>
|
||||
public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
||||
{
|
||||
public static IPropertyMapper<Frame, FrameHandler> Mapper =
|
||||
new PropertyMapper<Frame, FrameHandler>(ViewMapper)
|
||||
{
|
||||
[nameof(Frame.BorderColor)] = MapBorderColor,
|
||||
[nameof(Frame.CornerRadius)] = MapCornerRadius,
|
||||
[nameof(Frame.HasShadow)] = MapHasShadow,
|
||||
[nameof(Frame.BackgroundColor)] = MapBackgroundColor,
|
||||
[nameof(Frame.Padding)] = MapPadding,
|
||||
[nameof(Frame.Content)] = MapContent,
|
||||
};
|
||||
|
||||
public FrameHandler() : base(Mapper)
|
||||
{
|
||||
}
|
||||
|
||||
public FrameHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaFrame CreatePlatformView()
|
||||
{
|
||||
return new SkiaFrame();
|
||||
}
|
||||
|
||||
public static void MapBorderColor(FrameHandler handler, Frame frame)
|
||||
{
|
||||
if (frame.BorderColor != null)
|
||||
{
|
||||
handler.PlatformView.Stroke = new SKColor(
|
||||
(byte)(frame.BorderColor.Red * 255),
|
||||
(byte)(frame.BorderColor.Green * 255),
|
||||
(byte)(frame.BorderColor.Blue * 255),
|
||||
(byte)(frame.BorderColor.Alpha * 255));
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCornerRadius(FrameHandler handler, Frame frame)
|
||||
{
|
||||
handler.PlatformView.CornerRadius = frame.CornerRadius;
|
||||
}
|
||||
|
||||
public static void MapHasShadow(FrameHandler handler, Frame frame)
|
||||
{
|
||||
handler.PlatformView.HasShadow = frame.HasShadow;
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(FrameHandler handler, Frame frame)
|
||||
{
|
||||
if (frame.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = new SKColor(
|
||||
(byte)(frame.BackgroundColor.Red * 255),
|
||||
(byte)(frame.BackgroundColor.Green * 255),
|
||||
(byte)(frame.BackgroundColor.Blue * 255),
|
||||
(byte)(frame.BackgroundColor.Alpha * 255));
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPadding(FrameHandler handler, Frame frame)
|
||||
{
|
||||
handler.PlatformView.SetPadding(
|
||||
(float)frame.Padding.Left,
|
||||
(float)frame.Padding.Top,
|
||||
(float)frame.Padding.Right,
|
||||
(float)frame.Padding.Bottom);
|
||||
}
|
||||
|
||||
public static void MapContent(FrameHandler handler, Frame frame)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
handler.PlatformView.ClearChildren();
|
||||
|
||||
var content = frame.Content;
|
||||
if (content != null)
|
||||
{
|
||||
// Create handler for content if it doesn't exist
|
||||
if (content.Handler == null)
|
||||
{
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
handler.PlatformView.AddChild(skiaContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
[nameof(ILabel.Padding)] = MapPadding,
|
||||
[nameof(ILabel.TextDecorations)] = MapTextDecorations,
|
||||
[nameof(ILabel.LineHeight)] = MapLineHeight,
|
||||
[nameof(ILabel.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -151,4 +153,22 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
handler.PlatformView.LineHeight = (float)label.LineHeight;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (label.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (label is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,12 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
[nameof(ITextAlignment.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
[nameof(ILabel.TextDecorations)] = MapTextDecorations,
|
||||
[nameof(ILabel.LineHeight)] = MapLineHeight,
|
||||
["LineBreakMode"] = MapLineBreakMode,
|
||||
["MaxLines"] = MapMaxLines,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
||||
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
||||
};
|
||||
|
||||
public static CommandMapper<ILabel, LabelHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -121,6 +125,37 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
handler.PlatformView.LineHeight = (float)label.LineHeight;
|
||||
}
|
||||
|
||||
public static void MapLineBreakMode(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// LineBreakMode is on Label control, not ILabel interface
|
||||
if (label is Microsoft.Maui.Controls.Label mauiLabel)
|
||||
{
|
||||
handler.PlatformView.LineBreakMode = mauiLabel.LineBreakMode switch
|
||||
{
|
||||
Microsoft.Maui.LineBreakMode.NoWrap => Platform.LineBreakMode.NoWrap,
|
||||
Microsoft.Maui.LineBreakMode.WordWrap => Platform.LineBreakMode.WordWrap,
|
||||
Microsoft.Maui.LineBreakMode.CharacterWrap => Platform.LineBreakMode.CharacterWrap,
|
||||
Microsoft.Maui.LineBreakMode.HeadTruncation => Platform.LineBreakMode.HeadTruncation,
|
||||
Microsoft.Maui.LineBreakMode.TailTruncation => Platform.LineBreakMode.TailTruncation,
|
||||
Microsoft.Maui.LineBreakMode.MiddleTruncation => Platform.LineBreakMode.MiddleTruncation,
|
||||
_ => Platform.LineBreakMode.TailTruncation
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapMaxLines(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// MaxLines is on Label control, not ILabel interface
|
||||
if (label is Microsoft.Maui.Controls.Label mauiLabel)
|
||||
{
|
||||
handler.PlatformView.MaxLines = mauiLabel.MaxLines;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPadding(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
@@ -142,4 +177,32 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapVerticalLayoutAlignment(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.VerticalOptions = label.VerticalLayoutAlignment switch
|
||||
{
|
||||
Primitives.LayoutAlignment.Start => LayoutOptions.Start,
|
||||
Primitives.LayoutAlignment.Center => LayoutOptions.Center,
|
||||
Primitives.LayoutAlignment.End => LayoutOptions.End,
|
||||
Primitives.LayoutAlignment.Fill => LayoutOptions.Fill,
|
||||
_ => LayoutOptions.Start
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapHorizontalLayoutAlignment(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.HorizontalOptions = label.HorizontalLayoutAlignment switch
|
||||
{
|
||||
Primitives.LayoutAlignment.Start => LayoutOptions.Start,
|
||||
Primitives.LayoutAlignment.Center => LayoutOptions.Center,
|
||||
Primitives.LayoutAlignment.End => LayoutOptions.End,
|
||||
Primitives.LayoutAlignment.Fill => LayoutOptions.Fill,
|
||||
_ => LayoutOptions.Start
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
public static IPropertyMapper<ILayout, LayoutHandler> Mapper = new PropertyMapper<ILayout, LayoutHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(ILayout.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
[nameof(ILayout.ClipsToBounds)] = MapClipsToBounds,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -53,8 +55,46 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
return new SkiaStackLayout();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaLayoutView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Explicitly map BackgroundColor since it may be set before handler creation
|
||||
// (e.g., in ItemTemplates for CollectionView)
|
||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
platformView.Invalidate();
|
||||
}
|
||||
|
||||
// Add existing children (important for template-created views)
|
||||
if (VirtualView is ILayout layout && MauiContext != null)
|
||||
{
|
||||
for (int i = 0; i < layout.Count; i++)
|
||||
{
|
||||
var child = layout[i];
|
||||
if (child == null) continue;
|
||||
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
}
|
||||
|
||||
if (child.Handler?.PlatformView is SkiaView skiaChild)
|
||||
{
|
||||
platformView.AddChild(skiaChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackground(LayoutHandler handler, ILayout layout)
|
||||
{
|
||||
// Don't override if BackgroundColor is explicitly set
|
||||
if (layout is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
return;
|
||||
|
||||
var background = layout.Background;
|
||||
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
@@ -63,12 +103,36 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(LayoutHandler handler, ILayout layout)
|
||||
{
|
||||
if (layout is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapClipsToBounds(LayoutHandler handler, ILayout layout)
|
||||
{
|
||||
handler.PlatformView.ClipToBounds = layout.ClipsToBounds;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPadding(LayoutHandler handler, ILayout layout)
|
||||
{
|
||||
if (layout is IPadding paddable)
|
||||
{
|
||||
var padding = paddable.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
handler.PlatformView.InvalidateMeasure();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapAdd(LayoutHandler handler, ILayout layout, object? arg)
|
||||
{
|
||||
if (arg is LayoutHandlerUpdate update)
|
||||
@@ -194,9 +258,16 @@ public partial class GridHandler : LayoutHandler
|
||||
{
|
||||
[nameof(IGridLayout.ColumnSpacing)] = MapColumnSpacing,
|
||||
[nameof(IGridLayout.RowSpacing)] = MapRowSpacing,
|
||||
[nameof(IGridLayout.RowDefinitions)] = MapRowDefinitions,
|
||||
[nameof(IGridLayout.ColumnDefinitions)] = MapColumnDefinitions,
|
||||
};
|
||||
|
||||
public GridHandler() : base(Mapper)
|
||||
public static new CommandMapper<IGridLayout, GridHandler> GridCommandMapper = new(LayoutHandler.CommandMapper)
|
||||
{
|
||||
["Add"] = MapGridAdd,
|
||||
};
|
||||
|
||||
public GridHandler() : base(Mapper, GridCommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -205,6 +276,52 @@ public partial class GridHandler : LayoutHandler
|
||||
return new SkiaGrid();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaLayoutView platformView)
|
||||
{
|
||||
Console.WriteLine($"[GridHandler.ConnectHandler] Called! VirtualView={VirtualView?.GetType().Name}, PlatformView={platformView?.GetType().Name}, MauiContext={(MauiContext != null ? "set" : "null")}");
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Map definitions on connect
|
||||
if (VirtualView is IGridLayout gridLayout && platformView is SkiaGrid grid && MauiContext != null)
|
||||
{
|
||||
Console.WriteLine($"[GridHandler.ConnectHandler] Grid has {gridLayout.Count} children, RowDefs={gridLayout.RowDefinitions?.Count ?? 0}");
|
||||
UpdateRowDefinitions(grid, gridLayout);
|
||||
UpdateColumnDefinitions(grid, gridLayout);
|
||||
|
||||
// Add existing children (important for template-created views)
|
||||
for (int i = 0; i < gridLayout.Count; i++)
|
||||
{
|
||||
var child = gridLayout[i];
|
||||
if (child == null) continue;
|
||||
|
||||
Console.WriteLine($"[GridHandler.ConnectHandler] Child[{i}]: {child.GetType().Name}, Handler={child.Handler?.GetType().Name ?? "null"}");
|
||||
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
Console.WriteLine($"[GridHandler.ConnectHandler] Created handler for child[{i}]: {child.Handler?.GetType().Name ?? "failed"}");
|
||||
}
|
||||
|
||||
if (child.Handler?.PlatformView is SkiaView skiaChild)
|
||||
{
|
||||
// Get grid position from attached properties
|
||||
int row = 0, column = 0, rowSpan = 1, columnSpan = 1;
|
||||
if (child is Microsoft.Maui.Controls.View mauiView)
|
||||
{
|
||||
row = Microsoft.Maui.Controls.Grid.GetRow(mauiView);
|
||||
column = Microsoft.Maui.Controls.Grid.GetColumn(mauiView);
|
||||
rowSpan = Microsoft.Maui.Controls.Grid.GetRowSpan(mauiView);
|
||||
columnSpan = Microsoft.Maui.Controls.Grid.GetColumnSpan(mauiView);
|
||||
}
|
||||
Console.WriteLine($"[GridHandler.ConnectHandler] Adding child[{i}] at row={row}, col={column}");
|
||||
grid.AddChild(skiaChild, row, column, rowSpan, columnSpan);
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"[GridHandler.ConnectHandler] Grid now has {grid.Children.Count} SkiaView children");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapColumnSpacing(GridHandler handler, IGridLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaGrid grid)
|
||||
@@ -222,6 +339,79 @@ public partial class GridHandler : LayoutHandler
|
||||
grid.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapRowDefinitions(GridHandler handler, IGridLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaGrid grid)
|
||||
{
|
||||
UpdateRowDefinitions(grid, layout);
|
||||
grid.InvalidateMeasure();
|
||||
grid.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapColumnDefinitions(GridHandler handler, IGridLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaGrid grid)
|
||||
{
|
||||
UpdateColumnDefinitions(grid, layout);
|
||||
grid.InvalidateMeasure();
|
||||
grid.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateRowDefinitions(SkiaGrid grid, IGridLayout layout)
|
||||
{
|
||||
grid.RowDefinitions.Clear();
|
||||
foreach (var rowDef in layout.RowDefinitions)
|
||||
{
|
||||
var height = rowDef.Height;
|
||||
if (height.IsAbsolute)
|
||||
grid.RowDefinitions.Add(new GridLength((float)height.Value, GridUnitType.Absolute));
|
||||
else if (height.IsAuto)
|
||||
grid.RowDefinitions.Add(GridLength.Auto);
|
||||
else // Star
|
||||
grid.RowDefinitions.Add(new GridLength((float)height.Value, GridUnitType.Star));
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateColumnDefinitions(SkiaGrid grid, IGridLayout layout)
|
||||
{
|
||||
grid.ColumnDefinitions.Clear();
|
||||
foreach (var colDef in layout.ColumnDefinitions)
|
||||
{
|
||||
var width = colDef.Width;
|
||||
if (width.IsAbsolute)
|
||||
grid.ColumnDefinitions.Add(new GridLength((float)width.Value, GridUnitType.Absolute));
|
||||
else if (width.IsAuto)
|
||||
grid.ColumnDefinitions.Add(GridLength.Auto);
|
||||
else // Star
|
||||
grid.ColumnDefinitions.Add(new GridLength((float)width.Value, GridUnitType.Star));
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapGridAdd(GridHandler handler, ILayout layout, object? arg)
|
||||
{
|
||||
if (arg is LayoutHandlerUpdate update && handler.PlatformView is SkiaGrid grid)
|
||||
{
|
||||
var childHandler = update.View.Handler;
|
||||
if (childHandler?.PlatformView is SkiaView skiaView)
|
||||
{
|
||||
// Get grid position from attached properties
|
||||
int row = 0, column = 0, rowSpan = 1, columnSpan = 1;
|
||||
|
||||
if (update.View is Microsoft.Maui.Controls.View mauiView)
|
||||
{
|
||||
row = Microsoft.Maui.Controls.Grid.GetRow(mauiView);
|
||||
column = Microsoft.Maui.Controls.Grid.GetColumn(mauiView);
|
||||
rowSpan = Microsoft.Maui.Controls.Grid.GetRowSpan(mauiView);
|
||||
columnSpan = Microsoft.Maui.Controls.Grid.GetColumnSpan(mauiView);
|
||||
}
|
||||
|
||||
grid.AddChild(skiaView, row, column, rowSpan, columnSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,6 +17,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
{
|
||||
[nameof(ILayout.ClipsToBounds)] = MapClipsToBounds,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
};
|
||||
|
||||
public static CommandMapper<ILayout, LayoutHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -42,6 +43,38 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
return new SkiaStackLayout();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaLayoutView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Create handlers for all children and add them to the platform view
|
||||
if (VirtualView == null || MauiContext == null) return;
|
||||
|
||||
// Explicitly map BackgroundColor since it may be set before handler creation
|
||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
}
|
||||
|
||||
for (int i = 0; i < VirtualView.Count; i++)
|
||||
{
|
||||
var child = VirtualView[i];
|
||||
if (child == null) continue;
|
||||
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
}
|
||||
|
||||
// Add child's platform view to our layout
|
||||
if (child.Handler?.PlatformView is SkiaView skiaChild)
|
||||
{
|
||||
platformView.AddChild(skiaChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapClipsToBounds(LayoutHandler handler, ILayout layout)
|
||||
{
|
||||
if (handler.PlatformView == null) return;
|
||||
@@ -102,6 +135,23 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
// Force re-layout
|
||||
handler.PlatformView?.InvalidateMeasure();
|
||||
}
|
||||
|
||||
public static void MapPadding(LayoutHandler handler, ILayout layout)
|
||||
{
|
||||
if (handler.PlatformView == null) return;
|
||||
|
||||
if (layout is IPadding paddable)
|
||||
{
|
||||
var padding = paddable.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
handler.PlatformView.InvalidateMeasure();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -138,6 +188,29 @@ public partial class StackLayoutHandler : LayoutHandler
|
||||
return new SkiaStackLayout();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaLayoutView platformView)
|
||||
{
|
||||
// Set orientation first
|
||||
if (platformView is SkiaStackLayout stackLayout && VirtualView is IStackLayout stackView)
|
||||
{
|
||||
// Determine orientation based on view type
|
||||
if (VirtualView is Microsoft.Maui.Controls.HorizontalStackLayout)
|
||||
{
|
||||
stackLayout.Orientation = StackOrientation.Horizontal;
|
||||
}
|
||||
else if (VirtualView is Microsoft.Maui.Controls.VerticalStackLayout ||
|
||||
VirtualView is Microsoft.Maui.Controls.StackLayout)
|
||||
{
|
||||
stackLayout.Orientation = StackOrientation.Vertical;
|
||||
}
|
||||
|
||||
stackLayout.Spacing = (float)stackView.Spacing;
|
||||
}
|
||||
|
||||
// Let base handle children
|
||||
base.ConnectHandler(platformView);
|
||||
}
|
||||
|
||||
public static void MapSpacing(StackLayoutHandler handler, IStackLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaStackLayout stackLayout)
|
||||
@@ -156,6 +229,8 @@ public partial class GridHandler : LayoutHandler
|
||||
{
|
||||
[nameof(IGridLayout.RowSpacing)] = MapRowSpacing,
|
||||
[nameof(IGridLayout.ColumnSpacing)] = MapColumnSpacing,
|
||||
[nameof(IGridLayout.RowDefinitions)] = MapRowDefinitions,
|
||||
[nameof(IGridLayout.ColumnDefinitions)] = MapColumnDefinitions,
|
||||
};
|
||||
|
||||
public GridHandler() : base(Mapper)
|
||||
@@ -167,6 +242,80 @@ public partial class GridHandler : LayoutHandler
|
||||
return new SkiaGrid();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaLayoutView platformView)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Don't call base - we handle children specially for Grid
|
||||
if (VirtualView is not IGridLayout gridLayout || MauiContext == null || platformView is not SkiaGrid grid) return;
|
||||
|
||||
Console.WriteLine($"[GridHandler] ConnectHandler: {gridLayout.Count} children, {gridLayout.RowDefinitions.Count} rows, {gridLayout.ColumnDefinitions.Count} cols");
|
||||
|
||||
// Explicitly map BackgroundColor since it may be set before handler creation
|
||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
}
|
||||
|
||||
// Explicitly map Padding since it may be set before handler creation
|
||||
if (VirtualView is IPadding paddable)
|
||||
{
|
||||
var padding = paddable.Padding;
|
||||
platformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
Console.WriteLine($"[GridHandler] Applied Padding: L={padding.Left}, T={padding.Top}, R={padding.Right}, B={padding.Bottom}");
|
||||
}
|
||||
|
||||
// Map row/column definitions first
|
||||
MapRowDefinitions(this, gridLayout);
|
||||
MapColumnDefinitions(this, gridLayout);
|
||||
|
||||
// Add each child with its row/column position
|
||||
for (int i = 0; i < gridLayout.Count; i++)
|
||||
{
|
||||
var child = gridLayout[i];
|
||||
if (child == null) continue;
|
||||
|
||||
Console.WriteLine($"[GridHandler] Processing child {i}: {child.GetType().Name}");
|
||||
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
}
|
||||
|
||||
// Get grid position from attached properties
|
||||
int row = 0, column = 0, rowSpan = 1, columnSpan = 1;
|
||||
if (child is Microsoft.Maui.Controls.View mauiView)
|
||||
{
|
||||
row = Microsoft.Maui.Controls.Grid.GetRow(mauiView);
|
||||
column = Microsoft.Maui.Controls.Grid.GetColumn(mauiView);
|
||||
rowSpan = Microsoft.Maui.Controls.Grid.GetRowSpan(mauiView);
|
||||
columnSpan = Microsoft.Maui.Controls.Grid.GetColumnSpan(mauiView);
|
||||
}
|
||||
|
||||
Console.WriteLine($"[GridHandler] Child {i} at row={row}, col={column}, handler={child.Handler?.GetType().Name}");
|
||||
|
||||
// Add child's platform view to our grid
|
||||
if (child.Handler?.PlatformView is SkiaView skiaChild)
|
||||
{
|
||||
grid.AddChild(skiaChild, row, column, rowSpan, columnSpan);
|
||||
Console.WriteLine($"[GridHandler] Added child {i} to grid");
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"[GridHandler] ConnectHandler complete");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GridHandler] EXCEPTION in ConnectHandler: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine($"[GridHandler] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapRowSpacing(GridHandler handler, IGridLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaGrid grid)
|
||||
@@ -182,4 +331,38 @@ public partial class GridHandler : LayoutHandler
|
||||
grid.ColumnSpacing = (float)layout.ColumnSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapRowDefinitions(GridHandler handler, IGridLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is not SkiaGrid grid) return;
|
||||
|
||||
grid.RowDefinitions.Clear();
|
||||
foreach (var rowDef in layout.RowDefinitions)
|
||||
{
|
||||
var height = rowDef.Height;
|
||||
if (height.IsAbsolute)
|
||||
grid.RowDefinitions.Add(new Microsoft.Maui.Platform.GridLength((float)height.Value, Microsoft.Maui.Platform.GridUnitType.Absolute));
|
||||
else if (height.IsAuto)
|
||||
grid.RowDefinitions.Add(Microsoft.Maui.Platform.GridLength.Auto);
|
||||
else // Star
|
||||
grid.RowDefinitions.Add(new Microsoft.Maui.Platform.GridLength((float)height.Value, Microsoft.Maui.Platform.GridUnitType.Star));
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapColumnDefinitions(GridHandler handler, IGridLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is not SkiaGrid grid) return;
|
||||
|
||||
grid.ColumnDefinitions.Clear();
|
||||
foreach (var colDef in layout.ColumnDefinitions)
|
||||
{
|
||||
var width = colDef.Width;
|
||||
if (width.IsAbsolute)
|
||||
grid.ColumnDefinitions.Add(new Microsoft.Maui.Platform.GridLength((float)width.Value, Microsoft.Maui.Platform.GridUnitType.Absolute));
|
||||
else if (width.IsAuto)
|
||||
grid.ColumnDefinitions.Add(Microsoft.Maui.Platform.GridLength.Auto);
|
||||
else // Star
|
||||
grid.ColumnDefinitions.Add(new Microsoft.Maui.Platform.GridLength((float)width.Value, Microsoft.Maui.Platform.GridUnitType.Star));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
@@ -50,10 +51,15 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
platformView.Popped += OnPopped;
|
||||
platformView.PoppedToRoot += OnPoppedToRoot;
|
||||
|
||||
// Set initial root page if exists
|
||||
if (VirtualView.CurrentPage != null)
|
||||
// Subscribe to navigation events from virtual view
|
||||
if (VirtualView != null)
|
||||
{
|
||||
SetupInitialPage();
|
||||
VirtualView.Pushed += OnVirtualViewPushed;
|
||||
VirtualView.Popped += OnVirtualViewPopped;
|
||||
VirtualView.PoppedToRoot += OnVirtualViewPoppedToRoot;
|
||||
|
||||
// Set up initial navigation stack
|
||||
SetupNavigationStack();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,16 +68,195 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
platformView.Pushed -= OnPushed;
|
||||
platformView.Popped -= OnPopped;
|
||||
platformView.PoppedToRoot -= OnPoppedToRoot;
|
||||
|
||||
if (VirtualView != null)
|
||||
{
|
||||
VirtualView.Pushed -= OnVirtualViewPushed;
|
||||
VirtualView.Popped -= OnVirtualViewPopped;
|
||||
VirtualView.PoppedToRoot -= OnVirtualViewPoppedToRoot;
|
||||
}
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void SetupInitialPage()
|
||||
private void SetupNavigationStack()
|
||||
{
|
||||
var currentPage = VirtualView.CurrentPage;
|
||||
if (currentPage?.Handler?.PlatformView is SkiaPage skiaPage)
|
||||
if (VirtualView == null || PlatformView == null || MauiContext == null) return;
|
||||
|
||||
// Get all pages in the navigation stack
|
||||
var pages = VirtualView.Navigation.NavigationStack.ToList();
|
||||
Console.WriteLine($"[NavigationPageHandler] Setting up {pages.Count} pages");
|
||||
|
||||
// If no pages in stack, check CurrentPage
|
||||
if (pages.Count == 0 && VirtualView.CurrentPage != null)
|
||||
{
|
||||
PlatformView.SetRootPage(skiaPage);
|
||||
Console.WriteLine($"[NavigationPageHandler] No pages in stack, using CurrentPage: {VirtualView.CurrentPage.Title}");
|
||||
pages.Add(VirtualView.CurrentPage);
|
||||
}
|
||||
|
||||
foreach (var page in pages)
|
||||
{
|
||||
// Ensure the page has a handler
|
||||
if (page.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Creating handler for: {page.Title}");
|
||||
page.Handler = page.ToHandler(MauiContext);
|
||||
}
|
||||
|
||||
Console.WriteLine($"[NavigationPageHandler] Page handler type: {page.Handler?.GetType().Name}");
|
||||
Console.WriteLine($"[NavigationPageHandler] Page PlatformView type: {page.Handler?.PlatformView?.GetType().Name}");
|
||||
|
||||
if (page.Handler?.PlatformView is SkiaPage skiaPage)
|
||||
{
|
||||
// Set navigation bar properties
|
||||
skiaPage.ShowNavigationBar = true;
|
||||
skiaPage.TitleBarColor = PlatformView.BarBackgroundColor;
|
||||
skiaPage.TitleTextColor = PlatformView.BarTextColor;
|
||||
skiaPage.Title = page.Title ?? "";
|
||||
|
||||
Console.WriteLine($"[NavigationPageHandler] SkiaPage content: {skiaPage.Content?.GetType().Name ?? "null"}");
|
||||
|
||||
// If content is null, try to get it from ContentPage
|
||||
if (skiaPage.Content == null && page is ContentPage contentPage && contentPage.Content != null)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Content is null, manually creating handler for: {contentPage.Content.GetType().Name}");
|
||||
if (contentPage.Content.Handler == null)
|
||||
{
|
||||
contentPage.Content.Handler = contentPage.Content.ToHandler(MauiContext);
|
||||
}
|
||||
if (contentPage.Content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
skiaPage.Content = skiaContent;
|
||||
Console.WriteLine($"[NavigationPageHandler] Set content to: {skiaContent.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
// Map toolbar items
|
||||
MapToolbarItems(skiaPage, page);
|
||||
|
||||
if (PlatformView.StackDepth == 0)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Setting root page: {page.Title}");
|
||||
PlatformView.SetRootPage(skiaPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Pushing page: {page.Title}");
|
||||
PlatformView.Push(skiaPage, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Failed to get SkiaPage for: {page.Title}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Page, (SkiaPage, INotifyCollectionChanged)> _toolbarSubscriptions = new();
|
||||
|
||||
private void MapToolbarItems(SkiaPage skiaPage, Page page)
|
||||
{
|
||||
if (skiaPage is SkiaContentPage contentPage)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] MapToolbarItems for '{page.Title}', count={page.ToolbarItems.Count}");
|
||||
|
||||
contentPage.ToolbarItems.Clear();
|
||||
foreach (var item in page.ToolbarItems)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Adding toolbar item: '{item.Text}', Order={item.Order}");
|
||||
// Default and Primary should both be treated as Primary (shown in toolbar)
|
||||
// Only Secondary goes to overflow menu
|
||||
var order = item.Order == ToolbarItemOrder.Secondary
|
||||
? SkiaToolbarItemOrder.Secondary
|
||||
: SkiaToolbarItemOrder.Primary;
|
||||
|
||||
// Create a command that invokes the Clicked event
|
||||
var toolbarItem = item; // Capture for closure
|
||||
var clickCommand = new RelayCommand(() =>
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] ToolbarItem '{toolbarItem.Text}' clicked, invoking...");
|
||||
// Use IMenuItemController to send the click
|
||||
if (toolbarItem is IMenuItemController menuController)
|
||||
{
|
||||
menuController.Activate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: invoke Command if set
|
||||
toolbarItem.Command?.Execute(toolbarItem.CommandParameter);
|
||||
}
|
||||
});
|
||||
|
||||
contentPage.ToolbarItems.Add(new SkiaToolbarItem
|
||||
{
|
||||
Text = item.Text ?? "",
|
||||
Order = order,
|
||||
Command = clickCommand
|
||||
});
|
||||
}
|
||||
|
||||
// Subscribe to ToolbarItems changes if not already subscribed
|
||||
if (page.ToolbarItems is INotifyCollectionChanged notifyCollection && !_toolbarSubscriptions.ContainsKey(page))
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Subscribing to ToolbarItems changes for '{page.Title}'");
|
||||
notifyCollection.CollectionChanged += (s, e) =>
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] ToolbarItems changed for '{page.Title}', action={e.Action}");
|
||||
MapToolbarItems(skiaPage, page);
|
||||
skiaPage.Invalidate();
|
||||
};
|
||||
_toolbarSubscriptions[page] = (skiaPage, notifyCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVirtualViewPushed(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] VirtualView Pushed: {e.Page?.Title}");
|
||||
if (e.Page == null || PlatformView == null || MauiContext == null) return;
|
||||
|
||||
// Ensure the page has a handler
|
||||
if (e.Page.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Creating handler for page: {e.Page.GetType().Name}");
|
||||
e.Page.Handler = e.Page.ToHandler(MauiContext);
|
||||
Console.WriteLine($"[NavigationPageHandler] Handler created: {e.Page.Handler?.GetType().Name}");
|
||||
}
|
||||
|
||||
if (e.Page.Handler?.PlatformView is SkiaPage skiaPage)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Setting up skiaPage, content: {skiaPage.Content?.GetType().Name ?? "null"}");
|
||||
skiaPage.ShowNavigationBar = true;
|
||||
skiaPage.TitleBarColor = PlatformView.BarBackgroundColor;
|
||||
skiaPage.TitleTextColor = PlatformView.BarTextColor;
|
||||
Console.WriteLine($"[NavigationPageHandler] Mapping toolbar items");
|
||||
MapToolbarItems(skiaPage, e.Page);
|
||||
Console.WriteLine($"[NavigationPageHandler] Pushing page to platform");
|
||||
PlatformView.Push(skiaPage, true);
|
||||
Console.WriteLine($"[NavigationPageHandler] Push complete");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] EXCEPTION in OnVirtualViewPushed: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine($"[NavigationPageHandler] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVirtualViewPopped(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] VirtualView Popped: {e.Page?.Title}");
|
||||
// Pop on the platform side to sync with MAUI navigation
|
||||
PlatformView?.Pop(true);
|
||||
}
|
||||
|
||||
private void OnVirtualViewPoppedToRoot(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] VirtualView PoppedToRoot");
|
||||
PlatformView?.PopToRoot(true);
|
||||
}
|
||||
|
||||
private void OnPushed(object? sender, NavigationEventArgs e)
|
||||
@@ -81,7 +266,12 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
|
||||
private void OnPopped(object? sender, NavigationEventArgs e)
|
||||
{
|
||||
// Sync back to virtual view if needed
|
||||
// Sync back to virtual view - pop from MAUI navigation stack
|
||||
if (VirtualView?.Navigation.NavigationStack.Count > 1)
|
||||
{
|
||||
// Don't trigger another pop on platform side
|
||||
VirtualView.Navigation.RemovePage(VirtualView.Navigation.NavigationStack.Last());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPoppedToRoot(object? sender, NavigationEventArgs e)
|
||||
@@ -131,14 +321,29 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
|
||||
public static void MapRequestNavigation(NavigationPageHandler handler, NavigationPage navigationPage, object? args)
|
||||
{
|
||||
if (handler.PlatformView is null || args is not NavigationRequest request)
|
||||
if (handler.PlatformView is null || handler.MauiContext is null || args is not NavigationRequest request)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"[NavigationPageHandler] MapRequestNavigation: {request.NavigationStack.Count} pages");
|
||||
|
||||
// Handle navigation request
|
||||
foreach (var page in request.NavigationStack)
|
||||
foreach (var view in request.NavigationStack)
|
||||
{
|
||||
if (view is not Page page) continue;
|
||||
|
||||
// Ensure handler exists
|
||||
if (page.Handler == null)
|
||||
{
|
||||
page.Handler = page.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (page.Handler?.PlatformView is SkiaPage skiaPage)
|
||||
{
|
||||
skiaPage.ShowNavigationBar = true;
|
||||
skiaPage.TitleBarColor = handler.PlatformView.BarBackgroundColor;
|
||||
skiaPage.TitleTextColor = handler.PlatformView.BarTextColor;
|
||||
handler.MapToolbarItems(skiaPage, page);
|
||||
|
||||
if (handler.PlatformView.StackDepth == 0)
|
||||
{
|
||||
handler.PlatformView.SetRootPage(skiaPage);
|
||||
@@ -151,3 +356,26 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple relay command for invoking actions.
|
||||
/// </summary>
|
||||
internal class RelayCommand : System.Windows.Input.ICommand
|
||||
{
|
||||
private readonly Action _execute;
|
||||
private readonly Func<bool>? _canExecute;
|
||||
|
||||
public RelayCommand(Action execute, Func<bool>? canExecute = null)
|
||||
{
|
||||
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
|
||||
public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true;
|
||||
|
||||
public void Execute(object? parameter) => _execute();
|
||||
|
||||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
|
||||
private void OnAppearing(object? sender, EventArgs e)
|
||||
{
|
||||
Console.WriteLine($"[PageHandler] OnAppearing received for: {VirtualView?.Title}");
|
||||
(VirtualView as IPageController)?.SendAppearing();
|
||||
}
|
||||
|
||||
@@ -133,18 +134,29 @@ public partial class ContentPageHandler : PageHandler
|
||||
|
||||
public static void MapContent(ContentPageHandler handler, ContentPage page)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
// Get the platform view for the content
|
||||
var content = page.Content;
|
||||
if (content != null)
|
||||
{
|
||||
// The content's handler should provide the platform view
|
||||
var contentHandler = content.Handler;
|
||||
if (contentHandler?.PlatformView is SkiaView skiaContent)
|
||||
// Create handler for content if it doesn't exist
|
||||
if (content.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[ContentPageHandler] Creating handler for content: {content.GetType().Name}");
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
// The content's handler should provide the platform view
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
Console.WriteLine($"[ContentPageHandler] Setting content: {skiaContent.GetType().Name}");
|
||||
handler.PlatformView.Content = skiaContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[ContentPageHandler] Content handler PlatformView is not SkiaView: {content.Handler?.PlatformView?.GetType().Name ?? "null"}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
@@ -25,6 +26,7 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
[nameof(IPicker.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(IPicker.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(Picker.ItemsSource)] = MapItemsSource,
|
||||
};
|
||||
|
||||
public static CommandMapper<IPicker, PickerHandler> CommandMapper =
|
||||
@@ -32,6 +34,8 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
{
|
||||
};
|
||||
|
||||
private INotifyCollectionChanged? _itemsCollection;
|
||||
|
||||
public PickerHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
@@ -51,6 +55,13 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
|
||||
// Subscribe to items collection changes
|
||||
if (VirtualView is Picker picker && picker.Items is INotifyCollectionChanged items)
|
||||
{
|
||||
_itemsCollection = items;
|
||||
_itemsCollection.CollectionChanged += OnItemsCollectionChanged;
|
||||
}
|
||||
|
||||
// Load items
|
||||
ReloadItems();
|
||||
}
|
||||
@@ -58,9 +69,21 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
protected override void DisconnectHandler(SkiaPicker platformView)
|
||||
{
|
||||
platformView.SelectedIndexChanged -= OnSelectedIndexChanged;
|
||||
|
||||
if (_itemsCollection != null)
|
||||
{
|
||||
_itemsCollection.CollectionChanged -= OnItemsCollectionChanged;
|
||||
_itemsCollection = null;
|
||||
}
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
ReloadItems();
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is null || PlatformView is null) return;
|
||||
@@ -130,4 +153,9 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapItemsSource(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.ReloadItems();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
[nameof(IProgress.Progress)] = MapProgress,
|
||||
[nameof(IProgress.ProgressColor)] = MapProgressColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<IProgress, ProgressBarHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
@@ -40,4 +42,22 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
handler.PlatformView.IsEnabled = progress.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (progress.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (progress is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
109
Handlers/ScrollViewHandler.cs
Normal file
109
Handlers/ScrollViewHandler.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for ScrollView on Linux using SkiaScrollView.
|
||||
/// </summary>
|
||||
public partial class ScrollViewHandler : ViewHandler<IScrollView, SkiaScrollView>
|
||||
{
|
||||
public static IPropertyMapper<IScrollView, ScrollViewHandler> Mapper =
|
||||
new PropertyMapper<IScrollView, ScrollViewHandler>(ViewMapper)
|
||||
{
|
||||
[nameof(IScrollView.Content)] = MapContent,
|
||||
[nameof(IScrollView.HorizontalScrollBarVisibility)] = MapHorizontalScrollBarVisibility,
|
||||
[nameof(IScrollView.VerticalScrollBarVisibility)] = MapVerticalScrollBarVisibility,
|
||||
[nameof(IScrollView.Orientation)] = MapOrientation,
|
||||
};
|
||||
|
||||
public static CommandMapper<IScrollView, ScrollViewHandler> CommandMapper =
|
||||
new(ViewCommandMapper)
|
||||
{
|
||||
[nameof(IScrollView.RequestScrollTo)] = MapRequestScrollTo
|
||||
};
|
||||
|
||||
public ScrollViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public ScrollViewHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaScrollView CreatePlatformView()
|
||||
{
|
||||
return new SkiaScrollView();
|
||||
}
|
||||
|
||||
public static void MapContent(ScrollViewHandler handler, IScrollView scrollView)
|
||||
{
|
||||
if (handler.PlatformView == null || handler.MauiContext == null)
|
||||
return;
|
||||
|
||||
var content = scrollView.PresentedContent;
|
||||
if (content != null)
|
||||
{
|
||||
Console.WriteLine($"[ScrollViewHandler] MapContent: {content.GetType().Name}");
|
||||
|
||||
// Create handler for content if it doesn't exist
|
||||
if (content.Handler == null)
|
||||
{
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
Console.WriteLine($"[ScrollViewHandler] Setting content: {skiaContent.GetType().Name}");
|
||||
handler.PlatformView.Content = skiaContent;
|
||||
}
|
||||
}
|
||||
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.Default
|
||||
};
|
||||
}
|
||||
|
||||
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.Default
|
||||
};
|
||||
}
|
||||
|
||||
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? args)
|
||||
{
|
||||
if (args is ScrollToRequest request)
|
||||
{
|
||||
// Instant means no animation, so we pass !Instant for animated parameter
|
||||
handler.PlatformView.ScrollTo((float)request.HorizontalOffset, (float)request.VerticalOffset, !request.Instant);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
@@ -10,13 +11,13 @@ namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
/// <summary>
|
||||
/// Handler for Shell on Linux using Skia rendering.
|
||||
/// </summary>
|
||||
public partial class ShellHandler : ViewHandler<IView, SkiaShell>
|
||||
public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
||||
{
|
||||
public static IPropertyMapper<IView, ShellHandler> Mapper = new PropertyMapper<IView, ShellHandler>(ViewHandler.ViewMapper)
|
||||
public static IPropertyMapper<Shell, ShellHandler> Mapper = new PropertyMapper<Shell, ShellHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public static CommandMapper<IView, ShellHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
public static CommandMapper<Shell, ShellHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
};
|
||||
|
||||
@@ -39,12 +40,26 @@ public partial class ShellHandler : ViewHandler<IView, SkiaShell>
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.FlyoutIsPresentedChanged += OnFlyoutIsPresentedChanged;
|
||||
platformView.Navigated += OnNavigated;
|
||||
|
||||
// Subscribe to Shell navigation events
|
||||
if (VirtualView != null)
|
||||
{
|
||||
VirtualView.Navigating += OnShellNavigating;
|
||||
VirtualView.Navigated += OnShellNavigated;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaShell platformView)
|
||||
{
|
||||
platformView.FlyoutIsPresentedChanged -= OnFlyoutIsPresentedChanged;
|
||||
platformView.Navigated -= OnNavigated;
|
||||
|
||||
if (VirtualView != null)
|
||||
{
|
||||
VirtualView.Navigating -= OnShellNavigating;
|
||||
VirtualView.Navigated -= OnShellNavigated;
|
||||
}
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
@@ -55,6 +70,24 @@ public partial class ShellHandler : ViewHandler<IView, SkiaShell>
|
||||
|
||||
private void OnNavigated(object? sender, ShellNavigationEventArgs e)
|
||||
{
|
||||
// Handle navigation events
|
||||
// Handle platform navigation events
|
||||
}
|
||||
|
||||
private void OnShellNavigating(object? sender, ShellNavigatingEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"[ShellHandler] Shell Navigating to: {e.Target?.Location}");
|
||||
|
||||
// Route to platform view
|
||||
if (PlatformView != null && e.Target?.Location != null)
|
||||
{
|
||||
var route = e.Target.Location.ToString().TrimStart('/');
|
||||
Console.WriteLine($"[ShellHandler] Routing to: {route}");
|
||||
PlatformView.GoToAsync(route);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShellNavigated(object? sender, ShellNavigatedEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"[ShellHandler] Shell Navigated to: {e.Current?.Location}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
[nameof(ISlider.MaximumTrackColor)] = MapMaximumTrackColor,
|
||||
[nameof(ISlider.ThumbColor)] = MapThumbColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<ISlider, SliderHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
@@ -100,4 +102,22 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
handler.PlatformView.IsEnabled = slider.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (slider.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (slider is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
[nameof(ISlider.MaximumTrackColor)] = MapMaximumTrackColor,
|
||||
[nameof(ISlider.ThumbColor)] = MapThumbColor,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
public static CommandMapper<ISlider, SliderHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -48,6 +49,15 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
platformView.ValueChanged += OnValueChanged;
|
||||
platformView.DragStarted += OnDragStarted;
|
||||
platformView.DragCompleted += OnDragCompleted;
|
||||
|
||||
// Sync properties that may have been set before handler connection
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapMinimum(this, VirtualView);
|
||||
MapMaximum(this, VirtualView);
|
||||
MapValue(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaSlider platformView)
|
||||
@@ -133,4 +143,11 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = slider.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
[nameof(ISwitch.TrackColor)] = MapTrackColor,
|
||||
[nameof(ISwitch.ThumbColor)] = MapThumbColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<ISwitch, SwitchHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
@@ -71,4 +73,22 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
handler.PlatformView.IsEnabled = @switch.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (@switch.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (@switch is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
96
Handlers/WebViewHandler.cs
Normal file
96
Handlers/WebViewHandler.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for WebView control on Linux using WebKitGTK.
|
||||
/// </summary>
|
||||
public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
||||
{
|
||||
public static IPropertyMapper<IWebView, WebViewHandler> Mapper = new PropertyMapper<IWebView, WebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IWebView.Source)] = MapSource,
|
||||
};
|
||||
|
||||
public static CommandMapper<IWebView, WebViewHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
[nameof(IWebView.GoBack)] = MapGoBack,
|
||||
[nameof(IWebView.GoForward)] = MapGoForward,
|
||||
[nameof(IWebView.Reload)] = MapReload,
|
||||
};
|
||||
|
||||
public WebViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public WebViewHandler(IPropertyMapper? mapper = null, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaWebView CreatePlatformView()
|
||||
{
|
||||
return new SkiaWebView();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaWebView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
platformView.Navigating += OnNavigating;
|
||||
platformView.Navigated += OnNavigated;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaWebView platformView)
|
||||
{
|
||||
platformView.Navigating -= OnNavigating;
|
||||
platformView.Navigated -= OnNavigated;
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnNavigating(object? sender, WebNavigatingEventArgs e)
|
||||
{
|
||||
// Forward to virtual view if needed
|
||||
}
|
||||
|
||||
private void OnNavigated(object? sender, WebNavigatedEventArgs e)
|
||||
{
|
||||
// Forward to virtual view if needed
|
||||
}
|
||||
|
||||
public static void MapSource(WebViewHandler handler, IWebView webView)
|
||||
{
|
||||
if (handler.PlatformView == null) return;
|
||||
|
||||
var source = webView.Source;
|
||||
if (source is UrlWebViewSource urlSource)
|
||||
{
|
||||
handler.PlatformView.Source = urlSource.Url ?? "";
|
||||
}
|
||||
else if (source is HtmlWebViewSource htmlSource)
|
||||
{
|
||||
handler.PlatformView.Html = htmlSource.Html ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapGoBack(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
handler.PlatformView?.GoBack();
|
||||
}
|
||||
|
||||
public static void MapGoForward(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
handler.PlatformView?.GoForward();
|
||||
}
|
||||
|
||||
public static void MapReload(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
handler.PlatformView?.Reload();
|
||||
}
|
||||
}
|
||||
@@ -141,6 +141,7 @@ public partial class WindowHandler : ElementHandler<IWindow, SkiaWindow>
|
||||
|
||||
/// <summary>
|
||||
/// Skia window wrapper for Linux display servers.
|
||||
/// Handles rendering of content and popup overlays automatically.
|
||||
/// </summary>
|
||||
public class SkiaWindow
|
||||
{
|
||||
@@ -164,6 +165,28 @@ public class SkiaWindow
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the window content and popup overlays to the canvas.
|
||||
/// This should be called by the platform rendering loop.
|
||||
/// </summary>
|
||||
public void Render(SKCanvas canvas)
|
||||
{
|
||||
// Clear background
|
||||
canvas.Clear(SKColors.White);
|
||||
|
||||
// Draw main content
|
||||
if (_content != null)
|
||||
{
|
||||
_content.Measure(new SKSize(_width, _height));
|
||||
_content.Arrange(new SKRect(0, 0, _width, _height));
|
||||
_content.Draw(canvas);
|
||||
}
|
||||
|
||||
// Draw popup overlays on top (dropdowns, date pickers, etc.)
|
||||
// This ensures popups always render above all other content
|
||||
SkiaView.DrawPopupOverlays(canvas);
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
|
||||
Reference in New Issue
Block a user