diff --git a/Documentation/build.md b/Documentation/build.md
index 5f75290424..a7d68eb599 100644
--- a/Documentation/build.md
+++ b/Documentation/build.md
@@ -9,10 +9,24 @@ git clone https://github.com/AvaloniaUI/Avalonia.git
git submodule update --init
```
+### Install the required version of the .NET Core SDK
+
+Go to https://dotnet.microsoft.com/download/visual-studio-sdks and install the latest version of the .NET Core SDK compatible with Avalonia UI. Make sure to download the SDK (not just the "runtime") package. The version compatible is indicated within the [global.json](https://github.com/AvaloniaUI/Avalonia/blob/master/global.json) file. Note that Avalonia UI does not always use the latest version and is hardcoded to use the last version known to be compatible (SDK releases may break the builds from time-to-time).
+
### Open in Visual Studio
-Open the `Avalonia.sln` solution in Visual Studio 2019 or newer. The free Visual Studio Community
-edition works fine. Run the `Samples\ControlCatalog.Desktop` project to see the sample application.
+Open the `Avalonia.sln` solution in Visual Studio 2019 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application.
+
+### Troubleshooting
+
+ * **Error CS0006: Avalonia.DesktopRuntime.dll could not be found**
+
+ It is common for the first build to fail with the errors below (also discussed in [#4257](https://github.com/AvaloniaUI/Avalonia/issues/4257)).
+ ```
+ >CSC : error CS0006: Metadata file 'C:\...\Avalonia\src\Avalonia.DesktopRuntime\bin\Debug\netcoreapp2.0\Avalonia.DesktopRuntime.dll' could not be found
+ >CSC : error CS0006: Metadata file 'C:\...\Avalonia\packages\Avalonia\bin\Debug\netcoreapp2.0\Avalonia.dll' could not be found
+ ```
+ To correct this, right click on the `Avalonia.DesktopRuntime` project then press `Build` to build the project manually. Afterwards the solution should build normally and the ControlCatalog can be run.
# Linux/macOS
@@ -20,9 +34,9 @@ It's *not* possible to build the *whole* project on Linux/macOS. You can only bu
MonoDevelop, Xamarin Studio and Visual Studio for Mac aren't capable of properly opening our solution. You can use Rider (at least 2017.2 EAP) or VSCode instead. They will fail to load most of platform specific projects, but you don't need them to run on .NET Core.
-### Install the latest version of .NET Core
+### Install the latest version of the .NET Core SDK
-Go to https://www.microsoft.com/net/core and follow instructions for your OS. You need SDK (not just "runtime") package.
+Go to https://www.microsoft.com/net/core and follow the instructions for your OS. Make sure to download the SDK (not just the "runtime") package.
### Additional requirements for macOS
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 020fb2fff3..f3ec7b48aa 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -39,6 +39,10 @@ namespace ControlCatalog
public static Styles DefaultLight = new Styles
{
+ new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
+ },
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
@@ -60,6 +64,10 @@ namespace ControlCatalog
public static Styles DefaultDark = new Styles
{
+ new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
+ },
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml
index e78cf2bc22..ef802db33e 100644
--- a/samples/ControlCatalog/Pages/ViewboxPage.xaml
+++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml
@@ -1,66 +1,36 @@
-
-
- F1 M 16.6309,18.6563C 17.1309,
- 8.15625 29.8809,14.1563 29.8809,
- 14.1563C 30.8809,11.1563 34.1308,
- 11.4063 34.1308,11.4063C 33.5,12
- 34.6309,13.1563 34.6309,13.1563C
- 32.1309,13.1562 31.1309,14.9062
- 31.1309,14.9062C 41.1309,23.9062
- 32.6309,27.9063 32.6309,27.9062C
- 24.6309,24.9063 21.1309,22.1562
- 16.6309,18.6563 Z M 16.6309,19.9063C
- 21.6309,24.1563 25.1309,26.1562
- 31.6309,28.6562C 31.6309,28.6562
- 26.3809,39.1562 18.3809,36.1563C
- 18.3809,36.1563 18,38 16.3809,36.9063C
- 15,36 16.3809,34.9063 16.3809,34.9063C
- 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z
-
-
-
+
Viewbox
A control used to scale single child.
-
- None
- Fill
- Uniform
- UniformToFill
-
- Hello World!
-
-
- Hello World!
-
-
- Hello World!
-
-
- Hello World!
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
index 1b5f4bc7f4..94b3f3ea14 100644
--- a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.Media;
namespace ControlCatalog.Pages
{
@@ -7,7 +8,25 @@ namespace ControlCatalog.Pages
{
public ViewboxPage()
{
- this.InitializeComponent();
+ InitializeComponent();
+
+ var stretchSelector = this.FindControl("StretchSelector");
+
+ stretchSelector.Items = new[]
+ {
+ Stretch.Uniform, Stretch.UniformToFill, Stretch.Fill, Stretch.None
+ };
+
+ stretchSelector.SelectedIndex = 0;
+
+ var stretchDirectionSelector = this.FindControl("StretchDirectionSelector");
+
+ stretchDirectionSelector.Items = new[]
+ {
+ StretchDirection.Both, StretchDirection.DownOnly, StretchDirection.UpOnly
+ };
+
+ stretchDirectionSelector.SelectedIndex = 0;
}
private void InitializeComponent()
diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml
index 12fb31ea59..21c7d68b5d 100644
--- a/samples/RenderDemo/Pages/AnimationsPage.xaml
+++ b/samples/RenderDemo/Pages/AnimationsPage.xaml
@@ -1,7 +1,8 @@
+ x:Class="RenderDemo.Pages.AnimationsPage"
+ MaxWidth="600">
@@ -167,8 +168,8 @@
-
- Hover to activate Transform Keyframe Animations.
+
+ Hover to activate Keyframe Animations.
diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml b/samples/RenderDemo/Pages/TransitionsPage.xaml
index d6da293ff3..f9f69fb341 100644
--- a/samples/RenderDemo/Pages/TransitionsPage.xaml
+++ b/samples/RenderDemo/Pages/TransitionsPage.xaml
@@ -1,7 +1,8 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ x:Class="RenderDemo.Pages.TransitionsPage"
+ MaxWidth="600">
@@ -90,6 +91,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -98,8 +149,8 @@
-
- Hover to activate Transform Keyframe Animations.
+
+ Hover to activate Transitions.
@@ -109,6 +160,12 @@
+
+
+
+
+
+
diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs
index 05142532e9..c42153ec4f 100644
--- a/src/Avalonia.Animation/Animation.cs
+++ b/src/Avalonia.Animation/Animation.cs
@@ -209,6 +209,17 @@ namespace Avalonia.Animation
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ),
};
+ ///
+ /// Registers a that can handle
+ /// a value type that matches the specified condition.
+ ///
+ ///
+ /// The condition to which the
+ /// is to be activated and used.
+ ///
+ ///
+ /// The type of the animator to instantiate.
+ ///
public static void RegisterAnimator(Func condition)
where TAnimator : IAnimator
{
diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs
index 0660440e30..d784227620 100644
--- a/src/Avalonia.Animation/Animators/Animator`1.cs
+++ b/src/Avalonia.Animation/Animators/Animator`1.cs
@@ -104,6 +104,11 @@ namespace Avalonia.Animation.Animators
throw new Exception("Index time is out of keyframe time range.");
}
+ public virtual IDisposable BindAnimation(Animatable control, IObservable instance)
+ {
+ return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation);
+ }
+
///
/// Runs the KeyFrames Animation.
///
@@ -116,7 +121,8 @@ namespace Avalonia.Animation.Animators
clock ?? control.Clock ?? Clock.GlobalClock,
onComplete,
InterpolationHandler);
- return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation);
+
+ return BindAnimation(control, instance);
}
///
diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs
index 8cae1e1f81..d5bb1aac20 100644
--- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs
+++ b/src/Avalonia.Animation/Transitions/DoubleTransition.cs
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Animation.Animators;
+
namespace Avalonia.Animation
{
///
@@ -8,15 +10,13 @@ namespace Avalonia.Animation
///
public class DoubleTransition : Transition
{
+ private static readonly DoubleAnimator s_animator = new DoubleAnimator();
+
///
public override IObservable DoTransition(IObservable progress, double oldValue, double newValue)
{
return progress
- .Select(p =>
- {
- var f = Easing.Ease(p);
- return ((newValue - oldValue) * f) + oldValue;
- });
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}
diff --git a/src/Avalonia.Animation/Transitions/FloatTransition.cs b/src/Avalonia.Animation/Transitions/FloatTransition.cs
index 427563e559..37b644fa96 100644
--- a/src/Avalonia.Animation/Transitions/FloatTransition.cs
+++ b/src/Avalonia.Animation/Transitions/FloatTransition.cs
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Animation.Animators;
+
namespace Avalonia.Animation
{
///
@@ -8,12 +10,13 @@ namespace Avalonia.Animation
///
public class FloatTransition : Transition
{
+ private static readonly FloatAnimator s_animator = new FloatAnimator();
+
///
public override IObservable DoTransition(IObservable progress, float oldValue, float newValue)
{
- var delta = newValue - oldValue;
return progress
- .Select(p => (float)Easing.Ease(p) * delta + oldValue);
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}
diff --git a/src/Avalonia.Animation/Transitions/IntegerTransition.cs b/src/Avalonia.Animation/Transitions/IntegerTransition.cs
index 7a85bd75dc..223b2ba531 100644
--- a/src/Avalonia.Animation/Transitions/IntegerTransition.cs
+++ b/src/Avalonia.Animation/Transitions/IntegerTransition.cs
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Animation.Animators;
+
namespace Avalonia.Animation
{
///
@@ -8,12 +10,13 @@ namespace Avalonia.Animation
///
public class IntegerTransition : Transition
{
+ private static readonly Int32Animator s_animator = new Int32Animator();
+
///
public override IObservable DoTransition(IObservable progress, int oldValue, int newValue)
{
- var delta = newValue - oldValue;
return progress
- .Select(p => (int)(Easing.Ease(p) * delta + oldValue));
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}
diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs
index 1b29338f07..362736eb06 100644
--- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs
@@ -79,7 +79,7 @@ namespace Avalonia.PropertyStore
public void OnError(Exception error)
{
- throw new NotImplementedException();
+ throw new NotImplementedException("BindingEntry.OnError is not implemented", error);
}
public void OnNext(BindingValue value)
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index a070a1b9d7..1b4632d368 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -535,7 +535,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect(
nameof(SelectedIndex),
o => o.SelectedIndex,
- (o, v) => o.SelectedIndex = v);
+ (o, v) => o.SelectedIndex = v,
+ defaultBindingMode: BindingMode.TwoWay);
///
/// Gets or sets the index of the current selection.
@@ -553,7 +554,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect(
nameof(SelectedItem),
o => o.SelectedItem,
- (o, v) => o.SelectedItem = v);
+ (o, v) => o.SelectedItem = v,
+ defaultBindingMode: BindingMode.TwoWay);
///
/// Gets or sets the data item corresponding to the selected row.
diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt
index 7199c15d21..a79b3b4d7b 100644
--- a/src/Avalonia.Controls/ApiCompatBaseline.txt
+++ b/src/Avalonia.Controls/ApiCompatBaseline.txt
@@ -4,10 +4,11 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
+MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
-Total Issues: 11
+Total Issues: 12
diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs
index 5fc7d8b6b6..c448729643 100644
--- a/src/Avalonia.Controls/Image.cs
+++ b/src/Avalonia.Controls/Image.cs
@@ -1,5 +1,6 @@
using Avalonia.Media;
using Avalonia.Media.Imaging;
+using Avalonia.Metadata;
namespace Avalonia.Controls
{
@@ -30,13 +31,14 @@ namespace Avalonia.Controls
static Image()
{
- AffectsRender(SourceProperty, StretchProperty);
- AffectsMeasure(SourceProperty, StretchProperty);
+ AffectsRender(SourceProperty, StretchProperty, StretchDirectionProperty);
+ AffectsMeasure(SourceProperty, StretchProperty, StretchDirectionProperty);
}
///
/// Gets or sets the image that will be displayed.
///
+ [Content]
public IImage Source
{
get { return GetValue(SourceProperty); }
diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs
index 781c93bcbe..624c61bb82 100644
--- a/src/Avalonia.Controls/Viewbox.cs
+++ b/src/Avalonia.Controls/Viewbox.cs
@@ -1,40 +1,48 @@
-using System;
-using Avalonia.Media;
+using Avalonia.Media;
namespace Avalonia.Controls
{
///
- /// Viewbox is used to scale single child.
+ /// Viewbox is used to scale single child to fit in the available space.
///
///
public class Viewbox : Decorator
{
///
- /// The stretch property
+ /// Defines the property.
///
- public static readonly AvaloniaProperty StretchProperty =
- AvaloniaProperty.RegisterDirect(nameof(Stretch),
- v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform);
+ public static readonly StyledProperty StretchProperty =
+ AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform);
- private Stretch _stretch = Stretch.Uniform;
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StretchDirectionProperty =
+ AvaloniaProperty.Register(nameof(StretchDirection), StretchDirection.Both);
+
+ static Viewbox()
+ {
+ ClipToBoundsProperty.OverrideDefaultValue(true);
+ AffectsMeasure(StretchProperty, StretchDirectionProperty);
+ }
///
/// Gets or sets the stretch mode,
/// which determines how child fits into the available space.
///
- ///
- /// The stretch.
- ///
public Stretch Stretch
{
- get => _stretch;
- set => SetAndRaise(StretchProperty, ref _stretch, value);
+ get => GetValue(StretchProperty);
+ set => SetValue(StretchProperty, value);
}
- static Viewbox()
+ ///
+ /// Gets or sets a value controlling in what direction contents will be stretched.
+ ///
+ public StretchDirection StretchDirection
{
- ClipToBoundsProperty.OverrideDefaultValue(true);
- AffectsMeasure(StretchProperty);
+ get => GetValue(StretchDirectionProperty);
+ set => SetValue(StretchDirectionProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
@@ -47,9 +55,9 @@ namespace Avalonia.Controls
var childSize = child.DesiredSize;
- var scale = GetScale(availableSize, childSize, Stretch);
+ var size = Stretch.CalculateSize(availableSize, childSize, StretchDirection);
- return (childSize * scale).Constrain(availableSize);
+ return size.Constrain(availableSize);
}
return new Size();
@@ -62,7 +70,9 @@ namespace Avalonia.Controls
if (child != null)
{
var childSize = child.DesiredSize;
- var scale = GetScale(finalSize, childSize, Stretch);
+ var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection);
+
+ // TODO: Viewbox should have another decorator as a child so we won't affect other render transforms.
var scaleTransform = child.RenderTransform as ScaleTransform;
if (scaleTransform == null)
@@ -81,44 +91,5 @@ namespace Avalonia.Controls
return new Size();
}
-
- private static Vector GetScale(Size availableSize, Size childSize, Stretch stretch)
- {
- double scaleX = 1.0;
- double scaleY = 1.0;
-
- bool validWidth = !double.IsPositiveInfinity(availableSize.Width);
- bool validHeight = !double.IsPositiveInfinity(availableSize.Height);
-
- if (stretch != Stretch.None && (validWidth || validHeight))
- {
- scaleX = childSize.Width <= 0.0 ? 0.0 : availableSize.Width / childSize.Width;
- scaleY = childSize.Height <= 0.0 ? 0.0 : availableSize.Height / childSize.Height;
-
- if (!validWidth)
- {
- scaleX = scaleY;
- }
- else if (!validHeight)
- {
- scaleY = scaleX;
- }
- else
- {
- switch (stretch)
- {
- case Stretch.Uniform:
- scaleX = scaleY = Math.Min(scaleX, scaleY);
- break;
-
- case Stretch.UniformToFill:
- scaleX = scaleY = Math.Max(scaleX, scaleY);
- break;
- }
- }
- }
-
- return new Vector(scaleX, scaleY);
- }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToBrushConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToBrushConverter.cs
deleted file mode 100644
index 37ba5155fd..0000000000
--- a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToBrushConverter.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Globalization;
-using Avalonia.Data.Converters;
-using Avalonia.Media;
-
-namespace Avalonia.Diagnostics.Converters
-{
- internal class BoolToBrushConverter : IValueConverter
- {
- public IBrush Brush { get; set; }
-
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- return (bool)value ? Brush : Brushes.Transparent;
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs b/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs
index 65117dc3d1..36fe12d89c 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs
@@ -16,6 +16,8 @@ namespace Avalonia.Diagnostics.Models
public object Handler { get; }
+ public bool BeginsNewRoute { get; set; }
+
public string HandlerName
{
get
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
index fd2e4c3355..ef227f7374 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
@@ -1,4 +1,6 @@
+using System;
using System.ComponentModel;
+using System.Text;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.VisualTree;
@@ -95,12 +97,27 @@ namespace Avalonia.Diagnostics.ViewModels
{
string CreateConstraintInfo(StyledProperty minProperty, StyledProperty maxProperty)
{
- if (ao.IsSet(minProperty) || ao.IsSet(maxProperty))
+ bool hasMin = ao.IsSet(minProperty);
+ bool hasMax = ao.IsSet(maxProperty);
+
+ if (hasMin || hasMax)
{
- var minValue = ao.GetValue(minProperty);
- var maxValue = ao.GetValue(maxProperty);
+ var builder = new StringBuilder();
+
+ if (hasMin)
+ {
+ var minValue = ao.GetValue(minProperty);
+ builder.AppendFormat("Min: {0}", Math.Round(minValue, 2));
+ builder.AppendLine();
+ }
+
+ if (hasMax)
+ {
+ var maxValue = ao.GetValue(maxProperty);
+ builder.AppendFormat("Max: {0}", Math.Round(maxValue, 2));
+ }
- return $"{minValue} < size < {maxValue}";
+ return builder.ToString();
}
return null;
@@ -183,8 +200,8 @@ namespace Avalonia.Diagnostics.ViewModels
{
var size = _control.Bounds;
- Width = size.Width;
- Height = size.Height;
+ Width = Math.Round(size.Width, 2);
+ Height = Math.Round(size.Height, 2);
}
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs
index 60f247ead1..b56374d353 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs
@@ -2,25 +2,17 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
-using Avalonia.Controls;
-using Avalonia.Input;
using Avalonia.Interactivity;
namespace Avalonia.Diagnostics.ViewModels
{
internal class EventOwnerTreeNode : EventTreeNodeBase
{
- private static readonly RoutedEvent[] s_defaultEvents =
- {
- Button.ClickEvent, InputElement.KeyDownEvent, InputElement.KeyUpEvent, InputElement.TextInputEvent,
- InputElement.PointerReleasedEvent, InputElement.PointerPressedEvent
- };
-
public EventOwnerTreeNode(Type type, IEnumerable events, EventsPageViewModel vm)
: base(null, type.Name)
{
Children = new AvaloniaList(events.OrderBy(e => e.Name)
- .Select(e => new EventTreeNode(this, e, vm) { IsEnabled = s_defaultEvents.Contains(e) }));
+ .Select(e => new EventTreeNode(this, e, vm)));
IsExpanded = true;
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
index 9291b3bdf7..ea54302ebd 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
@@ -9,7 +9,6 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class EventTreeNode : EventTreeNodeBase
{
- private readonly RoutedEvent _event;
private readonly EventsPageViewModel _parentViewModel;
private bool _isRegistered;
private FiredEvent _currentEvent;
@@ -20,10 +19,12 @@ namespace Avalonia.Diagnostics.ViewModels
Contract.Requires(@event != null);
Contract.Requires(vm != null);
- _event = @event;
+ Event = @event;
_parentViewModel = vm;
}
+ public RoutedEvent Event { get; }
+
public override bool? IsEnabled
{
get => base.IsEnabled;
@@ -53,8 +54,10 @@ namespace Avalonia.Diagnostics.ViewModels
{
if (IsEnabled.GetValueOrDefault() && !_isRegistered)
{
+ var allRoutes = RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble;
+
// FIXME: This leaks event handlers.
- _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true);
+ Event.AddClassHandler(typeof(object), HandleEvent, allRoutes, handledEventsToo: true);
_isRegistered = true;
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs
index 1bd4117f23..c27cad29e8 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs
@@ -8,11 +8,13 @@ namespace Avalonia.Diagnostics.ViewModels
internal bool _updateParent = true;
private bool _isExpanded;
private bool? _isEnabled = false;
+ private bool _isVisible;
protected EventTreeNodeBase(EventTreeNodeBase parent, string text)
{
Parent = parent;
Text = text;
+ IsVisible = true;
}
public IAvaloniaReadOnlyList Children
@@ -33,6 +35,12 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _isEnabled, value);
}
+ public bool IsVisible
+ {
+ get => _isVisible;
+ set => RaiseAndSetIfChanged(ref _isVisible, value);
+ }
+
public EventTreeNodeBase Parent
{
get;
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs
index 361e82bc73..dd85fcf14c 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs
@@ -1,28 +1,43 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Globalization;
+using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
-using Avalonia.Data.Converters;
+using Avalonia.Diagnostics.Models;
+using Avalonia.Input;
using Avalonia.Interactivity;
-using Avalonia.Media;
namespace Avalonia.Diagnostics.ViewModels
{
internal class EventsPageViewModel : ViewModelBase
{
- private readonly IControl _root;
+ private static readonly HashSet s_defaultEvents = new HashSet()
+ {
+ Button.ClickEvent,
+ InputElement.KeyDownEvent,
+ InputElement.KeyUpEvent,
+ InputElement.TextInputEvent,
+ InputElement.PointerReleasedEvent,
+ InputElement.PointerPressedEvent
+ };
+
+ private readonly MainViewModel _mainViewModel;
+ private string _eventTypeFilter;
private FiredEvent _selectedEvent;
+ private EventTreeNodeBase _selectedNode;
- public EventsPageViewModel(IControl root)
+ public EventsPageViewModel(MainViewModel mainViewModel)
{
- _root = root;
+ _mainViewModel = mainViewModel;
Nodes = RoutedEventRegistry.Instance.GetAllRegistered()
.GroupBy(e => e.OwnerType)
.OrderBy(e => e.Key.Name)
.Select(g => new EventOwnerTreeNode(g.Key, g, this))
.ToArray();
+
+ EnableDefault();
}
public string Name => "Events";
@@ -37,9 +52,140 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _selectedEvent, value);
}
- private void Clear()
+ public EventTreeNodeBase SelectedNode
+ {
+ get => _selectedNode;
+ set => RaiseAndSetIfChanged(ref _selectedNode, value);
+ }
+
+ public string EventTypeFilter
+ {
+ get => _eventTypeFilter;
+ set => RaiseAndSetIfChanged(ref _eventTypeFilter, value);
+ }
+
+ public void Clear()
{
RecordedEvents.Clear();
}
+
+ public void DisableAll()
+ {
+ EvaluateNodeEnabled(_ => false);
+ }
+
+ public void EnableDefault()
+ {
+ EvaluateNodeEnabled(node => s_defaultEvents.Contains(node.Event));
+ }
+
+ public void RequestTreeNavigateTo(EventChainLink navTarget)
+ {
+ if (navTarget.Handler is IControl control)
+ {
+ _mainViewModel.RequestTreeNavigateTo(control, true);
+ }
+ }
+
+ public void SelectEventByType(RoutedEvent evt)
+ {
+ foreach (var node in Nodes)
+ {
+ var result = FindNode(node, evt);
+
+ if (result != null && result.IsVisible)
+ {
+ SelectedNode = result;
+
+ break;
+ }
+ }
+
+ static EventTreeNodeBase FindNode(EventTreeNodeBase node, RoutedEvent eventType)
+ {
+ if (node is EventTreeNode eventNode && eventNode.Event == eventType)
+ {
+ return node;
+ }
+
+ if (node.Children != null)
+ {
+ foreach (var child in node.Children)
+ {
+ var result = FindNode(child, eventType);
+
+ if (result != null)
+ {
+ return result;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+
+ protected override void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ base.OnPropertyChanged(e);
+
+ if (e.PropertyName == nameof(EventTypeFilter))
+ {
+ UpdateEventFilters();
+ }
+ }
+
+ private void EvaluateNodeEnabled(Func eval)
+ {
+ void ProcessNode(EventTreeNodeBase node)
+ {
+ if (node is EventTreeNode eventNode)
+ {
+ node.IsEnabled = eval(eventNode);
+ }
+
+ if (node.Children != null)
+ {
+ foreach (var childNode in node.Children)
+ {
+ ProcessNode(childNode);
+ }
+ }
+ }
+
+ foreach (var node in Nodes)
+ {
+ ProcessNode(node);
+ }
+ }
+
+ private void UpdateEventFilters()
+ {
+ var filter = EventTypeFilter;
+ bool hasFilter = !string.IsNullOrEmpty(filter);
+
+ foreach (var node in Nodes)
+ {
+ FilterNode(node, false);
+ }
+
+ bool FilterNode(EventTreeNodeBase node, bool isParentVisible)
+ {
+ bool matchesFilter = !hasFilter || node.Text.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
+ bool hasVisibleChild = false;
+
+ if (node.Children != null)
+ {
+ foreach (var childNode in node.Children)
+ {
+ hasVisibleChild |= FilterNode(childNode, matchesFilter);
+ }
+ }
+
+ node.IsVisible = hasVisibleChild || matchesFilter || isParentVisible;
+
+ return node.IsVisible;
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs
index ae53cf6154..5fb528eead 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs
@@ -62,13 +62,18 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
- public void AddToChain(object handler, bool handled, RoutingStrategies route)
- {
- AddToChain(new EventChainLink(handler, handled, route));
- }
-
public void AddToChain(EventChainLink link)
{
+ if (EventChain.Count > 0)
+ {
+ var prevLink = EventChain[EventChain.Count-1];
+
+ if (prevLink.Route != link.Route)
+ {
+ link.BeginsNewRoute = true;
+ }
+ }
+
EventChain.Add(link);
if (HandledBy == null && link.Handled)
HandledBy = link;
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index 3049431361..49263eafdc 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
using Avalonia.Threading;
+using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
@@ -27,7 +28,7 @@ namespace Avalonia.Diagnostics.ViewModels
_root = root;
_logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
_visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root));
- _events = new EventsPageViewModel(root);
+ _events = new EventsPageViewModel(this);
UpdateFocusedControl();
KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged;
@@ -193,5 +194,19 @@ namespace Avalonia.Diagnostics.ViewModels
UpdateFocusedControl();
}
}
+
+ public void RequestTreeNavigateTo(IControl control, bool isVisualTree)
+ {
+ var tree = isVisualTree ? _visualTree : _logicalTree;
+
+ var node = tree.FindNode(control);
+
+ if (node != null)
+ {
+ SelectedTab = isVisualTree ? 1 : 0;
+
+ tree.SelectControl(control);
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
index 9ba576c826..50534e77b8 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
@@ -8,11 +8,16 @@
+
+
+
+
+
@@ -24,43 +29,60 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -110,47 +132,67 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
index b7f0860e70..d16fc40aac 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
@@ -2,58 +2,129 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters"
- x:Class="Avalonia.Diagnostics.Views.EventsPageView">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ x:Class="Avalonia.Diagnostics.Views.EventsPageView"
+ Margin="2">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs
index c1c78d38f6..687a20c5f6 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs
@@ -1,7 +1,14 @@
-using System.Linq;
+using System;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
using Avalonia.Controls;
+using Avalonia.Diagnostics.Models;
using Avalonia.Diagnostics.ViewModels;
+using Avalonia.Input;
+using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
namespace Avalonia.Diagnostics.Views
{
@@ -12,13 +19,53 @@ namespace Avalonia.Diagnostics.Views
public EventsPageView()
{
InitializeComponent();
- _events = this.FindControl("events");
+ _events = this.FindControl("EventsList");
}
- private void RecordedEvents_CollectionChanged(object sender,
- System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ public void NavigateTo(object sender, TappedEventArgs e)
{
- _events.ScrollIntoView(_events.Items.OfType().LastOrDefault());
+ if (DataContext is EventsPageViewModel vm && sender is Control control)
+ {
+ switch (control.Tag)
+ {
+ case EventChainLink chainLink:
+ {
+ vm.RequestTreeNavigateTo(chainLink);
+ break;
+ }
+ case RoutedEvent evt:
+ {
+ vm.SelectEventByType(evt);
+
+ break;
+ }
+ }
+ }
+ }
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+
+ if (DataContext is EventsPageViewModel vm)
+ {
+ vm.RecordedEvents.CollectionChanged += OnRecordedEventsChanged;
+ }
+ }
+
+ private void OnRecordedEventsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (sender is ObservableCollection events)
+ {
+ var evt = events.LastOrDefault();
+
+ if (evt is null)
+ {
+ return;
+ }
+
+ Dispatcher.UIThread.Post(() => _events.ScrollIntoView(evt));
+ }
}
private void InitializeComponent()
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs
index c7611c8c46..2f03a23fdf 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs
@@ -1,21 +1,9 @@
using Avalonia.Controls;
using Avalonia.Data;
-using Avalonia.Data.Converters;
using Avalonia.Media;
namespace Avalonia.Diagnostics.Views
{
- internal static class Converters
- {
- public static IValueConverter HasConstraintConverter =
- new FuncValueConverter
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml b/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml
index 3c2547ba85..92f8177ead 100644
--- a/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml
@@ -1,7 +1,6 @@
1
- 0
diff --git a/src/Avalonia.Themes.Fluent/FluentLight.xaml b/src/Avalonia.Themes.Fluent/FluentLight.xaml
index 1bc51f655e..feb043c5f3 100644
--- a/src/Avalonia.Themes.Fluent/FluentLight.xaml
+++ b/src/Avalonia.Themes.Fluent/FluentLight.xaml
@@ -1,9 +1,9 @@
+
-
diff --git a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
new file mode 100644
index 0000000000..508891fd72
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using Avalonia.Logging;
+using Avalonia.Media;
+
+namespace Avalonia.Animation.Animators
+{
+ ///
+ /// Animator that handles all animations on properties
+ /// with as their type and
+ /// redirect them to the properly registered
+ /// animators in this class.
+ ///
+ public class BaseBrushAnimator : Animator
+ {
+ private IAnimator _targetAnimator;
+
+ private static readonly List<(Func Match, Type AnimatorType)> _brushAnimators =
+ new List<(Func Match, Type AnimatorType)>();
+
+ ///
+ /// Register an that handles a specific
+ /// 's descendant value type.
+ ///
+ ///
+ /// The condition to which the
+ /// is to be activated and used.
+ ///
+ ///
+ /// The type of the animator to instantiate.
+ ///
+ public static void RegisterBrushAnimator(Func condition)
+ where TAnimator : IAnimator
+ {
+ _brushAnimators.Insert(0, (condition, typeof(TAnimator)));
+ }
+
+ ///
+ public override IDisposable Apply(Animation animation, Animatable control, IClock clock,
+ IObservable match, Action onComplete)
+ {
+ foreach (var valueType in _brushAnimators)
+ {
+ if (!valueType.Match(this[0].Value.GetType())) continue;
+
+ _targetAnimator = (IAnimator)Activator.CreateInstance(valueType.AnimatorType);
+
+ foreach (var keyframe in this)
+ {
+ _targetAnimator.Add(keyframe);
+ }
+
+ _targetAnimator.Property = this.Property;
+
+ return _targetAnimator.Apply(animation, control, clock, match, onComplete);
+ }
+
+ Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log(
+ this,
+ "The animation's keyframe values didn't match any brush animators registered in BaseBrushAnimator.");
+
+ return Disposable.Empty;
+ }
+
+ ///
+ public override IBrush Interpolate(double progress, IBrush oldValue, IBrush newValue) => null;
+ }
+}
diff --git a/src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs
index 6d1f6c39bd..1686cafeb9 100644
--- a/src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs
+++ b/src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs
@@ -31,6 +31,11 @@ namespace Avalonia.Animation.Animators
}
public override Color Interpolate(double progress, Color oldValue, Color newValue)
+ {
+ return InterpolateCore(progress, oldValue, newValue);
+ }
+
+ internal static Color InterpolateCore(double progress, Color oldValue, Color newValue)
{
// normalize sRGB values.
var oldA = oldValue.A / 255d;
@@ -59,7 +64,7 @@ namespace Avalonia.Animation.Animators
var b = oldB + progress * (newB - oldB);
// convert back to sRGB in the [0..255] range
- a = a * 255d;
+ a *= 255d;
r = OECF_sRGB(r) * 255d;
g = OECF_sRGB(g) * 255d;
b = OECF_sRGB(b) * 255d;
diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
index a8e618af27..a56cc1de8c 100644
--- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
+++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
@@ -1,71 +1,42 @@
using System;
-using System.Reactive.Disposables;
+using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace Avalonia.Animation.Animators
{
///
- /// Animator that handles .
+ /// Animator that handles values.
///
- public class SolidColorBrushAnimator : Animator
+ public class ISolidColorBrushAnimator : Animator
{
- private ColorAnimator _colorAnimator;
-
- private void InitializeColorAnimator()
+ public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue)
{
- _colorAnimator = new ColorAnimator();
-
- foreach (AnimatorKeyFrame keyframe in this)
+ if (oldValue is null || newValue is null)
{
- _colorAnimator.Add(keyframe);
+ return oldValue;
}
- _colorAnimator.Property = SolidColorBrush.ColorProperty;
+ return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
}
- public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete)
+ public override IDisposable BindAnimation(Animatable control, IObservable instance)
{
- // Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
- foreach (var keyframe in this)
- {
- if (keyframe.Value is ISolidColorBrush colorBrush)
- {
- keyframe.Value = colorBrush.Color;
- }
- else
- {
- return Disposable.Empty;
- }
- }
-
- SolidColorBrush finalTarget;
- var targetVal = control.GetValue(Property);
- if (targetVal is null)
- {
- finalTarget = new SolidColorBrush(Colors.Transparent);
- control.SetValue(Property, finalTarget);
- }
- else if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush)
- {
- finalTarget = new SolidColorBrush(immutableSolidColorBrush.Color);
- control.SetValue(Property, finalTarget);
- }
- else if (targetVal is ISolidColorBrush)
- {
- finalTarget = targetVal as SolidColorBrush;
- }
- else
+ return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation);
+ }
+ }
+
+ [Obsolete]
+ public class SolidColorBrushAnimator : Animator
+ {
+ public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue)
+ {
+ if (oldValue is null || newValue is null)
{
- return Disposable.Empty;
+ return oldValue;
}
- if (_colorAnimator == null)
- InitializeColorAnimator();
-
- return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
+ return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
}
-
- public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null;
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs
new file mode 100644
index 0000000000..008613fb40
--- /dev/null
+++ b/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
+{
+ ///
+ /// Transition class that handles with type.
+ ///
+ public class BoxShadowsTransition : Transition
+ {
+ private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator();
+
+ ///
+ public override IObservable DoTransition(IObservable progress, BoxShadows oldValue, BoxShadows newValue)
+ {
+ return progress
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
index 0b0f04ca94..9ffdf53694 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
+++ b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Animation.Animators;
+
namespace Avalonia.Animation
{
///
@@ -8,26 +10,13 @@ namespace Avalonia.Animation
///
public class CornerRadiusTransition : Transition
{
+ private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator();
+
///
public override IObservable DoTransition(IObservable progress, CornerRadius oldValue, CornerRadius newValue)
{
return progress
- .Select(p =>
- {
- var f = Easing.Ease(p);
-
- var deltaTL = newValue.TopLeft - oldValue.TopLeft;
- var deltaTR = newValue.TopRight - oldValue.TopRight;
- var deltaBR = newValue.BottomRight - oldValue.BottomRight;
- var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
-
- var nTL = f * deltaTL + oldValue.TopLeft;
- var nTR = f * deltaTR + oldValue.TopRight;
- var nBR = f * deltaBR + oldValue.BottomRight;
- var nBL = f * deltaBL + oldValue.BottomLeft;
-
- return new CornerRadius(nTL, nTR, nBR, nBL);
- });
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
index 29db5fc868..fbe24c6d55 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
+++ b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Animation.Animators;
+
namespace Avalonia.Animation
{
///
@@ -8,15 +10,13 @@ namespace Avalonia.Animation
///
public class PointTransition : Transition
{
+ private static readonly PointAnimator s_animator = new PointAnimator();
+
///
public override IObservable DoTransition(IObservable progress, Point oldValue, Point newValue)
{
return progress
- .Select(p =>
- {
- var f = Easing.Ease(p);
- return ((newValue - oldValue) * f) + oldValue;
- });
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
index b40e789915..464f83bec7 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
+++ b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Animation.Animators;
+
namespace Avalonia.Animation
{
///
@@ -8,15 +10,13 @@ namespace Avalonia.Animation
///
public class SizeTransition : Transition
{
+ private static readonly SizeAnimator s_animator = new SizeAnimator();
+
///
public override IObservable DoTransition(IObservable progress, Size oldValue, Size newValue)
{
return progress
- .Select(p =>
- {
- var f = Easing.Ease(p);
- return ((newValue - oldValue) * f) + oldValue;
- });
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
index 28d4ea067f..9fb3380780 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
+++ b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Animation.Animators;
+
namespace Avalonia.Animation
{
///
@@ -8,15 +10,13 @@ namespace Avalonia.Animation
///
public class ThicknessTransition : Transition
{
+ private static readonly ThicknessAnimator s_animator = new ThicknessAnimator();
+
///
public override IObservable DoTransition(IObservable progress, Thickness oldValue, Thickness newValue)
{
return progress
- .Select(p =>
- {
- var f = Easing.Ease(p);
- return ((newValue - oldValue) * f) + oldValue;
- });
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
index c073e8e192..5038117faa 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
+++ b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
@@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Animation.Animators;
+
namespace Avalonia.Animation
{
///
@@ -8,15 +10,13 @@ namespace Avalonia.Animation
///
public class VectorTransition : Transition
{
+ private static readonly VectorAnimator s_animator = new VectorAnimator();
+
///
public override IObservable DoTransition(IObservable progress, Vector oldValue, Vector newValue)
{
return progress
- .Select(p =>
- {
- var f = Easing.Ease(p);
- return ((newValue - oldValue) * f) + oldValue;
- });
+ .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}
diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt
index 35ba8f2b19..f9fd125615 100644
--- a/src/Avalonia.Visuals/ApiCompatBaseline.txt
+++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt
@@ -64,3 +64,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGl
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract.
Total Issues: 64
+InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
+Total Issues: 11
diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs
index a19d5af8b7..fb03d19a4e 100644
--- a/src/Avalonia.Visuals/Media/Brush.cs
+++ b/src/Avalonia.Visuals/Media/Brush.cs
@@ -1,6 +1,7 @@
using System;
using System.ComponentModel;
using Avalonia.Animation;
+using Avalonia.Animation.Animators;
namespace Avalonia.Media
{
@@ -21,6 +22,7 @@ namespace Avalonia.Media
static Brush()
{
+ Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
AffectsRender(OpacityProperty);
}
diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs
index b86444bc2f..b0e1a954e6 100644
--- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs
+++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs
@@ -1,5 +1,8 @@
using System;
+using System.IO;
+using System.Threading.Tasks;
using Avalonia.Platform;
+using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media.Imaging
{
@@ -34,8 +37,50 @@ namespace Avalonia.Media.Imaging
{
}
+ private WriteableBitmap(IWriteableBitmapImpl impl) : base(impl)
+ {
+
+ }
+
public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock();
+ public static WriteableBitmap Decode(Stream stream)
+ {
+ var ri = AvaloniaLocator.Current.GetService();
+
+ return new WriteableBitmap(ri.LoadWriteableBitmap(stream));
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The stream to read the bitmap from. This can be any supported image format.
+ /// The desired width of the resulting bitmap.
+ /// The to use should any scaling be required.
+ /// An instance of the class.
+ public new static WriteableBitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ var ri = AvaloniaLocator.Current.GetService();
+
+ return new WriteableBitmap(ri.LoadWriteableBitmapToWidth(stream, width, interpolationMode));
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The stream to read the bitmap from. This can be any supported image format.
+ /// The desired height of the resulting bitmap.
+ /// The to use should any scaling be required.
+ /// An instance of the class.
+ public new static WriteableBitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ var ri = AvaloniaLocator.Current.GetService();
+
+ return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode));
+ }
+
private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat)
{
var ri = AvaloniaLocator.Current.GetService();
diff --git a/src/Avalonia.Visuals/Media/SolidColorBrush.cs b/src/Avalonia.Visuals/Media/SolidColorBrush.cs
index 8e30880489..fd94cbd214 100644
--- a/src/Avalonia.Visuals/Media/SolidColorBrush.cs
+++ b/src/Avalonia.Visuals/Media/SolidColorBrush.cs
@@ -1,4 +1,3 @@
-using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable;
@@ -17,7 +16,7 @@ namespace Avalonia.Media
static SolidColorBrush()
{
- Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
+ BaseBrushAnimator.RegisterBrushAnimator(match => typeof(ISolidColorBrush).IsAssignableFrom(match));
AffectsRender(ColorProperty);
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
index 300d61f81d..6533c34ba0 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
@@ -422,7 +422,7 @@ namespace Avalonia.Media.TextFormatting
}
else
{
- currentPosition = currentLength + lineBreaker.Current.PositionWrap;
+ currentPosition = currentLength + measuredLength;
}
breakFound = true;
diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
index f0dbfee718..de67aca5a8 100644
--- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
@@ -100,6 +100,38 @@ namespace Avalonia.Platform
/// An .
IBitmapImpl LoadBitmap(Stream stream);
+ ///
+ /// Loads a WriteableBitmap implementation from a stream to a specified width maintaining aspect ratio.
+ ///
+ /// The stream to read the bitmap from.
+ /// The desired width of the resulting bitmap.
+ /// The to use should resizing be required.
+ /// An .
+ IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality);
+
+ ///
+ /// Loads a WriteableBitmap implementation from a stream to a specified height maintaining aspect ratio.
+ ///
+ /// The stream to read the bitmap from.
+ /// The desired height of the resulting bitmap.
+ /// The to use should resizing be required.
+ /// An .
+ IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality);
+
+ ///
+ /// Loads a WriteableBitmap implementation from a file.
+ ///
+ /// The filename of the bitmap.
+ /// An .
+ IWriteableBitmapImpl LoadWriteableBitmap(string fileName);
+
+ ///
+ /// Loads a WriteableBitmap implementation from a file.
+ ///
+ /// The stream to read the bitmap from.
+ /// An .
+ IWriteableBitmapImpl LoadWriteableBitmap(Stream stream);
+
///
/// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio.
///
diff --git a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
index 245888a351..354a344ffe 100644
--- a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
+++ b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
@@ -11,16 +11,13 @@ namespace Avalonia.Rendering
///
public interface ICustomSimpleHitTest
{
+ /// The point to hit test in global coordinate space.
bool HitTest(Point point);
}
///
/// Allows customization of hit-testing for all renderers.
///
- ///
- /// Note that this interface can only used to make a portion of a control non-hittable, it
- /// cannot expand the hittable area of a control.
- ///
public interface ICustomHitTest : ICustomSimpleHitTest
{
}
diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
index 9ea1b84311..85feb06c44 100644
--- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
@@ -225,7 +225,7 @@ namespace Avalonia.Rendering
if (filter?.Invoke(visual) != false)
{
- bool containsPoint = false;
+ bool containsPoint;
if (visual is ICustomSimpleHitTest custom)
{
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
index 6a4c532d4a..36aa08c2f9 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
@@ -256,7 +256,8 @@ namespace Avalonia.Rendering.SceneGraph
if (childCount == 0 || wasVisited)
{
- if ((wasVisited || FilterAndClip(node, ref clip)) && node.HitTest(_point))
+ if ((wasVisited || FilterAndClip(node, ref clip)) &&
+ (node.Visual is ICustomSimpleHitTest custom ? custom.HitTest(_point) : node.HitTest(_point)))
{
_current = node.Visual;
@@ -311,8 +312,7 @@ namespace Avalonia.Rendering.SceneGraph
if (!clipped && node.Visual is ICustomHitTest custom)
{
- var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual);
- clipped = !custom.HitTest(controlPoint.Value);
+ clipped = !custom.HitTest(_point);
}
return !clipped;
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index b7d5d3ec59..2c5c2acd52 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -166,7 +166,10 @@ namespace Avalonia.Skia
{
using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
{
- Canvas.DrawLine((float) p1.X, (float) p1.Y, (float) p2.X, (float) p2.Y, paint.Paint);
+ if (paint.Paint is object)
+ {
+ Canvas.DrawLine((float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y, paint.Paint);
+ }
}
}
@@ -361,7 +364,6 @@ namespace Avalonia.Skia
{
Canvas.DrawRect(rc, paint.Paint);
}
-
}
}
@@ -397,15 +399,17 @@ namespace Avalonia.Skia
{
using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size))
{
- if (isRounded)
- {
- Canvas.DrawRoundRect(skRoundRect, paint.Paint);
- }
- else
+ if (paint.Paint is object)
{
- Canvas.DrawRect(rc, paint.Paint);
+ if (isRounded)
+ {
+ Canvas.DrawRoundRect(skRoundRect, paint.Paint);
+ }
+ else
+ {
+ Canvas.DrawRect(rc, paint.Paint);
+ }
}
-
}
}
}
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index 6b7f0e11cf..7bc83ec85b 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -77,6 +77,31 @@ namespace Avalonia.Skia
return new ImmutableBitmap(stream);
}
+ public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ return new WriteableBitmapImpl(stream, width, true, interpolationMode);
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ return new WriteableBitmapImpl(stream, height, false, interpolationMode);
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
+ {
+ using (var stream = File.OpenRead(fileName))
+ {
+ return LoadWriteableBitmap(stream);
+ }
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
+ {
+ return new WriteableBitmapImpl(stream);
+ }
+
///
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index d48e7d10e6..63a1e8f869 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Threading;
+using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Skia.Helpers;
+using Avalonia.Visuals.Media.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
@@ -15,7 +17,70 @@ namespace Avalonia.Skia
private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc;
private readonly SKBitmap _bitmap;
private readonly object _lock = new object();
+
+ ///
+ /// Create a WriteableBitmap from given stream.
+ ///
+ /// Stream containing encoded data.
+ 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;
+ }
+ }
+
///
/// Create new writeable bitmap.
///
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 15685c0187..f50167b39a 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -188,6 +188,28 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(stream);
}
+ public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ return new WriteableWicBitmapImpl(stream, width, true, interpolationMode);
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ return new WriteableWicBitmapImpl(stream, height, false, interpolationMode);
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
+ {
+ return new WriteableWicBitmapImpl(fileName);
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
+ {
+ return new WriteableWicBitmapImpl(stream);
+ }
+
///
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
index 3261c45f15..fef6a9aa82 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using Avalonia.Platform;
using SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat;
@@ -7,11 +8,27 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl
{
+ public WriteableWicBitmapImpl(Stream stream, int decodeSize, bool horizontal,
+ Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode)
+ : base(stream, decodeSize, horizontal, interpolationMode)
+ {
+ }
+
public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat, AlphaFormat? alphaFormat)
: base(size, dpi, pixelFormat, alphaFormat)
{
}
+ public WriteableWicBitmapImpl(Stream stream)
+ : base(stream)
+ {
+ }
+
+ public WriteableWicBitmapImpl(string fileName)
+ : base(fileName)
+ {
+ }
+
class LockedBitmap : ILockedFramebuffer
{
private readonly WriteableWicBitmapImpl _parent;
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 268977d662..8792cb5cb1 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -61,6 +61,28 @@ namespace Avalonia.Benchmarks
throw new NotImplementedException();
}
+ public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
+ {
+ throw new NotImplementedException();
+ }
+
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
throw new NotImplementedException();
diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
new file mode 100644
index 0000000000..79a11d0cea
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
@@ -0,0 +1,63 @@
+using System;
+
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml.Styling;
+using Avalonia.Shared.PlatformSupport;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
+
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Themes
+{
+ [MemoryDiagnoser]
+ public class ThemeBenchmark : IDisposable
+ {
+ private IDisposable _app;
+
+ public ThemeBenchmark()
+ {
+ AssetLoader.RegisterResUriParsers();
+
+ _app = UnitTestApplication.Start(TestServices.StyledWindow.With(theme: () => null));
+ // Add empty style to override it later
+ UnitTestApplication.Current.Styles.Add(new Style());
+ }
+
+ [Benchmark]
+ [Arguments("avares://Avalonia.Themes.Fluent/FluentDark.xaml")]
+ [Arguments("avares://Avalonia.Themes.Fluent/FluentLight.xaml")]
+ public bool InitFluentTheme(string themeUri)
+ {
+ UnitTestApplication.Current.Styles[0] = new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks"))
+ {
+ Source = new Uri(themeUri)
+ };
+ return ((IResourceHost)UnitTestApplication.Current).TryGetResource("SystemAccentColor", out _);
+ }
+
+ [Benchmark]
+ [Arguments("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml")]
+ [Arguments("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml")]
+ public bool InitDefaultTheme(string themeUri)
+ {
+ UnitTestApplication.Current.Styles[0] = new Styles
+ {
+ new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks"))
+ {
+ Source = new Uri(themeUri)
+ },
+ new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks"))
+ {
+ Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
+ }
+ };
+ return ((IResourceHost)UnitTestApplication.Current).TryGetResource("ThemeAccentColor", out _);
+ }
+
+ public void Dispose()
+ {
+ _app.Dispose();
+ }
+ }
+}
diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
index e005bafbf9..7eaec35506 100644
--- a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
@@ -114,5 +114,61 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(2.0, scaleTransform.ScaleX);
Assert.Equal(2.0, scaleTransform.ScaleY);
}
+
+ [Theory]
+ [InlineData(50, 100, 50, 100, 50, 100, 1)]
+ [InlineData(50, 100, 150, 150, 50, 100, 1)]
+ [InlineData(50, 100, 25, 50, 25, 50, 0.5)]
+ public void Viewbox_Should_Return_Correct_SizeAndScale_StretchDirection_DownOnly(
+ double childWidth, double childHeight,
+ double viewboxWidth, double viewboxHeight,
+ double expectedWidth, double expectedHeight,
+ double expectedScale)
+ {
+ var target = new Viewbox
+ {
+ Child = new Control { Width = childWidth, Height = childHeight },
+ StretchDirection = StretchDirection.DownOnly
+ };
+
+ target.Measure(new Size(viewboxWidth, viewboxHeight));
+ target.Arrange(new Rect(default, target.DesiredSize));
+
+ Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
+
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(expectedScale, scaleTransform.ScaleX);
+ Assert.Equal(expectedScale, scaleTransform.ScaleY);
+ }
+
+ [Theory]
+ [InlineData(50, 100, 50, 100, 50, 100, 1)]
+ [InlineData(50, 100, 25, 50, 25, 50, 1)]
+ [InlineData(50, 100, 150, 150, 75, 150, 1.5)]
+ public void Viewbox_Should_Return_Correct_SizeAndScale_StretchDirection_UpOnly(
+ double childWidth, double childHeight,
+ double viewboxWidth, double viewboxHeight,
+ double expectedWidth, double expectedHeight,
+ double expectedScale)
+ {
+ var target = new Viewbox
+ {
+ Child = new Control { Width = childWidth, Height = childHeight },
+ StretchDirection = StretchDirection.UpOnly
+ };
+
+ target.Measure(new Size(viewboxWidth, viewboxHeight));
+ target.Arrange(new Rect(default, target.DesiredSize));
+
+ Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
+
+ var scaleTransform = target.Child.RenderTransform as ScaleTransform;
+
+ Assert.NotNull(scaleTransform);
+ Assert.Equal(expectedScale, scaleTransform.ScaleX);
+ Assert.Equal(expectedScale, scaleTransform.ScaleY);
+ }
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/DrawingContextImplTests.cs b/tests/Avalonia.Skia.UnitTests/DrawingContextImplTests.cs
new file mode 100644
index 0000000000..df0cc2fc1a
--- /dev/null
+++ b/tests/Avalonia.Skia.UnitTests/DrawingContextImplTests.cs
@@ -0,0 +1,30 @@
+using Avalonia.Media;
+using Avalonia.Skia.Helpers;
+using SkiaSharp;
+using Xunit;
+
+namespace Avalonia.Skia.UnitTests
+{
+ public class DrawingContextImplTests
+ {
+ [Fact]
+ public void DrawLine_With_Zero_Thickness_Pen_Does_Not_Throw()
+ {
+ var target = CreateTarget();
+ target.DrawLine(new Pen(Brushes.Black, 0), new Point(0, 0), new Point(10, 10));
+ }
+
+ [Fact]
+ public void DrawRectangle_With_Zero_Thickness_Pen_Does_Not_Throw()
+ {
+ var target = CreateTarget();
+ target.DrawRectangle(Brushes.Black, new Pen(Brushes.Black, 0), new RoundedRect(new Rect(0, 0, 100, 100), new CornerRadius(4)));
+ }
+
+ private DrawingContextImpl CreateTarget()
+ {
+ var canvas = new SKCanvas(new SKBitmap(100, 100));
+ return (DrawingContextImpl)DrawingContextHelper.WrapSkiaCanvas(canvas, new Vector(96, 96));
+ }
+ }
+}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index 05dd32b84d..97af874238 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -401,6 +401,24 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(expectedOffset, textLine.Start);
}
}
+
+ [Fact]
+ public void Should_FormatLine_With_Emergency_Breaks()
+ {
+ using (Start())
+ {
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+ var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.Wrap);
+
+ var textSource = new SingleBufferTextSource("0123456789_0123456789_0123456789_0123456789", defaultProperties);
+ var formatter = new TextFormatterImpl();
+
+ var textLine =
+ formatter.FormatLine(textSource, 0, 33, paragraphProperties);
+
+ Assert.NotNull(textLine.TextLineBreak?.RemainingCharacters);
+ }
+ }
public static IDisposable Start()
{
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index 8015172e72..74366f9e26 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -66,6 +66,28 @@ namespace Avalonia.UnitTests
return Mock.Of();
}
+ public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
+ {
+ throw new NotImplementedException();
+ }
+
public IBitmapImpl LoadBitmap(string fileName)
{
return Mock.Of();
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs
new file mode 100644
index 0000000000..3221f097ad
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs
@@ -0,0 +1,17 @@
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Rendering;
+
+namespace Avalonia.Visuals.UnitTests.Rendering
+{
+ internal class CustomHitTestBorder : Border, ICustomHitTest
+ {
+ public bool HitTest(Point point)
+ {
+ // Move hit testing window halfway to the left
+ return Bounds
+ .WithX(Bounds.X - Bounds.Width / 2)
+ .Contains(point);
+ }
+ }
+}
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
index 90b01e2ddb..d24d183709 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
@@ -1,6 +1,9 @@
-using System.Linq;
+using System;
+using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Shapes;
+using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
@@ -9,8 +12,6 @@ using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
-using System;
-using Avalonia.Controls.Shapes;
namespace Avalonia.Visuals.UnitTests.Rendering
{
@@ -501,6 +502,42 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
}
+ [Fact]
+ public void HitTest_Should_Accommodate_ICustomHitTest()
+ {
+ using (TestApplication())
+ {
+ Border border;
+
+ var root = new TestRoot
+ {
+ Width = 300,
+ Height = 200,
+ Child = border = new CustomHitTestBorder
+ {
+ Width = 100,
+ Height = 100,
+ Background = Brushes.Red,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ };
+
+ root.Renderer = new DeferredRenderer(root, null);
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+
+ var result = root.Renderer.HitTest(new Point(75, 100), root, null);
+ Assert.Equal(new[] { border }, result);
+
+ result = root.Renderer.HitTest(new Point(125, 100), root, null);
+ Assert.Equal(new[] { border }, result);
+
+ result = root.Renderer.HitTest(new Point(175, 100), root, null);
+ Assert.Empty(result);
+ }
+ }
+
private IDisposable TestApplication()
{
return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
index c4ef9d5ee7..a3b0a0cdd5 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
@@ -413,6 +413,43 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
}
+ [Fact]
+ public void HitTest_Should_Accommodate_ICustomHitTest()
+ {
+ using (TestApplication())
+ {
+ Border border;
+
+ var root = new TestRoot
+ {
+ Width = 300,
+ Height = 200,
+ Child = border = new CustomHitTestBorder
+ {
+ Width = 100,
+ Height = 100,
+ Background = Brushes.Red,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ };
+
+ root.Renderer = new ImmediateRenderer(root);
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+ root.Renderer.Paint(new Rect(root.ClientSize));
+
+ var result = root.Renderer.HitTest(new Point(75, 100), root, null).First();
+ Assert.Equal(border, result);
+
+ result = root.Renderer.HitTest(new Point(125, 100), root, null).First();
+ Assert.Equal(border, result);
+
+ result = root.Renderer.HitTest(new Point(175, 100), root, null).First();
+ Assert.Equal(root, result);
+ }
+ }
+
private IDisposable TestApplication()
{
return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
index 1a6d003062..51ea1e893f 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
@@ -42,6 +42,28 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
+ BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
+ {
+ throw new NotImplementedException();
+ }
+
public IBitmapImpl LoadBitmap(string fileName)
{
throw new NotImplementedException();