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 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")) new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{ {
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
@ -60,6 +64,10 @@ namespace ControlCatalog
public static Styles DefaultDark = new Styles 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")) new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{ {
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") 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" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ViewboxPage"> 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"> <StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Viewbox</TextBlock> <TextBlock Classes="h1">Viewbox</TextBlock>
<TextBlock Classes="h2">A control used to scale single child.</TextBlock> <TextBlock Classes="h2">A control used to scale single child.</TextBlock>
</StackPanel> </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"> <Grid Grid.Row="1" ColumnDefinitions="*,Auto" HorizontalAlignment="Center" Margin="0,10,0,0">
<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>
<Viewbox Grid.Row="0" Grid.Column="2" Stretch="None"> <Border HorizontalAlignment="Center" Grid.Column="0" BorderThickness="1" BorderBrush="Orange" Width="200" Height="200">
<Path Fill="Blue" Data="{StaticResource Acorn}"/> <Border VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0" BorderThickness="1" BorderBrush="CornflowerBlue" Width="{Binding #WidthSlider.Value}" Height="{Binding #HeightSlider.Value}" >
</Viewbox> <Viewbox
<Viewbox Grid.Row="1" Grid.Column="2" Stretch="Fill"> Stretch="{Binding #StretchSelector.SelectedItem}"
<Path Fill="Blue" Data="{StaticResource Acorn}"/> StretchDirection="{Binding #StretchDirectionSelector.SelectedItem}">
</Viewbox> <Ellipse Width="50" Height="50" Fill="CornflowerBlue" />
<Viewbox Grid.Row="2" Grid.Column="2" Stretch="Uniform"> </Viewbox>
<Path Fill="Blue" Data="{StaticResource Acorn}"/> </Border>
</Viewbox> </Border>
<Viewbox Grid.Row="3" Grid.Column="2" Stretch="UniformToFill">
<Path Fill="Blue" Data="{StaticResource Acorn}"/> <StackPanel HorizontalAlignment="Left" Orientation="Vertical" Grid.Column="1" Margin="8,0,0,0" Width="150">
</Viewbox> <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>
</Grid> </Grid>
</UserControl> </UserControl>

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

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

7
samples/RenderDemo/Pages/AnimationsPage.xaml

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

65
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -1,7 +1,8 @@
<UserControl <UserControl
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.TransitionsPage"> x:Class="RenderDemo.Pages.TransitionsPage"
MaxWidth="600">
<UserControl.Styles> <UserControl.Styles>
<Styles> <Styles>
<Styles.Resources> <Styles.Resources>
@ -90,6 +91,56 @@
<Setter Property="RenderTransform" Value="none" /> <Setter Property="RenderTransform" Value="none" />
</Style> </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> </Styles>
</UserControl.Styles> </UserControl.Styles>
@ -98,8 +149,8 @@
<StackPanel.Clock> <StackPanel.Clock>
<Clock /> <Clock />
</StackPanel.Clock> </StackPanel.Clock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
<TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock> <TextBlock VerticalAlignment="Center">Hover to activate Transitions.</TextBlock>
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" /> <Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
</StackPanel> </StackPanel>
<WrapPanel ClipToBounds="False"> <WrapPanel ClipToBounds="False">
@ -109,6 +160,12 @@
<Border Classes="Test Rect4" Background="Navy"/> <Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/> <Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Orange"/> <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> </WrapPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

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

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

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

@ -1,6 +1,8 @@
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
@ -8,12 +10,13 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public class FloatTransition : Transition<float> public class FloatTransition : Transition<float>
{ {
private static readonly FloatAnimator s_animator = new FloatAnimator();
/// <inheritdocs/> /// <inheritdocs/>
public override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue) public override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue)
{ {
var delta = newValue - oldValue;
return progress 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;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
@ -8,12 +10,13 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public class IntegerTransition : Transition<int> public class IntegerTransition : Transition<int>
{ {
private static readonly Int32Animator s_animator = new Int32Animator();
/// <inheritdocs/> /// <inheritdocs/>
public override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue) public override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue)
{ {
var delta = newValue - oldValue;
return progress 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) public void OnError(Exception error)
{ {
throw new NotImplementedException(); throw new NotImplementedException("BindingEntry.OnError is not implemented", error);
} }
public void OnNext(BindingValue<T> value) public void OnNext(BindingValue<T> value)

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

@ -535,7 +535,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DataGrid, int>( AvaloniaProperty.RegisterDirect<DataGrid, int>(
nameof(SelectedIndex), nameof(SelectedIndex),
o => o.SelectedIndex, o => o.SelectedIndex,
(o, v) => o.SelectedIndex = v); (o, v) => o.SelectedIndex = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Gets or sets the index of the current selection. /// Gets or sets the index of the current selection.
@ -553,7 +554,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DataGrid, object>( AvaloniaProperty.RegisterDirect<DataGrid, object>(
nameof(SelectedItem), nameof(SelectedItem),
o => o.SelectedItem, o => o.SelectedItem,
(o, v) => o.SelectedItem = v); (o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Gets or sets the data item corresponding to the selected row. /// 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.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.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. 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 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. 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. 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.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. 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. 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() static Image()
{ {
AffectsRender<Image>(SourceProperty, StretchProperty); AffectsRender<Image>(SourceProperty, StretchProperty, StretchDirectionProperty);
AffectsMeasure<Image>(SourceProperty, StretchProperty); AffectsMeasure<Image>(SourceProperty, StretchProperty, StretchDirectionProperty);
} }
/// <summary> /// <summary>

87
src/Avalonia.Controls/Viewbox.cs

@ -1,40 +1,48 @@
using System; using Avalonia.Media;
using Avalonia.Media;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// Viewbox is used to scale single child. /// Viewbox is used to scale single child to fit in the available space.
/// </summary> /// </summary>
/// <seealso cref="Avalonia.Controls.Decorator" /> /// <seealso cref="Avalonia.Controls.Decorator" />
public class Viewbox : Decorator public class Viewbox : Decorator
{ {
/// <summary> /// <summary>
/// The stretch property /// Defines the <see cref="Stretch"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<Stretch> StretchProperty = public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.RegisterDirect<Viewbox, Stretch>(nameof(Stretch), AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
v => v.Stretch, (c, v) => c.Stretch = v, 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> /// <summary>
/// Gets or sets the stretch mode, /// Gets or sets the stretch mode,
/// which determines how child fits into the available space. /// which determines how child fits into the available space.
/// </summary> /// </summary>
/// <value>
/// The stretch.
/// </value>
public Stretch Stretch public Stretch Stretch
{ {
get => _stretch; get => GetValue(StretchProperty);
set => SetAndRaise(StretchProperty, ref _stretch, value); 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); get => GetValue(StretchDirectionProperty);
AffectsMeasure<Viewbox>(StretchProperty); set => SetValue(StretchDirectionProperty, value);
} }
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
@ -47,9 +55,9 @@ namespace Avalonia.Controls
var childSize = child.DesiredSize; 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(); return new Size();
@ -62,7 +70,9 @@ namespace Avalonia.Controls
if (child != null) if (child != null)
{ {
var childSize = child.DesiredSize; 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; var scaleTransform = child.RenderTransform as ScaleTransform;
if (scaleTransform == null) if (scaleTransform == null)
@ -81,44 +91,5 @@ namespace Avalonia.Controls
return new Size(); 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.ComponentModel;
using System.Text;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -95,12 +97,27 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
string CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty) 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 builder = new StringBuilder();
var maxValue = ao.GetValue(maxProperty);
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; return null;
@ -183,8 +200,8 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
var size = _control.Bounds; var size = _control.Bounds;
Width = size.Width; Width = Math.Round(size.Width, 2);
Height = size.Height; Height = Math.Round(size.Height, 2);
} }
} }
} }

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

@ -8,11 +8,16 @@
<UserControl.Resources> <UserControl.Resources>
<SolidColorBrush x:Key="ThicknessBorderBrush" Color="#666666" /> <SolidColorBrush x:Key="ThicknessBorderBrush" Color="#666666" />
<SolidColorBrush x:Key="HighlightBorderBrush" Color="CornflowerBlue" />
<SolidColorBrush x:Key="SizeGuidelineBrush" Color="#333333" /> <SolidColorBrush x:Key="SizeGuidelineBrush" Color="#333333" />
<SolidColorBrush x:Key="MarginBackgroundBrush" Color="#D78965" /> <SolidColorBrush x:Key="MarginBackgroundBrush" Color="#D78965" />
<SolidColorBrush x:Key="MarginHighlightBrush" Color="#EA966F" />
<SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" /> <SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" />
<SolidColorBrush x:Key="BorderHighlightBrush" Color="#EFCD88" />
<SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" /> <SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" />
<SolidColorBrush x:Key="PaddingHighlightBrush" Color="#CEDA8E" />
<SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" /> <SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" />
<SolidColorBrush x:Key="SizeHighlightBrush" Color="#9ED0DC" />
<conv:BoolToOpacityConverter x:Key="BoolToOpacity" Opacity="0.6"/> <conv:BoolToOpacityConverter x:Key="BoolToOpacity" Opacity="0.6"/>
</UserControl.Resources> </UserControl.Resources>
@ -24,43 +29,60 @@
<Setter Property="BorderBrush" Value="{StaticResource ThicknessBorderBrush}" /> <Setter Property="BorderBrush" Value="{StaticResource ThicknessBorderBrush}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border <Panel>
Background="{TemplateBinding Background}" <Rectangle x:Name="PART_Background" Classes.no-content-pointerover="{Binding !#PART_ContentPresenter.IsPointerOver}" />
BorderBrush="{TemplateBinding BorderBrush}" <Border
BorderThickness="{TemplateBinding BorderThickness}"> x:Name="PART_Border"
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*,Auto"> Classes.no-content-pointerover="{Binding !#PART_ContentPresenter.IsPointerOver}"
<Grid.Styles> BorderBrush="{TemplateBinding BorderBrush}"
<Style Selector="TextBox.thickness-edit"> BorderThickness="{TemplateBinding BorderThickness}">
<Setter Property="Background" Value="Transparent" /> <Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*,Auto">
<Setter Property="BorderThickness" Value="0" /> <Grid.Styles>
<Setter Property="Margin" Value="2" /> <Style Selector="TextBox.thickness-edit">
<Setter Property="HorizontalAlignment" Value="Stretch" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="VerticalAlignment" Value="Stretch" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="Margin" Value="2" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Disabled" /> <Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Disabled" /> <Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="IsVisible" Value="{Binding $parent[local:ThicknessEditor].IsPresent}" /> <Setter Property="VerticalContentAlignment" Value="Center" />
</Style> <Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Disabled" />
</Grid.Styles> <Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Disabled" />
<TextBlock IsVisible="{TemplateBinding IsPresent}" Margin="4,0,0,0" Text="{TemplateBinding Header}" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" /> <Setter Property="IsVisible" Value="{Binding $parent[local:ThicknessEditor].IsPresent}" />
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Left, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" /> </Style>
<TextBox x:Name="Right" Grid.Row="0" Grid.Column="1" Text="{Binding Top, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" /> </Grid.Styles>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Right, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" /> <TextBlock IsVisible="{TemplateBinding IsPresent}" Margin="4,0,0,0" Text="{TemplateBinding Header}" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Bottom, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" /> <TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Left, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<ContentPresenter Grid.Row="1" Grid.Column="1" <TextBox x:Name="Right" Grid.Row="0" Grid.Column="1" Text="{Binding Top, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
Name="PART_ContentPresenter" <TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Right, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
ContentTemplate="{TemplateBinding ContentTemplate}" <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Bottom, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
Content="{TemplateBinding Content}" <ContentPresenter Grid.Row="1" Grid.Column="1"
Padding="{TemplateBinding Padding}" Name="PART_ContentPresenter"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" /> Content="{TemplateBinding Content}"
</Grid> Padding="{TemplateBinding Padding}"
</Border> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Grid>
</Border>
</Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </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]"> <Style Selector="local|ThicknessEditor[IsPresent=False]">
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
</Style> </Style>
@ -110,47 +132,67 @@
<Grid Grid.Column="2" RowDefinitions="Auto,*, Auto,*,Auto" > <Grid Grid.Column="2" RowDefinitions="Auto,*, Auto,*,Auto" >
<TextBlock Grid.Row="0" Text="Layout Visualizer" Margin="4" /> <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"> <Viewbox Grid.Row="1" StretchDirection="DownOnly" >
<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" <Grid.Styles>
TextDecorations="{Binding Layout.HeightConstraint, Converter={x:Static local:Converters.HasConstraintConverter}}" <Style Selector="TextBlock.with-constraint">
Text="{Binding Layout.Height}" <Setter Property="TextDecorations" Value="Underline" />
ToolTip.Tip="{Binding Layout.HeightConstraint}" /> </Style>
</Border>
</Grid.Styles>
<Border x:Name="HorizontalSize" Grid.Row="1" Grid.Column="0" >
<TextBlock HorizontalAlignment="Center" FontWeight="Bold" <Border x:Name="VerticalSize" Grid.Row="0" Grid.Column="1" >
TextDecorations="{Binding Layout.WidthConstraint, Converter={x:Static local:Converters.HasConstraintConverter}}" <TextBlock VerticalAlignment="Center" FontWeight="Bold"
Text="{Binding Layout.Width}" Classes.with-constraint="{Binding Layout.HeightConstraint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
ToolTip.Tip="{Binding Layout.WidthConstraint}" /> Text="{Binding Layout.Height}"
</Border> ToolTip.Tip="{Binding Layout.HeightConstraint}" />
</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}"> <Border x:Name="HorizontalSize" Grid.Row="1" Grid.Column="0" >
<local:ThicknessEditor x:Name="PaddingArea" Header="padding" Background="{StaticResource PaddingBackgroundBrush}" Thickness="{Binding Layout.PaddingThickness}" IsPresent="{Binding Layout.HasPadding}"> <TextBlock HorizontalAlignment="Center" FontWeight="Bold"
<Border x:Name="ContentArea" BorderThickness="1" BorderBrush="{StaticResource ThicknessBorderBrush}" MinWidth="100" MinHeight="16" Background="{StaticResource SizeBackgroundBrush}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"> Classes.with-constraint="{Binding Layout.WidthConstraint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
<TextBlock Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" Text="content" /> Text="{Binding Layout.Width}"
</Border> 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> </local:ThicknessEditor>
</local:ThicknessEditor>
<Canvas Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2">
<Canvas Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2"> <Canvas.Styles>
<Canvas.Styles> <Style Selector="Rectangle">
<Style Selector="Rectangle"> <Setter Property="StrokeDashArray" Value="1,3" />
<Setter Property="StrokeDashArray" Value="1,3" /> <Setter Property="Stroke" Value="{StaticResource SizeGuidelineBrush}" />
<Setter Property="Stroke" Value="{StaticResource SizeGuidelineBrush}" /> <Setter Property="StrokeThickness" Value="1" />
<Setter Property="StrokeThickness" Value="1" /> <Setter Property="IsHitTestVisible" Value="False" />
</Style> </Style>
</Canvas.Styles> </Canvas.Styles>
<Rectangle x:Name="HorizontalSizeBegin" /> <Rectangle x:Name="HorizontalSizeBegin" />
<Rectangle x:Name="HorizontalSizeEnd" /> <Rectangle x:Name="HorizontalSizeEnd" />
<Rectangle x:Name="VerticalSizeBegin" /> <Rectangle x:Name="VerticalSizeBegin" />
<Rectangle x:Name="VerticalSizeEnd" /> <Rectangle x:Name="VerticalSizeEnd" />
</Canvas> </Canvas>
</Grid> </Grid>
</Viewbox>
<Grid Grid.Row="2" Margin="4" RowDefinitions="Auto,Auto"> <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.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Media; using Avalonia.Media;
namespace Avalonia.Diagnostics.Views 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 internal class ThicknessEditor : ContentControl
{ {
public static readonly DirectProperty<ThicknessEditor, Thickness> ThicknessProperty = public static readonly DirectProperty<ThicknessEditor, Thickness> ThicknessProperty =
@ -44,6 +32,14 @@ namespace Avalonia.Diagnostics.Views
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Bottom), o => o.Bottom, AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Bottom), o => o.Bottom,
(o, v) => o.Bottom = v); (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 Thickness _thickness;
private string _header; private string _header;
@ -52,7 +48,6 @@ namespace Avalonia.Diagnostics.Views
private double _top; private double _top;
private double _right; private double _right;
private double _bottom; private double _bottom;
private bool _isUpdatingThickness; private bool _isUpdatingThickness;
public Thickness Thickness 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)); 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) 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)); 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 System.Collections.Generic;
using Avalonia.Collections; using Avalonia.Collections;
#nullable enable
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
@ -90,7 +92,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Remvoes all non-pseudoclasses from the collection. /// Removes all non-pseudoclasses from the collection.
/// </summary> /// </summary>
public override void Clear() public override void Clear()
{ {
@ -135,7 +137,7 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public override void InsertRange(int index, IEnumerable<string> names) public override void InsertRange(int index, IEnumerable<string> names)
{ {
var c = new List<string>(); List<string>? toInsert = null;
foreach (var name in names) foreach (var name in names)
{ {
@ -143,11 +145,16 @@ namespace Avalonia.Controls
if (!Contains(name)) 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> /// <summary>
@ -176,19 +183,21 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public override void RemoveAll(IEnumerable<string> names) public override void RemoveAll(IEnumerable<string> names)
{ {
var c = new List<string>(); List<string>? toRemove = null;
foreach (var name in names) foreach (var name in names)
{ {
ThrowIfPseudoclass(name, "removed"); ThrowIfPseudoclass(name, "removed");
if (Contains(name)) toRemove ??= new List<string>();
{
c.Add(name); toRemove.Add(name);
}
} }
base.RemoveAll(c); if (toRemove != null)
{
base.RemoveAll(toRemove);
}
} }
/// <summary> /// <summary>
@ -223,7 +232,7 @@ namespace Avalonia.Controls
/// <param name="source">The new contents of the collection.</param> /// <param name="source">The new contents of the collection.</param>
public void Replace(IList<string> source) public void Replace(IList<string> source)
{ {
var toRemove = new List<string>(); List<string>? toRemove = null;
foreach (var name in source) foreach (var name in source)
{ {
@ -234,11 +243,17 @@ namespace Avalonia.Controls
{ {
if (!name.StartsWith(":")) if (!name.StartsWith(":"))
{ {
toRemove ??= new List<string>();
toRemove.Add(name); toRemove.Add(name);
} }
} }
base.RemoveAll(toRemove); if (toRemove != null)
{
base.RemoveAll(toRemove);
}
base.AddRange(source); base.AddRange(source);
} }

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

@ -1,6 +1,7 @@
<Style xmlns="https://github.com/avaloniaui" <Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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> <Style.Resources>
<!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN --> <!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN -->
<!-- SystemColor Color Resources (Reflect OS High Contrast Settings) --> <!-- SystemColor Color Resources (Reflect OS High Contrast Settings) -->
@ -30,5 +31,10 @@
<!-- ScrollBar animation --> <!-- ScrollBar animation -->
<TransformOperations x:Key="VerticalSmallScrollThumbScaleTransform">scaleX(0.125) translateX(-2px)</TransformOperations> <TransformOperations x:Key="VerticalSmallScrollThumbScaleTransform">scaleX(0.125) translateX(-2px)</TransformOperations>
<TransformOperations x:Key="HorizontalSmallScrollThumbScaleTransform">scaleY(0.125) translateY(-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.Resources>
</Style> </Style>

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

@ -27,14 +27,12 @@
<Style Selector="DataValidationErrors"> <Style Selector="DataValidationErrors">
<Style.Resources> <Style.Resources>
<DataTemplate x:Key="InlineDataValidationErrorTemplate"> <DataTemplate x:Key="InlineDataValidationErrorTemplate">
<ItemsControl Items="{Binding}"> <ItemsControl Items="{Binding}" TextBlock.Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}">
<ItemsControl.ItemTemplate> <ItemsControl.Styles>
<DataTemplate> <Style Selector="TextBlock">
<TextBlock Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}" <Setter Property="TextWrapping" Value="Wrap" />
Text="{Binding}" </Style>
TextWrapping="Wrap" /> </ItemsControl.Styles>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
</DataTemplate> </DataTemplate>
<ControlTemplate x:Key="InlineDataValidationContentTemplate" TargetType="DataValidationErrors"> <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"> <Style Selector="Expander">
<Setter Property="ContentTransition"> <Setter Property="Background" Value="{DynamicResource ExpanderBackground}" />
<Setter.Value> <Setter Property="BorderThickness" Value="{DynamicResource ExpanderBorderThickness}" />
<CrossFade Duration="00:00:00.25" /> <Setter Property="BorderBrush" Value="{DynamicResource ExpanderBorderBrush}" />
</Setter.Value> <Setter Property="Padding" Value="{DynamicResource ExpanderHeaderPadding}" />
</Setter> <Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style> </Style>
<Style Selector="Expander[ExpandDirection=Down]"> <Style Selector="Expander">
<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]">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}"> <DockPanel>
<Grid RowDefinitions="*,Auto"> <ToggleButton x:Name="ExpanderHeader"
<ToggleButton Name="PART_toggle" Grid.Row="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> Padding="{TemplateBinding Padding}"
<ContentPresenter Name="PART_ContentPresenter" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Grid.Row="0" Background="{TemplateBinding Background}"
IsVisible="{TemplateBinding IsExpanded}" BorderBrush="{TemplateBinding BorderBrush}"
ContentTemplate="{TemplateBinding ContentTemplate}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}" 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}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" 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}" Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" ContentTemplate="{TemplateBinding ContentTemplate}" />
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" </Border>
Padding="{TemplateBinding Padding}" /> </DockPanel>
</Grid>
</Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Expander[ExpandDirection=Left]">
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}"> <Border x:Name="ToggleButtonBackground">
<Grid ColumnDefinitions="*,Auto"> <Grid ColumnDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Column="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> <ContentPresenter x:Name="PART_ContentPresenter"
<ContentPresenter Name="PART_ContentPresenter" Margin="{TemplateBinding Padding}"
Grid.Column="0"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="Center"
Padding="{TemplateBinding Padding}" /> Background="Transparent"
</Grid> BorderBrush="Transparent"
</Border> BorderThickness="0"
</ControlTemplate> Content="{TemplateBinding Content}"
</Setter> ContentTemplate="{TemplateBinding ContentTemplate}"
</Style> TextBlock.Foreground="{DynamicResource ExpanderForeground}" />
<Style Selector="Expander /template/ ToggleButton#PART_toggle"> <Border x:Name="ExpandCollapseChevronBorder"
<Setter Property="Template"> Grid.Column="1"
<ControlTemplate> Width="32"
<Border BorderThickness="1" Background="Transparent"> Height="32"
<Grid ColumnDefinitions="Auto,Auto"> Margin="7"
<Border Grid.Column="0" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center"> RenderTransformOrigin="50%,50%">
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}" <Path x:Name="ExpandCollapseChevron"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Data="M 0 2 L 4 6 L 0 10 Z" /> RenderTransformOrigin="50%,50%"
Stretch="None"
Stroke="{DynamicResource ExpanderChevronForeground}"
StrokeThickness="1" />
<Border.RenderTransform>
<RotateTransform />
</Border.RenderTransform>
</Border> </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> </Grid>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Expander /template/ ToggleButton#PART_toggle:pointerover /template/ Border"> <Style Selector="Expander /template/ ToggleButton#ExpanderHeader:pressed">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumBrush}" /> <Setter Property="RenderTransform" Value="{x:Null}" />
</Style> </Style>
<Style Selector="Expander:down:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform"> <Style Selector="Expander:left /template/ ToggleButton#ExpanderHeader, Expander:right /template/ ToggleButton#ExpanderHeader">
<RotateTransform Angle="90" /> <Setter Property="VerticalAlignment" Value="Stretch" />
</Setter>
</Style> </Style>
<Style Selector="Expander:up:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="-90" /> <Style Selector="Expander /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
</Setter> <Setter Property="Background" Value="{TemplateBinding Background}" />
<Setter Property="BorderBrush" Value="{TemplateBinding BorderBrush}" />
<Setter Property="BorderThickness" Value="{TemplateBinding BorderThickness}" />
</Style> </Style>
<Style Selector="Expander:left:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform"> <Style Selector="Expander:not(:expanded) /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<RotateTransform Angle="180" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Setter>
</Style> </Style>
<Style Selector="Expander:right /template/ ToggleButton#PART_toggle /template/ Path"> <Style Selector="Expander:expanded:up /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="RenderTransform"> <Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
<RotateTransform Angle="180" />
</Setter>
</Style> </Style>
<Style Selector="Expander:right:expanded /template/ ToggleButton#PART_toggle /template/ Path"> <Style Selector="Expander:expanded:up /template/ Border#ExpanderContent">
<Setter Property="RenderTransform"> <Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
<RotateTransform Angle="0" /> </Style>
</Setter> <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> </Style>
</Styles> </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) 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)); 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) 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)); 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;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
@ -8,26 +10,13 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public class CornerRadiusTransition : Transition<CornerRadius> public class CornerRadiusTransition : Transition<CornerRadius>
{ {
private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator();
/// <inheritdocs/> /// <inheritdocs/>
public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue) public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue)
{ {
return progress return progress
.Select(p => .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
{
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);
});
} }
} }
} }

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

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

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

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

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

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

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

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

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. 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. 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 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;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.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 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) private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat)
{ {
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); 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> /// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(Stream stream); 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> /// <summary>
/// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio. /// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio.
/// </summary> /// </summary>

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

@ -77,6 +77,31 @@ namespace Avalonia.Skia
return new ImmutableBitmap(stream); 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 /> /// <inheritdoc />
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride) 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;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Skia.Helpers; using Avalonia.Skia.Helpers;
using Avalonia.Visuals.Media.Imaging;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
@ -15,7 +17,70 @@ namespace Avalonia.Skia
private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc; private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc;
private readonly SKBitmap _bitmap; private readonly SKBitmap _bitmap;
private readonly object _lock = new object(); 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> /// <summary>
/// Create new writeable bitmap. /// Create new writeable bitmap.
/// </summary> /// </summary>

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

@ -188,6 +188,28 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(stream); 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 /> /// <inheritdoc />
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) 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;
using System.IO;
using Avalonia.Platform; using Avalonia.Platform;
using SharpDX.WIC; using SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat; using PixelFormat = Avalonia.Platform.PixelFormat;
@ -7,11 +8,27 @@ namespace Avalonia.Direct2D1.Media.Imaging
{ {
class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl 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) public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat, AlphaFormat? alphaFormat)
: base(size, dpi, pixelFormat, alphaFormat) : base(size, dpi, pixelFormat, alphaFormat)
{ {
} }
public WriteableWicBitmapImpl(Stream stream)
: base(stream)
{
}
public WriteableWicBitmapImpl(string fileName)
: base(fileName)
{
}
class LockedBitmap : ILockedFramebuffer class LockedBitmap : ILockedFramebuffer
{ {
private readonly WriteableWicBitmapImpl _parent; private readonly WriteableWicBitmapImpl _parent;

22
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -61,6 +61,28 @@ namespace Avalonia.Benchmarks
throw new NotImplementedException(); 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) public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{ {
throw new NotImplementedException(); 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.ScaleX);
Assert.Equal(2.0, scaleTransform.ScaleY); 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>(); 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) public IBitmapImpl LoadBitmap(string fileName)
{ {
return Mock.Of<IBitmapImpl>(); return Mock.Of<IBitmapImpl>();

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

@ -42,6 +42,28 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException(); 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) public IBitmapImpl LoadBitmap(string fileName)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

Loading…
Cancel
Save