Преглед на файлове

Implement StretchDirection for Viewbox. Cleanup Viewbox.

Dariusz Komosinski преди 4 години
родител
ревизия
b3e2b05d1f

+ 26 - 2
samples/ControlCatalog/Pages/ViewboxPage.xaml

@@ -22,14 +22,38 @@
         </StreamGeometry>
     </UserControl.Resources>
 
-    <Grid RowDefinitions="Auto,*">
+    <Grid RowDefinitions="Auto,*,*">
         <StackPanel Orientation="Vertical" Spacing="4">
             <TextBlock Classes="h1">Viewbox</TextBlock>
             <TextBlock Classes="h2">A control used to scale single child.</TextBlock>
         </StackPanel>
+
+        <Grid Grid.Row="1" ColumnDefinitions="*,Auto" HorizontalAlignment="Center" Margin="0,10,0,0">
+
+          <Border HorizontalAlignment="Center" Grid.Column="0" BorderThickness="1" BorderBrush="Orange" Width="200" Height="200">
+            <Border VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0" BorderThickness="1" BorderBrush="CornflowerBlue" Width="{Binding #WidthSlider.Value}" Height="{Binding #HeightSlider.Value}" >
+              <Viewbox x:Name="Viewbox"
+                StretchDirection="{Binding #StretchDirectionSelector.SelectedItem}">
+                <Path Width="50" Stretch="Uniform" Fill="CornflowerBlue" Data="{StaticResource Acorn}"/>
+              </Viewbox>
+            </Border>
+          </Border>
+
+          <StackPanel HorizontalAlignment="Left" Orientation="Vertical" Grid.Column="1" Margin="8,0,0,0" Width="150">
+            <TextBlock Text="Width" />
+            <Slider Minimum="10" Maximum="200" Value="100" x:Name="WidthSlider" TickFrequency="25" TickPlacement="TopLeft" />
+            <TextBlock Text="Height" />
+            <Slider Minimum="10" Maximum="200" Value="100" x:Name="HeightSlider" TickFrequency="25" TickPlacement="TopLeft" />
+            <TextBlock Text="Stretch" />
+            <ComboBox x:Name="StretchSelector" HorizontalAlignment="Stretch" Margin="0,0,0,2" SelectionChanged="StretchSelector_OnSelectionChanged" />
+            <TextBlock Text="Stretch Direction" />
+            <ComboBox x:Name="StretchDirectionSelector" HorizontalAlignment="Stretch" />
+          </StackPanel>
+        </Grid>
+
         <Grid ColumnDefinitions="Auto,*,*"
               RowDefinitions="*,*,*,*"
-              Grid.Row="1" Margin="48"
+              Grid.Row="2" Margin="48"
               MaxWidth="400">
             <TextBlock Grid.Row="0" VerticalAlignment="Center">None</TextBlock>
             <TextBlock Grid.Row="1" VerticalAlignment="Center">Fill</TextBlock>

+ 30 - 1
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>("Viewbox");
+
+            _stretchSelector = this.FindControl<ComboBox>("StretchSelector");
+
+            _stretchSelector.Items = new[]
+            {
+                Stretch.Uniform, Stretch.UniformToFill, Stretch.Fill, Stretch.None
+            };
+
+            _stretchSelector.SelectedIndex = 0;
+
+            var stretchDirectionSelector = this.FindControl<ComboBox>("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!;
+        }
     }
 }

+ 2 - 2
src/Avalonia.Controls/Image.cs

@@ -31,8 +31,8 @@ namespace Avalonia.Controls
 
         static Image()
         {
-            AffectsRender<Image>(SourceProperty, StretchProperty);
-            AffectsMeasure<Image>(SourceProperty, StretchProperty);
+            AffectsRender<Image>(SourceProperty, StretchProperty, StretchDirectionProperty);
+            AffectsMeasure<Image>(SourceProperty, StretchProperty, StretchDirectionProperty);
         }
 
         /// <summary>

+ 27 - 54
src/Avalonia.Controls/Viewbox.cs

@@ -1,40 +1,50 @@
-using System;
-using Avalonia.Media;
+using Avalonia.Media;
 
 namespace Avalonia.Controls
 {
     /// <summary>
-    /// Viewbox is used to scale single child.
+    /// Viewbox is used to scale single child to fit in the available space.
     /// </summary>
     /// <seealso cref="Avalonia.Controls.Decorator" />
     public class Viewbox : Decorator
     {
         /// <summary>
-        /// The stretch property
+        /// Defines the <see cref="Stretch"/> property.
         /// </summary>
         public static readonly AvaloniaProperty<Stretch> StretchProperty =
-                AvaloniaProperty.RegisterDirect<Viewbox, Stretch>(nameof(Stretch),
-                    v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform);
+            AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
+
+        /// <summary>
+        /// Defines the <see cref="StretchDirection"/> property.
+        /// </summary>
+        public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
+            AvaloniaProperty.Register<Viewbox, StretchDirection>(nameof(StretchDirection), StretchDirection.Both);
 
         private Stretch _stretch = Stretch.Uniform;
 
+        static Viewbox()
+        {
+            ClipToBoundsProperty.OverrideDefaultValue<Viewbox>(true);
+            AffectsMeasure<Viewbox>(StretchProperty, StretchDirectionProperty);
+        }
+
         /// <summary>
         /// Gets or sets the stretch mode, 
         /// which determines how child fits into the available space.
         /// </summary>
-        /// <value>
-        /// The stretch.
-        /// </value>
         public Stretch Stretch
         {
             get => _stretch;
             set => SetAndRaise(StretchProperty, ref _stretch, value);
         }
 
-        static Viewbox()
+        /// <summary>
+        /// Gets or sets a value controlling in what direction contents will be stretched.
+        /// </summary>
+        public StretchDirection StretchDirection
         {
-            ClipToBoundsProperty.OverrideDefaultValue<Viewbox>(true);
-            AffectsMeasure<Viewbox>(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);
-        }
     }
 }

+ 56 - 0
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);
+        }
     }
 }