diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml
index e78cf2bc22..6e649b39a5 100644
--- a/samples/ControlCatalog/Pages/ViewboxPage.xaml
+++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml
@@ -1,66 +1,35 @@
-
-
- F1 M 16.6309,18.6563C 17.1309,
- 8.15625 29.8809,14.1563 29.8809,
- 14.1563C 30.8809,11.1563 34.1308,
- 11.4063 34.1308,11.4063C 33.5,12
- 34.6309,13.1563 34.6309,13.1563C
- 32.1309,13.1562 31.1309,14.9062
- 31.1309,14.9062C 41.1309,23.9062
- 32.6309,27.9063 32.6309,27.9062C
- 24.6309,24.9063 21.1309,22.1562
- 16.6309,18.6563 Z M 16.6309,19.9063C
- 21.6309,24.1563 25.1309,26.1562
- 31.6309,28.6562C 31.6309,28.6562
- 26.3809,39.1562 18.3809,36.1563C
- 18.3809,36.1563 18,38 16.3809,36.9063C
- 15,36 16.3809,34.9063 16.3809,34.9063C
- 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z
-
-
-
+
Viewbox
A control used to scale single child.
-
- None
- Fill
- Uniform
- UniformToFill
-
- Hello World!
-
-
- Hello World!
-
-
- Hello World!
-
-
- Hello World!
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
index 1b5f4bc7f4..7a2bceb8f2 100644
--- a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
@@ -1,18 +1,47 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.Media;
namespace ControlCatalog.Pages
{
public class ViewboxPage : UserControl
{
+ private readonly Viewbox _viewbox;
+ private readonly ComboBox _stretchSelector;
+
public ViewboxPage()
{
- this.InitializeComponent();
+ InitializeComponent();
+
+ _viewbox = this.FindControl("Viewbox");
+
+ _stretchSelector = this.FindControl("StretchSelector");
+
+ _stretchSelector.Items = new[]
+ {
+ Stretch.Uniform, Stretch.UniformToFill, Stretch.Fill, Stretch.None
+ };
+
+ _stretchSelector.SelectedIndex = 0;
+
+ var stretchDirectionSelector = this.FindControl("StretchDirectionSelector");
+
+ stretchDirectionSelector.Items = new[]
+ {
+ StretchDirection.Both, StretchDirection.DownOnly, StretchDirection.UpOnly
+ };
+
+ stretchDirectionSelector.SelectedIndex = 0;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
+
+ private void StretchSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ _viewbox.Stretch = (Stretch) _stretchSelector.SelectedItem!;
+ }
}
}
diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs
index 247b62d3cf..c448729643 100644
--- a/src/Avalonia.Controls/Image.cs
+++ b/src/Avalonia.Controls/Image.cs
@@ -31,8 +31,8 @@ namespace Avalonia.Controls
static Image()
{
- AffectsRender(SourceProperty, StretchProperty);
- AffectsMeasure(SourceProperty, StretchProperty);
+ AffectsRender(SourceProperty, StretchProperty, StretchDirectionProperty);
+ AffectsMeasure(SourceProperty, StretchProperty, StretchDirectionProperty);
}
///
diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs
index 781c93bcbe..b65f4b31d8 100644
--- a/src/Avalonia.Controls/Viewbox.cs
+++ b/src/Avalonia.Controls/Viewbox.cs
@@ -1,40 +1,50 @@
-using System;
-using Avalonia.Media;
+using Avalonia.Media;
namespace Avalonia.Controls
{
///
- /// Viewbox is used to scale single child.
+ /// Viewbox is used to scale single child to fit in the available space.
///
///
public class Viewbox : Decorator
{
///
- /// The stretch property
+ /// Defines the property.
///
public static readonly AvaloniaProperty StretchProperty =
- AvaloniaProperty.RegisterDirect(nameof(Stretch),
- v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform);
+ AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StretchDirectionProperty =
+ AvaloniaProperty.Register(nameof(StretchDirection), StretchDirection.Both);
private Stretch _stretch = Stretch.Uniform;
+ static Viewbox()
+ {
+ ClipToBoundsProperty.OverrideDefaultValue(true);
+ AffectsMeasure(StretchProperty, StretchDirectionProperty);
+ }
+
///
/// Gets or sets the stretch mode,
/// which determines how child fits into the available space.
///
- ///
- /// The stretch.
- ///
public Stretch Stretch
{
get => _stretch;
set => SetAndRaise(StretchProperty, ref _stretch, value);
}
- static Viewbox()
+ ///
+ /// Gets or sets a value controlling in what direction contents will be stretched.
+ ///
+ public StretchDirection StretchDirection
{
- ClipToBoundsProperty.OverrideDefaultValue(true);
- AffectsMeasure(StretchProperty);
+ get => GetValue(StretchDirectionProperty);
+ set => SetValue(StretchDirectionProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
@@ -47,9 +57,9 @@ namespace Avalonia.Controls
var childSize = child.DesiredSize;
- var scale = GetScale(availableSize, childSize, Stretch);
+ var size = Stretch.CalculateSize(availableSize, childSize, StretchDirection);
- return (childSize * scale).Constrain(availableSize);
+ return size.Constrain(availableSize);
}
return new Size();
@@ -62,7 +72,9 @@ namespace Avalonia.Controls
if (child != null)
{
var childSize = child.DesiredSize;
- var scale = GetScale(finalSize, childSize, Stretch);
+ var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection);
+
+ // TODO: Viewbox should have another decorator as a child so we won't affect other render transforms.
var scaleTransform = child.RenderTransform as ScaleTransform;
if (scaleTransform == null)
@@ -81,44 +93,5 @@ namespace Avalonia.Controls
return new Size();
}
-
- private static Vector GetScale(Size availableSize, Size childSize, Stretch stretch)
- {
- double scaleX = 1.0;
- double scaleY = 1.0;
-
- bool validWidth = !double.IsPositiveInfinity(availableSize.Width);
- bool validHeight = !double.IsPositiveInfinity(availableSize.Height);
-
- if (stretch != Stretch.None && (validWidth || validHeight))
- {
- scaleX = childSize.Width <= 0.0 ? 0.0 : availableSize.Width / childSize.Width;
- scaleY = childSize.Height <= 0.0 ? 0.0 : availableSize.Height / childSize.Height;
-
- if (!validWidth)
- {
- scaleX = scaleY;
- }
- else if (!validHeight)
- {
- scaleY = scaleX;
- }
- else
- {
- switch (stretch)
- {
- case Stretch.Uniform:
- scaleX = scaleY = Math.Min(scaleX, scaleY);
- break;
-
- case Stretch.UniformToFill:
- scaleX = scaleY = Math.Max(scaleX, scaleY);
- break;
- }
- }
- }
-
- return new Vector(scaleX, scaleY);
- }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
index e005bafbf9..7eaec35506 100644
--- a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
@@ -114,5 +114,61 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(2.0, scaleTransform.ScaleX);
Assert.Equal(2.0, scaleTransform.ScaleY);
}
+
+ [Theory]
+ [InlineData(50, 100, 50, 100, 50, 100, 1)]
+ [InlineData(50, 100, 150, 150, 50, 100, 1)]
+ [InlineData(50, 100, 25, 50, 25, 50, 0.5)]
+ public void Viewbox_Should_Return_Correct_SizeAndScale_StretchDirection_DownOnly(
+ double childWidth, double childHeight,
+ double viewboxWidth, double viewboxHeight,
+ double expectedWidth, double expectedHeight,
+ double expectedScale)
+ {
+ var target = new Viewbox
+ {
+ Child = new Control { Width = childWidth, Height = childHeight },
+ StretchDirection = StretchDirection.DownOnly
+ };
+
+ target.Measure(new Size(viewboxWidth, viewboxHeight));
+ target.Arrange(new Rect(default, target.DesiredSize));
+
+ Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
+
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(expectedScale, scaleTransform.ScaleX);
+ Assert.Equal(expectedScale, scaleTransform.ScaleY);
+ }
+
+ [Theory]
+ [InlineData(50, 100, 50, 100, 50, 100, 1)]
+ [InlineData(50, 100, 25, 50, 25, 50, 1)]
+ [InlineData(50, 100, 150, 150, 75, 150, 1.5)]
+ public void Viewbox_Should_Return_Correct_SizeAndScale_StretchDirection_UpOnly(
+ double childWidth, double childHeight,
+ double viewboxWidth, double viewboxHeight,
+ double expectedWidth, double expectedHeight,
+ double expectedScale)
+ {
+ var target = new Viewbox
+ {
+ Child = new Control { Width = childWidth, Height = childHeight },
+ StretchDirection = StretchDirection.UpOnly
+ };
+
+ target.Measure(new Size(viewboxWidth, viewboxHeight));
+ target.Arrange(new Rect(default, target.DesiredSize));
+
+ Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
+
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(expectedScale, scaleTransform.ScaleX);
+ Assert.Equal(expectedScale, scaleTransform.ScaleY);
+ }
}
}