Browse Source

Merge branch 'master' into scenegraph

Conflicts:
	src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
	src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs
	src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
	src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
	src/Windows/Avalonia.Direct2D1/RenderTarget.cs
scenegraph-after-breakage
Steven Kirk 10 years ago
parent
commit
7ce58c92aa
  1. 1
      .gitignore
  2. 21
      Avalonia.sln.DotSettings
  3. 6
      samples/ControlCatalog/Pages/ImagePage.xaml
  4. 23
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  5. 6
      src/Android/Avalonia.Android/PlatformIconLoader.cs
  6. 18
      src/Avalonia.Base/DirectProperty.cs
  7. 9
      src/Avalonia.Base/DirectPropertyMetadata`1.cs
  8. 2
      src/Avalonia.Base/IDirectPropertyMetadata.cs
  9. 17
      src/Avalonia.Controls/Control.cs
  10. 5
      src/Avalonia.Controls/IVirtualizingPanel.cs
  11. 2
      src/Avalonia.Controls/Platform/IWindowIconImpl.cs
  12. 4
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  13. 27
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  14. 11
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  15. 4
      src/Avalonia.Controls/ScrollViewer.cs
  16. 27
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  17. 2
      src/Avalonia.Controls/WindowIcon.cs
  18. 31
      src/Avalonia.Styling/Controls/NameScope.cs
  19. 5
      src/Gtk/Avalonia.Gtk/IconImpl.cs
  20. 4
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  21. 1
      src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
  22. 16
      src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
  23. 2
      src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs
  24. 14
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  25. 41
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  26. 152
      src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs
  27. 9
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  28. 130
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  29. 57
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  30. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
  31. 130
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  32. 136
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  33. 8
      src/Windows/Avalonia.Win32/IconImpl.cs
  34. 7
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  35. 5
      src/Windows/Avalonia.Win32/WindowImpl.cs
  36. 8
      src/iOS/Avalonia.iOS/PlatformIconLoader.cs
  37. 29
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  38. 2
      tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
  39. 18
      tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs
  40. 18
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  41. 16
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs
  42. 5
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  43. 18
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  44. 1
      tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
  45. 8
      tests/Avalonia.LeakTests/MemberSelectorTests.cs
  46. 24
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs
  47. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  48. 63
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Self.cs
  49. 44
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

1
.gitignore

@ -9,6 +9,7 @@
*.suo
*.user
*.sln.docstates
.vs/
# Build results

21
Avalonia.sln.DotSettings

@ -2,6 +2,24 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=DECLSPEC_005FPROPERTY/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=ENUM/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=ENUMERATOR/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=GETTER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=GLOBAL_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=GLOBAL_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=LOCAL_005FTYPEDEF/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=LOCAL_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=NAMESPACE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=PARAMETER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=SETTER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="set_" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=STRUCT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=STRUCT_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="_" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=STRUCT_005FMETHODS/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=TEMPLATE_005FPARAMETER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=TYPEDEF/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION_005FMEMBER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Interfaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="I" Suffix="" Style="AaBb" /&gt;</s:String>
@ -10,8 +28,9 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Other/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Parameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="s_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String></wpf:ResourceDictionary>

6
samples/ControlCatalog/Pages/ImagePage.xaml

@ -34,6 +34,10 @@
Width="100" Height="200"
Stretch="UniformToFill"/>
</StackPanel>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Window Icon as an Image</TextBlock>
<Image Name="Icon" Width="100" Height="200" Stretch="None" />
</StackPanel>
</StackPanel>
</UserControl>

23
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<Image>("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);
}
}
}
}
}
}

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

18
src/Avalonia.Base/DirectProperty.cs

@ -30,7 +30,7 @@ namespace Avalonia
string name,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter,
PropertyMetadata metadata)
DirectPropertyMetadata<TValue> metadata)
: base(name, typeof(TOwner), metadata)
{
Contract.Requires<ArgumentNullException>(getter != null);
@ -50,7 +50,7 @@ namespace Avalonia
AvaloniaProperty<TValue> source,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter,
PropertyMetadata metadata)
DirectPropertyMetadata<TValue> metadata)
: base(source, typeof(TOwner), metadata)
{
Contract.Requires<ArgumentNullException>(getter != null);
@ -93,18 +93,22 @@ namespace Avalonia
Func<TNewOwner, TValue> getter,
Action<TNewOwner, TValue> setter = null,
TValue unsetValue = default(TValue),
BindingMode defaultBindingMode = BindingMode.OneWay,
BindingMode defaultBindingMode = BindingMode.Default,
bool enableDataValidation = false)
where TNewOwner : AvaloniaObject
{
var metadata = new DirectPropertyMetadata<TValue>(
unsetValue: unsetValue,
defaultBindingMode: defaultBindingMode,
enableDataValidation: enableDataValidation);
metadata.Merge(GetMetadata<TOwner>(), this);
var result = new DirectProperty<TNewOwner, TValue>(
this,
getter,
setter,
new DirectPropertyMetadata<TValue>(
unsetValue: unsetValue,
defaultBindingMode: defaultBindingMode,
enableDataValidation: enableDataValidation));
metadata);
AvaloniaPropertyRegistry.Instance.Register(typeof(TNewOwner), result);
return result;

9
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.
/// </remarks>
public bool EnableDataValidation { get; }
public bool? EnableDataValidation { get; private set; }
/// <inheritdoc/>
object IDirectPropertyMetadata.UnsetValue => UnsetValue;
@ -62,6 +62,11 @@ namespace Avalonia
{
UnsetValue = src.UnsetValue;
}
if (EnableDataValidation == null)
{
EnableDataValidation = src.EnableDataValidation;
}
}
}
}

2
src/Avalonia.Base/IDirectPropertyMetadata.cs

@ -16,6 +16,6 @@ namespace Avalonia
/// <summary>
/// Gets a value indicating whether the property is interested in data validation.
/// </summary>
bool EnableDataValidation { get; }
bool? EnableDataValidation { get; }
}
}

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

5
src/Avalonia.Controls/IVirtualizingPanel.cs

@ -66,6 +66,11 @@ namespace Avalonia.Controls
/// </summary>
double PixelOffset { get; set; }
/// <summary>
/// Gets or sets the current scroll offset in the cross axis.
/// </summary>
double CrossAxisOffset { get; set; }
/// <summary>
/// Invalidates the measure of the control and forces a call to
/// <see cref="IVirtualizingController.UpdateControls"/> on the next measure.

2
src/Avalonia.Controls/Platform/IWindowIconImpl.cs

@ -7,6 +7,6 @@ namespace Avalonia.Platform
{
public interface IWindowIconImpl
{
Stream Save();
void Save(Stream outputStream);
}
}

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

27
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;
/// <summary>
/// Initializes a new instance of the <see cref="ItemVirtualizer"/> 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());
}
}
/// <summary>
@ -195,8 +206,17 @@ namespace Avalonia.Controls.Presenters
/// <returns>The actual size used.</returns>
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
/// <inheritdoc/>
public virtual void Dispose()
{
_subscriptions?.Dispose();
_subscriptions = null;
if (VirtualizingPanel != null)
{
VirtualizingPanel.Controller = null;

11
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)
{

4
src/Avalonia.Controls/ScrollViewer.cs

@ -157,10 +157,6 @@ namespace Avalonia.Controls
/// </summary>
public ScrollViewer()
{
Observable.CombineLatest(
this.GetObservable(ExtentProperty),
this.GetObservable(ViewportProperty))
.Select(x => new { Extent = x[0], Viewport = x[1] });
}
/// <summary>

27
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)

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

31
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
/// </summary>
public event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// Finds the containing name scope for a visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>The containing name scope.</returns>
public static INameScope FindNameScope(Visual visual)
{
Contract.Requires<ArgumentNullException>(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;
}
/// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a visual.
/// </summary>
@ -36,6 +63,8 @@ namespace Avalonia.Controls
/// <returns>The value of the NameScope attached property.</returns>
public static INameScope GetNameScope(Visual visual)
{
Contract.Requires<ArgumentNullException>(visual != null);
return visual.GetValue(NameScopeProperty);
}
@ -46,6 +75,8 @@ namespace Avalonia.Controls
/// <param name="value">The value to set.</param>
public static void SetNameScope(Visual visual, INameScope value)
{
Contract.Requires<ArgumentNullException>(visual != null);
visual.SetValue(NameScopeProperty, value);
}

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

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

1
src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs

@ -5,6 +5,7 @@ namespace Avalonia.Markup.Xaml.Data
{
public enum RelativeSourceMode
{
Self,
DataContext,
TemplatedParent,
}

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

2
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)

14
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -53,6 +53,15 @@
<HintPath>..\..\..\packages\SharpDX.DXGI.3.1.1\lib\net45\SharpDX.DXGI.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SharpDX.Direct2D1, Version=3.1.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SharpDX.Direct2D1.3.1.1\lib\net45\SharpDX.Direct2D1.dll</HintPath>
</Reference>
<Reference Include="SharpDX.Direct3D11, Version=3.1.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SharpDX.Direct3D11.3.1.1\lib\net45\SharpDX.Direct3D11.dll</HintPath>
</Reference>
<Reference Include="SharpDX.DXGI, Version=3.1.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SharpDX.DXGI.3.1.1\lib\net45\SharpDX.DXGI.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
@ -66,12 +75,15 @@
</Compile>
<Compile Include="Direct2D1Platform.cs" />
<Compile Include="Disposable.cs" />
<Compile Include="HwndRenderTarget.cs" />
<Compile Include="Media\BrushImpl.cs" />
<Compile Include="Media\BrushWrapper.cs" />
<Compile Include="Media\DrawingContextImpl.cs" />
<Compile Include="Media\GeometryImpl.cs" />
<Compile Include="Media\Imaging\RenderTargetBitmapImpl.cs" />
<Compile Include="Media\Imaging\BitmapImpl.cs" />
<Compile Include="Media\Imaging\D2DBitmapImpl.cs" />
<Compile Include="Media\Imaging\WicBitmapImpl.cs" />
<Compile Include="Media\RadialGradientBrushImpl.cs" />
<Compile Include="Media\LinearGradientBrushImpl.cs" />
<Compile Include="Media\AvaloniaTextRenderer.cs" />
@ -83,8 +95,8 @@
<Compile Include="Media\FormattedTextImpl.cs" />
<Compile Include="PrimitiveExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="HwndRenderTarget.cs" />
<Compile Include="RenderTarget.cs" />
<Compile Include="SwapChainRenderTarget.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

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

@ -47,13 +47,46 @@ namespace Avalonia.Direct2D1
.Bind<SharpDX.Direct2D1.Factory>().ToConstant(s_d2D1Factory)
.Bind<SharpDX.Direct2D1.Factory1>().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<SharpDX.DXGI.Device>();
}
using (var factory1 = s_d2D1Factory.QueryInterface<SharpDX.Direct2D1.Factory1>())
{
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);
}
}
}

152
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;
/// <summary>
/// Initializes a new instance of the <see cref="HwndRenderTarget"/> class.
/// </summary>
/// <param name="hwnd">The window handle.</param>
public HwndRenderTarget(IntPtr hwnd)
{
_hwnd = hwnd;
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory1>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
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);
}
/// <summary>
/// Gets the Direct2D factory.
/// </summary>
public Factory1 Direct2DFactory
protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc)
{
get;
return new SwapChain1(dxgiFactory, DxgiDevice, _hwnd, ref swapChainDesc);
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public SharpDX.DirectWrite.Factory DirectWriteFactory
{
get;
}
/// <summary>
/// Creates a drawing context for a rendering session.
/// </summary>
/// <returns>An <see cref="Avalonia.Media.DrawingContext"/>.</returns>
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<SharpDX.DXGI.Device>())
using (var d2dDevice = new Device(Direct2DFactory, dxgiDevice))
using (var dxgiAdaptor = dxgiDevice.Adapter)
using (var dxgiFactory = dxgiAdaptor.GetParent<SharpDX.DXGI.Factory2>())
{
_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<SharpDX.DXGI.Surface>(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);

9
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -27,6 +27,7 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
/// <param name="renderTarget">The render target to draw to.</param>
/// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
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
{

130
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
{
/// <summary>
/// A Direct2D implementation of a <see cref="Avalonia.Media.Imaging.Bitmap"/>.
/// </summary>
public class BitmapImpl : IBitmapImpl
public abstract class BitmapImpl : IBitmapImpl, IDisposable
{
private readonly ImagingFactory _factory;
/// <summary>
/// Initializes a new instance of the <see cref="BitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="fileName">The filename of the bitmap to load.</param>
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);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="BitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="stream">The stream to read the bitmap from.</param>
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);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="BitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
public BitmapImpl(ImagingFactory factory, int width, int height)
{
_factory = factory;
WicImpl = new Bitmap(
factory,
width,
height,
PixelFormat.Format32bppPBGRA,
BitmapCreateCacheOption.CacheOnLoad);
}
/// <summary>
/// Gets the width of the bitmap, in pixels.
/// </summary>
public int PixelWidth => WicImpl.Size.Width;
/// <summary>
/// Gets the height of the bitmap, in pixels.
/// </summary>
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();
}
/// <summary>
/// Gets the WIC implementation of the bitmap.
/// </summary>
public Bitmap WicImpl
{
get;
}
/// <summary>
/// Gets a Direct2D bitmap to use on the specified render target.
/// </summary>
/// <param name="renderTarget">The render target.</param>
/// <returns>The Direct2D bitmap.</returns>
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);
}
/// <summary>
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
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();
}
}
}

57
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
{
/// <summary>
/// A Direct2D Bitmap implementation that uses a GPU memory bitmap as its image.
/// </summary>
public class D2DBitmapImpl : BitmapImpl
{
private Bitmap _direct2D;
/// <summary>
/// Initialize a new instance of the <see cref="BitmapImpl"/> class
/// with a bitmap backed by GPU memory.
/// </summary>
/// <param name="d2DBitmap">The GPU bitmap.</param>
/// <remarks>
/// This bitmap must be either from the same render target,
/// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable.
/// </remarks>
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();
}
}
}

2
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;

130
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
{
/// <summary>
/// A WIC implementation of a <see cref="Avalonia.Media.Imaging.Bitmap"/>.
/// </summary>
public class WicBitmapImpl : BitmapImpl
{
private readonly ImagingFactory _factory;
private SharpDX.Direct2D1.Bitmap _direct2D;
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="fileName">The filename of the bitmap to load.</param>
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);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="stream">The stream to read the bitmap from.</param>
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);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height)
{
_factory = factory;
WicImpl = new Bitmap(
factory,
width,
height,
PixelFormat.Format32bppPBGRA,
BitmapCreateCacheOption.CacheOnLoad);
}
/// <summary>
/// Gets the width of the bitmap, in pixels.
/// </summary>
public override int PixelWidth => WicImpl.Size.Width;
/// <summary>
/// Gets the height of the bitmap, in pixels.
/// </summary>
public override int PixelHeight => WicImpl.Size.Height;
public override void Dispose()
{
WicImpl.Dispose();
_direct2D?.Dispose();
}
/// <summary>
/// Gets the WIC implementation of the bitmap.
/// </summary>
public Bitmap WicImpl { get; }
/// <summary>
/// Gets a Direct2D bitmap to use on the specified render target.
/// </summary>
/// <param name="renderTarget">The render target.</param>
/// <returns>The Direct2D bitmap.</returns>
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);
}
/// <summary>
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
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();
}
}
}

136
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<SharpDX.DXGI.Device>();
D2DDevice = AvaloniaLocator.Current.GetService<Device>();
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
}
/// <summary>
/// Gets the Direct2D factory.
/// </summary>
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public SharpDX.DirectWrite.Factory DirectWriteFactory
{
get;
}
protected SharpDX.DXGI.Device DxgiDevice { get; }
public Device D2DDevice { get; }
/// <summary>
/// Creates a drawing context for a rendering session.
/// </summary>
/// <returns>An <see cref="Avalonia.Media.DrawingContext"/>.</returns>
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<Factory2>())
{
_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<Surface>(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();
}
}

8
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;
}
}
}

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

5
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -709,7 +709,10 @@ namespace Avalonia.Win32
MaximizeWithoutCoveringTaskbar();
}
SetFocus(_hwnd);
if (!Design.IsDesignMode)
{
SetFocus(_hwnd);
}
}
private void MaximizeWithoutCoveringTaskbar()

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

29
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<Class1, string>(
"foo",
o => "foo",
null,
new DirectPropertyMetadata<string>(defaultBindingMode: BindingMode.TwoWay));
var bar = foo.AddOwner<Class2>(o => "bar");
Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class1>().DefaultBindingMode);
Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class2>().DefaultBindingMode);
}
[Fact]
public void AddOwner_Can_Override_DefaultBindingMode()
{
var foo = new DirectProperty<Class1, string>(
"foo",
o => "foo",
null,
new DirectPropertyMetadata<string>(defaultBindingMode: BindingMode.TwoWay));
var bar = foo.AddOwner<Class2>(o => "bar", defaultBindingMode: BindingMode.OneWayToSource);
Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class1>().DefaultBindingMode);
Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode);
}
private class Class1 : AvaloniaObject
{
public static readonly DirectProperty<Class1, string> FooProperty =

2
tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs

@ -34,7 +34,7 @@ namespace Avalonia.Base.UnitTests
"test",
o => null,
null,
new PropertyMetadata());
new DirectPropertyMetadata<string>());
Assert.True(target.IsDirect);
}

18
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<UserControl>("foo"));
Assert.Same(userControl, userControl.FindControl<UserControl>("foo"));
}
}
}

18
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<string>(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<ListBox>(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));
}

16
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()
{

5
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));

18
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

1
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)

8
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()
{

24
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"));
}
/// <summary>
/// Test for #831 - Bound properties are incorrectly updated when changing tab items.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
[Fact]
public void Pushing_Null_To_RootObservable_Updates_Leaf_Node()
{
var data = new Class1 { Foo = new Class2 { Bar = "bar" } };
var rootObservable = new BehaviorSubject<Class1>(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;

1
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -96,6 +96,7 @@
<Compile Include="Data\BindingTests_DataValidation.cs" />
<Compile Include="Data\BindingTests_Source.cs" />
<Compile Include="Data\BindingTests_ElementName.cs" />
<Compile Include="Data\BindingTests_Self.cs" />
<Compile Include="Data\MultiBindingTests.cs" />
<Compile Include="Data\BindingTests_TemplatedParent.cs" />
<Compile Include="Data\BindingTests.cs" />

63
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<IControl> CreateTarget(
ITemplatedControl templatedParent = null,
string text = null)
{
var result = new Mock<IControl>();
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<AvaloniaProperty>(), It.IsAny<IObservable<object>>(), It.IsAny<BindingPriority>()))
.Returns(Disposable.Empty);
return result;
}
}
}

44
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 = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<TextBlock Name='textblock' Text='{Binding Tag, RelativeSource={RelativeSource Self}}'/>
</Window>";
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 = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<TextBlock Name='textblock' Tag='foo'>
<TextBlock.Text>
<Binding RelativeSource='{RelativeSource Self}' Path='Tag'/>
</TextBlock.Text>
</TextBlock>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = (TextBlock)window.Content;
window.ApplyTemplate();
Assert.Equal("foo", textBlock.Text);
}
}
}
}

Loading…
Cancel
Save