Browse Source

Merge branch 'master' into upstream-wrap-layout-fixes

pull/5913/head
Dariusz Komosiński 5 years ago
committed by GitHub
parent
commit
656a3ac581
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      samples/ControlCatalog/App.xaml.cs
  2. 76
      samples/ControlCatalog/Pages/ViewboxPage.xaml
  3. 21
      samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
  4. 7
      samples/RenderDemo/Pages/AnimationsPage.xaml
  5. 65
      samples/RenderDemo/Pages/TransitionsPage.xaml
  6. 10
      src/Avalonia.Animation/Transitions/DoubleTransition.cs
  7. 7
      src/Avalonia.Animation/Transitions/FloatTransition.cs
  8. 7
      src/Avalonia.Animation/Transitions/IntegerTransition.cs
  9. 2
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  10. 6
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  11. 3
      src/Avalonia.Controls/ApiCompatBaseline.txt
  12. 4
      src/Avalonia.Controls/Image.cs
  13. 87
      src/Avalonia.Controls/Viewbox.cs
  14. 29
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
  15. 186
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  16. 21
      src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs
  17. 22
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  18. 39
      src/Avalonia.Styling/Controls/Classes.cs
  19. 8
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  20. 14
      src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml
  21. 317
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  22. 10
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  23. 23
      src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs
  24. 21
      src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
  25. 10
      src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
  26. 10
      src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
  27. 10
      src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
  28. 10
      src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
  29. 5
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  30. 45
      src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs
  31. 32
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  32. 25
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  33. 65
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  34. 22
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  35. 17
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
  36. 22
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  37. 56
      tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
  38. 22
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  39. 22
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

8
samples/ControlCatalog/App.xaml.cs

@ -39,6 +39,10 @@ namespace ControlCatalog
public static Styles DefaultLight = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
@ -60,6 +64,10 @@ namespace ControlCatalog
public static Styles DefaultDark = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")

76
samples/ControlCatalog/Pages/ViewboxPage.xaml

@ -1,66 +1,36 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ViewboxPage">
<UserControl.Resources>
<StreamGeometry x:Key="Acorn">
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
</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 ColumnDefinitions="Auto,*,*"
RowDefinitions="*,*,*,*"
Grid.Row="1" Margin="48"
MaxWidth="400">
<TextBlock Grid.Row="0" VerticalAlignment="Center">None</TextBlock>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Fill</TextBlock>
<TextBlock Grid.Row="2" VerticalAlignment="Center">Uniform</TextBlock>
<TextBlock Grid.Row="3" VerticalAlignment="Center">UniformToFill</TextBlock>
<Viewbox Grid.Row="0" Grid.Column="1" Stretch="None">
<TextBlock>Hello World!</TextBlock>
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="1" Stretch="Fill">
<TextBlock>Hello World!</TextBlock>
</Viewbox>
<Viewbox Grid.Row="2" Grid.Column="1" Stretch="Uniform">
<TextBlock>Hello World!</TextBlock>
</Viewbox>
<Viewbox Grid.Row="3" Grid.Column="1" Stretch="UniformToFill">
<TextBlock>Hello World!</TextBlock>
</Viewbox>
<Grid Grid.Row="1" ColumnDefinitions="*,Auto" HorizontalAlignment="Center" Margin="0,10,0,0">
<Viewbox Grid.Row="0" Grid.Column="2" Stretch="None">
<Path Fill="Blue" Data="{StaticResource Acorn}"/>
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="2" Stretch="Fill">
<Path Fill="Blue" Data="{StaticResource Acorn}"/>
</Viewbox>
<Viewbox Grid.Row="2" Grid.Column="2" Stretch="Uniform">
<Path Fill="Blue" Data="{StaticResource Acorn}"/>
</Viewbox>
<Viewbox Grid.Row="3" Grid.Column="2" Stretch="UniformToFill">
<Path Fill="Blue" Data="{StaticResource Acorn}"/>
</Viewbox>
<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
Stretch="{Binding #StretchSelector.SelectedItem}"
StretchDirection="{Binding #StretchDirectionSelector.SelectedItem}">
<Ellipse Width="50" Height="50" Fill="CornflowerBlue" />
</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" />
<TextBlock Text="Stretch Direction" />
<ComboBox x:Name="StretchDirectionSelector" HorizontalAlignment="Stretch" />
</StackPanel>
</Grid>
</Grid>
</UserControl>

21
samples/ControlCatalog/Pages/ViewboxPage.xaml.cs

@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
namespace ControlCatalog.Pages
{
@ -7,7 +8,25 @@ namespace ControlCatalog.Pages
{
public ViewboxPage()
{
this.InitializeComponent();
InitializeComponent();
var 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()

7
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -1,7 +1,8 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.AnimationsPage">
x:Class="RenderDemo.Pages.AnimationsPage"
MaxWidth="600">
<UserControl.Styles>
<Styles>
<Styles.Resources>
@ -167,8 +168,8 @@
<StackPanel.Clock>
<Clock />
</StackPanel.Clock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
<TextBlock VerticalAlignment="Center">Hover to activate Keyframe Animations.</TextBlock>
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
</StackPanel>
<WrapPanel ClipToBounds="False">

65
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -1,7 +1,8 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.TransitionsPage">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.TransitionsPage"
MaxWidth="600">
<UserControl.Styles>
<Styles>
<Styles.Resources>
@ -90,6 +91,56 @@
<Setter Property="RenderTransform" Value="none" />
</Style>
<Style Selector="Border.Rect7">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Height" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style>
<Style Selector="Border.Rect7:pointerover">
<Setter Property="Height" Value="50" />
</Style>
<Style Selector="Border.Rect8">
<Setter Property="Transitions">
<Transitions>
<CornerRadiusTransition Property="CornerRadius" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style>
<Style Selector="Border.Rect8:pointerover">
<Setter Property="CornerRadius" Value="50" />
</Style>
<Style Selector="Border.Rect9">
<Setter Property="Transitions">
<Transitions>
<ThicknessTransition Property="Padding" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style>
<Style Selector="Border.Rect9:pointerover">
<Setter Property="Padding" Value="10" />
</Style>
<Style Selector="Border.Shadow">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BoxShadow" Value="inset 0 0 0 2 Red, -15 -15 Green"/>
<Setter Property="Transitions">
<Transitions>
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style>
<Style Selector="Border.Shadow:pointerover">
<Setter Property="BoxShadow" Value="inset 30 30 20 30 Green, 20 40 20 10 Red"/>
</Style>
</Styles>
</UserControl.Styles>
@ -98,8 +149,8 @@
<StackPanel.Clock>
<Clock />
</StackPanel.Clock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
<TextBlock VerticalAlignment="Center">Hover to activate Transitions.</TextBlock>
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
</StackPanel>
<WrapPanel ClipToBounds="False">
@ -109,6 +160,12 @@
<Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Orange"/>
<Border Classes="Test Rect7" Background="Gold"/>
<Border Classes="Test Rect8" Background="Gray" />
<Border Classes="Test Rect9" Background="Red" />
<Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" />
<Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" />
</WrapPanel>
</StackPanel>
</Grid>

10
src/Avalonia.Animation/Transitions/DoubleTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class DoubleTransition : Transition<double>
{
private static readonly DoubleAnimator s_animator = new DoubleAnimator();
/// <inheritdocs/>
public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

7
src/Avalonia.Animation/Transitions/FloatTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,12 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class FloatTransition : Transition<float>
{
private static readonly FloatAnimator s_animator = new FloatAnimator();
/// <inheritdocs/>
public override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue)
{
var delta = newValue - oldValue;
return progress
.Select(p => (float)Easing.Ease(p) * delta + oldValue);
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

7
src/Avalonia.Animation/Transitions/IntegerTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,12 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class IntegerTransition : Transition<int>
{
private static readonly Int32Animator s_animator = new Int32Animator();
/// <inheritdocs/>
public override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue)
{
var delta = newValue - oldValue;
return progress
.Select(p => (int)(Easing.Ease(p) * delta + oldValue));
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

2
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@ -79,7 +79,7 @@ namespace Avalonia.PropertyStore
public void OnError(Exception error)
{
throw new NotImplementedException();
throw new NotImplementedException("BindingEntry.OnError is not implemented", error);
}
public void OnNext(BindingValue<T> value)

6
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -535,7 +535,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DataGrid, int>(
nameof(SelectedIndex),
o => o.SelectedIndex,
(o, v) => o.SelectedIndex = v);
(o, v) => o.SelectedIndex = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets the index of the current selection.
@ -553,7 +554,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DataGrid, object>(
nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v);
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets the data item corresponding to the selected row.

3
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -4,10 +4,11 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 11
Total Issues: 12

4
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>

87
src/Avalonia.Controls/Viewbox.cs

@ -1,40 +1,48 @@
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);
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
private Stretch _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);
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);
get => GetValue(StretchProperty);
set => SetValue(StretchProperty, 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 +55,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 +70,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 +91,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);
}
}
}

29
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs

@ -1,4 +1,6 @@
using System;
using System.ComponentModel;
using System.Text;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.VisualTree;
@ -95,12 +97,27 @@ namespace Avalonia.Diagnostics.ViewModels
{
string CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty)
{
if (ao.IsSet(minProperty) || ao.IsSet(maxProperty))
bool hasMin = ao.IsSet(minProperty);
bool hasMax = ao.IsSet(maxProperty);
if (hasMin || hasMax)
{
var minValue = ao.GetValue(minProperty);
var maxValue = ao.GetValue(maxProperty);
var builder = new StringBuilder();
if (hasMin)
{
var minValue = ao.GetValue(minProperty);
builder.AppendFormat("Min: {0}", Math.Round(minValue, 2));
builder.AppendLine();
}
if (hasMax)
{
var maxValue = ao.GetValue(maxProperty);
builder.AppendFormat("Max: {0}", Math.Round(maxValue, 2));
}
return $"{minValue} < size < {maxValue}";
return builder.ToString();
}
return null;
@ -183,8 +200,8 @@ namespace Avalonia.Diagnostics.ViewModels
{
var size = _control.Bounds;
Width = size.Width;
Height = size.Height;
Width = Math.Round(size.Width, 2);
Height = Math.Round(size.Height, 2);
}
}
}

186
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -8,11 +8,16 @@
<UserControl.Resources>
<SolidColorBrush x:Key="ThicknessBorderBrush" Color="#666666" />
<SolidColorBrush x:Key="HighlightBorderBrush" Color="CornflowerBlue" />
<SolidColorBrush x:Key="SizeGuidelineBrush" Color="#333333" />
<SolidColorBrush x:Key="MarginBackgroundBrush" Color="#D78965" />
<SolidColorBrush x:Key="MarginHighlightBrush" Color="#EA966F" />
<SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" />
<SolidColorBrush x:Key="BorderHighlightBrush" Color="#EFCD88" />
<SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" />
<SolidColorBrush x:Key="PaddingHighlightBrush" Color="#CEDA8E" />
<SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" />
<SolidColorBrush x:Key="SizeHighlightBrush" Color="#9ED0DC" />
<conv:BoolToOpacityConverter x:Key="BoolToOpacity" Opacity="0.6"/>
</UserControl.Resources>
@ -24,43 +29,60 @@
<Setter Property="BorderBrush" Value="{StaticResource ThicknessBorderBrush}" />
<Setter Property="Template">
<ControlTemplate>
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*,Auto">
<Grid.Styles>
<Style Selector="TextBox.thickness-edit">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Disabled" />
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Disabled" />
<Setter Property="IsVisible" Value="{Binding $parent[local:ThicknessEditor].IsPresent}" />
</Style>
</Grid.Styles>
<TextBlock IsVisible="{TemplateBinding IsPresent}" Margin="4,0,0,0" Text="{TemplateBinding Header}" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Left, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox x:Name="Right" Grid.Row="0" Grid.Column="1" Text="{Binding Top, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Right, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Bottom, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<ContentPresenter Grid.Row="1" Grid.Column="1"
Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Grid>
</Border>
<Panel>
<Rectangle x:Name="PART_Background" Classes.no-content-pointerover="{Binding !#PART_ContentPresenter.IsPointerOver}" />
<Border
x:Name="PART_Border"
Classes.no-content-pointerover="{Binding !#PART_ContentPresenter.IsPointerOver}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*,Auto">
<Grid.Styles>
<Style Selector="TextBox.thickness-edit">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Disabled" />
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Disabled" />
<Setter Property="IsVisible" Value="{Binding $parent[local:ThicknessEditor].IsPresent}" />
</Style>
</Grid.Styles>
<TextBlock IsVisible="{TemplateBinding IsPresent}" Margin="4,0,0,0" Text="{TemplateBinding Header}" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Left, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox x:Name="Right" Grid.Row="0" Grid.Column="1" Text="{Binding Top, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Right, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Bottom, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<ContentPresenter Grid.Row="1" Grid.Column="1"
Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Grid>
</Border>
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="local|ThicknessEditor /template/ Rectangle#PART_Background">
<Setter Property="Fill" Value="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
<Style Selector="local|ThicknessEditor:pointerover /template/ Rectangle#PART_Background.no-content-pointerover">
<Setter Property="Fill" Value="{Binding Highlight, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
<Style Selector="local|ThicknessEditor:pointerover /template/ Border#PART_Border.no-content-pointerover">
<Setter Property="BorderBrush" Value="{StaticResource HighlightBorderBrush}" />
</Style>
<Style Selector="local|ThicknessEditor[IsPresent=False]">
<Setter Property="BorderThickness" Value="0" />
</Style>
@ -110,47 +132,67 @@
<Grid Grid.Column="2" RowDefinitions="Auto,*, Auto,*,Auto" >
<TextBlock Grid.Row="0" Text="Layout Visualizer" Margin="4" />
<Grid Grid.Row="1" x:Name="LayoutRoot" Margin="8,0,8,8" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto">
<Border x:Name="VerticalSize" Grid.Row="0" Grid.Column="1" >
<TextBlock VerticalAlignment="Center" FontWeight="Bold"
TextDecorations="{Binding Layout.HeightConstraint, Converter={x:Static local:Converters.HasConstraintConverter}}"
Text="{Binding Layout.Height}"
ToolTip.Tip="{Binding Layout.HeightConstraint}" />
</Border>
<Border x:Name="HorizontalSize" Grid.Row="1" Grid.Column="0" >
<TextBlock HorizontalAlignment="Center" FontWeight="Bold"
TextDecorations="{Binding Layout.WidthConstraint, Converter={x:Static local:Converters.HasConstraintConverter}}"
Text="{Binding Layout.Width}"
ToolTip.Tip="{Binding Layout.WidthConstraint}" />
</Border>
<local:ThicknessEditor Grid.Row="0" Grid.Column="0" Header="margin" VerticalAlignment="Top" HorizontalAlignment="Center" Background="{StaticResource MarginBackgroundBrush}" Thickness="{Binding Layout.MarginThickness}">
<local:ThicknessEditor x:Name="BorderArea" Header="border" Background="{StaticResource BorderBackgroundBrush}" Thickness="{Binding Layout.BorderThickness}" IsPresent="{Binding Layout.HasBorder}">
<local:ThicknessEditor x:Name="PaddingArea" Header="padding" Background="{StaticResource PaddingBackgroundBrush}" Thickness="{Binding Layout.PaddingThickness}" IsPresent="{Binding Layout.HasPadding}">
<Border x:Name="ContentArea" BorderThickness="1" BorderBrush="{StaticResource ThicknessBorderBrush}" MinWidth="100" MinHeight="16" Background="{StaticResource SizeBackgroundBrush}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TextBlock Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" Text="content" />
</Border>
<Viewbox Grid.Row="1" StretchDirection="DownOnly" >
<Grid Grid.Row="1" x:Name="LayoutRoot" Margin="8,0,8,8" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto">
<Grid.Styles>
<Style Selector="TextBlock.with-constraint">
<Setter Property="TextDecorations" Value="Underline" />
</Style>
</Grid.Styles>
<Border x:Name="VerticalSize" Grid.Row="0" Grid.Column="1" >
<TextBlock VerticalAlignment="Center" FontWeight="Bold"
Classes.with-constraint="{Binding Layout.HeightConstraint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Text="{Binding Layout.Height}"
ToolTip.Tip="{Binding Layout.HeightConstraint}" />
</Border>
<Border x:Name="HorizontalSize" Grid.Row="1" Grid.Column="0" >
<TextBlock HorizontalAlignment="Center" FontWeight="Bold"
Classes.with-constraint="{Binding Layout.WidthConstraint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Text="{Binding Layout.Width}"
ToolTip.Tip="{Binding Layout.WidthConstraint}" />
</Border>
<local:ThicknessEditor Grid.Row="0" Grid.Column="0" Header="margin" VerticalAlignment="Top" HorizontalAlignment="Center" Background="{StaticResource MarginBackgroundBrush}" Highlight="{StaticResource MarginHighlightBrush}" Thickness="{Binding Layout.MarginThickness}">
<local:ThicknessEditor x:Name="BorderArea" Header="border" Background="{StaticResource BorderBackgroundBrush}" Highlight="{StaticResource BorderHighlightBrush}" Thickness="{Binding Layout.BorderThickness}" IsPresent="{Binding Layout.HasBorder}">
<local:ThicknessEditor x:Name="PaddingArea" Header="padding" Background="{StaticResource PaddingBackgroundBrush}" Highlight="{StaticResource PaddingHighlightBrush}" Thickness="{Binding Layout.PaddingThickness}" IsPresent="{Binding Layout.HasPadding}">
<Border x:Name="ContentArea" BorderThickness="1" MinWidth="100" MinHeight="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border.Styles>
<Style Selector="Border">
<Setter Property="Background" Value="{StaticResource SizeBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource ThicknessBorderBrush}" />
</Style>
<Style Selector="Border:pointerover">
<Setter Property="Background" Value="{StaticResource SizeHighlightBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource HighlightBorderBrush}" />
</Style>
</Border.Styles>
<TextBlock Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" Text="content" />
</Border>
</local:ThicknessEditor>
</local:ThicknessEditor>
</local:ThicknessEditor>
</local:ThicknessEditor>
<Canvas Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2">
<Canvas.Styles>
<Style Selector="Rectangle">
<Setter Property="StrokeDashArray" Value="1,3" />
<Setter Property="Stroke" Value="{StaticResource SizeGuidelineBrush}" />
<Setter Property="StrokeThickness" Value="1" />
</Style>
</Canvas.Styles>
<Rectangle x:Name="HorizontalSizeBegin" />
<Rectangle x:Name="HorizontalSizeEnd" />
<Rectangle x:Name="VerticalSizeBegin" />
<Rectangle x:Name="VerticalSizeEnd" />
</Canvas>
</Grid>
<Canvas Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2">
<Canvas.Styles>
<Style Selector="Rectangle">
<Setter Property="StrokeDashArray" Value="1,3" />
<Setter Property="Stroke" Value="{StaticResource SizeGuidelineBrush}" />
<Setter Property="StrokeThickness" Value="1" />
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
</Canvas.Styles>
<Rectangle x:Name="HorizontalSizeBegin" />
<Rectangle x:Name="HorizontalSizeEnd" />
<Rectangle x:Name="VerticalSizeBegin" />
<Rectangle x:Name="VerticalSizeEnd" />
</Canvas>
</Grid>
</Viewbox>
<Grid Grid.Row="2" Margin="4" RowDefinitions="Auto,Auto">

21
src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs

@ -1,21 +1,9 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace Avalonia.Diagnostics.Views
{
internal static class Converters
{
public static IValueConverter HasConstraintConverter =
new FuncValueConverter<object, TextDecorationCollection>(ConvertToDecoration);
private static TextDecorationCollection ConvertToDecoration(object arg)
{
return arg != null ? TextDecorations.Underline : null;
}
}
internal class ThicknessEditor : ContentControl
{
public static readonly DirectProperty<ThicknessEditor, Thickness> ThicknessProperty =
@ -44,6 +32,14 @@ namespace Avalonia.Diagnostics.Views
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Bottom), o => o.Bottom,
(o, v) => o.Bottom = v);
public static readonly StyledProperty<IBrush> HighlightProperty =
AvaloniaProperty.Register<ThicknessEditor, IBrush>(nameof(Highlight));
public IBrush Highlight
{
get => GetValue(HighlightProperty);
set => SetValue(HighlightProperty, value);
}
private Thickness _thickness;
private string _header;
@ -52,7 +48,6 @@ namespace Avalonia.Diagnostics.Views
private double _top;
private double _right;
private double _bottom;
private bool _isUpdatingThickness;
public Thickness Thickness

22
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -70,6 +70,28 @@ namespace Avalonia.Headless
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));

39
src/Avalonia.Styling/Controls/Classes.cs

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using Avalonia.Collections;
#nullable enable
namespace Avalonia.Controls
{
/// <summary>
@ -90,7 +92,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Remvoes all non-pseudoclasses from the collection.
/// Removes all non-pseudoclasses from the collection.
/// </summary>
public override void Clear()
{
@ -135,7 +137,7 @@ namespace Avalonia.Controls
/// </remarks>
public override void InsertRange(int index, IEnumerable<string> names)
{
var c = new List<string>();
List<string>? toInsert = null;
foreach (var name in names)
{
@ -143,11 +145,16 @@ namespace Avalonia.Controls
if (!Contains(name))
{
c.Add(name);
toInsert ??= new List<string>();
toInsert.Add(name);
}
}
base.InsertRange(index, c);
if (toInsert != null)
{
base.InsertRange(index, toInsert);
}
}
/// <summary>
@ -176,19 +183,21 @@ namespace Avalonia.Controls
/// </remarks>
public override void RemoveAll(IEnumerable<string> names)
{
var c = new List<string>();
List<string>? toRemove = null;
foreach (var name in names)
{
ThrowIfPseudoclass(name, "removed");
if (Contains(name))
{
c.Add(name);
}
toRemove ??= new List<string>();
toRemove.Add(name);
}
base.RemoveAll(c);
if (toRemove != null)
{
base.RemoveAll(toRemove);
}
}
/// <summary>
@ -223,7 +232,7 @@ namespace Avalonia.Controls
/// <param name="source">The new contents of the collection.</param>
public void Replace(IList<string> source)
{
var toRemove = new List<string>();
List<string>? toRemove = null;
foreach (var name in source)
{
@ -234,11 +243,17 @@ namespace Avalonia.Controls
{
if (!name.StartsWith(":"))
{
toRemove ??= new List<string>();
toRemove.Add(name);
}
}
base.RemoveAll(toRemove);
if (toRemove != null)
{
base.RemoveAll(toRemove);
}
base.AddRange(source);
}

8
src/Avalonia.Themes.Fluent/Accents/Base.xaml

@ -1,6 +1,7 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="using:System"
xmlns:converters="using:Avalonia.Controls.Converters">
<Style.Resources>
<!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN -->
<!-- SystemColor Color Resources (Reflect OS High Contrast Settings) -->
@ -30,5 +31,10 @@
<!-- ScrollBar animation -->
<TransformOperations x:Key="VerticalSmallScrollThumbScaleTransform">scaleX(0.125) translateX(-2px)</TransformOperations>
<TransformOperations x:Key="HorizontalSmallScrollThumbScaleTransform">scaleY(0.125) translateY(-2px)</TransformOperations>
<converters:CornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" Filter="TopLeft, TopRight"/>
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
</Style.Resources>
</Style>

14
src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml

@ -27,14 +27,12 @@
<Style Selector="DataValidationErrors">
<Style.Resources>
<DataTemplate x:Key="InlineDataValidationErrorTemplate">
<ItemsControl Items="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}"
Text="{Binding}"
TextWrapping="Wrap" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl Items="{Binding}" TextBlock.Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}">
<ItemsControl.Styles>
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</ItemsControl.Styles>
</ItemsControl>
</DataTemplate>
<ControlTemplate x:Key="InlineDataValidationContentTemplate" TargetType="DataValidationErrors">

317
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -1,141 +1,232 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Orientation="Vertical" Spacing="20">
<Expander ExpandDirection="Up" Header="Expand Up">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Down" Header="Expand Down">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Left" Header="Expand Left">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Right" Header="Expand Right">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
</StackPanel>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<Thickness x:Key="ExpanderHeaderPadding">16</Thickness>
<Thickness x:Key="ExpanderContentPadding">16</Thickness>
<Thickness x:Key="ExpanderBorderThickness">1</Thickness>
<Thickness x:Key="ExpanderDropdownLeftBorderThickness">1,1,0,1</Thickness>
<Thickness x:Key="ExpanderDropdownUpBorderThickness">1,1,1,0</Thickness>
<Thickness x:Key="ExpanderDropdownRightBorderThickness">0,1,1,1</Thickness>
<Thickness x:Key="ExpanderDropdownDownBorderThickness">1,0,1,1</Thickness>
<SolidColorBrush x:Key="ExpanderBackground" Color="{DynamicResource SystemAltMediumHighColor}" />
<SolidColorBrush x:Key="ExpanderBorderBrush" Color="{DynamicResource SystemBaseLowColor}" />
<SolidColorBrush x:Key="ExpanderDropDownBackground" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="ExpanderDropDownBorderBrush" Color="{DynamicResource SystemBaseLowColor}" />
<SolidColorBrush x:Key="ExpanderForeground" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="ExpanderChevronForeground" Color="{DynamicResource SystemBaseHighColor}" />
</Styles.Resources>
<Style Selector="Expander">
<Setter Property="ContentTransition">
<Setter.Value>
<CrossFade Duration="00:00:00.25" />
</Setter.Value>
</Setter>
<Setter Property="Background" Value="{DynamicResource ExpanderBackground}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderBorderThickness}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderBorderBrush}" />
<Setter Property="Padding" Value="{DynamicResource ExpanderHeaderPadding}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="Expander[ExpandDirection=Down]">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Grid RowDefinitions="Auto,*">
<ToggleButton Name="PART_toggle" Grid.Row="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Row="1"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander[IsExpanded=true] /template/ ToggleButton#PART_toggle /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForeground}"/>
</Style>
<Style Selector="Expander[ExpandDirection=Up]">
<Style Selector="Expander">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Grid RowDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Row="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Row="0"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
<DockPanel>
<ToggleButton x:Name="ExpanderHeader"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsEnabled="{TemplateBinding IsEnabled}" />
<Border x:Name="ExpanderContent"
Padding="{DynamicResource ExpanderContentPadding}"
Background="{DynamicResource ExpanderDropDownBackground}"
BorderBrush="{DynamicResource ExpanderDropDownBorderBrush}"
IsVisible="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter x:Name="PART_ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander[ExpandDirection=Right]">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Grid ColumnDefinitions="Auto,*">
<ToggleButton Name="PART_toggle" Grid.Column="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander[ExpandDirection=Left]">
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Border x:Name="ToggleButtonBackground">
<Grid ColumnDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Column="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="0"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
<ContentPresenter x:Name="PART_ContentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander /template/ ToggleButton#PART_toggle">
<Setter Property="Template">
<ControlTemplate>
<Border BorderThickness="1" Background="Transparent">
<Grid ColumnDefinitions="Auto,Auto">
<Border Grid.Column="0" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 2 L 4 6 L 0 10 Z" />
VerticalContentAlignment="Center"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextBlock.Foreground="{DynamicResource ExpanderForeground}" />
<Border x:Name="ExpandCollapseChevronBorder"
Grid.Column="1"
Width="32"
Height="32"
Margin="7"
RenderTransformOrigin="50%,50%">
<Path x:Name="ExpandCollapseChevron"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="50%,50%"
Stretch="None"
Stroke="{DynamicResource ExpanderChevronForeground}"
StrokeThickness="1" />
<Border.RenderTransform>
<RotateTransform />
</Border.RenderTransform>
</Border>
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1"
Background="Transparent"
Content="{TemplateBinding Content}"
VerticalAlignment="Center"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander /template/ ToggleButton#PART_toggle:pointerover /template/ Border">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumBrush}" />
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader:pressed">
<Setter Property="RenderTransform" Value="{x:Null}" />
</Style>
<Style Selector="Expander:down:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="90" />
</Setter>
<Style Selector="Expander:left /template/ ToggleButton#ExpanderHeader, Expander:right /template/ ToggleButton#ExpanderHeader">
<Setter Property="VerticalAlignment" Value="Stretch" />
</Style>
<Style Selector="Expander:up:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="-90" />
</Setter>
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="Background" Value="{TemplateBinding Background}" />
<Setter Property="BorderBrush" Value="{TemplateBinding BorderBrush}" />
<Setter Property="BorderThickness" Value="{TemplateBinding BorderThickness}" />
</Style>
<Style Selector="Expander:left:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="180" />
</Setter>
<Style Selector="Expander:not(:expanded) /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="Expander:right /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="180" />
</Setter>
<Style Selector="Expander:expanded:up /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:right:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="0" />
</Setter>
<Style Selector="Expander:expanded:up /template/ Border#ExpanderContent">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:down /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:down /template/ Border#ExpanderContent">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:left /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource RightCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:left /template/ Border#ExpanderContent">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource LeftCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:right /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource LeftCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:right /template/ Border#ExpanderContent">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource RightCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:left /template/ ToggleButton#ExpanderHeader">
<Setter Property="DockPanel.Dock" Value="Right" />
</Style>
<Style Selector="Expander:up /template/ ToggleButton#ExpanderHeader">
<Setter Property="DockPanel.Dock" Value="Bottom" />
</Style>
<Style Selector="Expander:right /template/ ToggleButton#ExpanderHeader">
<Setter Property="DockPanel.Dock" Value="Left" />
</Style>
<Style Selector="Expander:down /template/ ToggleButton#ExpanderHeader">
<Setter Property="DockPanel.Dock" Value="Top" />
</Style>
<Style Selector="Expander:expanded /template/ ToggleButton#ExpanderHeader /template/ Border#ExpandCollapseChevronBorder">
<Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625">
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="180" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Expander:not(:expanded) /template/ ToggleButton#ExpanderHeader /template/ Border#ExpandCollapseChevronBorder">
<Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="180" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="0" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Expander:left /template/ ToggleButton#ExpanderHeader /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 7 0 L 0 7 L 7 14" />
</Style>
<Style Selector="Expander:up /template/ ToggleButton#ExpanderHeader /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 7 L 7 0 L 14 7" />
</Style>
<Style Selector="Expander:right /template/ ToggleButton#ExpanderHeader /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 0 L 7 7 L 0 14" />
</Style>
<Style Selector="Expander:down /template/ ToggleButton#ExpanderHeader /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 0 L 7 7 L 14 0" />
</Style>
<Style Selector="Expander:left /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownLeftBorderThickness}" />
</Style>
<Style Selector="Expander:up /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownUpBorderThickness}" />
</Style>
<Style Selector="Expander:right /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownRightBorderThickness}" />
</Style>
<Style Selector="Expander:down /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownDownBorderThickness}" />
</Style>
</Styles>

10
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -12,6 +12,11 @@ namespace Avalonia.Animation.Animators
{
public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue)
{
if (oldValue is null || newValue is null)
{
return oldValue;
}
return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
}
@ -26,6 +31,11 @@ namespace Avalonia.Animation.Animators
{
public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue)
{
if (oldValue is null || newValue is null)
{
return oldValue;
}
return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
}
}

23
src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs

@ -0,0 +1,23 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Media;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="BoxShadows"/> type.
/// </summary>
public class BoxShadowsTransition : Transition<BoxShadows>
{
private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator();
/// <inheritdocs/>
public override IObservable<BoxShadows> DoTransition(IObservable<double> progress, BoxShadows oldValue, BoxShadows newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

21
src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,26 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class CornerRadiusTransition : Transition<CornerRadius>
{
private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator();
/// <inheritdocs/>
public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
var deltaTL = newValue.TopLeft - oldValue.TopLeft;
var deltaTR = newValue.TopRight - oldValue.TopRight;
var deltaBR = newValue.BottomRight - oldValue.BottomRight;
var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
var nTL = f * deltaTL + oldValue.TopLeft;
var nTR = f * deltaTR + oldValue.TopRight;
var nBR = f * deltaBR + oldValue.BottomRight;
var nBL = f * deltaBL + oldValue.BottomLeft;
return new CornerRadius(nTL, nTR, nBR, nBL);
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

10
src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class PointTransition : Transition<Point>
{
private static readonly PointAnimator s_animator = new PointAnimator();
/// <inheritdocs/>
public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

10
src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class SizeTransition : Transition<Size>
{
private static readonly SizeAnimator s_animator = new SizeAnimator();
/// <inheritdocs/>
public override IObservable<Size> DoTransition(IObservable<double> progress, Size oldValue, Size newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

10
src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class ThicknessTransition : Transition<Thickness>
{
private static readonly ThicknessAnimator s_animator = new ThicknessAnimator();
/// <inheritdocs/>
public override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

10
src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class VectorTransition : Transition<Vector>
{
private static readonly VectorAnimator s_animator = new VectorAnimator();
/// <inheritdocs/>
public override IObservable<Vector> DoTransition(IObservable<double> progress, Vector oldValue, Vector newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

5
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -64,3 +64,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGl
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract.
Total Issues: 64
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 11

45
src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs

@ -1,5 +1,8 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media.Imaging
{
@ -34,8 +37,50 @@ namespace Avalonia.Media.Imaging
{
}
private WriteableBitmap(IWriteableBitmapImpl impl) : base(impl)
{
}
public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock();
public static WriteableBitmap Decode(Stream stream)
{
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return new WriteableBitmap(ri.LoadWriteableBitmap(stream));
}
/// <summary>
/// Loads a WriteableBitmap from a stream and decodes at the desired width. Aspect ratio is maintained.
/// This is more efficient than loading and then resizing.
/// </summary>
/// <param name="stream">The stream to read the bitmap from. This can be any supported image format.</param>
/// <param name="width">The desired width of the resulting bitmap.</param>
/// <param name="interpolationMode">The <see cref="BitmapInterpolationMode"/> to use should any scaling be required.</param>
/// <returns>An instance of the <see cref="WriteableBitmap"/> class.</returns>
public new static WriteableBitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return new WriteableBitmap(ri.LoadWriteableBitmapToWidth(stream, width, interpolationMode));
}
/// <summary>
/// Loads a Bitmap from a stream and decodes at the desired height. Aspect ratio is maintained.
/// This is more efficient than loading and then resizing.
/// </summary>
/// <param name="stream">The stream to read the bitmap from. This can be any supported image format.</param>
/// <param name="height">The desired height of the resulting bitmap.</param>
/// <param name="interpolationMode">The <see cref="BitmapInterpolationMode"/> to use should any scaling be required.</param>
/// <returns>An instance of the <see cref="WriteableBitmap"/> class.</returns>
public new static WriteableBitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode));
}
private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat)
{
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();

32
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -100,6 +100,38 @@ namespace Avalonia.Platform
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(Stream stream);
/// <summary>
/// Loads a WriteableBitmap implementation from a stream to a specified width maintaining aspect ratio.
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <param name="width">The desired width of the resulting bitmap.</param>
/// <param name="interpolationMode">The <see cref="BitmapInterpolationMode"/> to use should resizing be required.</param>
/// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality);
/// <summary>
/// Loads a WriteableBitmap implementation from a stream to a specified height maintaining aspect ratio.
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <param name="height">The desired height of the resulting bitmap.</param>
/// <param name="interpolationMode">The <see cref="BitmapInterpolationMode"/> to use should resizing be required.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality);
/// <summary>
/// Loads a WriteableBitmap implementation from a file.
/// </summary>
/// <param name="fileName">The filename of the bitmap.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IWriteableBitmapImpl LoadWriteableBitmap(string fileName);
/// <summary>
/// Loads a WriteableBitmap implementation from a file.
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IWriteableBitmapImpl LoadWriteableBitmap(Stream stream);
/// <summary>
/// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio.
/// </summary>

25
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -77,6 +77,31 @@ namespace Avalonia.Skia
return new ImmutableBitmap(stream);
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new WriteableBitmapImpl(stream, width, true, interpolationMode);
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new WriteableBitmapImpl(stream, height, false, interpolationMode);
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
using (var stream = File.OpenRead(fileName))
{
return LoadWriteableBitmap(stream);
}
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
return new WriteableBitmapImpl(stream);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{

65
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Threading;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Skia.Helpers;
using Avalonia.Visuals.Media.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
@ -15,7 +17,70 @@ namespace Avalonia.Skia
private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc;
private readonly SKBitmap _bitmap;
private readonly object _lock = new object();
/// <summary>
/// Create a WriteableBitmap from given stream.
/// </summary>
/// <param name="stream">Stream containing encoded data.</param>
public WriteableBitmapImpl(Stream stream)
{
using (var skiaStream = new SKManagedStream(stream))
{
_bitmap = SKBitmap.Decode(skiaStream);
if (_bitmap == null)
{
throw new ArgumentException("Unable to load bitmap from provided data");
}
PixelSize = new PixelSize(_bitmap.Width, _bitmap.Height);
Dpi = SkiaPlatform.DefaultDpi;
}
}
public WriteableBitmapImpl(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode)
{
using (var skStream = new SKManagedStream(stream))
using (var codec = SKCodec.Create(skStream))
{
var info = codec.Info;
// get the scale that is nearest to what we want (eg: jpg returned 512)
var supportedScale = codec.GetScaledDimensions(horizontal ? ((float)decodeSize / info.Width) : ((float)decodeSize / info.Height));
// decode the bitmap at the nearest size
var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height);
var bmp = SKBitmap.Decode(codec, nearest);
// now scale that to the size that we want
var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height);
SKImageInfo desired;
if (horizontal)
{
desired = new SKImageInfo(decodeSize, (int)(realScale * decodeSize));
}
else
{
desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize);
}
if (bmp.Width != desired.Width || bmp.Height != desired.Height)
{
var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality());
bmp.Dispose();
bmp = scaledBmp;
}
_bitmap = bmp;
PixelSize = new PixelSize(bmp.Width, bmp.Height);
Dpi = SkiaPlatform.DefaultDpi;
}
}
/// <summary>
/// Create new writeable bitmap.
/// </summary>

22
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -188,6 +188,28 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(stream);
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new WriteableWicBitmapImpl(stream, width, true, interpolationMode);
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new WriteableWicBitmapImpl(stream, height, false, interpolationMode);
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
return new WriteableWicBitmapImpl(fileName);
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
return new WriteableWicBitmapImpl(stream);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{

17
src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs

@ -1,4 +1,5 @@
using System;
using System.IO;
using Avalonia.Platform;
using SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat;
@ -7,11 +8,27 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl
{
public WriteableWicBitmapImpl(Stream stream, int decodeSize, bool horizontal,
Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode)
: base(stream, decodeSize, horizontal, interpolationMode)
{
}
public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat, AlphaFormat? alphaFormat)
: base(size, dpi, pixelFormat, alphaFormat)
{
}
public WriteableWicBitmapImpl(Stream stream)
: base(stream)
{
}
public WriteableWicBitmapImpl(string fileName)
: base(fileName)
{
}
class LockedBitmap : ILockedFramebuffer
{
private readonly WriteableWicBitmapImpl _parent;

22
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -61,6 +61,28 @@ namespace Avalonia.Benchmarks
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
throw new NotImplementedException();

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

22
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -66,6 +66,28 @@ namespace Avalonia.UnitTests
return Mock.Of<IBitmapImpl>();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
return Mock.Of<IBitmapImpl>();

22
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

@ -42,6 +42,28 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
throw new NotImplementedException();

Loading…
Cancel
Save