using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
///
/// Viewbox is used to scale single child to fit in the available space.
///
public class Viewbox : Control
{
private readonly ViewboxContainer _containerVisual;
///
/// Defines the property.
///
public static readonly StyledProperty StretchProperty =
AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform);
///
/// Defines the property.
///
public static readonly StyledProperty StretchDirectionProperty =
AvaloniaProperty.Register(nameof(StretchDirection), StretchDirection.Both);
///
/// Defines the property
///
public static readonly StyledProperty ChildProperty =
Decorator.ChildProperty.AddOwner();
static Viewbox()
{
ClipToBoundsProperty.OverrideDefaultValue(true);
UseLayoutRoundingProperty.OverrideDefaultValue(true);
AffectsMeasure(StretchProperty, StretchDirectionProperty);
}
public Viewbox()
{
// The Child control is hosted inside a ViewboxContainer control so that the transform
// can be applied independently of the Viewbox and Child transforms.
_containerVisual = new ViewboxContainer();
_containerVisual.RenderTransformOrigin = RelativePoint.TopLeft;
VisualChildren.Add(_containerVisual);
}
///
/// Gets or sets the stretch mode,
/// which determines how child fits into the available space.
///
public Stretch Stretch
{
get => GetValue(StretchProperty);
set => SetValue(StretchProperty, value);
}
///
/// Gets or sets a value controlling in what direction contents will be stretched.
///
public StretchDirection StretchDirection
{
get => GetValue(StretchDirectionProperty);
set => SetValue(StretchDirectionProperty, value);
}
///
/// Gets or sets the child of the Viewbox
///
[Content]
public IControl? Child
{
get => GetValue(ChildProperty);
set => SetValue(ChildProperty, value);
}
///
/// Gets or sets the transform applied to the container visual that
/// hosts the child of the Viewbox
///
protected internal ITransform? InternalTransform
{
get => _containerVisual.RenderTransform;
set => _containerVisual.RenderTransform = value;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ChildProperty)
{
var (oldChild, newChild) = change.GetOldAndNewValue();
if (oldChild is not null)
{
((ISetLogicalParent)oldChild).SetParent(null);
LogicalChildren.Remove(oldChild);
}
_containerVisual.Child = newChild;
if (newChild is not null)
{
((ISetLogicalParent)newChild).SetParent(this);
LogicalChildren.Add(newChild);
}
InvalidateMeasure();
}
}
protected override Size MeasureOverride(Size availableSize)
{
var child = _containerVisual;
if (child != null)
{
child.Measure(Size.Infinity);
var childSize = child.DesiredSize;
var size = Stretch.CalculateSize(availableSize, childSize, StretchDirection);
return size;
}
return new Size();
}
protected override Size ArrangeOverride(Size finalSize)
{
var child = _containerVisual;
if (child != null)
{
var childSize = child.DesiredSize;
var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection);
InternalTransform = new ImmutableTransform(Matrix.CreateScale(scale.X, scale.Y));
child.Arrange(new Rect(childSize));
return childSize * scale;
}
return finalSize;
}
///
/// A simple container control which hosts its child as a visual but not logical child.
///
private class ViewboxContainer : Control
{
private IControl? _child;
public IControl? Child
{
get => _child;
set
{
if (_child != value)
{
if (_child is not null)
VisualChildren.Remove(_child);
_child = value;
if (_child is not null)
VisualChildren.Add(_child);
InvalidateMeasure();
}
}
}
}
}
}