diff --git a/.gitignore b/.gitignore
index a510c4e49f..d16287cfb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
*.suo
*.user
*.sln.docstates
+.vs/
# Build results
diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings
index bf98899847..ab21d6e50b 100644
--- a/Avalonia.sln.DotSettings
+++ b/Avalonia.sln.DotSettings
@@ -2,6 +2,24 @@
ExplicitlyExcluded
ExplicitlyExcluded
HINT
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="set_" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="_" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="False" Prefix="I" Suffix="" Style="AaBb" />
@@ -10,8 +28,9 @@
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="s_" Suffix="" Style="aaBb" /></Policy>
+ <Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml
index 1aaedc4420..dc93808f27 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml
@@ -34,6 +34,10 @@
Width="100" Height="200"
Stretch="UniformToFill"/>
-
+
+
+ Window Icon as an Image
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
index cc35b4d237..792b25963e 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
@@ -1,10 +1,14 @@
+using System.IO;
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.Media.Imaging;
namespace ControlCatalog.Pages
{
public class ImagePage : UserControl
{
+ private Image iconImage;
public ImagePage()
{
this.InitializeComponent();
@@ -13,6 +17,25 @@ namespace ControlCatalog.Pages
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
+ iconImage = this.Get("Icon");
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ if (iconImage.Source == null)
+ {
+ var windowRoot = e.Root as Window;
+ if (windowRoot != null)
+ {
+ using (var stream = new MemoryStream())
+ {
+ windowRoot.Icon.Save(stream);
+ stream.Seek(0, SeekOrigin.Begin);
+ iconImage.Source = new Bitmap(stream);
+ }
+ }
+ }
}
}
}
diff --git a/src/Android/Avalonia.Android/PlatformIconLoader.cs b/src/Android/Avalonia.Android/PlatformIconLoader.cs
index 11fc5583a5..289025ce1d 100644
--- a/src/Android/Avalonia.Android/PlatformIconLoader.cs
+++ b/src/Android/Avalonia.Android/PlatformIconLoader.cs
@@ -49,11 +49,9 @@ namespace Avalonia.Android
stream.CopyTo(this.stream);
}
- public Stream Save()
+ public void Save(Stream outputStream)
{
- var returnStream = new MemoryStream();
- stream.CopyTo(returnStream);
- return returnStream;
+ stream.CopyTo(outputStream);
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs
index fad6cf983a..8352528285 100644
--- a/src/Avalonia.Base/DirectProperty.cs
+++ b/src/Avalonia.Base/DirectProperty.cs
@@ -30,7 +30,7 @@ namespace Avalonia
string name,
Func getter,
Action setter,
- PropertyMetadata metadata)
+ DirectPropertyMetadata metadata)
: base(name, typeof(TOwner), metadata)
{
Contract.Requires(getter != null);
@@ -50,7 +50,7 @@ namespace Avalonia
AvaloniaProperty source,
Func getter,
Action setter,
- PropertyMetadata metadata)
+ DirectPropertyMetadata metadata)
: base(source, typeof(TOwner), metadata)
{
Contract.Requires(getter != null);
@@ -93,18 +93,22 @@ namespace Avalonia
Func getter,
Action setter = null,
TValue unsetValue = default(TValue),
- BindingMode defaultBindingMode = BindingMode.OneWay,
+ BindingMode defaultBindingMode = BindingMode.Default,
bool enableDataValidation = false)
where TNewOwner : AvaloniaObject
{
+ var metadata = new DirectPropertyMetadata(
+ unsetValue: unsetValue,
+ defaultBindingMode: defaultBindingMode,
+ enableDataValidation: enableDataValidation);
+
+ metadata.Merge(GetMetadata(), this);
+
var result = new DirectProperty(
this,
getter,
setter,
- new DirectPropertyMetadata(
- unsetValue: unsetValue,
- defaultBindingMode: defaultBindingMode,
- enableDataValidation: enableDataValidation));
+ metadata);
AvaloniaPropertyRegistry.Instance.Register(typeof(TNewOwner), result);
return result;
diff --git a/src/Avalonia.Base/DirectPropertyMetadata`1.cs b/src/Avalonia.Base/DirectPropertyMetadata`1.cs
index d22801e35a..26de578a45 100644
--- a/src/Avalonia.Base/DirectPropertyMetadata`1.cs
+++ b/src/Avalonia.Base/DirectPropertyMetadata`1.cs
@@ -23,7 +23,7 @@ namespace Avalonia
public DirectPropertyMetadata(
TValue unsetValue = default(TValue),
BindingMode defaultBindingMode = BindingMode.Default,
- bool enableDataValidation = false)
+ bool? enableDataValidation = null)
: base(defaultBindingMode)
{
UnsetValue = unsetValue;
@@ -44,7 +44,7 @@ namespace Avalonia
/// control (such as a TextBox's Text property) will be interested in recieving data
/// validation messages so this feature must be explicitly enabled by setting this flag.
///
- public bool EnableDataValidation { get; }
+ public bool? EnableDataValidation { get; private set; }
///
object IDirectPropertyMetadata.UnsetValue => UnsetValue;
@@ -62,6 +62,11 @@ namespace Avalonia
{
UnsetValue = src.UnsetValue;
}
+
+ if (EnableDataValidation == null)
+ {
+ EnableDataValidation = src.EnableDataValidation;
+ }
}
}
}
diff --git a/src/Avalonia.Base/IDirectPropertyMetadata.cs b/src/Avalonia.Base/IDirectPropertyMetadata.cs
index 9dc014f0b8..c283855e5f 100644
--- a/src/Avalonia.Base/IDirectPropertyMetadata.cs
+++ b/src/Avalonia.Base/IDirectPropertyMetadata.cs
@@ -16,6 +16,6 @@ namespace Avalonia
///
/// Gets a value indicating whether the property is interested in data validation.
///
- bool EnableDataValidation { get; }
+ bool? EnableDataValidation { get; }
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 5cd2ddfc35..ef253a28e2 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -671,6 +671,23 @@ namespace Avalonia.Controls
if (Name != null)
{
_nameScope?.Register(Name, this);
+
+ var visualParent = Parent as Visual;
+
+ if (this is INameScope && visualParent != null)
+ {
+ // If we have e.g. a named UserControl in a window then we want that control
+ // to be findable by name from the Window, so register with both name scopes.
+ // This differs from WPF's behavior in that XAML manually registers controls
+ // with name scopes based on the XAML file in which the name attribute appears,
+ // but we're trying to avoid XAML magic in Avalonia in order to made code-
+ // created UIs easy. This will cause problems if a UserControl declares a name
+ // in its XAML and that control is included multiple times in a parent control
+ // (as the name will be duplicated), however at the moment I'm fine with saying
+ // "don't do that".
+ var parentNameScope = NameScope.FindNameScope(visualParent);
+ parentNameScope?.Register(Name, this);
+ }
}
}
diff --git a/src/Avalonia.Controls/IVirtualizingPanel.cs b/src/Avalonia.Controls/IVirtualizingPanel.cs
index 792dee8ae8..2d8dcb42e3 100644
--- a/src/Avalonia.Controls/IVirtualizingPanel.cs
+++ b/src/Avalonia.Controls/IVirtualizingPanel.cs
@@ -66,6 +66,11 @@ namespace Avalonia.Controls
///
double PixelOffset { get; set; }
+ ///
+ /// Gets or sets the current scroll offset in the cross axis.
+ ///
+ double CrossAxisOffset { get; set; }
+
///
/// Invalidates the measure of the control and forces a call to
/// on the next measure.
diff --git a/src/Avalonia.Controls/Platform/IWindowIconImpl.cs b/src/Avalonia.Controls/Platform/IWindowIconImpl.cs
index cd339ff404..d106e2a616 100644
--- a/src/Avalonia.Controls/Platform/IWindowIconImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowIconImpl.cs
@@ -7,6 +7,6 @@ namespace Avalonia.Platform
{
public interface IWindowIconImpl
{
- Stream Save();
+ void Save(Stream outputStream);
}
}
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index c17b11a374..40fc2f302c 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -232,6 +232,10 @@ namespace Avalonia.Controls.Presenters
{
DataContext = content;
}
+ else
+ {
+ ClearValue(DataContextProperty);
+ }
// Update the Child.
if (newChild == null)
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
index 3a2cb688cb..fee326dacc 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Specialized;
+using System.Reactive.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
@@ -17,6 +18,7 @@ namespace Avalonia.Controls.Presenters
internal abstract class ItemVirtualizer : IVirtualizingController, IDisposable
{
private double _crossAxisOffset;
+ private IDisposable _subscriptions;
///
/// Initializes a new instance of the class.
@@ -27,6 +29,15 @@ namespace Avalonia.Controls.Presenters
Owner = owner;
Items = owner.Items;
ItemCount = owner.Items.Count();
+
+ var panel = VirtualizingPanel;
+
+ if (panel != null)
+ {
+ _subscriptions = panel.GetObservable(Panel.BoundsProperty)
+ .Skip(1)
+ .Subscribe(_ => InvalidateScroll());
+ }
}
///
@@ -195,8 +206,17 @@ namespace Avalonia.Controls.Presenters
/// The actual size used.
public virtual Size ArrangeOverride(Size finalSize)
{
- var origin = Vertical ? new Point(-_crossAxisOffset, 0) : new Point(0, _crossAxisOffset);
- Owner.Panel.Arrange(new Rect(origin, finalSize));
+ if (VirtualizingPanel != null)
+ {
+ VirtualizingPanel.CrossAxisOffset = _crossAxisOffset;
+ Owner.Panel.Arrange(new Rect(finalSize));
+ }
+ else
+ {
+ var origin = Vertical ? new Point(-_crossAxisOffset, 0) : new Point(0, _crossAxisOffset);
+ Owner.Panel.Arrange(new Rect(origin, finalSize));
+ }
+
return finalSize;
}
@@ -240,6 +260,9 @@ namespace Avalonia.Controls.Presenters
///
public virtual void Dispose()
{
+ _subscriptions?.Dispose();
+ _subscriptions = null;
+
if (VirtualizingPanel != null)
{
VirtualizingPanel.Controller = null;
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index d29a6aaaef..59f0d551b0 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -173,10 +173,13 @@ namespace Avalonia.Controls.Presenters
{
if (this.GetVisualParent() != null)
{
- _caretBlink = true;
- _caretTimer.Stop();
- _caretTimer.Start();
- InvalidateVisual();
+ if (_caretTimer.IsEnabled)
+ {
+ _caretBlink = true;
+ _caretTimer.Stop();
+ _caretTimer.Start();
+ InvalidateVisual();
+ }
if (IsMeasureValid)
{
diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs
index 396905c26b..ad771d333e 100644
--- a/src/Avalonia.Controls/ScrollViewer.cs
+++ b/src/Avalonia.Controls/ScrollViewer.cs
@@ -157,10 +157,6 @@ namespace Avalonia.Controls
///
public ScrollViewer()
{
- Observable.CombineLatest(
- this.GetObservable(ExtentProperty),
- this.GetObservable(ViewportProperty))
- .Select(x => new { Extent = x[0], Viewport = x[1] });
}
///
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index 2e5afaf170..834f6d218b 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -19,6 +19,7 @@ namespace Avalonia.Controls
private double _averageItemSize;
private int _averageCount;
private double _pixelOffset;
+ private double _crossAxisOffset;
private bool _forceRemeasure;
bool IVirtualizingPanel.IsFull
@@ -60,6 +61,20 @@ namespace Avalonia.Controls
}
}
+ double IVirtualizingPanel.CrossAxisOffset
+ {
+ get { return _crossAxisOffset; }
+
+ set
+ {
+ if (_crossAxisOffset != value)
+ {
+ _crossAxisOffset = value;
+ InvalidateArrange();
+ }
+ }
+ }
+
private IVirtualizingController Controller => ((IVirtualizingPanel)this).Controller;
void IVirtualizingPanel.ForceInvalidateMeasure()
@@ -140,7 +155,11 @@ namespace Avalonia.Controls
{
if (orientation == Orientation.Vertical)
{
- rect = new Rect(rect.X, rect.Y - _pixelOffset, rect.Width, rect.Height);
+ rect = new Rect(
+ rect.X - _crossAxisOffset,
+ rect.Y - _pixelOffset,
+ rect.Width,
+ rect.Height);
child.Arrange(rect);
if (rect.Y >= _availableSpace.Height)
@@ -157,7 +176,11 @@ namespace Avalonia.Controls
}
else
{
- rect = new Rect(rect.X - _pixelOffset, rect.Y, rect.Width, rect.Height);
+ rect = new Rect(
+ rect.X - _pixelOffset,
+ rect.Y - _crossAxisOffset,
+ rect.Width,
+ rect.Height);
child.Arrange(rect);
if (rect.X >= _availableSpace.Width)
diff --git a/src/Avalonia.Controls/WindowIcon.cs b/src/Avalonia.Controls/WindowIcon.cs
index debac7c981..dff84ff6ef 100644
--- a/src/Avalonia.Controls/WindowIcon.cs
+++ b/src/Avalonia.Controls/WindowIcon.cs
@@ -31,6 +31,6 @@ namespace Avalonia.Controls
public IWindowIconImpl PlatformImpl { get; }
- public Stream Save() => PlatformImpl.Save();
+ public void Save(Stream stream) => PlatformImpl.Save(stream);
}
}
diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs
index ddfb3ea173..4c5875479e 100644
--- a/src/Avalonia.Styling/Controls/NameScope.cs
+++ b/src/Avalonia.Styling/Controls/NameScope.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
@@ -29,6 +30,32 @@ namespace Avalonia.Controls
///
public event EventHandler Unregistered;
+ ///
+ /// Finds the containing name scope for a visual.
+ ///
+ /// The visual.
+ /// The containing name scope.
+ public static INameScope FindNameScope(Visual visual)
+ {
+ Contract.Requires(visual != null);
+
+ INameScope result;
+
+ while (visual != null)
+ {
+ result = visual as INameScope ?? GetNameScope(visual);
+
+ if (result != null)
+ {
+ return result;
+ }
+
+ visual = (visual as ILogical).LogicalParent as Visual;
+ }
+
+ return null;
+ }
+
///
/// Gets the value of the attached on a visual.
///
@@ -36,6 +63,8 @@ namespace Avalonia.Controls
/// The value of the NameScope attached property.
public static INameScope GetNameScope(Visual visual)
{
+ Contract.Requires(visual != null);
+
return visual.GetValue(NameScopeProperty);
}
@@ -46,6 +75,8 @@ namespace Avalonia.Controls
/// The value to set.
public static void SetNameScope(Visual visual, INameScope value)
{
+ Contract.Requires(visual != null);
+
visual.SetValue(NameScopeProperty, value);
}
diff --git a/src/Gtk/Avalonia.Gtk/IconImpl.cs b/src/Gtk/Avalonia.Gtk/IconImpl.cs
index a3cf3be47a..3203e59f21 100644
--- a/src/Gtk/Avalonia.Gtk/IconImpl.cs
+++ b/src/Gtk/Avalonia.Gtk/IconImpl.cs
@@ -18,9 +18,10 @@ namespace Avalonia.Gtk
public Pixbuf Pixbuf { get; }
- public Stream Save()
+ public void Save(Stream stream)
{
- return new MemoryStream(Pixbuf.SaveToBuffer("png"));
+ var buffer = Pixbuf.SaveToBuffer("png");
+ stream.Write(buffer, 0, buffer.Length);
}
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
index 086257f24c..649596a74e 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
@@ -115,6 +115,10 @@ namespace Avalonia.Markup.Xaml.Data
anchor,
enableDataValidation);
}
+ else if (RelativeSource.Mode == RelativeSourceMode.Self)
+ {
+ observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation);
+ }
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
observer = CreateTemplatedParentObserver(target, pathInfo.Path);
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
index 6771aaf644..c0eb581af2 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
@@ -5,6 +5,7 @@ namespace Avalonia.Markup.Xaml.Data
{
public enum RelativeSourceMode
{
+ Self,
DataContext,
TemplatedParent,
}
diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs b/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
index 93f20e4c77..56c0072eaa 100644
--- a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
+++ b/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
@@ -131,20 +131,14 @@ namespace Avalonia.Markup.Data
}
else
{
- if (notification.Error != null)
+ if (Next != null)
{
- _observer.OnNext(notification);
+ Next.Target = new WeakReference(notification.Value);
}
- else if (notification.HasValue)
+
+ if (Next == null || notification.Error != null)
{
- if (Next != null)
- {
- Next.Target = new WeakReference(notification.Value);
- }
- else
- {
- _observer.OnNext(value);
- }
+ _observer.OnNext(value);
}
}
}
diff --git a/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs b/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs
index dab5756976..51afe1ffbf 100644
--- a/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs
+++ b/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs
@@ -26,7 +26,7 @@ namespace Avalonia.Markup.Data
_nodes = null;
}
- public bool HasNodes => _nodes.Count > 0;
+ public bool HasNodes => _nodes?.Count > 0;
public void AddNode(string node) => _nodes.Add(node);
public void Commit(string expression)
diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
index 31131a7258..877c4290a4 100644
--- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
+++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
@@ -53,6 +53,15 @@
..\..\..\packages\SharpDX.DXGI.3.1.1\lib\net45\SharpDX.DXGI.dll
True
+
+ ..\..\..\packages\SharpDX.Direct2D1.3.1.1\lib\net45\SharpDX.Direct2D1.dll
+
+
+ ..\..\..\packages\SharpDX.Direct3D11.3.1.1\lib\net45\SharpDX.Direct3D11.dll
+
+
+ ..\..\..\packages\SharpDX.DXGI.3.1.1\lib\net45\SharpDX.DXGI.dll
+
@@ -66,12 +75,15 @@
+
+
+
@@ -83,8 +95,8 @@
-
+
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 683a144bb7..b591dece9a 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -47,13 +47,46 @@ namespace Avalonia.Direct2D1
.Bind().ToConstant(s_d2D1Factory)
.Bind().ToConstant(s_d2D1Factory)
.BindToSelf(s_dwfactory)
- .BindToSelf(s_imagingFactory);
+ .BindToSelf(s_imagingFactory)
+ .BindToSelf(s_dxgiDevice)
+ .BindToSelf(s_d2D1Device);
SharpDX.Configuration.EnableReleaseOnFinalizer = true;
}
+ private static readonly SharpDX.DXGI.Device s_dxgiDevice;
+
+ private static readonly SharpDX.Direct2D1.Device s_d2D1Device;
+
+ static Direct2D1Platform()
+ {
+ var featureLevels = new[]
+ {
+ SharpDX.Direct3D.FeatureLevel.Level_11_1,
+ SharpDX.Direct3D.FeatureLevel.Level_11_0,
+ SharpDX.Direct3D.FeatureLevel.Level_10_1,
+ SharpDX.Direct3D.FeatureLevel.Level_10_0,
+ SharpDX.Direct3D.FeatureLevel.Level_9_3,
+ SharpDX.Direct3D.FeatureLevel.Level_9_2,
+ SharpDX.Direct3D.FeatureLevel.Level_9_1,
+ };
+
+ using (var d3dDevice = new SharpDX.Direct3D11.Device(
+ SharpDX.Direct3D.DriverType.Hardware,
+ SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport,
+ featureLevels))
+ {
+ s_dxgiDevice = d3dDevice.QueryInterface();
+ }
+
+ using (var factory1 = s_d2D1Factory.QueryInterface())
+ {
+ s_d2D1Device = new SharpDX.Direct2D1.Device(factory1, s_dxgiDevice);
+ }
+ }
+
public IBitmapImpl CreateBitmap(int width, int height)
{
- return new BitmapImpl(s_imagingFactory, width, height);
+ return new WicBitmapImpl(s_imagingFactory, width, height);
}
public IFormattedTextImpl CreateFormattedText(
@@ -126,12 +159,12 @@ namespace Avalonia.Direct2D1
public IBitmapImpl LoadBitmap(string fileName)
{
- return new BitmapImpl(s_imagingFactory, fileName);
+ return new WicBitmapImpl(s_imagingFactory, fileName);
}
public IBitmapImpl LoadBitmap(Stream stream)
{
- return new BitmapImpl(s_imagingFactory, stream);
+ return new WicBitmapImpl(s_imagingFactory, stream);
}
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs
index 0121345259..49d4c91c52 100644
--- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs
+++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs
@@ -1,159 +1,29 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using Avalonia.Direct2D1.Media;
-using Avalonia.Media;
-using Avalonia.Platform;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
using Avalonia.Win32.Interop;
using SharpDX;
-using SharpDX.Direct2D1;
+using SharpDX.DXGI;
namespace Avalonia.Direct2D1
{
- public class HwndRenderTarget : IRenderTarget
+ class HwndRenderTarget : SwapChainRenderTarget
{
private readonly IntPtr _hwnd;
- private readonly SharpDX.Direct3D11.Device _d3dDevice;
- private DeviceContext _deviceContext;
- private SharpDX.DXGI.SwapChain1 _swapChain;
- private Size2 _size;
- private Size2F _dpi;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The window handle.
public HwndRenderTarget(IntPtr hwnd)
{
_hwnd = hwnd;
- Direct2DFactory = AvaloniaLocator.Current.GetService();
- DirectWriteFactory = AvaloniaLocator.Current.GetService();
-
- var featureLevels = new[]
- {
- SharpDX.Direct3D.FeatureLevel.Level_12_1,
- SharpDX.Direct3D.FeatureLevel.Level_12_0,
- SharpDX.Direct3D.FeatureLevel.Level_11_1,
- SharpDX.Direct3D.FeatureLevel.Level_11_0,
- SharpDX.Direct3D.FeatureLevel.Level_10_1,
- SharpDX.Direct3D.FeatureLevel.Level_10_0,
- SharpDX.Direct3D.FeatureLevel.Level_9_3,
- SharpDX.Direct3D.FeatureLevel.Level_9_2,
- SharpDX.Direct3D.FeatureLevel.Level_9_1,
- };
-
- _d3dDevice = new SharpDX.Direct3D11.Device(
- SharpDX.Direct3D.DriverType.Hardware,
- SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport,
- featureLevels);
-
- var size = GetWindowSize();
- var dpi = GetWindowDpi();
- UpdateSwapChain(size, dpi);
}
- ///
- /// Gets the Direct2D factory.
- ///
- public Factory1 Direct2DFactory
+ protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc)
{
- get;
+ return new SwapChain1(dxgiFactory, DxgiDevice, _hwnd, ref swapChainDesc);
}
- ///
- /// Gets the DirectWrite factory.
- ///
- public SharpDX.DirectWrite.Factory DirectWriteFactory
- {
- get;
- }
-
- ///
- /// Creates a drawing context for a rendering session.
- ///
- /// An .
- public IDrawingContextImpl CreateDrawingContext()
- {
- var size = GetWindowSize();
- var dpi = GetWindowDpi();
-
- if (size != _size || _dpi != dpi)
- {
- UpdateSwapChain(size, dpi);
- }
-
- return new DrawingContextImpl(_deviceContext, DirectWriteFactory, _swapChain);
- }
-
- public void Dispose()
- {
- _deviceContext.Dispose();
- _swapChain.Dispose();
- _d3dDevice.Dispose();
- }
-
- private void UpdateSwapChain(Size2 size, Size2F dpi)
- {
- _size = size;
- _dpi = dpi;
-
- using (var dxgiDevice = _d3dDevice.QueryInterface())
- using (var d2dDevice = new Device(Direct2DFactory, dxgiDevice))
- using (var dxgiAdaptor = dxgiDevice.Adapter)
- using (var dxgiFactory = dxgiAdaptor.GetParent())
- {
- _deviceContext?.Dispose();
-
- if (_swapChain == null)
- {
- var swapChainDesc = new SharpDX.DXGI.SwapChainDescription1
- {
- Width = 0,
- Height = 0,
- Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
- Stereo = false,
- SampleDescription = new SharpDX.DXGI.SampleDescription
- {
- Count = 1,
- Quality = 0,
- },
- Usage = SharpDX.DXGI.Usage.RenderTargetOutput,
- BufferCount = 2,
- Scaling = SharpDX.DXGI.Scaling.None,
- SwapEffect = SharpDX.DXGI.SwapEffect.FlipSequential,
- Flags = 0,
- };
-
- _swapChain = new SharpDX.DXGI.SwapChain1(dxgiFactory, _d3dDevice, _hwnd, ref swapChainDesc);
- }
- else
- {
- _swapChain.ResizeBuffers(2, 0, 0, SharpDX.DXGI.Format.B8G8R8A8_UNorm, 0);
- }
-
- _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None);
- _deviceContext.DotsPerInch = dpi;
-
- using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0))
- using (var d2dBackBuffer = new Bitmap1(
- _deviceContext,
- dxgiBackBuffer,
- new BitmapProperties1(
- new PixelFormat
- {
- AlphaMode = AlphaMode.Ignore,
- Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm
- },
- dpi.Width,
- dpi.Height,
- BitmapOptions.Target | BitmapOptions.CannotDraw)))
- {
- _deviceContext.Target = d2dBackBuffer;
- }
- }
- }
- private Size2F GetWindowDpi()
+ protected override Size2F GetWindowDpi()
{
if (UnmanagedMethods.ShCoreAvailable)
{
@@ -176,7 +46,7 @@ namespace Avalonia.Direct2D1
return new Size2F(96, 96);
}
- private Size2 GetWindowSize()
+ protected override Size2 GetWindowSize()
{
UnmanagedMethods.RECT rc;
UnmanagedMethods.GetClientRect(_hwnd, out rc);
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 5b82cfc0c2..fa937ccb2f 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -27,6 +27,7 @@ namespace Avalonia.Direct2D1.Media
///
/// The render target to draw to.
/// The DirectWrite factory.
+ /// An optional swap chain associated with this drawing context.
public DrawingContextImpl(
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory,
@@ -35,6 +36,7 @@ namespace Avalonia.Direct2D1.Media
_renderTarget = renderTarget;
_swapChain = swapChain;
_directWriteFactory = directWriteFactory;
+ _swapChain = swapChain;
_renderTarget.BeginDraw();
}
@@ -63,11 +65,8 @@ namespace Avalonia.Direct2D1.Media
try
{
_renderTarget.EndDraw();
-
- if (_swapChain != null)
- {
- _swapChain.Present(1, SharpDX.DXGI.PresentFlags.None);
- }
+
+ _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None);
}
catch (SharpDXException ex) when((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
index c526f1ed04..63596bdf54 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
@@ -1,130 +1,24 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
+using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
using Avalonia.Platform;
-using SharpDX.WIC;
+using SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media
{
- ///
- /// A Direct2D implementation of a .
- ///
- public class BitmapImpl : IBitmapImpl
+ public abstract class BitmapImpl : IBitmapImpl, IDisposable
{
- private readonly ImagingFactory _factory;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The WIC imaging factory to use.
- /// The filename of the bitmap to load.
- public BitmapImpl(ImagingFactory factory, string fileName)
- {
- _factory = factory;
-
- using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand))
- {
- WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
- }
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The WIC imaging factory to use.
- /// The stream to read the bitmap from.
- public BitmapImpl(ImagingFactory factory, Stream stream)
- {
- _factory = factory;
-
- using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad))
- {
- WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
- }
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The WIC imaging factory to use.
- /// The width of the bitmap.
- /// The height of the bitmap.
- public BitmapImpl(ImagingFactory factory, int width, int height)
- {
- _factory = factory;
- WicImpl = new Bitmap(
- factory,
- width,
- height,
- PixelFormat.Format32bppPBGRA,
- BitmapCreateCacheOption.CacheOnLoad);
- }
-
- ///
- /// Gets the width of the bitmap, in pixels.
- ///
- public int PixelWidth => WicImpl.Size.Width;
-
- ///
- /// Gets the height of the bitmap, in pixels.
- ///
- public int PixelHeight => WicImpl.Size.Height;
+ public abstract Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
+ public abstract int PixelWidth { get; }
+ public abstract int PixelHeight { get; }
+ public abstract void Save(string fileName);
+ public abstract void Save(Stream stream);
public virtual void Dispose()
{
- WicImpl.Dispose();
- }
-
- ///
- /// Gets the WIC implementation of the bitmap.
- ///
- public Bitmap WicImpl
- {
- get;
- }
-
- ///
- /// Gets a Direct2D bitmap to use on the specified render target.
- ///
- /// The render target.
- /// The Direct2D bitmap.
- public SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
- {
- FormatConverter converter = new FormatConverter(_factory);
- converter.Initialize(WicImpl, PixelFormat.Format32bppPBGRA);
- return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
- }
-
- ///
- /// Saves the bitmap to a file.
- ///
- /// The filename.
- public void Save(string fileName)
- {
- if (Path.GetExtension(fileName) != ".png")
- {
- // Yeah, we need to support other formats.
- throw new NotSupportedException("Use PNG, stoopid.");
- }
-
- using (FileStream s = new FileStream(fileName, FileMode.Create))
- {
- Save(s);
- }
- }
-
- public void Save(Stream stream)
- {
- PngBitmapEncoder encoder = new PngBitmapEncoder(_factory);
- encoder.Initialize(stream);
-
- BitmapFrameEncode frame = new BitmapFrameEncode(encoder);
- frame.Initialize();
- frame.WriteSource(WicImpl);
- frame.Commit();
- encoder.Commit();
}
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
new file mode 100644
index 0000000000..5378ae3257
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+using SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+ ///
+ /// A Direct2D Bitmap implementation that uses a GPU memory bitmap as its image.
+ ///
+ public class D2DBitmapImpl : BitmapImpl
+ {
+ private Bitmap _direct2D;
+
+ ///
+ /// Initialize a new instance of the class
+ /// with a bitmap backed by GPU memory.
+ ///
+ /// The GPU bitmap.
+ ///
+ /// This bitmap must be either from the same render target,
+ /// or if the render target is a ,
+ /// the device associated with this context, to be renderable.
+ ///
+ public D2DBitmapImpl(Bitmap d2DBitmap)
+ {
+ if (d2DBitmap == null) throw new ArgumentNullException(nameof(d2DBitmap));
+
+ _direct2D = d2DBitmap;
+ }
+
+ public override Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) => _direct2D;
+
+ public override int PixelWidth => _direct2D.PixelSize.Width;
+ public override int PixelHeight => _direct2D.PixelSize.Height;
+
+ public override void Save(string fileName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Save(Stream stream)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Dispose()
+ {
+ base.Dispose();
+ _direct2D.Dispose();
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
index 66bfb16b50..96b51e4407 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
@@ -10,7 +10,7 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media
{
- public class RenderTargetBitmapImpl : BitmapImpl, IRenderTargetBitmapImpl, IDisposable
+ public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
new file mode 100644
index 0000000000..7c46663748
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
@@ -0,0 +1,130 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.IO;
+using Avalonia.Platform;
+using SharpDX.WIC;
+
+namespace Avalonia.Direct2D1.Media
+{
+ ///
+ /// A WIC implementation of a .
+ ///
+ public class WicBitmapImpl : BitmapImpl
+ {
+ private readonly ImagingFactory _factory;
+
+ private SharpDX.Direct2D1.Bitmap _direct2D;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The WIC imaging factory to use.
+ /// The filename of the bitmap to load.
+ public WicBitmapImpl(ImagingFactory factory, string fileName)
+ {
+ _factory = factory;
+
+ using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand))
+ {
+ WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The WIC imaging factory to use.
+ /// The stream to read the bitmap from.
+ public WicBitmapImpl(ImagingFactory factory, Stream stream)
+ {
+ _factory = factory;
+
+ using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad))
+ {
+ WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The WIC imaging factory to use.
+ /// The width of the bitmap.
+ /// The height of the bitmap.
+ public WicBitmapImpl(ImagingFactory factory, int width, int height)
+ {
+ _factory = factory;
+ WicImpl = new Bitmap(
+ factory,
+ width,
+ height,
+ PixelFormat.Format32bppPBGRA,
+ BitmapCreateCacheOption.CacheOnLoad);
+ }
+
+ ///
+ /// Gets the width of the bitmap, in pixels.
+ ///
+ public override int PixelWidth => WicImpl.Size.Width;
+
+ ///
+ /// Gets the height of the bitmap, in pixels.
+ ///
+ public override int PixelHeight => WicImpl.Size.Height;
+
+ public override void Dispose()
+ {
+ WicImpl.Dispose();
+ _direct2D?.Dispose();
+ }
+
+ ///
+ /// Gets the WIC implementation of the bitmap.
+ ///
+ public Bitmap WicImpl { get; }
+
+ ///
+ /// Gets a Direct2D bitmap to use on the specified render target.
+ ///
+ /// The render target.
+ /// The Direct2D bitmap.
+ public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
+ {
+ FormatConverter converter = new FormatConverter(_factory);
+ converter.Initialize(WicImpl, PixelFormat.Format32bppPBGRA);
+ return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
+ }
+
+ ///
+ /// Saves the bitmap to a file.
+ ///
+ /// The filename.
+ public override void Save(string fileName)
+ {
+ if (Path.GetExtension(fileName) != ".png")
+ {
+ // Yeah, we need to support other formats.
+ throw new NotSupportedException("Use PNG, stoopid.");
+ }
+
+ using (FileStream s = new FileStream(fileName, FileMode.Create))
+ {
+ Save(s);
+ }
+ }
+
+ public override void Save(Stream stream)
+ {
+ PngBitmapEncoder encoder = new PngBitmapEncoder(_factory);
+ encoder.Initialize(stream);
+
+ BitmapFrameEncode frame = new BitmapFrameEncode(encoder);
+ frame.Initialize();
+ frame.WriteSource(WicImpl);
+ frame.Commit();
+ encoder.Commit();
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
new file mode 100644
index 0000000000..4eea7640ed
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Direct2D1.Media;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Win32.Interop;
+using SharpDX;
+using SharpDX.Direct2D1;
+using SharpDX.DXGI;
+using AlphaMode = SharpDX.Direct2D1.AlphaMode;
+using Device = SharpDX.Direct2D1.Device;
+using Factory = SharpDX.Direct2D1.Factory;
+using Factory2 = SharpDX.DXGI.Factory2;
+
+namespace Avalonia.Direct2D1
+{
+ public abstract class SwapChainRenderTarget : IRenderTarget
+ {
+ private Size2 _savedSize;
+ private Size2F _savedDpi;
+ private DeviceContext _deviceContext;
+ private SwapChain1 _swapChain;
+
+ protected SwapChainRenderTarget()
+ {
+ DxgiDevice = AvaloniaLocator.Current.GetService();
+ D2DDevice = AvaloniaLocator.Current.GetService();
+ Direct2DFactory = AvaloniaLocator.Current.GetService();
+ DirectWriteFactory = AvaloniaLocator.Current.GetService();
+ }
+
+
+ ///
+ /// Gets the Direct2D factory.
+ ///
+ public Factory Direct2DFactory
+ {
+ get;
+ }
+
+ ///
+ /// Gets the DirectWrite factory.
+ ///
+ public SharpDX.DirectWrite.Factory DirectWriteFactory
+ {
+ get;
+ }
+
+ protected SharpDX.DXGI.Device DxgiDevice { get; }
+
+ public Device D2DDevice { get; }
+
+ ///
+ /// Creates a drawing context for a rendering session.
+ ///
+ /// An .
+ public IDrawingContextImpl CreateDrawingContext()
+ {
+ var size = GetWindowSize();
+ var dpi = GetWindowDpi();
+
+ if (size != _savedSize || dpi != _savedDpi)
+ {
+ _savedSize = size;
+ _savedDpi = dpi;
+ CreateSwapChain();
+ }
+
+ return new DrawingContextImpl(_deviceContext, DirectWriteFactory, _swapChain);
+ }
+
+ public void Dispose()
+ {
+ _deviceContext?.Dispose();
+ _swapChain?.Dispose();
+ }
+
+ private void CreateSwapChain()
+ {
+ using (var dxgiAdaptor = DxgiDevice.Adapter)
+ using (var dxgiFactory = dxgiAdaptor.GetParent())
+ {
+ _deviceContext?.Dispose();
+ _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi};
+
+
+ var swapChainDesc = new SwapChainDescription1
+ {
+ Width = _savedSize.Width,
+ Height = _savedSize.Height,
+ Format = Format.B8G8R8A8_UNorm,
+ Stereo = false,
+ SampleDescription = new SampleDescription
+ {
+ Count = 1,
+ Quality = 0,
+ },
+ Usage = Usage.RenderTargetOutput,
+ BufferCount = 2,
+ Scaling = Scaling.None,
+ SwapEffect = SwapEffect.FlipSequential,
+ Flags = 0,
+ };
+
+ _swapChain?.Dispose();
+ _swapChain = CreateSwapChain(dxgiFactory, swapChainDesc);
+
+ using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0))
+ using (var d2dBackBuffer = new Bitmap1(
+ _deviceContext,
+ dxgiBackBuffer,
+ new BitmapProperties1(
+ new PixelFormat
+ {
+ AlphaMode = AlphaMode.Ignore,
+ Format = Format.B8G8R8A8_UNorm
+ },
+ _savedDpi.Width,
+ _savedDpi.Height,
+ BitmapOptions.Target | BitmapOptions.CannotDraw)))
+ {
+ _deviceContext.Target = d2dBackBuffer;
+ }
+ }
+ }
+
+ protected abstract SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc);
+
+ protected abstract Size2F GetWindowDpi();
+
+ protected abstract Size2 GetWindowSize();
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/IconImpl.cs b/src/Windows/Avalonia.Win32/IconImpl.cs
index b9e3378b6f..b7293397d7 100644
--- a/src/Windows/Avalonia.Win32/IconImpl.cs
+++ b/src/Windows/Avalonia.Win32/IconImpl.cs
@@ -27,18 +27,16 @@ namespace Avalonia.Win32
public IntPtr HIcon => icon?.Handle ?? bitmap.GetHicon();
- public Stream Save()
+ public void Save(Stream outputStream)
{
- var stream = new MemoryStream();
if (icon != null)
{
- icon.Save(stream);
+ icon.Save(outputStream);
}
else
{
- bitmap.Save(stream, ImageFormat.Png);
+ bitmap.Save(outputStream, ImageFormat.Png);
}
- return stream;
}
}
}
diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs
index f153b26412..1941d6b60e 100644
--- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs
+++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs
@@ -88,9 +88,16 @@ namespace Avalonia.Win32
var pofn = &ofn;
+ // We should save the current directory to restore it later.
+ var currentDirectory = Environment.CurrentDirectory;
+
var res = dialog is OpenFileDialog
? UnmanagedMethods.GetOpenFileName(new IntPtr(pofn))
: UnmanagedMethods.GetSaveFileName(new IntPtr(pofn));
+
+ // Restore the old current directory, since GetOpenFileName and GetSaveFileName change it after they're called
+ Environment.CurrentDirectory = currentDirectory;
+
if (!res)
return null;
if (dialog?.Filters.Count > 0)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index b769a8dbdb..73c647abf5 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -709,7 +709,10 @@ namespace Avalonia.Win32
MaximizeWithoutCoveringTaskbar();
}
- SetFocus(_hwnd);
+ if (!Design.IsDesignMode)
+ {
+ SetFocus(_hwnd);
+ }
}
private void MaximizeWithoutCoveringTaskbar()
diff --git a/src/iOS/Avalonia.iOS/PlatformIconLoader.cs b/src/iOS/Avalonia.iOS/PlatformIconLoader.cs
index dc9d87660f..ca54a660b3 100644
--- a/src/iOS/Avalonia.iOS/PlatformIconLoader.cs
+++ b/src/iOS/Avalonia.iOS/PlatformIconLoader.cs
@@ -31,18 +31,16 @@ namespace Avalonia.iOS
// Stores the icon created as a stream to support saving even though an icon is never shown
public class FakeIcon : IWindowIconImpl
{
- private Stream stream = new MemoryStream();
+ private readonly Stream stream = new MemoryStream();
public FakeIcon(Stream stream)
{
stream.CopyTo(this.stream);
}
- public Stream Save()
+ public void Save(Stream outputStream)
{
- var returnStream = new MemoryStream();
- stream.CopyTo(returnStream);
- return returnStream;
+ stream.CopyTo(outputStream);
}
}
}
\ No newline at end of file
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
index 7b7a949e7c..ecb555252d 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
+using Avalonia;
using Avalonia.Data;
using Avalonia.Logging;
using Avalonia.UnitTests;
@@ -410,6 +411,34 @@ namespace Avalonia.Base.UnitTests
Assert.True(called);
}
+ [Fact]
+ public void AddOwner_Should_Inherit_DefaultBindingMode()
+ {
+ var foo = new DirectProperty(
+ "foo",
+ o => "foo",
+ null,
+ new DirectPropertyMetadata(defaultBindingMode: BindingMode.TwoWay));
+ var bar = foo.AddOwner(o => "bar");
+
+ Assert.Equal(BindingMode.TwoWay, bar.GetMetadata().DefaultBindingMode);
+ Assert.Equal(BindingMode.TwoWay, bar.GetMetadata().DefaultBindingMode);
+ }
+
+ [Fact]
+ public void AddOwner_Can_Override_DefaultBindingMode()
+ {
+ var foo = new DirectProperty(
+ "foo",
+ o => "foo",
+ null,
+ new DirectPropertyMetadata(defaultBindingMode: BindingMode.TwoWay));
+ var bar = foo.AddOwner(o => "bar", defaultBindingMode: BindingMode.OneWayToSource);
+
+ Assert.Equal(BindingMode.TwoWay, bar.GetMetadata().DefaultBindingMode);
+ Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata().DefaultBindingMode);
+ }
+
private class Class1 : AvaloniaObject
{
public static readonly DirectProperty FooProperty =
diff --git a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
index 7e40926679..3a37585dc0 100644
--- a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
+++ b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
@@ -34,7 +34,7 @@ namespace Avalonia.Base.UnitTests
"test",
o => null,
null,
- new PropertyMetadata());
+ new DirectPropertyMetadata());
Assert.True(target.IsDirect);
}
diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs b/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs
index ec75c2390b..9f39f7a47a 100644
--- a/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs
+++ b/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs
@@ -70,5 +70,23 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(NameScope.GetNameScope((Control)root.Presenter).Find("foo"));
}
+
+ [Fact]
+ public void Control_That_Is_NameScope_Should_Register_With_Parent_NameScope()
+ {
+ UserControl userControl;
+ var root = new TestTemplatedRoot
+ {
+ Content = userControl = new UserControl
+ {
+ Name = "foo",
+ }
+ };
+
+ root.ApplyTemplate();
+
+ Assert.Same(userControl, root.FindControl("foo"));
+ Assert.Same(userControl, userControl.FindControl("foo"));
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index f8eea8c4eb..a588e88eb2 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -153,6 +153,23 @@ namespace Avalonia.Controls.UnitTests
Assert.False(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
}
+ [Fact]
+ public void ScrollViewer_Should_Have_Correct_Extent_And_Viewport()
+ {
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
+ ItemTemplate = new FuncDataTemplate(x => new TextBlock { Width = 20, Height = 10 }),
+ SelectedIndex = 0,
+ };
+
+ Prepare(target);
+
+ Assert.Equal(new Size(20, 20), target.Scroll.Extent);
+ Assert.Equal(new Size(100, 10), target.Scroll.Viewport);
+ }
+
private FuncControlTemplate ListBoxTemplate()
{
return new FuncControlTemplate(parent =>
@@ -233,6 +250,7 @@ namespace Avalonia.Controls.UnitTests
i.InvalidateMeasure();
}
+ target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
}
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs
index a54ff022ac..88d26334ed 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs
@@ -171,6 +171,22 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal("foo", target.DataContext);
}
+ [Fact]
+ public void Assigning_Control_To_Content_After_NonControl_Should_Clear_DataContext()
+ {
+ var target = new ContentPresenter();
+
+ target.Content = "foo";
+ target.UpdateChild();
+
+ Assert.True(target.IsSet(Control.DataContextProperty));
+
+ target.Content = new Border();
+ target.UpdateChild();
+
+ Assert.False(target.IsSet(Control.DataContextProperty));
+ }
+
[Fact]
public void Tries_To_Recycle_DataTemplate()
{
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
index a0192cfab8..58f4127cdc 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
@@ -113,7 +112,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroll = (ScrollContentPresenter)target.Parent;
Assert.Equal(new Size(10, 20), scroll.Extent);
- Assert.Equal(new Size(0, 10), scroll.Viewport);
+ Assert.Equal(new Size(100, 10), scroll.Viewport);
}
[Fact]
@@ -255,7 +254,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(10, target.Panel.Children.Count);
Assert.Equal(new Size(10, 20), scroll.Extent);
- Assert.Equal(new Size(0, 10), scroll.Viewport);
+ Assert.Equal(new Size(100, 10), scroll.Viewport);
target.VirtualizationMode = ItemVirtualizationMode.None;
target.Measure(new Size(100, 100));
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
index 5414fd80da..38b51dfd49 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
@@ -786,6 +786,24 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Size(10, 20), ((ILogicalScrollable)target).Extent);
Assert.Equal(new Size(5, 10), ((ILogicalScrollable)target).Viewport);
}
+
+ [Fact]
+ public void Horizontal_Scroll_Should_Update_Item_Position()
+ {
+ var target = CreateTarget();
+
+ target.ApplyTemplate();
+
+ target.Measure(new Size(5, 100));
+ target.Arrange(new Rect(0, 0, 5, 100));
+
+ ((ILogicalScrollable)target).Offset = new Vector(5, 0);
+
+ target.Measure(new Size(5, 100));
+ target.Arrange(new Rect(0, 0, 5, 100));
+
+ Assert.Equal(new Rect(-5, 0, 10, 10), target.Panel.Children[0].Bounds);
+ }
}
public class Horizontal
diff --git a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
index 8410c2aa3e..54f9a87f94 100644
--- a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
+++ b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
@@ -6,6 +6,7 @@ using Xunit.Abstractions;
namespace Avalonia.LeakTests
{
+ [DotMemoryUnit(FailIfRunWithoutSupport = false)]
public class AvaloniaObjectTests
{
public AvaloniaObjectTests(ITestOutputHelper atr)
diff --git a/tests/Avalonia.LeakTests/MemberSelectorTests.cs b/tests/Avalonia.LeakTests/MemberSelectorTests.cs
index d794e788fd..ffee18ae0a 100644
--- a/tests/Avalonia.LeakTests/MemberSelectorTests.cs
+++ b/tests/Avalonia.LeakTests/MemberSelectorTests.cs
@@ -4,12 +4,20 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using JetBrains.dotMemoryUnit;
using Xunit;
+using Xunit.Abstractions;
namespace Avalonia.LeakTests
{
+ [DotMemoryUnit(FailIfRunWithoutSupport = false)]
public class MemberSelectorTests
{
+ public MemberSelectorTests(ITestOutputHelper atr)
+ {
+ DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
+ }
+
[Fact]
public void Should_Not_Hold_Reference_To_Object()
{
diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs
index 3238435841..0705ae9c5a 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs
@@ -2,8 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Collections.Generic;
using System.Reactive.Linq;
+using System.Reactive.Subjects;
using Avalonia.Markup.Data;
using Avalonia.UnitTests;
using Xunit;
@@ -54,6 +54,28 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.False(target.SetValue("foo"));
}
+ ///
+ /// Test for #831 - Bound properties are incorrectly updated when changing tab items.
+ ///
+ ///
+ /// There was a bug whereby pushing a null as the ExpressionObserver root didn't update
+ /// the leaf node, cauing a subsequent SetValue to update an object that should have become
+ /// unbound.
+ ///
+ [Fact]
+ public void Pushing_Null_To_RootObservable_Updates_Leaf_Node()
+ {
+ var data = new Class1 { Foo = new Class2 { Bar = "bar" } };
+ var rootObservable = new BehaviorSubject(data);
+ var target = new ExpressionObserver(rootObservable, "Foo.Bar");
+
+ target.Subscribe(_ => { });
+ rootObservable.OnNext(null);
+ target.SetValue("baz");
+
+ Assert.Equal("bar", data.Foo.Bar);
+ }
+
private class Class1 : NotifyingBase
{
private Class2 _foo;
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
index 4ad740bab0..f820f7d5ab 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
@@ -96,6 +96,7 @@
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Self.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Self.cs
new file mode 100644
index 0000000000..e0d16a9563
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Self.cs
@@ -0,0 +1,63 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Moq;
+using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Markup.Xaml.Data;
+using Avalonia.Styling;
+using Xunit;
+using System.Reactive.Disposables;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Data
+{
+ public class BindingTests_Self
+ {
+ [Fact]
+ public void Binding_To_Property_On_Self_Should_Work()
+ {
+ var target = new TextBlock
+ {
+ Tag = "Hello World!",
+ [!TextBlock.TextProperty] = new Binding("Tag")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.Self)
+ },
+ };
+
+ Assert.Equal("Hello World!", target.Text);
+ }
+
+ [Fact]
+ public void TwoWay_Binding_To_Property_On_Self_Should_Work()
+ {
+ var target = new TextBlock
+ {
+ Tag = "Hello World!",
+ [!TextBlock.TextProperty] = new Binding("Tag", BindingMode.TwoWay)
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.Self)
+ },
+ };
+
+ Assert.Equal("Hello World!", target.Text);
+ target.Text = "Goodbye cruel world :(";
+ Assert.Equal("Goodbye cruel world :(", target.Text);
+ }
+
+ private Mock CreateTarget(
+ ITemplatedControl templatedParent = null,
+ string text = null)
+ {
+ var result = new Mock();
+
+ result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent);
+ result.Setup(x => x.GetValue((AvaloniaProperty)Control.TemplatedParentProperty)).Returns(templatedParent);
+ result.Setup(x => x.GetValue((AvaloniaProperty)TextBox.TextProperty)).Returns(text);
+ result.Setup(x => x.Bind(It.IsAny(), It.IsAny>(), It.IsAny()))
+ .Returns(Disposable.Empty);
+ return result;
+ }
+ }
+}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
index 868471466a..a66d6ac6d8 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
@@ -145,5 +145,49 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal("foo", border.DataContext);
}
}
+
+ [Fact(Skip = "OmniXaml doesn't support nested markup extensions. #119")]
+ public void Binding_To_Self_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var textBlock = (TextBlock)window.Content;
+
+ window.ApplyTemplate();
+
+ Assert.Equal("foo", textBlock.Text);
+ }
+ }
+
+ [Fact]
+ public void Longform_Binding_To_Self_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var textBlock = (TextBlock)window.Content;
+
+ window.ApplyTemplate();
+
+ Assert.Equal("foo", textBlock.Text);
+ }
+ }
}
}