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(); } } } } } }