From 934cc3bafd376943394b86177d05d86734ca94cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20Nieto=20S=C3=A1nchez?= Date: Wed, 28 Dec 2016 18:15:57 +0100 Subject: [PATCH 01/29] Environment.CurrentDirectory is modified after call to GetOpenFileName | GetSaveFileName call. --- src/Windows/Avalonia.Win32/SystemDialogImpl.cs | 7 +++++++ 1 file changed, 7 insertions(+) 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) From a0a201006f255f6777acb4aed4cb6cc6e36045f1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 4 Jan 2017 00:57:39 +0100 Subject: [PATCH 02/29] Merge overridden direct property metadata. --- src/Avalonia.Base/DirectProperty.cs | 18 +++++++----- src/Avalonia.Base/DirectPropertyMetadata`1.cs | 9 ++++-- src/Avalonia.Base/IDirectPropertyMetadata.cs | 2 +- .../AvaloniaObjectTests_Direct.cs | 29 +++++++++++++++++++ .../DirectPropertyTests.cs | 2 +- 5 files changed, 49 insertions(+), 11 deletions(-) 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/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); } From 15896f3158082a32100d31bce077e3fcfbfd339e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 6 Jan 2017 12:15:06 +0100 Subject: [PATCH 03/29] Added failing test for #831. --- .../Data/ExpressionObserverTests_SetValue.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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; From f4f0597dca4a06afab5ddeb704f4c9eb50eb5e49 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 6 Jan 2017 12:56:36 +0100 Subject: [PATCH 04/29] Update next node in binding chain on error. Fixes #831 and makes `Pushing_Null_To_RootObservable_Updates_Leaf_Node` test pass. --- .../Avalonia.Markup/Data/ExpressionNode.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) 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); } } } From f73a10b59308d48c9232738ce47948c3fea7993b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 6 Jan 2017 14:30:26 +0100 Subject: [PATCH 05/29] Clear ContentPresenter data context. When assigning a control to `ContentPresenter.Content` after a non-control the data context should get cleared. Fixes another problem in #831. --- .../Presenters/ContentPresenter.cs | 4 ++++ .../Presenters/ContentPresenterTests.cs | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) 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/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() { From f63a64e11b57417bb3a205d885eab7de97735394 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 7 Jan 2017 19:20:38 +0100 Subject: [PATCH 06/29] Remove pointless code. --- src/Avalonia.Controls/ScrollViewer.cs | 4 ---- 1 file changed, 4 deletions(-) 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] }); } /// From 44f6d12157fc16cd2df27fdf9b2fa95ae1542f41 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 7 Jan 2017 19:21:04 +0100 Subject: [PATCH 07/29] Add failing test for #834. --- .../ListBoxTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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)); } From 49757372a90e1117f102d23faeea6b1ee0b467cf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 7 Jan 2017 19:32:23 +0100 Subject: [PATCH 08/29] Fix for #834. Listen for bounds changes on the `VirtualizingPanel` and update the scroll accordingly. --- .../Presenters/ItemVirtualizer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs index 3a2cb688cb..c4edaf5387 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()); + } } /// @@ -240,6 +251,9 @@ namespace Avalonia.Controls.Presenters /// public virtual void Dispose() { + _subscriptions?.Dispose(); + _subscriptions = null; + if (VirtualizingPanel != null) { VirtualizingPanel.Controller = null; From 799ffe72b8ca4197944c1f825de1cdd8fd663cd8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 7 Jan 2017 19:32:42 +0100 Subject: [PATCH 09/29] Fix erroneous test expectations. --- .../Presenters/ItemsPresenterTests_Virtualization.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 847662e629..1ea64b915c 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)); From 5e844308685ea679d3fca0eadf38ffb727a9649c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 7 Jan 2017 20:21:01 +0100 Subject: [PATCH 10/29] Don't focus Win32 window in design mode. Fixes #837. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 2129090a64..db46538796 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() From 90f4cfbea3977963e61b35b7907ac7cf8c1cfade Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 8 Jan 2017 01:44:43 +0100 Subject: [PATCH 11/29] Register namescoped controls with parent namescope. 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". Fixes #829. --- src/Avalonia.Controls/Control.cs | 17 ++++++++++ src/Avalonia.Styling/Controls/NameScope.cs | 31 +++++++++++++++++++ .../ControlTests_NameScope.cs | 18 +++++++++++ 3 files changed, 66 insertions(+) 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.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/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")); + } } } From 7cf208208b2095d4af2c65df6a4256804fcfd259 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 8 Jan 2017 02:13:11 +0100 Subject: [PATCH 12/29] Don't show TextBox caret when control not focused. Fixes #836. --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index d3cf4e5509..39759f78f1 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) { From 8f21388e28dbd39587df309dd28998538b13e024 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 8 Jan 2017 15:16:50 +0100 Subject: [PATCH 13/29] Added RelativeSource=Self binding mode. --- .../Avalonia.Markup.Xaml/Data/Binding.cs | 4 ++ .../Data/RelativeSource.cs | 1 + .../Data/BindingTests_Self.cs | 63 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Self.cs 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/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; + } + } +} From 7d6503a5f76a6f463ffcef23bafb2208d8bac172 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 8 Jan 2017 16:50:09 +0100 Subject: [PATCH 14/29] Added RelativeSource=Self XAML tests. --- .../Avalonia.Markup.Xaml.UnitTests.csproj | 1 + .../Xaml/BindingTests.cs | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) 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/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); + } + } } } From 48a33cc415a7aae627d4bd86f1e265fd4482160f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 8 Jan 2017 16:50:25 +0100 Subject: [PATCH 15/29] Handle null in MarkupBindingChainException --- src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 34d779df45c4a7c03497b015cde51a255c9017aa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 09:49:12 -0600 Subject: [PATCH 16/29] Updated gitignore to remove vs2017 specific files. --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From f44468a3ab6f2abb0d1cb5250ebe835b952dcaa2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 10:47:48 -0600 Subject: [PATCH 17/29] Added swap chain backed render target. --- Avalonia.sln.DotSettings | 19 +++ .../Avalonia.Direct2D1.csproj | 20 +-- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 36 ++++- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 56 ++++++++ .../Media/DrawingContext.cs | 8 +- .../SwapChainRenderTarget.cs | 134 ++++++++++++++++++ src/Windows/Avalonia.Direct2D1/app.config | 4 +- .../Avalonia.Direct2D1/packages.config | 7 +- 8 files changed, 265 insertions(+), 19 deletions(-) create mode 100644 src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs create mode 100644 src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index bf98899847..16c9218a7e 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,6 +28,7 @@ <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="" Suffix="" Style="AaBb" /> diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index 9f9558ff76..95ccc98692 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -37,17 +37,17 @@ true - - ..\..\..\packages\SharpDX.3.1.0\lib\net45\SharpDX.dll - True + + ..\..\..\packages\SharpDX.3.1.1\lib\net45\SharpDX.dll - - ..\..\..\packages\SharpDX.Direct2D1.3.1.0\lib\net45\SharpDX.Direct2D1.dll - True + + ..\..\..\packages\SharpDX.Direct2D1.3.1.1\lib\net45\SharpDX.Direct2D1.dll - - ..\..\..\packages\SharpDX.DXGI.3.1.0\lib\net45\SharpDX.DXGI.dll - True + + ..\..\..\packages\SharpDX.Direct3D11.3.1.1\lib\net45\SharpDX.Direct3D11.dll + + + ..\..\..\packages\SharpDX.DXGI.3.1.1\lib\net45\SharpDX.DXGI.dll @@ -62,6 +62,7 @@ + @@ -79,6 +80,7 @@ + diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index b43eef2fa9..f86fa0b93a 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -8,6 +8,7 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Controls; using Avalonia.Rendering; +using SharpDX.Direct3D11; namespace Avalonia { @@ -29,20 +30,47 @@ namespace Avalonia.Direct2D1 private static readonly SharpDX.Direct2D1.Factory s_d2D1Factory = #if DEBUG - new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded, SharpDX.Direct2D1.DebugLevel.Error); + new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); #else - new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded, SharpDX.Direct2D1.DebugLevel.None); + new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); #endif private static readonly SharpDX.DirectWrite.Factory s_dwfactory = new SharpDX.DirectWrite.Factory(); private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory(); + private static readonly SharpDX.DXGI.Device s_device; + + static Direct2D1Platform() + { + 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, + }; + + using (var d3dDevice = new SharpDX.Direct3D11.Device( + SharpDX.Direct3D.DriverType.Hardware, + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, + featureLevels)) + { + s_device = d3dDevice.QueryInterface(); + } + } + public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .BindToSelf(s_d2D1Factory) .BindToSelf(s_dwfactory) - .BindToSelf(s_imagingFactory); + .BindToSelf(s_imagingFactory) + .BindToSelf(s_device); public IBitmapImpl CreateBitmap(int width, int height) { @@ -70,7 +98,7 @@ namespace Avalonia.Direct2D1 { if (handle.HandleDescriptor == "HWND") { - return new RenderTarget(handle.Handle); + return new HwndRenderTarget(handle.Handle); } else { diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs new file mode 100644 index 0000000000..5c0c460dcb --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Win32.Interop; +using SharpDX; +using SharpDX.DXGI; + +namespace Avalonia.Direct2D1 +{ + class HwndRenderTarget : SwapChainRenderTarget + { + private readonly IntPtr _hwnd; + + public HwndRenderTarget(IntPtr hwnd) + { + _hwnd = hwnd; + } + + protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) + { + return new SwapChain1(dxgiFactory, Device, _hwnd, ref swapChainDesc); + } + + protected override Size2F GetWindowDpi() + { + if (UnmanagedMethods.ShCoreAvailable) + { + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow( + _hwnd, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size2F(dpix, dpiy); + } + } + + return new Size2F(96, 96); + } + + protected override Size2 GetWindowSize() + { + UnmanagedMethods.RECT rc; + UnmanagedMethods.GetClientRect(_hwnd, out rc); + return new Size2(rc.right - rc.left, rc.bottom - rc.top); + } + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs index 75a0f43d9f..decf3c6fc6 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs @@ -27,6 +27,8 @@ namespace Avalonia.Direct2D1.Media /// private SharpDX.DirectWrite.Factory _directWriteFactory; + private SharpDX.DXGI.SwapChain1 _swapChain; + /// /// Initializes a new instance of the class. /// @@ -34,10 +36,12 @@ namespace Avalonia.Direct2D1.Media /// The DirectWrite factory. public DrawingContext( SharpDX.Direct2D1.RenderTarget renderTarget, - SharpDX.DirectWrite.Factory directWriteFactory) + SharpDX.DirectWrite.Factory directWriteFactory, + SharpDX.DXGI.SwapChain1 swapChain = null) { _renderTarget = renderTarget; _directWriteFactory = directWriteFactory; + _swapChain = swapChain; _renderTarget.BeginDraw(); } @@ -60,6 +64,8 @@ namespace Avalonia.Direct2D1.Media try { _renderTarget.EndDraw(); + + _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/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs new file mode 100644 index 0000000000..a7b2f532c2 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +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() + { + Device = 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 Device { get; } + + /// + /// Creates a drawing context for a rendering session. + /// + /// An . + public DrawingContext CreateDrawingContext() + { + var size = GetWindowSize(); + var dpi = GetWindowDpi(); + + if (size != _savedSize || dpi != _savedDpi) + { + _savedSize = size; + _savedDpi = dpi; + CreateSwapChain(); + } + + return new DrawingContext(new Media.DrawingContext(_deviceContext, DirectWriteFactory, _swapChain)); + } + + public void Dispose() + { + _deviceContext.Dispose(); + _swapChain.Dispose(); + } + + private void CreateSwapChain() + { + using (var d2dDevice = new Device(Device)) + using (var dxgiAdaptor = Device.Adapter) + using (var dxgiFactory = dxgiAdaptor.GetParent()) + { + _deviceContext?.Dispose(); + _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None); + + 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, + }; + + var dpi = Direct2DFactory.DesktopDpi; + + _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.Direct2D1/app.config b/src/Windows/Avalonia.Direct2D1/app.config index 743de168f3..60a1012655 100644 --- a/src/Windows/Avalonia.Direct2D1/app.config +++ b/src/Windows/Avalonia.Direct2D1/app.config @@ -8,11 +8,11 @@ - + - + diff --git a/src/Windows/Avalonia.Direct2D1/packages.config b/src/Windows/Avalonia.Direct2D1/packages.config index 57031c2b9d..780e6014e5 100644 --- a/src/Windows/Avalonia.Direct2D1/packages.config +++ b/src/Windows/Avalonia.Direct2D1/packages.config @@ -1,6 +1,7 @@  - - - + + + + \ No newline at end of file From cc4c3d02d0fcb61b74843b8391597ac2f2058d20 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 11:08:12 -0600 Subject: [PATCH 18/29] Fixed DPI issues. --- src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs | 1 + src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs index decf3c6fc6..486116c27b 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs @@ -34,6 +34,7 @@ namespace Avalonia.Direct2D1.Media /// /// The render target to draw to. /// The DirectWrite factory. + /// An optional swap chain associated with this drawing context. public DrawingContext( SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.DirectWrite.Factory directWriteFactory, diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index a7b2f532c2..2fbf65ed15 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -81,7 +81,8 @@ namespace Avalonia.Direct2D1 using (var dxgiFactory = dxgiAdaptor.GetParent()) { _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None); + _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; + var swapChainDesc = new SwapChainDescription1 { @@ -101,8 +102,6 @@ namespace Avalonia.Direct2D1 Flags = 0, }; - var dpi = Direct2DFactory.DesktopDpi; - _swapChain?.Dispose(); _swapChain = CreateSwapChain(dxgiFactory, swapChainDesc); From bde461f4005458cb69d266ddaa63cd4371fff1e6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 11:30:36 -0600 Subject: [PATCH 19/29] Use the factory associated with the Direct2D1 device. --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 21 +++++++++---------- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 2 +- .../SwapChainRenderTarget.cs | 12 ++++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index f86fa0b93a..c1ce7ce6f8 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -28,17 +28,13 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); - private static readonly SharpDX.Direct2D1.Factory s_d2D1Factory = -#if DEBUG - new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); -#else - new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); -#endif private static readonly SharpDX.DirectWrite.Factory s_dwfactory = new SharpDX.DirectWrite.Factory(); private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory(); - private static readonly SharpDX.DXGI.Device s_device; + private static readonly SharpDX.DXGI.Device s_dxgiDevice; + + private static readonly SharpDX.Direct2D1.Device s_d2d1Device; static Direct2D1Platform() { @@ -60,17 +56,20 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, featureLevels)) { - s_device = d3dDevice.QueryInterface(); + s_dxgiDevice = d3dDevice.QueryInterface(); } + + s_d2d1Device = new SharpDX.Direct2D1.Device(s_dxgiDevice); } public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .BindToSelf(s_d2D1Factory) + .BindToSelf(s_d2d1Device.Factory) .BindToSelf(s_dwfactory) .BindToSelf(s_imagingFactory) - .BindToSelf(s_device); + .BindToSelf(s_dxgiDevice) + .BindToSelf(s_d2d1Device); public IBitmapImpl CreateBitmap(int width, int height) { @@ -110,7 +109,7 @@ namespace Avalonia.Direct2D1 public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) { - return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Factory, width, height); + return new RenderTargetBitmapImpl(s_imagingFactory, s_d2d1Device.Factory, width, height); } public IStreamGeometryImpl CreateStreamGeometry() diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 5c0c460dcb..49d4c91c52 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -20,7 +20,7 @@ namespace Avalonia.Direct2D1 protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) { - return new SwapChain1(dxgiFactory, Device, _hwnd, ref swapChainDesc); + return new SwapChain1(dxgiFactory, DxgiDevice, _hwnd, ref swapChainDesc); } protected override Size2F GetWindowDpi() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 2fbf65ed15..0d3799f1b1 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -25,7 +25,8 @@ namespace Avalonia.Direct2D1 protected SwapChainRenderTarget() { - Device = AvaloniaLocator.Current.GetService(); + DxgiDevice = AvaloniaLocator.Current.GetService(); + D2DDevice = AvaloniaLocator.Current.GetService(); Direct2DFactory = AvaloniaLocator.Current.GetService(); DirectWriteFactory = AvaloniaLocator.Current.GetService(); } @@ -47,7 +48,9 @@ namespace Avalonia.Direct2D1 get; } - protected SharpDX.DXGI.Device Device { get; } + protected SharpDX.DXGI.Device DxgiDevice { get; } + + public Device D2DDevice { get; } /// /// Creates a drawing context for a rendering session. @@ -76,12 +79,11 @@ namespace Avalonia.Direct2D1 private void CreateSwapChain() { - using (var d2dDevice = new Device(Device)) - using (var dxgiAdaptor = Device.Adapter) + using (var dxgiAdaptor = DxgiDevice.Adapter) using (var dxgiFactory = dxgiAdaptor.GetParent()) { _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; + _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; var swapChainDesc = new SwapChainDescription1 From 4575c1abc9841f7ebf3bf5e3eb69d90f2b30b619 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 11:38:20 -0600 Subject: [PATCH 20/29] Updated Resharper naming rules to match our conventions. --- Avalonia.sln.DotSettings | 2 +- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index 16c9218a7e..ab21d6e50b 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -30,7 +30,7 @@ <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/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index c1ce7ce6f8..6d6e1a1149 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -34,7 +34,7 @@ namespace Avalonia.Direct2D1 private static readonly SharpDX.DXGI.Device s_dxgiDevice; - private static readonly SharpDX.Direct2D1.Device s_d2d1Device; + private static readonly SharpDX.Direct2D1.Device s_d2D1Device; static Direct2D1Platform() { @@ -59,17 +59,17 @@ namespace Avalonia.Direct2D1 s_dxgiDevice = d3dDevice.QueryInterface(); } - s_d2d1Device = new SharpDX.Direct2D1.Device(s_dxgiDevice); + s_d2D1Device = new SharpDX.Direct2D1.Device(s_dxgiDevice); } public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .BindToSelf(s_d2d1Device.Factory) + .BindToSelf(s_d2D1Device.Factory) .BindToSelf(s_dwfactory) .BindToSelf(s_imagingFactory) .BindToSelf(s_dxgiDevice) - .BindToSelf(s_d2d1Device); + .BindToSelf(s_d2D1Device); public IBitmapImpl CreateBitmap(int width, int height) { @@ -109,7 +109,7 @@ namespace Avalonia.Direct2D1 public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) { - return new RenderTargetBitmapImpl(s_imagingFactory, s_d2d1Device.Factory, width, height); + return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Device.Factory, width, height); } public IStreamGeometryImpl CreateStreamGeometry() From d02b7cbe9285e3056c3a94852ebc832e8c76a447 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 11:56:20 -0600 Subject: [PATCH 21/29] Create D2D1 device with a factory. --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 14 +++- .../Avalonia.Direct2D1/RenderTarget.cs | 79 ------------------- .../SwapChainRenderTarget.cs | 4 +- 3 files changed, 13 insertions(+), 84 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 6d6e1a1149..f4515a3814 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -8,7 +8,6 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Controls; using Avalonia.Rendering; -using SharpDX.Direct3D11; namespace Avalonia { @@ -28,6 +27,12 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); + private static readonly SharpDX.Direct2D1.Factory s_d2D1Factory = +#if DEBUG + new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); +#else + new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); +#endif private static readonly SharpDX.DirectWrite.Factory s_dwfactory = new SharpDX.DirectWrite.Factory(); private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory(); @@ -59,13 +64,16 @@ namespace Avalonia.Direct2D1 s_dxgiDevice = d3dDevice.QueryInterface(); } - s_d2D1Device = new SharpDX.Direct2D1.Device(s_dxgiDevice); + using (var factory1 = s_d2D1Factory.QueryInterface()) + { + s_d2D1Device = new SharpDX.Direct2D1.Device(factory1, s_dxgiDevice); + } } public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .BindToSelf(s_d2D1Device.Factory) + .BindToSelf(s_d2D1Factory) .BindToSelf(s_dwfactory) .BindToSelf(s_imagingFactory) .BindToSelf(s_dxgiDevice) diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index 180e1a7472..52146d77c1 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -13,42 +13,11 @@ namespace Avalonia.Direct2D1 { public class RenderTarget : IRenderTarget { - private readonly IntPtr _hwnd; - private Size2 _savedSize; - private Size2F _savedDpi; - /// /// The render target. /// private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; - /// - /// Initializes a new instance of the class. - /// - /// The window handle. - public RenderTarget(IntPtr hwnd) - { - _hwnd = hwnd; - Direct2DFactory = AvaloniaLocator.Current.GetService(); - DirectWriteFactory = AvaloniaLocator.Current.GetService(); - - RenderTargetProperties renderTargetProperties = new RenderTargetProperties - { - }; - - HwndRenderTargetProperties hwndProperties = new HwndRenderTargetProperties - { - Hwnd = hwnd, - PixelSize = _savedSize = GetWindowSize(), - PresentOptions = PresentOptions.Immediately, - }; - - _renderTarget = new WindowRenderTarget( - Direct2DFactory, - renderTargetProperties, - hwndProperties); - } - /// /// Initializes a new instance of the class. /// @@ -82,24 +51,6 @@ namespace Avalonia.Direct2D1 /// An . public DrawingContext CreateDrawingContext() { - var window = _renderTarget as WindowRenderTarget; - - if (window != null) - { - var size = GetWindowSize(); - var dpi = GetWindowDpi(); - - if (size != _savedSize) - { - window.Resize(_savedSize = size); - } - - if (dpi != _savedDpi) - { - window.DotsPerInch = _savedDpi = dpi; - } - } - return new DrawingContext(new Media.DrawingContext(_renderTarget, DirectWriteFactory)); } @@ -107,35 +58,5 @@ namespace Avalonia.Direct2D1 { _renderTarget.Dispose(); } - - private Size2F GetWindowDpi() - { - if (UnmanagedMethods.ShCoreAvailable) - { - uint dpix, dpiy; - - var monitor = UnmanagedMethods.MonitorFromWindow( - _hwnd, - UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); - - if (UnmanagedMethods.GetDpiForMonitor( - monitor, - UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, - out dpix, - out dpiy) == 0) - { - return new Size2F(dpix, dpiy); - } - } - - return new Size2F(96, 96); - } - - private Size2 GetWindowSize() - { - UnmanagedMethods.RECT rc; - UnmanagedMethods.GetClientRect(_hwnd, out rc); - return new Size2(rc.right - rc.left, rc.bottom - rc.top); - } } } diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 0d3799f1b1..8362305b9f 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -73,8 +73,8 @@ namespace Avalonia.Direct2D1 public void Dispose() { - _deviceContext.Dispose(); - _swapChain.Dispose(); + _deviceContext?.Dispose(); + _swapChain?.Dispose(); } private void CreateSwapChain() From 1a96efa8af20cc9884667cf2514eff42deb71f0a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 12:45:47 -0600 Subject: [PATCH 22/29] Update implementation and added test case to control catalog. Fixes #807. --- samples/ControlCatalog/Pages/ImagePage.xaml | 6 ++++- .../ControlCatalog/Pages/ImagePage.xaml.cs | 23 +++++++++++++++++++ .../Avalonia.Android/PlatformIconLoader.cs | 6 ++--- .../Platform/IWindowIconImpl.cs | 2 +- src/Avalonia.Controls/WindowIcon.cs | 2 +- src/Gtk/Avalonia.Gtk/IconImpl.cs | 5 ++-- src/Windows/Avalonia.Win32/IconImpl.cs | 8 +++---- src/iOS/Avalonia.iOS/PlatformIconLoader.cs | 8 +++---- 8 files changed, 41 insertions(+), 19 deletions(-) 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.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/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/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/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/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 From e2ba8fb5bb0b25e61d8774d5461e3b124969b54b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 13:15:13 -0600 Subject: [PATCH 23/29] Make video support creation flag only be set when running on Windows 8 or newer (Direct3D11.1 or newer). --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index f4515a3814..69e0811b5e 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -55,10 +55,16 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D.FeatureLevel.Level_9_2, SharpDX.Direct3D.FeatureLevel.Level_9_1, }; + var creationFlags = SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport; + var osVersion = Environment.OSVersion.Version; + if (osVersion.Major > 6 || (osVersion.Major == 6 && osVersion.Minor >= 2)) // If Windows 8 or newer + { + creationFlags |= SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport; + } using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, + creationFlags, featureLevels)) { s_dxgiDevice = d3dDevice.QueryInterface(); From 9d73868da75d1ad53844814066ec15dab4cbca7c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 13:29:54 -0600 Subject: [PATCH 24/29] Remove the VideoSupport flag because AppVeyor machines do not support it. --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 69e0811b5e..49a5130282 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -55,16 +55,10 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D.FeatureLevel.Level_9_2, SharpDX.Direct3D.FeatureLevel.Level_9_1, }; - var creationFlags = SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport; - var osVersion = Environment.OSVersion.Version; - if (osVersion.Major > 6 || (osVersion.Major == 6 && osVersion.Minor >= 2)) // If Windows 8 or newer - { - creationFlags |= SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport; - } using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - creationFlags, + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport, featureLevels)) { s_dxgiDevice = d3dDevice.QueryInterface(); From 619e64ef1d7c4f4a3c08c43ddda910861addd44b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 15:28:17 -0600 Subject: [PATCH 25/29] Remove DirectX 12 feature level choices. Device will be created with feature levels up to DirectX 11.1 (Windows 8), which is all we use. --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 49a5130282..a073407a6c 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -45,8 +45,6 @@ namespace Avalonia.Direct2D1 { 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, @@ -58,7 +56,7 @@ namespace Avalonia.Direct2D1 using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport, + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, featureLevels)) { s_dxgiDevice = d3dDevice.QueryInterface(); From d73350e0fd1f307635abc5651c91f3df799ccdf1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 16:07:22 -0600 Subject: [PATCH 26/29] Fixes intermittent test failures on AppVeyor when running Leak Tests. --- tests/Avalonia.LeakTests/AvaloniaObjectTests.cs | 1 + tests/Avalonia.LeakTests/MemberSelectorTests.cs | 8 ++++++++ 2 files changed, 9 insertions(+) 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() { From 17a43dacfa4bf2e5489a72242b0a65f69b30e2a7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 13 Jan 2017 15:15:36 -0600 Subject: [PATCH 27/29] Make it possible to construct a Direct2D BitmapImpl from a ID2D1Bitmap directly, instead of only via WIC imaging factories. --- .../Media/Imaging/BitmapImpl.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index d0f0aaff21..13dccf7714 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -64,6 +64,21 @@ namespace Avalonia.Direct2D1.Media BitmapCreateCacheOption.CacheOnLoad); } + /// + /// 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 BitmapImpl(SharpDX.Direct2D1.Bitmap d2DBitmap) + { + _direct2D = d2DBitmap; + } + /// /// Gets the width of the bitmap, in pixels. /// @@ -77,14 +92,13 @@ namespace Avalonia.Direct2D1.Media public virtual void Dispose() { WicImpl.Dispose(); + _direct2D?.Dispose(); } /// /// Gets the WIC implementation of the bitmap. /// - public Bitmap WicImpl - { - get; } + public Bitmap WicImpl { get; } /// /// Gets a Direct2D bitmap to use on the specified render target. From 240bc4d2ca0986a25c0568cb174407bea1f770ec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 16 Jan 2017 17:25:12 -0800 Subject: [PATCH 28/29] Fix horizontal scroll with virtualized items. Fixes #849 --- src/Avalonia.Controls/IVirtualizingPanel.cs | 5 ++++ .../Presenters/ItemVirtualizer.cs | 13 +++++++-- .../VirtualizingStackPanel.cs | 27 +++++++++++++++++-- ...emsPresenterTests_Virtualization_Simple.cs | 18 +++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) 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/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs index c4edaf5387..fee326dacc 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs @@ -206,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; } 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/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index e603925e31..2f98cccadf 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 From 1fd692f262baa12b48abbf2f0c5e8c027e219383 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 17 Jan 2017 16:45:06 -0600 Subject: [PATCH 29/29] Change implementation to be via different classes so as to not break invariants and pixel measurements. --- .../Avalonia.Direct2D1.csproj | 4 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 6 +- .../Media/DrawingContext.cs | 2 +- .../Media/Imaging/BitmapImpl.cs | 150 ++---------------- .../Media/Imaging/D2DBitmapImpl.cs | 57 +++++++ .../Media/Imaging/RenderTargetBitmapImpl.cs | 2 +- .../Media/Imaging/WicBitmapImpl.cs | 135 ++++++++++++++++ 7 files changed, 212 insertions(+), 144 deletions(-) create mode 100644 src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs create mode 100644 src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index 9f9558ff76..8a3a100ba8 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -65,8 +65,10 @@ - + + + diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index b43eef2fa9..5c135a2201 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -46,7 +46,7 @@ namespace Avalonia.Direct2D1 public IBitmapImpl CreateBitmap(int width, int height) { - return new BitmapImpl(s_imagingFactory, width, height); + return new WicBitmapImpl(s_imagingFactory, width, height); } public IFormattedTextImpl CreateFormattedText( @@ -92,12 +92,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/Media/DrawingContext.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs index 75a0f43d9f..0d936b7057 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs @@ -76,7 +76,7 @@ namespace Avalonia.Direct2D1.Media /// The rect in the output to draw to. public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) { - BitmapImpl impl = (BitmapImpl)source.PlatformImpl; + var impl = (BitmapImpl)source.PlatformImpl; Bitmap d2d = impl.GetDirect2DBitmap(_renderTarget); _renderTarget.DrawBitmap( d2d, diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 13dccf7714..63596bdf54 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -1,150 +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; - - 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 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); - } - - /// - /// 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 BitmapImpl(SharpDX.Direct2D1.Bitmap d2DBitmap) - { - _direct2D = d2DBitmap; - } - - /// - /// 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(); - _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 SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) - { - if (_direct2D == null) - { - FormatConverter converter = new FormatConverter(_factory); - converter.Initialize(WicImpl, PixelFormat.Format32bppPBGRA); - _direct2D = SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter); - } - - return _direct2D; - } - - /// - /// 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 eff832407e..59f3734649 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs @@ -10,7 +10,7 @@ using SharpDX.WIC; namespace Avalonia.Direct2D1.Media { - public class RenderTargetBitmapImpl : BitmapImpl, IRenderTargetBitmapImpl, IDisposable + public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl { 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..f17c516edd --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -0,0 +1,135 @@ +// 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) + { + if (_direct2D == null) + { + FormatConverter converter = new FormatConverter(_factory); + converter.Initialize(WicImpl, PixelFormat.Format32bppPBGRA); + _direct2D = SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter); + } + + return _direct2D; + } + + /// + /// 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(); + } + } +}