258 lines
7.9 KiB
C#
258 lines
7.9 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
using SkiaSharp;
|
|
|
|
namespace Microsoft.Maui.Platform;
|
|
|
|
/// <summary>
|
|
/// Presents content within a ControlTemplate.
|
|
/// This control acts as a placeholder that gets replaced with the actual content
|
|
/// when the template is applied to a control.
|
|
/// </summary>
|
|
public class SkiaContentPresenter : SkiaView
|
|
{
|
|
#region BindableProperties
|
|
|
|
public static readonly BindableProperty ContentProperty =
|
|
BindableProperty.Create(nameof(Content), typeof(SkiaView), typeof(SkiaContentPresenter), null,
|
|
propertyChanged: (b, o, n) => ((SkiaContentPresenter)b).OnContentChanged((SkiaView?)o, (SkiaView?)n));
|
|
|
|
public static readonly BindableProperty HorizontalContentAlignmentProperty =
|
|
BindableProperty.Create(nameof(HorizontalContentAlignment), typeof(LayoutAlignment), typeof(SkiaContentPresenter), LayoutAlignment.Fill,
|
|
propertyChanged: (b, o, n) => ((SkiaContentPresenter)b).InvalidateMeasure());
|
|
|
|
public static readonly BindableProperty VerticalContentAlignmentProperty =
|
|
BindableProperty.Create(nameof(VerticalContentAlignment), typeof(LayoutAlignment), typeof(SkiaContentPresenter), LayoutAlignment.Fill,
|
|
propertyChanged: (b, o, n) => ((SkiaContentPresenter)b).InvalidateMeasure());
|
|
|
|
public static readonly BindableProperty PaddingProperty =
|
|
BindableProperty.Create(nameof(Padding), typeof(SKRect), typeof(SkiaContentPresenter), SKRect.Empty,
|
|
propertyChanged: (b, o, n) => ((SkiaContentPresenter)b).InvalidateMeasure());
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets the content to present.
|
|
/// </summary>
|
|
public SkiaView? Content
|
|
{
|
|
get => (SkiaView?)GetValue(ContentProperty);
|
|
set => SetValue(ContentProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the horizontal alignment of the content.
|
|
/// </summary>
|
|
public LayoutAlignment HorizontalContentAlignment
|
|
{
|
|
get => (LayoutAlignment)GetValue(HorizontalContentAlignmentProperty);
|
|
set => SetValue(HorizontalContentAlignmentProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the vertical alignment of the content.
|
|
/// </summary>
|
|
public LayoutAlignment VerticalContentAlignment
|
|
{
|
|
get => (LayoutAlignment)GetValue(VerticalContentAlignmentProperty);
|
|
set => SetValue(VerticalContentAlignmentProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the padding around the content.
|
|
/// </summary>
|
|
public SKRect Padding
|
|
{
|
|
get => (SKRect)GetValue(PaddingProperty);
|
|
set => SetValue(PaddingProperty, value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void OnContentChanged(SkiaView? oldContent, SkiaView? newContent)
|
|
{
|
|
if (oldContent != null)
|
|
{
|
|
oldContent.Parent = null;
|
|
}
|
|
|
|
if (newContent != null)
|
|
{
|
|
newContent.Parent = this;
|
|
|
|
// Propagate binding context to new content
|
|
if (BindingContext != null)
|
|
{
|
|
SetInheritedBindingContext(newContent, BindingContext);
|
|
}
|
|
}
|
|
|
|
InvalidateMeasure();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when binding context changes. Propagates to content.
|
|
/// </summary>
|
|
protected override void OnBindingContextChanged()
|
|
{
|
|
base.OnBindingContextChanged();
|
|
|
|
// Propagate binding context to content
|
|
if (Content != null)
|
|
{
|
|
SetInheritedBindingContext(Content, BindingContext);
|
|
}
|
|
}
|
|
|
|
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
|
{
|
|
// Draw background if set
|
|
if (BackgroundColor != SKColors.Transparent)
|
|
{
|
|
using var bgPaint = new SKPaint
|
|
{
|
|
Color = BackgroundColor,
|
|
Style = SKPaintStyle.Fill
|
|
};
|
|
canvas.DrawRect(bounds, bgPaint);
|
|
}
|
|
|
|
// Draw content
|
|
Content?.Draw(canvas);
|
|
}
|
|
|
|
protected override SKSize MeasureOverride(SKSize availableSize)
|
|
{
|
|
var padding = Padding;
|
|
|
|
if (Content == null)
|
|
return new SKSize(padding.Left + padding.Right, padding.Top + padding.Bottom);
|
|
|
|
// When alignment is not Fill, give content unlimited size in that dimension
|
|
// so it can measure its natural size without truncation
|
|
var measureWidth = HorizontalContentAlignment == LayoutAlignment.Fill
|
|
? Math.Max(0, availableSize.Width - padding.Left - padding.Right)
|
|
: float.PositiveInfinity;
|
|
var measureHeight = VerticalContentAlignment == LayoutAlignment.Fill
|
|
? Math.Max(0, availableSize.Height - padding.Top - padding.Bottom)
|
|
: float.PositiveInfinity;
|
|
|
|
var contentSize = Content.Measure(new SKSize(measureWidth, measureHeight));
|
|
return new SKSize(
|
|
contentSize.Width + padding.Left + padding.Right,
|
|
contentSize.Height + padding.Top + padding.Bottom);
|
|
}
|
|
|
|
protected override SKRect ArrangeOverride(SKRect bounds)
|
|
{
|
|
if (Content != null)
|
|
{
|
|
var padding = Padding;
|
|
var contentBounds = new SKRect(
|
|
bounds.Left + padding.Left,
|
|
bounds.Top + padding.Top,
|
|
bounds.Right - padding.Right,
|
|
bounds.Bottom - padding.Bottom);
|
|
|
|
// Apply alignment
|
|
var contentSize = Content.DesiredSize;
|
|
var arrangedBounds = ApplyAlignment(contentBounds, contentSize, HorizontalContentAlignment, VerticalContentAlignment);
|
|
Content.Arrange(arrangedBounds);
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
|
|
private static SKRect ApplyAlignment(SKRect availableBounds, SKSize contentSize, LayoutAlignment horizontal, LayoutAlignment vertical)
|
|
{
|
|
float x = availableBounds.Left;
|
|
float y = availableBounds.Top;
|
|
float width = horizontal == LayoutAlignment.Fill ? availableBounds.Width : contentSize.Width;
|
|
float height = vertical == LayoutAlignment.Fill ? availableBounds.Height : contentSize.Height;
|
|
|
|
// Horizontal alignment
|
|
switch (horizontal)
|
|
{
|
|
case LayoutAlignment.Center:
|
|
x = availableBounds.Left + (availableBounds.Width - width) / 2;
|
|
break;
|
|
case LayoutAlignment.End:
|
|
x = availableBounds.Right - width;
|
|
break;
|
|
}
|
|
|
|
// Vertical alignment
|
|
switch (vertical)
|
|
{
|
|
case LayoutAlignment.Center:
|
|
y = availableBounds.Top + (availableBounds.Height - height) / 2;
|
|
break;
|
|
case LayoutAlignment.End:
|
|
y = availableBounds.Bottom - height;
|
|
break;
|
|
}
|
|
|
|
return new SKRect(x, y, x + width, y + height);
|
|
}
|
|
|
|
public override SkiaView? HitTest(float x, float y)
|
|
{
|
|
if (!IsVisible || !Bounds.Contains(x, y))
|
|
return null;
|
|
|
|
// Check content first
|
|
if (Content != null)
|
|
{
|
|
var hit = Content.HitTest(x, y);
|
|
if (hit != null)
|
|
return hit;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
public override void OnPointerPressed(PointerEventArgs e)
|
|
{
|
|
Content?.OnPointerPressed(e);
|
|
}
|
|
|
|
public override void OnPointerMoved(PointerEventArgs e)
|
|
{
|
|
Content?.OnPointerMoved(e);
|
|
}
|
|
|
|
public override void OnPointerReleased(PointerEventArgs e)
|
|
{
|
|
Content?.OnPointerReleased(e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Layout alignment options.
|
|
/// </summary>
|
|
public enum LayoutAlignment
|
|
{
|
|
/// <summary>
|
|
/// Fill the available space.
|
|
/// </summary>
|
|
Fill,
|
|
|
|
/// <summary>
|
|
/// Align to the start (left or top).
|
|
/// </summary>
|
|
Start,
|
|
|
|
/// <summary>
|
|
/// Align to the center.
|
|
/// </summary>
|
|
Center,
|
|
|
|
/// <summary>
|
|
/// Align to the end (right or bottom).
|
|
/// </summary>
|
|
End
|
|
}
|