From f63a64e11b57417bb3a205d885eab7de97735394 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 7 Jan 2017 19:20:38 +0100 Subject: [PATCH 01/96] 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 02/96] 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 03/96] 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 04/96] 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 05/96] 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 06/96] 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 07/96] 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 08/96] 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 09/96] 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 10/96] 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 11/96] 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 12/96] 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 13/96] 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 14/96] 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 15/96] 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 16/96] 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 17/96] 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 18/96] 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 19/96] 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 20/96] 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 21/96] 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 22/96] 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 23/96] 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 24/96] 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(); + } + } +} From 374d6002752e966709602b952e9dbf090491e07a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Jan 2017 01:02:24 +0300 Subject: [PATCH 25/96] Added support for multiple drawing methods for window implementations. Added framebuffer support --- .../Platform/SkiaPlatform/WindowImpl.cs | 3 + .../Avalonia.Controls.csproj | 4 + .../Platform/ITopLevelImpl.cs | 6 + .../Surfaces/IFramebufferPlatformSurface.cs | 13 ++ .../Platform/Surfaces/ILockedFramebuffer.cs | 18 +++ .../Surfaces/INativeWindowPlatformSurface.cs | 23 ++++ .../Platform/Surfaces/PixelFormat.cs | 15 ++ src/Avalonia.Controls/TopLevel.cs | 2 +- .../Platform/IPlatformRenderInterface.cs | 5 +- src/Gtk/Avalonia.Cairo/CairoPlatform.cs | 22 +-- src/Gtk/Avalonia.Cairo/RenderTarget.cs | 19 +-- src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj | 2 + src/Gtk/Avalonia.Gtk/FramebufferManager.cs | 42 ++++++ src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs | 58 ++++++++ src/Gtk/Avalonia.Gtk/WindowImpl.cs | 2 +- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 32 ++++- .../AndroidPlatformRenderInterface.cs | 26 ++++ .../Avalonia.Skia.Android.csproj | 1 + .../Avalonia.Skia.Android/RenderTarget.cs | 16 +-- .../Avalonia.Skia.Android/SkiaRenderView.cs | 2 +- .../Avalonia.Skia.Desktop.csproj | 5 +- .../PlatformRenderInterfaceDesktop.cs | 21 +++ .../Avalonia.Skia.iOS.csproj | 1 + .../PlatformRenderingInterfaceIos.cs | 18 +++ src/Skia/Avalonia.Skia.iOS/RenderTarget.cs | 5 +- .../Avalonia.Skia/Avalonia.Skia.projitems | 1 + .../Avalonia.Skia/FramebufferRenderTarget.cs | 77 +++++++++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 10 +- .../Avalonia.Skia/WindowDrawingContextImpl.cs | 2 + .../Avalonia.Direct2D1/Direct2D1Platform.cs | 18 +-- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 13 +- .../Avalonia.Win32/Avalonia.Win32.csproj | 2 + .../Avalonia.Win32/FramebufferManager.cs | 41 ++++++ .../Interop/UnmanagedMethods.cs | 48 +++++++ .../Avalonia.Win32/WindowFramebuffer.cs | 130 ++++++++++++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 10 +- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 + .../InputElement_HitTesting.cs | 2 +- .../VisualTree/MockRenderInterface.cs | 2 +- 39 files changed, 644 insertions(+), 75 deletions(-) create mode 100644 src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs create mode 100644 src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs create mode 100644 src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs create mode 100644 src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs create mode 100644 src/Gtk/Avalonia.Gtk/FramebufferManager.cs create mode 100644 src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs create mode 100644 src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs create mode 100644 src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs create mode 100644 src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs create mode 100644 src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs create mode 100644 src/Windows/Avalonia.Win32/FramebufferManager.cs create mode 100644 src/Windows/Avalonia.Win32/WindowFramebuffer.cs diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs index 0e1540b5fd..7816a8af27 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -9,6 +9,7 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Skia.Android; using System; +using System.Collections.Generic; using Avalonia.Controls; namespace Avalonia.Android.Platform.SkiaPlatform @@ -193,5 +194,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform { // No window icons for mobile platforms } + + public IEnumerable Surfaces => new object[] {this}; } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 10fbe746e3..0fe068dab5 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -57,6 +57,10 @@ + + + + diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 087a333e8a..e11f1a3d79 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; @@ -37,6 +38,11 @@ namespace Avalonia.Platform /// IPlatformHandle Handle { get; } + /// + /// Supported methods of image output + /// + IEnumerable Surfaces { get; } + /// /// Gets or sets a method called when the window is activated (receives focus). /// diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs new file mode 100644 index 0000000000..6959947415 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public interface IFramebufferPlatformSurface + { + ILockedFramebuffer Lock(); + } +} diff --git a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs new file mode 100644 index 0000000000..1810479a70 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public interface ILockedFramebuffer : IDisposable + { + IntPtr Address { get; } + int Width { get; } + int Height { get; } + int RowBytes { get; } + Size Dpi { get; } + PixelFormat Format { get; } + } +} diff --git a/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs new file mode 100644 index 0000000000..33aa969c72 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public interface INativeWindowPlatformSurface + { + IntPtr Handle { get; } + } + + public class NativeWindowPlatformSurface : INativeWindowPlatformSurface + { + public NativeWindowPlatformSurface(IntPtr handle) + { + Handle = handle; + } + + public IntPtr Handle { get; } + } +} diff --git a/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs b/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs new file mode 100644 index 0000000000..c9f8eabe97 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public enum PixelFormat + { + Rgb565, + Rgba8888, + Bgra8888 + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 43c59f0b31..f314629d02 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -237,7 +237,7 @@ namespace Avalonia.Controls /// IRenderTarget IRenderRoot.CreateRenderTarget() { - return _renderInterface.CreateRenderTarget(PlatformImpl.Handle); + return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces); } /// diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index c129cbd905..ba48a72512 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using System.IO; using Avalonia.Media; @@ -40,9 +41,9 @@ namespace Avalonia.Platform /// /// Creates a renderer. /// - /// The platform handle for the renderer. + /// The list of surfaces that can be used for output. /// An . - IRenderTarget CreateRenderTarget(IPlatformHandle handle); + IRenderTarget CreateRenderTarget(IEnumerable surfaces); /// /// Creates a render target bitmap implementation. diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index e59bcfffa1..8b3c6899d3 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -2,6 +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.Linq; using Avalonia.Cairo.Media; using Avalonia.Cairo.Media.Imaging; using Avalonia.Media; @@ -50,24 +52,14 @@ namespace Avalonia.Cairo return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { - var window = handle as Gtk.Window; - if (window != null) - { - window.DoubleBuffered = true; - return new RenderTarget(window); - } - var area = handle as Gtk.DrawingArea; - if (area != null) - { - area.DoubleBuffered = true; - return new RenderTarget(area); - } + var accessor = surfaces?.OfType>().FirstOrDefault(); + if(accessor!=null) + return new RenderTarget(accessor); throw new NotSupportedException(string.Format( - "Don't know how to create a Cairo renderer from a '{0}' handle which isn't Gtk.Window or Gtk.DrawingArea", - handle.HandleDescriptor)); + "Don't know how to create a Cairo renderer from any of provided surfaces")); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Gtk/Avalonia.Cairo/RenderTarget.cs b/src/Gtk/Avalonia.Cairo/RenderTarget.cs index d285986762..49f5e18dec 100644 --- a/src/Gtk/Avalonia.Cairo/RenderTarget.cs +++ b/src/Gtk/Avalonia.Cairo/RenderTarget.cs @@ -20,8 +20,8 @@ namespace Avalonia.Cairo public class RenderTarget : IRenderTarget { private readonly Surface _surface; - private readonly Gtk.Window _window; - private readonly Gtk.DrawingArea _area; + private readonly Func _drawableAccessor; + /// /// Initializes a new instance of the class. @@ -29,9 +29,9 @@ namespace Avalonia.Cairo /// The window. /// The width of the window. /// The height of the window. - public RenderTarget(Gtk.Window window) + public RenderTarget(Func drawable) { - _window = window; + _drawableAccessor = drawable; } public RenderTarget(ImageSurface surface) @@ -39,11 +39,6 @@ namespace Avalonia.Cairo _surface = surface; } - public RenderTarget(DrawingArea area) - { - _area = area; - } - /// /// Creates a cairo surface that targets a platform-specific resource. /// @@ -52,12 +47,10 @@ namespace Avalonia.Cairo public IDrawingContextImpl CreateMediaDrawingContext() { - if (_window != null) - return new Media.DrawingContext(_window.GdkWindow); + if (_drawableAccessor != null) + return new Media.DrawingContext(_drawableAccessor()); if (_surface != null) return new Media.DrawingContext(_surface); - if (_area != null) - return new Media.DrawingContext(_area.GdkWindow); throw new InvalidOperationException("Unspecified render target"); } diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj index b001c7bc19..2c10b1188b 100644 --- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj +++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj @@ -48,7 +48,9 @@ + + diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs new file mode 100644 index 0000000000..3da3c3d70c --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; + +namespace Avalonia.Gtk +{ + class FramebufferManager : IFramebufferPlatformSurface, IDisposable + { + private readonly WindowImplBase _window; + private PixbufFramebuffer _fb; + public FramebufferManager(WindowImplBase window) + { + _window = window; + } + + public void Dispose() + { + _fb?.Deallocate(); + } + + public ILockedFramebuffer Lock() + { + if(_window.CurrentDrawable == null) + throw new InvalidOperationException("Window is not in drawing state"); + + var drawable = _window.CurrentDrawable; + var width = (int) _window.ClientSize.Width; + var height = (int) _window.ClientSize.Height; + if (_fb == null || _fb.Width != width || + _fb.Height != height) + { + _fb?.Dispose(); + _fb = new PixbufFramebuffer(width, height); + } + _fb.SetDrawable(drawable); + return _fb; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs b/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs new file mode 100644 index 0000000000..76e9e8a307 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using Gdk; + +namespace Avalonia.Gtk +{ + class PixbufFramebuffer : ILockedFramebuffer + { + private Pixbuf _pixbuf; + private Drawable _drawable; + + public PixbufFramebuffer(int width, int height) + { + _pixbuf = new Pixbuf(Gdk.Colorspace.Rgb, false, 8, width, height); + } + + public void SetDrawable(Drawable drawable) + { + _drawable = drawable; + } + + public void Deallocate() + { + _pixbuf.Dispose(); + _pixbuf = null; + } + + public void Dispose() + { + using (var gc = new Gdk.GC(_drawable)) + _drawable.DrawPixbuf(gc, _pixbuf, 0, 0, 0, 0, Width, Height, RgbDither.None, 0, 0); + _drawable = null; + } + + public IntPtr Address => _pixbuf.Pixels; + public int Width => _pixbuf.Width; + public int Height => _pixbuf.Height; + public int RowBytes => _pixbuf.Rowstride; + //TODO: Proper DPI detect + public Size Dpi => new Size(96, 96); + public PixelFormat Format + { + get + { + if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == + OperatingSystemType.WinNT) + return PixelFormat.Bgra8888; + return PixelFormat.Rgba8888; + } + } + } +} + diff --git a/src/Gtk/Avalonia.Gtk/WindowImpl.cs b/src/Gtk/Avalonia.Gtk/WindowImpl.cs index e075a31c4c..eca7c24136 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImpl.cs @@ -10,7 +10,7 @@ namespace Avalonia.Gtk { private Gtk.Window _window; private Gtk.Window Window => _window ?? (_window = (Gtk.Window) Widget); - + public WindowImpl(Gtk.WindowType type) : base(new PlatformHandleAwareWindow(type)) { Init(); diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index 8641f2f431..ba9b984c56 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -2,10 +2,12 @@ // 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.Disposables; using System.Runtime.InteropServices; using Gdk; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Input; @@ -22,7 +24,8 @@ namespace Avalonia.Gtk private IInputRoot _inputRoot; protected Gtk.Widget _window; public Gtk.Widget Widget => _window; - + public Gdk.Drawable CurrentDrawable { get; private set; } + private FramebufferManager _framebuffer; private Gtk.IMContext _imContext; @@ -33,6 +36,7 @@ namespace Avalonia.Gtk protected WindowImplBase(Gtk.Widget window) { _window = window; + _framebuffer = new FramebufferManager(this); Init(); } @@ -43,7 +47,6 @@ namespace Avalonia.Gtk _imContext = new Gtk.IMMulticontext(); _imContext.Commit += ImContext_Commit; _window.Realized += OnRealized; - _window.DoubleBuffered = false; _window.Realize(); _window.ButtonPressEvent += OnButtonPressEvent; _window.ButtonReleaseEvent += OnButtonReleaseEvent; @@ -283,7 +286,9 @@ namespace Avalonia.Gtk void OnExposeEvent(object o, Gtk.ExposeEventArgs args) { + CurrentDrawable = args.Event.Window; Paint(args.Event.Area.ToAvalonia()); + CurrentDrawable = null; args.RetVal = true; } @@ -311,9 +316,32 @@ namespace Avalonia.Gtk public void Dispose() { + _framebuffer.Dispose(); _window.Hide(); _window.Dispose(); _window = null; } + + + //We need that for DrawingArea which does not have an HWND until parent window is realized + //and could also *change* it's HWND + class DynamicNativeWindowSurface : INativeWindowPlatformSurface + { + private readonly IPlatformHandle _handle; + + public DynamicNativeWindowSurface(IPlatformHandle handle) + { + _handle = handle; + } + + public IntPtr Handle => _handle.Handle; + } + + public IEnumerable Surfaces => new object[] + { + new DynamicNativeWindowSurface(Handle), + new Func(() => CurrentDrawable), + _framebuffer + }; } } diff --git a/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs new file mode 100644 index 0000000000..610d6f5703 --- /dev/null +++ b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using Avalonia.Platform; + +namespace Avalonia.Skia +{ + partial class PlatformRenderInterface + { + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + var surfaceView = surfaces?.OfType().FirstOrDefault(); + if (surfaceView == null) + throw new ArgumentException("Avalonia.Skia.Android is only capable to draw on SurfaceView"); + return new WindowRenderTarget(surfaceView); + } + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj index cf352607f0..fdde5553eb 100644 --- a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj +++ b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj @@ -87,6 +87,7 @@ + diff --git a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs index 59ad3b9fbb..03ddf49851 100644 --- a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs @@ -26,14 +26,14 @@ namespace Avalonia.Skia internal class WindowRenderTarget : RenderTarget { - private readonly IPlatformHandle _hwnd; + private readonly SurfaceView _surfaceView; Bitmap _bitmap; int Width { get; set; } int Height { get; set; } - public WindowRenderTarget(IPlatformHandle hwnd) + public WindowRenderTarget(SurfaceView surfaceView) { - _hwnd = hwnd; + _surfaceView = surfaceView; FixSize(); } @@ -63,9 +63,8 @@ namespace Avalonia.Skia private void GetPlatformWindowSize(out int w, out int h) { - var surfaceView = _hwnd as SurfaceView; - w = surfaceView.Width; - h = surfaceView.Height; + w = _surfaceView.Width; + h = _surfaceView.Height; } public override DrawingContext CreateDrawingContext() @@ -85,11 +84,10 @@ namespace Avalonia.Skia public void Present() { - var surfaceView = _hwnd as SurfaceView; Canvas canvas = null; try { - canvas = surfaceView.Holder.LockCanvas(null); + canvas = _surfaceView.Holder.LockCanvas(null); _bitmap.UnlockPixels(); canvas.DrawBitmap(_bitmap, 0, 0, null); } @@ -99,7 +97,7 @@ namespace Avalonia.Skia finally { if (canvas != null) - surfaceView.Holder.UnlockCanvasAndPost(canvas); + _surfaceView.Holder.UnlockCanvasAndPost(canvas); } _bitmap.UnlockPixels(); diff --git a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs index 62d8e7bd18..fc6c994bd5 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs +++ b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs @@ -22,7 +22,7 @@ namespace Avalonia.Skia.Android { _renderTarget = AvaloniaLocator.Current.GetService() - .CreateRenderTarget(this); + .CreateRenderTarget(new object[]{this}); } protected override void Draw() diff --git a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj index d7d865225a..62586f750d 100644 --- a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj +++ b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj @@ -72,11 +72,8 @@ + - - UnmanagedMethods.cs - - diff --git a/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs b/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs new file mode 100644 index 0000000000..9382a4f6e2 --- /dev/null +++ b/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; + +namespace Avalonia.Skia +{ + partial class PlatformRenderInterface + { + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + var fb = surfaces?.OfType().FirstOrDefault(); + if (fb == null) + throw new Exception("Avalonia.Skia.Deskop currently only supports framebuffer render target"); + return new FramebufferRenderTarget(fb); + } + } +} diff --git a/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj b/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj index 28892c3c01..c8a7f36317 100644 --- a/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj +++ b/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj @@ -37,6 +37,7 @@ true + diff --git a/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs b/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs new file mode 100644 index 0000000000..fdc7389800 --- /dev/null +++ b/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Avalonia.Platform; +using Foundation; +using UIKit; + +namespace Avalonia.Skia +{ + partial class PlatformRenderInterface + { + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + return new WindowRenderTarget(); + } + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs b/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs index 083b611d5c..abe663e44b 100644 --- a/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs @@ -26,14 +26,13 @@ namespace Avalonia.Skia internal class WindowRenderTarget : RenderTarget { - private readonly IPlatformHandle _hwnd; + SKBitmap _bitmap; int Width { get; set; } int Height { get; set; } - public WindowRenderTarget(IPlatformHandle hwnd) + public WindowRenderTarget() { - _hwnd = hwnd; FixSize(); } diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems b/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems index 426be548e2..303de8ee56 100644 --- a/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems +++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems @@ -12,6 +12,7 @@ + diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs new file mode 100644 index 0000000000..3d3e322e0d --- /dev/null +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Media; +using Avalonia.Platform; +using SkiaSharp; + +namespace Avalonia.Skia +{ + public class FramebufferRenderTarget : IRenderTarget + { + private readonly IFramebufferPlatformSurface _surface; + + public FramebufferRenderTarget(IFramebufferPlatformSurface surface) + { + _surface = surface; + } + + public void Dispose() + { + //Nothing to do here, since we don't own framebuffer + } + + class FramebufferDrawingContextImpl : DrawingContextImpl + { + private readonly SKCanvas _canvas; + private readonly SKSurface _surface; + private readonly ILockedFramebuffer _framebuffer; + + public FramebufferDrawingContextImpl(SKCanvas canvas, SKSurface surface, ILockedFramebuffer framebuffer) : base(canvas) + { + _canvas = canvas; + _surface = surface; + _framebuffer = framebuffer; + } + + public override void Dispose() + { + _canvas.Dispose(); + _surface.Dispose(); + _framebuffer.Dispose(); + base.Dispose(); + } + } + + SKColorType TranslatePixelFormat(PixelFormat fmt) + { + if(fmt == PixelFormat.Rgb565) + return SKColorType.Rgb565; + if(fmt == PixelFormat.Bgra8888) + return SKColorType.Bgra8888; + if (fmt == PixelFormat.Rgba8888) + return SKColorType.Rgba8888; + throw new ArgumentException("Unknown pixel format: " + fmt); + } + + public DrawingContext CreateDrawingContext() + { + var fb = _surface.Lock(); + + SKImageInfo nfo = new SKImageInfo(fb.Width, fb.Height, TranslatePixelFormat(fb.Format), + SKAlphaType.Opaque); + var surface = SKSurface.Create(nfo, fb.Address, fb.RowBytes); + if (surface == null) + throw new Exception("Unable to create a surface for pixel format " + fb.Format); + var canvas = surface.Canvas; + canvas.RestoreToCount(0); + canvas.Save(); + canvas.Clear(SKColors.Red); + canvas.ResetMatrix(); + + return new DrawingContext(new FramebufferDrawingContextImpl(canvas, surface, fb), + Matrix.CreateScale(fb.Dpi.Width / 96, fb.Dpi.Height / 96)); + } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index f8558c7428..56b334073b 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Avalonia.Media; using Avalonia.Platform; @@ -7,7 +8,7 @@ using SkiaSharp; namespace Avalonia.Skia { - public class PlatformRenderInterface : IPlatformRenderInterface, IRendererFactory + public partial class PlatformRenderInterface : IPlatformRenderInterface, IRendererFactory { public IBitmapImpl CreateBitmap(int width, int height) { @@ -54,6 +55,8 @@ namespace Avalonia.Skia return new Renderer(root, renderLoop); } + + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) { if (width < 1) @@ -63,10 +66,5 @@ namespace Avalonia.Skia return new BitmapImpl(width, height); } - - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) - { - return new WindowRenderTarget(handle); - } } } diff --git a/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs index a4e29fc022..db9d10a346 100644 --- a/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs @@ -1,6 +1,7 @@ namespace Avalonia.Skia { +#if !DESKTOP // not sure we need this yet internal class WindowDrawingContextImpl : DrawingContextImpl { @@ -18,4 +19,5 @@ namespace Avalonia.Skia _target.Present(); } } +#endif } \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index e53692bef0..4ef267e0de 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -2,11 +2,14 @@ // 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.IO; +using System.Linq; using Avalonia.Direct2D1.Media; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Rendering; namespace Avalonia @@ -99,18 +102,15 @@ namespace Avalonia.Direct2D1 return new Renderer(root, renderLoop); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { - if (handle.HandleDescriptor == "HWND") - { - return new HwndRenderTarget(handle.Handle); - } - else + var nativeWindow = surfaces?.OfType().FirstOrDefault(); + + if (nativeWindow != null) { - throw new NotSupportedException(string.Format( - "Don't know how to create a Direct2D1 renderer from a '{0}' handle", - handle.HandleDescriptor)); + return new HwndRenderTarget(nativeWindow); } + throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 49d4c91c52..317e7fc34f 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -11,16 +12,16 @@ namespace Avalonia.Direct2D1 { class HwndRenderTarget : SwapChainRenderTarget { - private readonly IntPtr _hwnd; + private readonly INativeWindowPlatformSurface _window; - public HwndRenderTarget(IntPtr hwnd) + public HwndRenderTarget(INativeWindowPlatformSurface window) { - _hwnd = hwnd; + _window = window; } protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) { - return new SwapChain1(dxgiFactory, DxgiDevice, _hwnd, ref swapChainDesc); + return new SwapChain1(dxgiFactory, DxgiDevice, _window.Handle, ref swapChainDesc); } protected override Size2F GetWindowDpi() @@ -30,7 +31,7 @@ namespace Avalonia.Direct2D1 uint dpix, dpiy; var monitor = UnmanagedMethods.MonitorFromWindow( - _hwnd, + _window.Handle, UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); if (UnmanagedMethods.GetDpiForMonitor( @@ -49,7 +50,7 @@ namespace Avalonia.Direct2D1 protected override Size2 GetWindowSize() { UnmanagedMethods.RECT rc; - UnmanagedMethods.GetClientRect(_hwnd, out rc); + UnmanagedMethods.GetClientRect(_window.Handle, out rc); return new Size2(rc.right - rc.left, rc.bottom - rc.top); } } diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 4b190dc469..851233a19e 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -69,6 +69,8 @@ Component + + diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs new file mode 100644 index 0000000000..ecd05f41b4 --- /dev/null +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class FramebufferManager : IFramebufferPlatformSurface, IDisposable + { + private readonly IntPtr _hwnd; + private WindowFramebuffer _fb; + + public FramebufferManager(IntPtr hwnd) + { + _hwnd = hwnd; + } + + public ILockedFramebuffer Lock() + { + UnmanagedMethods.RECT rc; + UnmanagedMethods.GetClientRect(_hwnd, out rc); + var width = rc.right - rc.left; + var height = rc.bottom - rc.top; + if (_fb == null || _fb.Width != width || _fb.Height != height) + { + _fb?.Deallocate(); + _fb = null; + _fb = new WindowFramebuffer(_hwnd, width, height); + } + return _fb; + } + + public void Dispose() + { + _fb?.Deallocate(); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c2785095d0..4d5af748f4 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -568,6 +568,27 @@ namespace Avalonia.Win32.Interop public byte rgbReserved; } + [StructLayout(LayoutKind.Sequential)] + public struct BITMAPINFOHEADER + { + public uint biSize; + public int biWidth; + public int biHeight; + public ushort biPlanes; + public ushort biBitCount; + public uint biCompression; + public uint biSizeImage; + public int biXPelsPerMeter; + public int biYPelsPerMeter; + public uint biClrUsed; + public uint biClrImportant; + + public void Init() + { + biSize = (uint)Marshal.SizeOf(this); + } + } + [StructLayout(LayoutKind.Sequential)] public struct BITMAPINFO { @@ -859,6 +880,31 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + [DllImport("gdi32.dll")] + public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint + dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines, + IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, uint fuColorUse); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CreateDIBSection(IntPtr hDC, ref BITMAPINFOHEADER pBitmapInfo, int un, out IntPtr lplpVoid, IntPtr handle, int dw); + [DllImport("gdi32.dll")] + public static extern int DeleteObject(IntPtr hObject); + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + [DllImport("gdi32.dll")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateFileMapping(IntPtr hFile, + IntPtr lpFileMappingAttributes, + uint flProtect, + uint dwMaximumSizeHigh, + uint dwMaximumSizeLow, + string lpName); + public enum MONITOR { MONITOR_DEFAULTTONULL = 0x00000000, @@ -1177,6 +1223,7 @@ namespace Avalonia.Win32.Interop [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + } @@ -1197,5 +1244,6 @@ namespace Avalonia.Win32.Interop [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } } diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs new file mode 100644 index 0000000000..2731604a22 --- /dev/null +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Interop; +using System.Windows.Media; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Win32.Interop; +using PixelFormat = Avalonia.Controls.Platform.Surfaces.PixelFormat; + +namespace Avalonia.Win32 +{ + public class WindowFramebuffer : ILockedFramebuffer + { + private readonly IntPtr _handle; + private IntPtr _pBitmap; + UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; + + + public WindowFramebuffer(IntPtr handle, int width, int height) + { + + if (width <= 0) + throw new ArgumentException("width is less than zero"); + if (height <= 0) + throw new ArgumentException("height is less than zero"); + _handle = handle; + _bmpInfo.Init(); + _bmpInfo.biPlanes = 1; + _bmpInfo.biBitCount = 32; + _bmpInfo.Init(); + _bmpInfo.biWidth = width; + _bmpInfo.biHeight = -height; + _pBitmap = Marshal.AllocHGlobal(width * height * 4); + } + + + + public int Width => _bmpInfo.biWidth; + + public int Height => -_bmpInfo.biHeight; + + public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, + int height = -1) + { + if(_pBitmap == IntPtr.Zero) + throw new ObjectDisposedException("Framebuffer"); + if (width == -1) + width = Width; + if (height == -1) + height = Height; + UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY, + 0, (uint)Height, _pBitmap, ref _bmpInfo, 0); + } + + public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, + int height = -1) + { + + if (_pBitmap == IntPtr.Zero) + throw new ObjectDisposedException("Framebuffer"); + if (hWnd == IntPtr.Zero) + return false; + IntPtr hDC = UnmanagedMethods.GetDC(hWnd); + if (hDC == IntPtr.Zero) + return false; + DrawToDevice(hDC, destX, destY, srcX, srcY, width, height); + UnmanagedMethods.ReleaseDC(hWnd, hDC); + return true; + } + + + + public void Dispose() + { + //It's not an *actual* dispose. This call meand "We are done drawing" + DrawToWindow(_handle); + } + + public void Deallocate() + { + if (_pBitmap != IntPtr.Zero) + { + Marshal.FreeHGlobal(_pBitmap); + _pBitmap = IntPtr.Zero; + } + } + + ~WindowFramebuffer() + { + Deallocate(); + } + + public IntPtr Address => _pBitmap; + public int RowBytes => Width * 4; + public PixelFormat Format => PixelFormat.Bgra8888; + + //TODO: Proper DPI support here + /* + private Size GetWindowDpiWin32() + { + if (UnmanagedMethods.ShCoreAvailable) + { + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow( + _hwnd.Handle, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size(dpix, dpiy); + } + } + + return new Size(96, 96); + } + */ + public Size Dpi => new Size(96, 96); + + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index db46538796..878be61664 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -11,6 +11,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Runtime.InteropServices; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Win32.Input; @@ -36,10 +37,11 @@ namespace Avalonia.Win32 private bool _coverTaskBarWhenMaximized = true; private double _scaling = 1; private WindowState _showWindowState; - + private FramebufferManager _framebuffer; public WindowImpl() { CreateWindow(); + _framebuffer = new FramebufferManager(_hwnd); s_instances.Add(this); } @@ -175,6 +177,7 @@ namespace Avalonia.Win32 public void Dispose() { s_instances.Remove(this); + _framebuffer.Dispose(); UnmanagedMethods.DestroyWindow(_hwnd); } @@ -761,5 +764,10 @@ namespace Avalonia.Win32 ShowWindow(WindowState.Maximized); } } + + public IEnumerable Surfaces => new object[] + { + new NativeWindowPlatformSurface(_hwnd), _framebuffer + }; } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 67817ef62a..e9d095483b 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -232,6 +232,8 @@ namespace Avalonia.iOS public void SetIcon(IWindowIconImpl icon) { } + + public IEnumerable Surfaces => new object[]{this}; } class AvaloniaViewController : UIViewController diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs index e4e7551f5c..e00d504124 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs @@ -340,7 +340,7 @@ namespace Avalonia.Input.UnitTests throw new NotImplementedException(); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 3d6a9093e4..ac31b3852b 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -20,7 +20,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); } From 154b5836b70a2c31a4635dda11508c1d788d9de4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Jan 2017 01:17:58 +0300 Subject: [PATCH 26/96] Fixed dpi --- .../Avalonia.Win32/WindowFramebuffer.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs index 2731604a22..cd89c01131 100644 --- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -99,32 +99,28 @@ namespace Avalonia.Win32 public int RowBytes => Width * 4; public PixelFormat Format => PixelFormat.Bgra8888; - //TODO: Proper DPI support here - /* - private Size GetWindowDpiWin32() + public Size Dpi { - if (UnmanagedMethods.ShCoreAvailable) + get { - uint dpix, dpiy; - - var monitor = UnmanagedMethods.MonitorFromWindow( - _hwnd.Handle, - UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); - - if (UnmanagedMethods.GetDpiForMonitor( - monitor, - UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, - out dpix, - out dpiy) == 0) + if (UnmanagedMethods.ShCoreAvailable) { - return new Size(dpix, dpiy); + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow(_handle, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size(dpix, dpiy); + } } + return new Size(96, 96); } - - return new Size(96, 96); } - */ - public Size Dpi => new Size(96, 96); - } } \ No newline at end of file From 55eb55dc8842db5072a01976d1b3f677e416aebd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Jan 2017 18:44:01 +0300 Subject: [PATCH 27/96] Removed INativeWindowPlatformSurface and added some docs --- .../Avalonia.Controls.csproj | 1 - .../Platform/ITopLevelImpl.cs | 11 +++++++-- .../Surfaces/IFramebufferPlatformSurface.cs | 6 +++++ .../Platform/Surfaces/ILockedFramebuffer.cs | 18 +++++++++++++++ .../Surfaces/INativeWindowPlatformSurface.cs | 23 ------------------- .../Platform/IPlatformRenderInterface.cs | 2 +- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 17 +------------- .../AndroidPlatformRenderInterface.cs | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 5 ++-- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 5 ++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 11 files changed, 43 insertions(+), 49 deletions(-) delete mode 100644 src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 0fe068dab5..5fc7329f97 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -59,7 +59,6 @@ - diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index e11f1a3d79..77884acf73 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -39,8 +39,15 @@ namespace Avalonia.Platform IPlatformHandle Handle { get; } /// - /// Supported methods of image output - /// + /// The list of native platform's surfaces that can be consumed by rendering subsystems. + /// + /// + /// Rendering platform will check that list and see if it can utilize one of them to output. + /// It should be enough to expose a native window handle via IPlatformHandle + /// and add support for framebuffer (even if it's emulated one) via IFramebufferPlatformSurface. + /// If you have some rendering platform that's tied to your particular windowing platform, + /// just expose some toolkit-specific object (e. g. Func<Gdk.Drawable> in case of GTK#+Cairo) + /// IEnumerable Surfaces { get; } /// diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs index 6959947415..84988e912f 100644 --- a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs +++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs @@ -8,6 +8,12 @@ namespace Avalonia.Controls.Platform.Surfaces { public interface IFramebufferPlatformSurface { + /// + /// Provides a framebuffer descriptor for drawing. + /// + /// + /// Contents should be drawn on actual window after disposing + /// ILockedFramebuffer Lock(); } } diff --git a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs index 1810479a70..e771401dfe 100644 --- a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs +++ b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs @@ -8,11 +8,29 @@ namespace Avalonia.Controls.Platform.Surfaces { public interface ILockedFramebuffer : IDisposable { + /// + /// Address of the first pixel + /// IntPtr Address { get; } + /// + /// Framebuffer width + /// int Width { get; } + /// + /// Framebuffer height + /// int Height { get; } + /// + /// Number of bytes per row + /// int RowBytes { get; } + /// + /// DPI of underling screen + /// Size Dpi { get; } + /// + /// Pixel format + /// PixelFormat Format { get; } } } diff --git a/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs deleted file mode 100644 index 33aa969c72..0000000000 --- a/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Avalonia.Controls.Platform.Surfaces -{ - public interface INativeWindowPlatformSurface - { - IntPtr Handle { get; } - } - - public class NativeWindowPlatformSurface : INativeWindowPlatformSurface - { - public NativeWindowPlatformSurface(IntPtr handle) - { - Handle = handle; - } - - public IntPtr Handle { get; } - } -} diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index ba48a72512..34600c145f 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -41,7 +41,7 @@ namespace Avalonia.Platform /// /// Creates a renderer. /// - /// The list of surfaces that can be used for output. + /// The list of native platform's surfaces that can be used for output. /// An . IRenderTarget CreateRenderTarget(IEnumerable surfaces); diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index ba9b984c56..bd48998dff 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -322,24 +322,9 @@ namespace Avalonia.Gtk _window = null; } - - //We need that for DrawingArea which does not have an HWND until parent window is realized - //and could also *change* it's HWND - class DynamicNativeWindowSurface : INativeWindowPlatformSurface - { - private readonly IPlatformHandle _handle; - - public DynamicNativeWindowSurface(IPlatformHandle handle) - { - _handle = handle; - } - - public IntPtr Handle => _handle.Handle; - } - public IEnumerable Surfaces => new object[] { - new DynamicNativeWindowSurface(Handle), + Handle, new Func(() => CurrentDrawable), _framebuffer }; diff --git a/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs index 610d6f5703..3355839314 100644 --- a/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs @@ -19,7 +19,7 @@ namespace Avalonia.Skia { var surfaceView = surfaces?.OfType().FirstOrDefault(); if (surfaceView == null) - throw new ArgumentException("Avalonia.Skia.Android is only capable to draw on SurfaceView"); + throw new ArgumentException("Avalonia.Skia.Android is only capable of drawing on SurfaceView"); return new WindowRenderTarget(surfaceView); } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 4ef267e0de..34595fecc8 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -104,10 +104,11 @@ namespace Avalonia.Direct2D1 public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { - var nativeWindow = surfaces?.OfType().FirstOrDefault(); - + var nativeWindow = surfaces?.OfType().FirstOrDefault(); if (nativeWindow != null) { + if(nativeWindow.HandleDescriptor != "HWND") + throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor); return new HwndRenderTarget(nativeWindow); } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 317e7fc34f..49402d54b9 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -12,9 +13,9 @@ namespace Avalonia.Direct2D1 { class HwndRenderTarget : SwapChainRenderTarget { - private readonly INativeWindowPlatformSurface _window; + private readonly IPlatformHandle _window; - public HwndRenderTarget(INativeWindowPlatformSurface window) + public HwndRenderTarget(IPlatformHandle window) { _window = window; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 878be61664..19a7ca5e58 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -767,7 +767,7 @@ namespace Avalonia.Win32 public IEnumerable Surfaces => new object[] { - new NativeWindowPlatformSurface(_hwnd), _framebuffer + Handle, _framebuffer }; } } From e35677081ab38b0988c052df765ee3022c4aa587 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Oct 2016 15:05:42 +0300 Subject: [PATCH 28/96] Initial commit of GTK3 backend that shows something on screen --- Avalonia.sln | 43 ++++++ .../ControlCatalog.Desktop.csproj | 4 + samples/ControlCatalog.Desktop/Program.cs | 4 +- src/Gtk/Avalonia.Gtk3/.gitignore | 1 + src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 80 ++++++++++ src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 91 +++++++++++ src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs | 142 ++++++++++++++++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 87 +++++++++++ .../Avalonia.Gtk3/Interop/NativeException.cs | 24 +++ src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs | 104 +++++++++++++ src/Gtk/Avalonia.Gtk3/Interop/Signal.cs | 45 ++++++ src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs | 32 ++++ .../Avalonia.Gtk3/Properties/AssemblyInfo.cs | 30 ++++ src/Gtk/Avalonia.Gtk3/README.md | 9 ++ src/Gtk/Avalonia.Gtk3/Stubs.cs | 47 ++++++ src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 95 ++++++++++++ src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 48 ++++++ src/Gtk/Avalonia.Gtk3/project.json | 10 ++ 18 files changed, 895 insertions(+), 1 deletion(-) create mode 100644 src/Gtk/Avalonia.Gtk3/.gitignore create mode 100644 src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj create mode 100644 src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Native.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Signal.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs create mode 100644 src/Gtk/Avalonia.Gtk3/README.md create mode 100644 src/Gtk/Avalonia.Gtk3/Stubs.cs create mode 100644 src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs create mode 100644 src/Gtk/Avalonia.Gtk3/WindowImpl.cs create mode 100644 src/Gtk/Avalonia.Gtk3/project.json diff --git a/Avalonia.sln b/Avalonia.sln index a6fdcbd421..0eda7e5fc7 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -155,6 +155,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\Rende EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Gtk3", "src\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj", "{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 @@ -2329,6 +2331,46 @@ Global {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.ActiveCfg = Release|Any CPU {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Build.0 = Release|Any CPU {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Deploy.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Mono.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Mono.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|x86.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|x86.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Mono.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Mono.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|x86.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|x86.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Mono.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Mono.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|x86.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2382,5 +2424,6 @@ Global {BD7F352C-6DC1-4740-BAF2-2D34A038728C} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098} {29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} EndGlobalSection EndGlobal diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj index 53cb277233..6fec660e55 100644 --- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj +++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj @@ -72,6 +72,10 @@ {FB05AC90-89BA-4F2F-A924-F37875FB547C} Avalonia.Cairo + + {bb1f7bb5-6ad4-4776-94d9-c09d0a972658} + Avalonia.Gtk3 + {54F237D5-A70A-4752-9656-0C70B1A7B047} Avalonia.Gtk diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b67c5ea51d..f2634865b9 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using Avalonia; using Avalonia.Controls; +using Avalonia.Gtk3; using Avalonia.Logging.Serilog; using Avalonia.Platform; using Serilog; @@ -17,7 +18,8 @@ namespace ControlCatalog // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. AppBuilder.Configure() - .UsePlatformDetect() + .UseDirect2D1() + .UseGtk3() .Start(); } diff --git a/src/Gtk/Avalonia.Gtk3/.gitignore b/src/Gtk/Avalonia.Gtk3/.gitignore new file mode 100644 index 0000000000..ddb0c6248b --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/.gitignore @@ -0,0 +1 @@ +project.lock.json \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj new file mode 100644 index 0000000000..5e645fb542 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -0,0 +1,80 @@ + + + + + 14.0 + Debug + AnyCPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} + Library + Properties + Avalonia.Gtk3 + Avalonia.Gtk3 + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + v5.0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {B09B78D8-9B26-48B0-9149-D64A2F120F3F} + Avalonia.Base + + + {D2221C82-4A25-4583-9B43-D791E3F6820C} + Avalonia.Controls + + + {62024B2D-53EB-4638-B26B-85EEAA54866E} + Avalonia.Input + + + {EB582467-6ABB-43A1-B052-E981BA910E3A} + Avalonia.SceneGraph + + + + + \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs new file mode 100644 index 0000000000..164e987802 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Gtk3.Interop; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface + { + internal static readonly Gtk3Platform Instance = new Gtk3Platform(); + internal static readonly MouseDevice Mouse = new MouseDevice(); + internal static readonly KeyboardDevice Keyboard = new KeyboardDevice(); + internal static IntPtr App { get; set; } + public static void Initialize() + { + Resolver.Resolve(); + Native.GtkInit(0, IntPtr.Zero); + App = Native.GtkApplicationNew("avalonia.app." + Guid.NewGuid(), 0); + //Mark current thread as UI thread + s_tlsMarker = true; + + AvaloniaLocator.CurrentMutable.Bind().ToConstant(Instance) + .Bind().ToSingleton() + .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToConstant(Keyboard) + .Bind().ToConstant(Mouse) + .Bind().ToConstant(Instance) + .Bind().ToConstant(Instance) + .Bind().ToSingleton() + .Bind().ToConstant(new PlatformIconLoaderStub()); + + } + + public IWindowImpl CreateWindow() => new WindowImpl(); + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + throw new NotImplementedException(); + } + + public IPopupImpl CreatePopup() + { + throw new NotImplementedException(); + } + + + + public Size DoubleClickSize => new Size(4, 4); + + public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(100); //STUB + public double RenderScalingFactor { get; } = 1; + public double LayoutScalingFactor { get; } = 1; + + public void RunLoop(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + Native.GtkMainIteration(); + } + + public IDisposable StartTimer(TimeSpan interval, Action tick) + { + return null; + } + + public void Signal() + { + } + public event Action Signaled; + + + [ThreadStatic] + private static bool s_tlsMarker; + + public bool CurrentThreadIsLoopThread => s_tlsMarker; + + } + + public static class Gtk3AppBuilderExtensions + { + public static T UseGtk3(this AppBuilderBase builder) where T : AppBuilderBase, new() + => builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs new file mode 100644 index 0000000000..93ba7cc195 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs @@ -0,0 +1,142 @@ +using System; +using System.Runtime.InteropServices; + +/* + * Source code imported from https://github.com/kekekeks/evhttp-sharp + * Source is provided under MIT license for Avalonia project and derived works + */ + + +namespace Avalonia.Gtk3.Interop +{ + internal interface IDynLoader + { + IntPtr LoadLibrary(string basePath, string dll); + IntPtr GetProcAddress(IntPtr dll, string proc); + + } + + class UnixLoader : IDynLoader + { + // ReSharper disable InconsistentNaming + static class LinuxImports + { + + [DllImport("libdl.so.2")] + private static extern IntPtr dlopen(string path, int flags); + + [DllImport("libdl.so.2")] + private static extern IntPtr dlsym(IntPtr handle, string symbol); + + [DllImport("libdl.so.2")] + private static extern IntPtr dlerror(); + + public static void Init() + { + DlOpen = dlopen; + DlSym = dlsym; + DlError = dlerror; + Postfix = ".so.0"; + } + } + static class OsXImports + { + + + [DllImport("/usr/lib/libSystem.dylib")] + private static extern IntPtr dlopen(string path, int flags); + + [DllImport("/usr/lib/libSystem.dylib")] + private static extern IntPtr dlsym(IntPtr handle, string symbol); + + [DllImport("/usr/lib/libSystem.dylib")] + private static extern IntPtr dlerror(); + + public static void Init() + { + DlOpen = dlopen; + DlSym = dlsym; + DlError = dlerror; + Postfix = ".dylib"; //TODO + } + + } + + + [DllImport("libc")] + static extern int uname(IntPtr buf); + + + + static UnixLoader() + { + var buffer = Marshal.AllocHGlobal(0x1000); + uname(buffer); + var unixName = Marshal.PtrToStringAnsi(buffer); + Marshal.FreeHGlobal(buffer); + if(unixName == "Darwin") + OsXImports.Init(); + else + LinuxImports.Init(); + } + + private static Func DlOpen; + private static Func DlSym; + private static Func DlError; + private static string Postfix; + // ReSharper restore InconsistentNaming + + static string DlErrorString() + { + + return Marshal.PtrToStringAnsi(DlError()); + } + + public IntPtr LoadLibrary(string basePath, string dll) + { + dll += Postfix; + if (basePath != null) + dll = System.IO.Path.Combine(basePath, dll); + var handle = DlOpen(dll, 1); + if (handle == IntPtr.Zero) + throw new NativeException(DlErrorString()); + return handle; + } + + public IntPtr GetProcAddress(IntPtr dll, string proc) + { + var ptr = DlSym(dll, proc); + if (ptr == IntPtr.Zero) + throw new NativeException(DlErrorString()); + return ptr; + } + } + + internal class Win32Loader : IDynLoader + { + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr LoadLibrary(string lpszLib); + + IntPtr IDynLoader.LoadLibrary(string basePath, string dll) + { + dll += "-0.dll"; + if (basePath != null) + dll = System.IO.Path.Combine(basePath, dll); + var handle = LoadLibrary(dll); + if (handle == IntPtr.Zero) + throw new NativeException("Error " + Marshal.GetLastWin32Error()); + return handle; + } + + IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc) + { + var ptr = GetProcAddress(dll, proc); + if (ptr == IntPtr.Zero) + throw new NativeException("Error " + Marshal.GetLastWin32Error()); + return ptr; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs new file mode 100644 index 0000000000..a07d84c710 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + static class Native + { + public static class D + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_application_new([MarshalAs(UnmanagedType.AnsiBStr)] string appId, int flags); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_main_iteration(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_window_new(GtkWindowType windowType); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_init(int argc, IntPtr argv); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_present(IntPtr gtkWindow); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_widget_hide(IntPtr gtkWidget); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] //No manual import + public delegate IntPtr gdk_get_native_handle(IntPtr gdkWindow); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_widget_get_window(IntPtr gtkWidget); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_widget_set_double_buffered(IntPtr gtkWidget, bool value); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_widget_realize(IntPtr gtkWidget); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_set_title(IntPtr gtkWindow, Utf8Buffer title); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_set_decorated(IntPtr gtkWindow, bool decorated); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_get_size(IntPtr gtkWindow, out int width, out int height); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] + public delegate ulong g_signal_connect_object(IntPtr instance, [MarshalAs(UnmanagedType.AnsiBStr)]string signal, IntPtr handler, IntPtr userData, int flags); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] + public delegate ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool signal_widget_draw(IntPtr gtkWidget, IntPtr cairoContext, IntPtr userData); + } + + + public static D.gtk_window_set_decorated GtkWindowSetDecorated; + public static D.gtk_window_set_title GtkWindowSetTitle; + public static D.gtk_application_new GtkApplicationNew; + public static D.gtk_main_iteration GtkMainIteration; + public static D.gtk_window_new GtkWindowNew; + public static D.gtk_init GtkInit; + public static D.gtk_window_present GtkWindowPresent; + public static D.gtk_widget_hide GtkWidgetHide; + public static D.gdk_get_native_handle GetNativeGdkWindowHandle; + public static D.gtk_widget_get_window GtkWidgetGetWindow; + public static D.gtk_widget_realize GtkWidgetRealize; + public static D.gtk_window_get_size GtkWindowGetSize; + public static D.g_signal_connect_object GSignalConnectObject; + public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; + public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered; + + + + } + + public enum GtkWindowType + { + TopLevel, + Popup + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs b/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs new file mode 100644 index 0000000000..43659f8f83 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + public class NativeException : Exception + { + public NativeException() + { + } + + public NativeException(string message) : base(message) + { + } + + public NativeException(string message, Exception inner) : base(message, inner) + { + } + + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs new file mode 100644 index 0000000000..2410af7591 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + internal class GtkImportAttribute : Attribute + { + public GtkDll Dll { get; set; } + public string Name { get; set; } + public bool Optional { get; set; } + + public GtkImportAttribute(GtkDll dll, string name = null, bool optional = false) + { + Dll = dll; + Name = name; + Optional = optional; + } + } + + internal enum GtkDll + { + Gdk, + Gtk, + Glib, + Gio, + Gobject + } + + static class Resolver + { + [DllImport("kernel32.dll")] + static extern int GetVersion(); + + static bool IsWin32() + { + try + { + GetVersion(); + return true; + } + catch + { + return false; + } + } + + + + public static void Resolve(string basePath = null) + { + var loader = IsWin32() ? (IDynLoader)new Win32Loader() : new UnixLoader(); + + + var gdk = loader.LoadLibrary(basePath, "libgdk-3"); + var gtk = loader.LoadLibrary(basePath, "libgtk-3"); + var gio = loader.LoadLibrary(basePath, "libgio-2.0"); + var glib = loader.LoadLibrary(basePath, "libglib-2.0"); + var gobject = loader.LoadLibrary(basePath, "libgobject-2.0"); + foreach (var fieldInfo in typeof(Native).GetTypeInfo().DeclaredFields) + { + var import = fieldInfo.FieldType.GetTypeInfo().GetCustomAttributes(typeof(GtkImportAttribute), true).Cast().FirstOrDefault(); + if(import == null) + continue; + IntPtr lib; + if (import.Dll == GtkDll.Gtk) + lib = gtk; + else if (import.Dll == GtkDll.Gdk) + lib = gdk; + else if (import.Dll == GtkDll.Gio) + lib = gio; + else if (import.Dll == GtkDll.Glib) + lib = glib; + else if (import.Dll == GtkDll.Gobject) + lib = gobject; + else + throw new ArgumentException("Invalid GtkImportAttribute for " + fieldInfo.FieldType); + + var funcPtr = loader.GetProcAddress(lib, import.Name ?? fieldInfo.FieldType.Name); + fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType)); + } + + var nativeHandleNames = new[] {"gdk_x11_window_get_xid", "gdk_win32_window_get_handle"}; + foreach (var name in nativeHandleNames) + { + try + { + Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal + .GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle)); + } + catch { } + } + if (Native.GetNativeGdkWindowHandle == null) + throw new Exception($"Unable to locate any of [{string.Join(", ", nativeHandleNames)}] in libgdk"); + + } + + + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs new file mode 100644 index 0000000000..5896c547f2 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + public class Signal + { + class ConnectedSignal : IDisposable + { + private readonly IntPtr _instance; + private GCHandle _handle; + private readonly ulong _id; + + public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id) + { + _instance = instance; + _handle = handle; + _id = id; + } + + public void Dispose() + { + if (_handle.IsAllocated) + { + Native.GSignalHandlerDisconnect(_instance, _id); + _handle.Free(); + } + } + } + + public static IDisposable Connect(IntPtr obj, string name, T handler) + { + var handle = GCHandle.Alloc(handler); + var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler); + var id = Native.GSignalConnectObject(obj, name, ptr, IntPtr.Zero, 0); + if (id == 0) + throw new ArgumentException("Unable to connect to signal " + name); + return new ConnectedSignal(obj, handle, id); + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs b/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs new file mode 100644 index 0000000000..7a9868857f --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Avalonia.Gtk3.Interop +{ + class Utf8Buffer : SafeHandle + { + private GCHandle _gchandle; + private byte[] _data; + + public Utf8Buffer(string s) : base(IntPtr.Zero, true) + { + _data = Encoding.UTF8.GetBytes(s); + _gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned); + handle = _gchandle.AddrOfPinnedObject(); + } + + protected override bool ReleaseHandle() + { + if (handle != IntPtr.Zero) + { + handle = IntPtr.Zero; + _data = null; + _gchandle.Free(); + } + return true; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..72e6388531 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Avalonia.Gtk3")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Avalonia.Gtk3")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Gtk/Avalonia.Gtk3/README.md b/src/Gtk/Avalonia.Gtk3/README.md new file mode 100644 index 0000000000..7f117d24b3 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/README.md @@ -0,0 +1,9 @@ +P/Invoke based GTK3 backend +=========================== + +Code is EXPERIMENTAL at this point. It also needs Direct2D/Skia for rendering. + +Windows GTK3 binaries aren't included in the repo, you need to download them from http://www.tarnyko.net/repo/gtk3_build_system/gtk+-bundle_3.4.2-20130513_win32.zip +Then you need to extract them somewhere and add `bin` directory to PATH. Support for specifying exact path to binaries will be implemented later. + +On Linux it should work out of the box with system-provided GTK3. On OSX you should be able to wire GTK3 using DYLD_LIBRARY_PATH environment variable. \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Stubs.cs b/src/Gtk/Avalonia.Gtk3/Stubs.cs new file mode 100644 index 0000000000..76333aba60 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Stubs.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; + +//TODO: This file should be empty once everything is implemented + +namespace Avalonia.Gtk3 +{ + class ClipboardStub : IClipboard + { + public Task GetTextAsync() => Task.FromResult(""); + + public Task SetTextAsync(string text) => Task.FromResult(0); + + public Task ClearAsync() => Task.FromResult(0); + } + + class CursorFactoryStub : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); + } + + class SystemDialogStub : ISystemDialogImpl + { + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => Task.FromResult(new string[0]); + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + => Task.FromResult((string) null); + } + + class PlatformIconLoaderStub : IPlatformIconLoader + { + public IWindowIconImpl LoadIcon(string fileName) => null; + + public IWindowIconImpl LoadIcon(Stream stream) => null; + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => null; + } +} diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs new file mode 100644 index 0000000000..2f6181c893 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -0,0 +1,95 @@ +using System; +using Avalonia.Controls; +using Avalonia.Gtk3.Interop; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + abstract class TopLevelImpl : ITopLevelImpl, IPlatformHandle + { + protected readonly IntPtr _gtkWidget; + private IInputRoot _inputRoot; + + public TopLevelImpl(IntPtr gtkWidget) + { + _gtkWidget = gtkWidget; + Native.GtkWidgetRealize(gtkWidget); + Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); + Signal.Connect(_gtkWidget, "draw", OnDraw); + } + + private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) + { + Paint?.Invoke(new Rect(ClientSize)); + return true; + } + + public void Dispose() + { + //STUB + } + + public abstract Size ClientSize { get; set; } + + public Size MaxClientSize => new Size(1024, 768); //STUB + public double Scaling => 1; //TODO: Implement scaling + public IPlatformHandle Handle => this; + + string IPlatformHandle.HandleDescriptor => "HWND"; + + public Action Activated { get; set; } + public Action Closed { get; set; } + public Action Deactivated { get; set; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action PositionChanged { get; set; } + public void Activate() + { + throw new NotImplementedException(); + } + + public void Invalidate(Rect rect) + { + throw new NotImplementedException(); + } + + public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; + + public Point PointToClient(Point point) + { + throw new NotImplementedException(); + } + + public Point PointToScreen(Point point) + { + throw new NotImplementedException(); + } + + public void SetCursor(IPlatformHandle cursor) + { + //STUB + } + + public void Show() => Native.GtkWindowPresent(_gtkWidget); + + public void Hide() => Native.GtkWidgetHide(_gtkWidget); + + public void BeginMoveDrag() + { + //STUB + } + + public void BeginResizeDrag(WindowEdge edge) + { + //STUB + } + + public Point Position { get; set; } + + IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(_gtkWidget)); + } +} diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs new file mode 100644 index 0000000000..e3fd52f971 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -0,0 +1,48 @@ +using System; +using Avalonia.Controls; +using Avalonia.Gtk3.Interop; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + class WindowImpl : TopLevelImpl, IWindowImpl + { + public WindowState WindowState { get; set; } //STUB + public void SetTitle(string title) + { + using (var t = new Utf8Buffer(title)) + Native.GtkWindowSetTitle(_gtkWidget, t); + } + + public IDisposable ShowDialog() + { + return null; + //STUB + } + + public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(_gtkWidget, enabled); + + public void SetIcon(IWindowIconImpl icon) + { + //STUB + } + + public WindowImpl() : base(Native.GtkWindowNew(GtkWindowType.TopLevel)) + { + } + + public override Size ClientSize + { + get + { + int w, h; + Native.GtkWindowGetSize(_gtkWidget, out w, out h); + return new Size(w, h); + } + set + { + //STUB + } + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/project.json b/src/Gtk/Avalonia.Gtk3/project.json new file mode 100644 index 0000000000..7af946da1a --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/project.json @@ -0,0 +1,10 @@ +{ + "supports": {}, + "dependencies": { + "Microsoft.NETCore.Portable.Compatibility": "1.0.1", + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.1": {} + } +} \ No newline at end of file From a85e70a124d15c567bd0ebe1e2c2a95e063f16a1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 22:57:33 +0000 Subject: [PATCH 29/96] Remove CoverTaskbarWhenMaximized property from Window. --- src/Avalonia.Controls/Window.cs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 6755dee073..40c52a748d 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -64,13 +64,6 @@ namespace Avalonia.Controls public static readonly StyledProperty HasSystemDecorationsProperty = AvaloniaProperty.Register(nameof(HasSystemDecorations), true); - /// - /// Sets if the window should cover the taskbar when maximized. Only applies to Windows - /// with HasSystemDecorations = false. - /// - public static readonly StyledProperty CoverTaskbarOnMaximizeProperty = - AvaloniaProperty.Register(nameof(CoverTaskbarOnMaximize), true); - /// /// Defines the property. /// @@ -97,9 +90,6 @@ namespace Avalonia.Controls HasSystemDecorationsProperty.Changed.AddClassHandler( (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue)); - CoverTaskbarOnMaximizeProperty.Changed.AddClassHandler( - (s, e) => s.PlatformImpl.SetCoverTaskbarWhenMaximized((bool)e.NewValue)); - IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); } @@ -168,16 +158,6 @@ namespace Avalonia.Controls set { SetValue(HasSystemDecorationsProperty, value); } } - /// - /// Sets if the window should cover the taskbar when maximized. Only applies to Windows - /// with HasSystemDecorations = false. - /// - public bool CoverTaskbarOnMaximize - { - get { return GetValue(CoverTaskbarOnMaximizeProperty); } - set { SetValue(CoverTaskbarOnMaximizeProperty, value); } - } - /// /// Gets or sets the minimized/maximized state of the window. /// From c09755cfbff589dfbf74ee47bf2501988a24bf9f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 22:57:56 +0000 Subject: [PATCH 30/96] Remove SetCoverTaskbarWhenMaximized method from IWindowImpl --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index fd2feb539f..609e9834cb 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -35,11 +35,6 @@ namespace Avalonia.Platform /// void SetSystemDecorations(bool enabled); - /// - /// When system decorations are disabled sets if the maximized state covers the entire screen or just the working area. - /// - void SetCoverTaskbarWhenMaximized(bool enable); - /// /// Sets the icon of this window. /// From 1394995927dc87c017dc3160e5c586e26f10da12 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 23:02:01 +0000 Subject: [PATCH 31/96] Win32 WindowImpl now obeys taskbar when maximizing by default. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index db46538796..ed6d9c32d3 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -33,7 +33,6 @@ namespace Avalonia.Win32 private bool _trackingMouse; private bool _isActive; private bool _decorated = true; - private bool _coverTaskBarWhenMaximized = true; private double _scaling = 1; private WindowState _showWindowState; @@ -678,8 +677,6 @@ namespace Avalonia.Win32 { UnmanagedMethods.ShowWindowCommand command; - bool maximizeFillsDesktop = false; // otherwise we cover entire screen. - switch (state) { case WindowState.Minimized: @@ -687,11 +684,6 @@ namespace Avalonia.Win32 break; case WindowState.Maximized: command = ShowWindowCommand.Maximize; - - if (!_decorated && !_coverTaskBarWhenMaximized) - { - maximizeFillsDesktop = true; - } break; case WindowState.Normal: @@ -704,10 +696,7 @@ namespace Avalonia.Win32 UnmanagedMethods.ShowWindow(_hwnd, command); - if (maximizeFillsDesktop) - { - MaximizeWithoutCoveringTaskbar(); - } + MaximizeWithoutCoveringTaskbar(); if (!Design.IsDesignMode) { @@ -751,15 +740,5 @@ namespace Avalonia.Win32 return (int)(ptr.ToInt64() & 0xffffffff); } - - public void SetCoverTaskbarWhenMaximized(bool enable) - { - _coverTaskBarWhenMaximized = enable; - - if (_showWindowState == WindowState.Maximized) - { - ShowWindow(WindowState.Maximized); - } - } } } From 80e42b5882ca5b7be9062b3a871c314a878c4e1e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 23:04:06 +0000 Subject: [PATCH 32/96] Remove SetCoverTaskbarWhenMaximized from other backends. --- .../Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs | 5 ----- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 5 ----- src/iOS/Avalonia.iOS/AvaloniaView.cs | 5 ----- 3 files changed, 15 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs index 0e1540b5fd..f1c5d248ac 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -108,11 +108,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { } - public void SetCoverTaskbarWhenMaximized(bool enable) - { - //Not supported - } - public void Invalidate(Rect rect) { if (Holder?.Surface?.IsValid == true) base.Invalidate(); diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index 8641f2f431..a9ecfa4058 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -304,11 +304,6 @@ namespace Avalonia.Gtk args.RetVal = true; } - public void SetCoverTaskbarWhenMaximized(bool enable) - { - // No action neccesary on Gtk. - } - public void Dispose() { _window.Hide(); diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 67817ef62a..8b14d09573 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -170,11 +170,6 @@ namespace Avalonia.iOS //Not supported } - public void SetCoverTaskbarWhenMaximized(bool enable) - { - //Not supported - } - public override void TouchesEnded(NSSet touches, UIEvent evt) { var touch = touches.AnyObject as UITouch; From e486b27357e42b3a19954eee8c7d6c8e09866c9f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 23:16:59 +0000 Subject: [PATCH 33/96] Only call maximize without covering taskbar if we are in maximized state. --- 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 ed6d9c32d3..1ddd1ed10f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -696,7 +696,10 @@ namespace Avalonia.Win32 UnmanagedMethods.ShowWindow(_hwnd, command); - MaximizeWithoutCoveringTaskbar(); + if (state == WindowState.Maximized) + { + MaximizeWithoutCoveringTaskbar(); + } if (!Design.IsDesignMode) { From f8e392a520caaefc860eb8a05d474a52a1e8279b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 02:55:48 +0300 Subject: [PATCH 34/96] Updated to the recent code base, implemented threading, resize and invalidate handling --- Avalonia.sln | 3 + .../ControlCatalog.Desktop.csproj | 5 +- src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 3 +- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 21 +++++- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 64 +++++++++++++++++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 55 +++++++++++++++- src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs | 3 +- src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 62 +++++++++++++----- src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 16 +++-- 9 files changed, 203 insertions(+), 29 deletions(-) create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs diff --git a/Avalonia.sln b/Avalonia.sln index 0eda7e5fc7..46dafa9c76 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -130,6 +130,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog", "samples\ControlCatalog\ControlCatalog.csproj", "{D0A739B9-3C68-4BA6-A328-41606954B6BD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Desktop", "samples\ControlCatalog.Desktop\ControlCatalog.Desktop.csproj", "{2B888490-D14A-4BCA-AB4B-48676FA93C9B}" + ProjectSection(ProjectDependencies) = postProject + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{57E0455D-D565-44BB-B069-EE1AA20F8337}" EndProject diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj index 6fec660e55..fb2eba3c2f 100644 --- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj +++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj @@ -5,7 +5,7 @@ Debug AnyCPU {2B888490-D14A-4BCA-AB4B-48676FA93C9B} - WinExe + Exe Properties ControlCatalog.Desktop ControlCatalog.Desktop @@ -33,6 +33,9 @@ prompt 4 + + + ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index 5e645fb542..df5a712c18 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -41,6 +41,7 @@ + @@ -64,7 +65,7 @@ {62024B2D-53EB-4638-B26B-85EEAA54866E} Avalonia.Input - + {EB582467-6ABB-43A1-B052-E981BA910E3A} Avalonia.SceneGraph diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 164e987802..faf1983250 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -10,6 +10,7 @@ using Avalonia.Gtk3.Interop; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; +using Avalonia.Rendering; namespace Avalonia.Gtk3 { @@ -35,6 +36,7 @@ namespace Avalonia.Gtk3 .Bind().ToConstant(Instance) .Bind().ToConstant(Instance) .Bind().ToSingleton() + .Bind().ToConstant(new DefaultRenderLoop(60)) .Bind().ToConstant(new PlatformIconLoaderStub()); } @@ -67,11 +69,28 @@ namespace Avalonia.Gtk3 public IDisposable StartTimer(TimeSpan interval, Action tick) { - return null; + return GlibTimeout.StarTimer((uint) interval.TotalMilliseconds, tick); } + private bool _signaled = false; + object _lock = new object(); + public void Signal() { + lock(_lock) + if (!_signaled) + { + _signaled = true; + GlibTimeout.Add(0, () => + { + lock (_lock) + { + _signaled = false; + } + Signaled?.Invoke(); + return false; + }); + } } public event Action Signaled; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs new file mode 100644 index 0000000000..9971d8881d --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + static class GlibTimeout + { + static bool Handler(IntPtr data) + { + var handle = GCHandle.FromIntPtr(data); + var cb = (Func) handle.Target; + if (!cb()) + { + handle.Free(); + return false; + } + return true; + } + + private static readonly GCHandle PinnedHandle; + private static readonly Native.D.timeout_callback PinnedHandler; + static GlibTimeout() + { + PinnedHandler = Handler; + + } + + + public static void Add(uint interval, Func callback) + { + var handle = GCHandle.Alloc(callback); + Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle)); + } + + class Timer : IDisposable + { + public bool Stopped; + public void Dispose() + { + + Stopped = true; + } + } + + public static IDisposable StarTimer(uint interval, Action tick) + { + var timer = new Timer (); + GlibTimeout.Add(interval, + () => + { + if (timer.Stopped) + return false; + tick(); + return !timer.Stopped; + }); + + return timer; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index a07d84c710..2a5abfe603 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -36,7 +36,19 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate IntPtr gtk_widget_get_window(IntPtr gtkWidget); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_widget_get_screen(IntPtr gtkWidget); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate IntPtr gtk_widget_set_double_buffered(IntPtr gtkWidget, bool value); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_widget_set_events(IntPtr gtkWidget, uint flags); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate int gdk_screen_get_height(IntPtr screen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate int gdk_screen_get_width(IntPtr screen); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_realize(IntPtr gtkWidget); @@ -50,12 +62,29 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_get_size(IntPtr gtkWindow, out int width, out int height); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_resize(IntPtr gtkWindow, int width, int height); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_widget_queue_draw_area(IntPtr gtkWindow, int x, int y, int width, int height); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate void gdk_window_invalidate_rect(IntPtr window, ref GdkRectangle rect, bool invalidate_children); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_connect_object(IntPtr instance, [MarshalAs(UnmanagedType.AnsiBStr)]string signal, IntPtr handler, IntPtr userData, int flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] + public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool signal_widget_draw(IntPtr gtkWidget, IntPtr cairoContext, IntPtr userData); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool signal_onevent(IntPtr gtkWidget, IntPtr ev, IntPtr userData); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool timeout_callback(IntPtr data); } @@ -69,12 +98,19 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_widget_hide GtkWidgetHide; public static D.gdk_get_native_handle GetNativeGdkWindowHandle; public static D.gtk_widget_get_window GtkWidgetGetWindow; + public static D.gtk_widget_get_screen GtkWidgetGetScreen; public static D.gtk_widget_realize GtkWidgetRealize; public static D.gtk_window_get_size GtkWindowGetSize; + public static D.gtk_window_resize GtkWindowResize; public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; + public static D.g_timeout_add GTimeoutAdd; public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered; - + public static D.gtk_widget_set_events GtkWidgetSetEvents; + public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect; + public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea; + public static D.gdk_screen_get_height GdkScreenGetHeight; + public static D.gdk_screen_get_width GdkScreenGetWidth; } @@ -84,4 +120,21 @@ namespace Avalonia.Gtk3.Interop TopLevel, Popup } + + [StructLayout(LayoutKind.Sequential)] + public struct GdkRectangle + { + public int X, Y, Width, Height; + + public static GdkRectangle FromRect(Rect rect) + { + return new GdkRectangle + { + X = (int) rect.X, + Y = (int) rect.Y, + Width = (int) rect.Width, + Height = (int) rect.Height + }; + } + } } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs index 2410af7591..15a2f90ade 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -84,13 +84,14 @@ namespace Avalonia.Gtk3.Interop fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType)); } - var nativeHandleNames = new[] {"gdk_x11_window_get_xid", "gdk_win32_window_get_handle"}; + var nativeHandleNames = new[] { "gdk_win32_window_get_handle", "gdk_x11_window_get_xid", "gdk_quartz_window_get_nswindow" }; foreach (var name in nativeHandleNames) { try { Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal .GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle)); + break; } catch { } } diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index 2f6181c893..672e98fe18 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using Avalonia.Controls; using Avalonia.Gtk3.Interop; using Avalonia.Input; @@ -9,44 +11,69 @@ namespace Avalonia.Gtk3 { abstract class TopLevelImpl : ITopLevelImpl, IPlatformHandle { - protected readonly IntPtr _gtkWidget; + protected readonly IntPtr GtkWidget; private IInputRoot _inputRoot; + protected readonly List _disposables = new List(); public TopLevelImpl(IntPtr gtkWidget) { - _gtkWidget = gtkWidget; + GtkWidget = gtkWidget; + Native.GtkWidgetSetEvents(gtkWidget, uint.MaxValue); Native.GtkWidgetRealize(gtkWidget); - Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); - Signal.Connect(_gtkWidget, "draw", OnDraw); + Connect("draw", OnDraw); + Connect("configure-event", OnConfigured); } + private bool OnConfigured(IntPtr gtkwidget, IntPtr ev, IntPtr userdata) + { + Debug.WriteLine("Configured"); + Resized?.Invoke(ClientSize); + return false; + } + + + void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); + private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) { + Debug.WriteLine("Draw"); Paint?.Invoke(new Rect(ClientSize)); return true; } public void Dispose() { - //STUB + foreach(var d in _disposables) + d.Dispose(); + _disposables.Clear(); + //TODO } public abstract Size ClientSize { get; set; } - public Size MaxClientSize => new Size(1024, 768); //STUB + public Size MaxClientSize + { + get + { + var s = Native.GtkWidgetGetScreen(GtkWidget); + return new Size(Native.GdkScreenGetWidth(s), Native.GdkScreenGetHeight(s)); + } + } + + public double Scaling => 1; //TODO: Implement scaling public IPlatformHandle Handle => this; string IPlatformHandle.HandleDescriptor => "HWND"; - public Action Activated { get; set; } - public Action Closed { get; set; } - public Action Deactivated { get; set; } - public Action Input { get; set; } + public Action Activated { get; set; } //TODO + public Action Closed { get; set; } //TODO + public Action Deactivated { get; set; } //TODO + public Action Input { get; set; } //TODO public Action Paint { get; set; } - public Action Resized { get; set; } - public Action ScalingChanged { get; set; } - public Action PositionChanged { get; set; } + public Action Resized { get; set; } //TODO + public Action ScalingChanged { get; set; } //TODO + public Action PositionChanged { get; set; } //TODO public void Activate() { throw new NotImplementedException(); @@ -54,7 +81,7 @@ namespace Avalonia.Gtk3 public void Invalidate(Rect rect) { - throw new NotImplementedException(); + Native.GtkWidgetQueueDrawArea(GtkWidget, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height); } public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; @@ -74,9 +101,9 @@ namespace Avalonia.Gtk3 //STUB } - public void Show() => Native.GtkWindowPresent(_gtkWidget); + public void Show() => Native.GtkWindowPresent(GtkWidget); - public void Hide() => Native.GtkWidgetHide(_gtkWidget); + public void Hide() => Native.GtkWidgetHide(GtkWidget); public void BeginMoveDrag() { @@ -90,6 +117,7 @@ namespace Avalonia.Gtk3 public Point Position { get; set; } - IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(_gtkWidget)); + IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); + public IEnumerable Surfaces => new object[] {Handle}; } } diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index e3fd52f971..2cd1c5fd6a 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -11,7 +11,7 @@ namespace Avalonia.Gtk3 public void SetTitle(string title) { using (var t = new Utf8Buffer(title)) - Native.GtkWindowSetTitle(_gtkWidget, t); + Native.GtkWindowSetTitle(GtkWidget, t); } public IDisposable ShowDialog() @@ -20,7 +20,7 @@ namespace Avalonia.Gtk3 //STUB } - public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(_gtkWidget, enabled); + public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(GtkWidget, enabled); public void SetIcon(IWindowIconImpl icon) { @@ -36,13 +36,15 @@ namespace Avalonia.Gtk3 get { int w, h; - Native.GtkWindowGetSize(_gtkWidget, out w, out h); + Native.GtkWindowGetSize(GtkWidget, out w, out h); return new Size(w, h); } - set - { - //STUB - } + set { Native.GtkWindowResize(GtkWidget, (int) value.Width, (int) value.Height); } + } + + public void SetCoverTaskbarWhenMaximized(bool enable) + { + //Why do we even have that? } } } From 04359041d7aef90f20ef87dc7f5a6acaa400ad9d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 03:42:54 +0300 Subject: [PATCH 35/96] Implemented click support --- src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 6 ++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 96 +++++++++++++++++++++ src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 97 +++++++++++++++++++--- src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 11 --- 4 files changed, 189 insertions(+), 21 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index df5a712c18..ab52c8f9c4 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -25,6 +25,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -33,6 +34,7 @@ TRACE prompt 4 + true @@ -65,6 +67,10 @@ {62024B2D-53EB-4638-B26B-85EEAA54866E} Avalonia.Input + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Avalonia.Interactivity + {EB582467-6ABB-43A1-B052-E981BA910E3A} Avalonia.SceneGraph diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 2a5abfe603..3ee8871b40 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -4,6 +4,12 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using gint8 = System.Byte; +using gint32 = System.Int32; +using gint = System.Int32; +using guint32 = System.UInt32; +using guint = System.UInt32; +using gdouble = System.Double; namespace Avalonia.Gtk3.Interop { @@ -48,6 +54,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] public delegate int gdk_screen_get_width(IntPtr screen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate int gdk_window_get_origin(IntPtr gdkWindow, out int x, out int y); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] @@ -64,6 +73,10 @@ namespace Avalonia.Gtk3.Interop public delegate void gtk_window_get_size(IntPtr gtkWindow, out int width, out int height); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_resize(IntPtr gtkWindow, int width, int height); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_get_position(IntPtr gtkWindow, out int x, out int y); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_move(IntPtr gtkWindow, int x, int y); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_queue_draw_area(IntPtr gtkWindow, int x, int y, int width, int height); @@ -102,6 +115,8 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_widget_realize GtkWidgetRealize; public static D.gtk_window_get_size GtkWindowGetSize; public static D.gtk_window_resize GtkWindowResize; + public static D.gtk_window_get_position GtkWindowGetPosition; + public static D.gtk_window_move GtkWindowMove; public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_timeout_add GTimeoutAdd; @@ -111,6 +126,7 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea; public static D.gdk_screen_get_height GdkScreenGetHeight; public static D.gdk_screen_get_width GdkScreenGetWidth; + public static D.gdk_window_get_origin GdkWindowGetOrigin; } @@ -137,4 +153,84 @@ namespace Avalonia.Gtk3.Interop }; } } + + enum GdkEventType + { + Nothing = -1, + Delete = 0, + Destroy = 1, + Expose = 2, + MotionNotify = 3, + ButtonPress = 4, + TwoButtonPress = 5, + ThreeButtonPress = 6, + ButtonRelease = 7, + KeyPress = 8, + KeyRelease = 9, + EnterNotify = 10, + LeaveNotify = 11, + FocusChange = 12, + Configure = 13, + Map = 14, + Unmap = 15, + PropertyNotify = 16, + SelectionClear = 17, + SelectionRequest = 18, + SelectionNotify = 19, + ProximityIn = 20, + ProximityOut = 21, + DragEnter = 22, + DragLeave = 23, + DragMotion = 24, + DragStatus = 25, + DropStart = 26, + DropFinished = 27, + ClientEvent = 28, + VisibilityNotify = 29, + NoExpose = 30, + Scroll = 31, + WindowState = 32, + Setting = 33, + OwnerChange = 34, + GrabBroken = 35, + } + + public enum GdkModifierType + { + ShiftMask = 1, + LockMask = 2, + ControlMask = 4, + Mod1Mask = 8, + Mod2Mask = 16, + Mod3Mask = 32, + Mod4Mask = 64, + Mod5Mask = 128, + Button1Mask = 256, + Button2Mask = 512, + Button3Mask = 1024, + Button4Mask = 2048, + Button5Mask = 4096, + SuperMask = 67108864, + HyperMask = 134217728, + MetaMask = 268435456, + ReleaseMask = 1073741824, + ModifierMask = ReleaseMask | Button5Mask | Button4Mask | Button3Mask | Button2Mask | Button1Mask | Mod5Mask | Mod4Mask | Mod3Mask | Mod2Mask | Mod1Mask | ControlMask | LockMask | ShiftMask, + None = 0, + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct GdkEventButton + { + public GdkEventType type; + public IntPtr window; + public gint8 send_event; + public guint32 time; + public gdouble x; + public gdouble y; + public gdouble* axes; + public GdkModifierType state; + public guint button; + public IntPtr device; + public gdouble x_root, y_root; + }; } diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index 672e98fe18..c16415828f 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -22,21 +22,73 @@ namespace Avalonia.Gtk3 Native.GtkWidgetRealize(gtkWidget); Connect("draw", OnDraw); Connect("configure-event", OnConfigured); + Connect("button-press-event", OnButton); + Connect("button-release-event", OnButton); } + private Size _lastSize; + private Point _lastPosition; + private bool OnConfigured(IntPtr gtkwidget, IntPtr ev, IntPtr userdata) { - Debug.WriteLine("Configured"); - Resized?.Invoke(ClientSize); + var size = ClientSize; + if (_lastSize != size) + { + _lastSize = size; + Resized?.Invoke(size); + } + var pos = Position; + if (_lastPosition != pos) + { + _lastPosition = pos; + PositionChanged?.Invoke(pos); + } + return false; } + private static InputModifiers GetModifierKeys(GdkModifierType state) + { + var rv = InputModifiers.None; + if (state.HasFlag(GdkModifierType.ControlMask)) + rv |= InputModifiers.Control; + if (state.HasFlag(GdkModifierType.ShiftMask)) + rv |= InputModifiers.Shift; + if (state.HasFlag(GdkModifierType.Mod1Mask)) + rv |= InputModifiers.Control; + if (state.HasFlag(GdkModifierType.Button1Mask)) + rv |= InputModifiers.LeftMouseButton; + if (state.HasFlag(GdkModifierType.Button2Mask)) + rv |= InputModifiers.RightMouseButton; + if (state.HasFlag(GdkModifierType.Button3Mask)) + rv |= InputModifiers.MiddleMouseButton; + return rv; + } + + private unsafe bool OnButton(IntPtr w, IntPtr ev, IntPtr userdata) + { + var evnt = (GdkEventButton*)ev; + var e = new RawMouseEventArgs( + Gtk3Platform.Mouse, + evnt->time, + _inputRoot, + evnt->type == GdkEventType.ButtonRelease + ? evnt->button == 1 + ? RawMouseEventType.LeftButtonUp + : evnt->button == 3 ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp + : evnt->button == 1 + ? RawMouseEventType.LeftButtonDown + : evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown, + new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state)); + Input?.Invoke(e); + return false; + } + void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) { - Debug.WriteLine("Draw"); Paint?.Invoke(new Rect(ClientSize)); return true; } @@ -49,8 +101,6 @@ namespace Avalonia.Gtk3 //TODO } - public abstract Size ClientSize { get; set; } - public Size MaxClientSize { get @@ -71,9 +121,10 @@ namespace Avalonia.Gtk3 public Action Deactivated { get; set; } //TODO public Action Input { get; set; } //TODO public Action Paint { get; set; } - public Action Resized { get; set; } //TODO + public Action Resized { get; set; } public Action ScalingChanged { get; set; } //TODO - public Action PositionChanged { get; set; } //TODO + public Action PositionChanged { get; set; } + public void Activate() { throw new NotImplementedException(); @@ -88,12 +139,17 @@ namespace Avalonia.Gtk3 public Point PointToClient(Point point) { - throw new NotImplementedException(); + int x, y; + Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); + + return new Point(point.X - x, point.Y - y); } public Point PointToScreen(Point point) { - throw new NotImplementedException(); + int x, y; + Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); + return new Point(point.X + x, point.Y + y); } public void SetCursor(IPlatformHandle cursor) @@ -115,7 +171,28 @@ namespace Avalonia.Gtk3 //STUB } - public Point Position { get; set; } + + public Size ClientSize + { + get + { + int w, h; + Native.GtkWindowGetSize(GtkWidget, out w, out h); + return new Size(w, h); + } + set { Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); } + } + + public Point Position + { + get + { + int x, y; + Native.GtkWindowGetPosition(GtkWidget, out x, out y); + return new Point(x, y); + } + set { Native.GtkWindowMove(GtkWidget, (int)value.X, (int)value.Y); } + } IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); public IEnumerable Surfaces => new object[] {Handle}; diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index 2cd1c5fd6a..b8c561c56c 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -31,17 +31,6 @@ namespace Avalonia.Gtk3 { } - public override Size ClientSize - { - get - { - int w, h; - Native.GtkWindowGetSize(GtkWidget, out w, out h); - return new Size(w, h); - } - set { Native.GtkWindowResize(GtkWidget, (int) value.Width, (int) value.Height); } - } - public void SetCoverTaskbarWhenMaximized(bool enable) { //Why do we even have that? From f55a49729831db65472e26f31fbea0944ba0ae49 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 04:01:38 +0300 Subject: [PATCH 36/96] Implemented motion and scroll events --- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 48 +++++++++++++++++++++++-- src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 39 ++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 3ee8871b40..d9cf9fff05 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -5,8 +5,10 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using gint8 = System.Byte; +using gint16 = System.Int16; using gint32 = System.Int32; using gint = System.Int32; +using guint16 = System.UInt16; using guint32 = System.UInt32; using guint = System.UInt32; using gdouble = System.Double; @@ -195,7 +197,7 @@ namespace Avalonia.Gtk3.Interop GrabBroken = 35, } - public enum GdkModifierType + enum GdkModifierType { ShiftMask = 1, LockMask = 2, @@ -218,6 +220,15 @@ namespace Avalonia.Gtk3.Interop None = 0, } + enum GdkScrollDirection + { + Up, + Down, + Left, + Right, + Smooth + } + [StructLayout(LayoutKind.Sequential)] unsafe struct GdkEventButton { @@ -232,5 +243,38 @@ namespace Avalonia.Gtk3.Interop public guint button; public IntPtr device; public gdouble x_root, y_root; - }; + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct GdkEventMotion + { + public GdkEventType type; + public IntPtr window; + public gint8 send_event; + public guint32 time; + public gdouble x; + public gdouble y; + public gdouble* axes; + public GdkModifierType state; + public gint16 is_hint; + public IntPtr device; + public gdouble x_root, y_root; + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct GdkEventScroll + { + public GdkEventType type; + public IntPtr window; + public gint8 send_event; + public guint32 time; + public gdouble x; + public gdouble y; + public GdkModifierType state; + public GdkScrollDirection direction; + public IntPtr device; + public gdouble x_root, y_root; + public gdouble delta_x; + public gdouble delta_y; + } } diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index c16415828f..fc0893bdb8 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -24,6 +24,8 @@ namespace Avalonia.Gtk3 Connect("configure-event", OnConfigured); Connect("button-press-event", OnButton); Connect("button-release-event", OnButton); + Connect("motion-notify-event", OnMotion); + Connect("scroll-event", OnScroll); } private Size _lastSize; @@ -85,6 +87,43 @@ namespace Avalonia.Gtk3 return false; } + private unsafe bool OnMotion(IntPtr w, IntPtr ev, IntPtr userdata) + { + var evnt = (GdkEventMotion*)ev; + var position = new Point(evnt->x, evnt->y); + + + var e = new RawMouseEventArgs( + Gtk3Platform.Mouse, + evnt->time, + _inputRoot, + RawMouseEventType.Move, + position, GetModifierKeys(evnt->state)); + Input(e); + return false; + } + private unsafe bool OnScroll(IntPtr w, IntPtr ev, IntPtr userdata) + { + var evnt = (GdkEventScroll*)ev; + var delta = new Vector(); + var step = (double) 1; + if (evnt->direction == GdkScrollDirection.Down) + delta = new Vector(0, -step); + else if (evnt->direction == GdkScrollDirection.Up) + delta = new Vector(0, step); + else if (evnt->direction == GdkScrollDirection.Right) + delta = new Vector(-step, 0); + else if (evnt->direction == GdkScrollDirection.Left) + delta = new Vector(step, 0); + else if (evnt->direction == GdkScrollDirection.Smooth) + delta = new Vector(evnt->delta_x, evnt->delta_y); + + var e = new RawMouseWheelEventArgs(Gtk3Platform.Mouse, evnt->time, _inputRoot, + new Point(evnt->x, evnt->y), delta, GetModifierKeys(evnt->state)); + Input(e); + return false; + } + void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) From 5bb9e6913ed756fe91a3316a3479fe65d35a550d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 22:21:53 +0300 Subject: [PATCH 37/96] Implemented framebuffer and smarter dll search logic --- samples/ControlCatalog.Desktop/Program.cs | 2 +- src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 3 + src/Gtk/Avalonia.Gtk3/FramebufferManager.cs | 42 +++++++ src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 8 +- .../Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 71 +++++++++++ src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs | 33 ++--- .../ICustomGtk3NativeLibraryResolver.cs | 17 +++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 36 +++++- src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs | 113 +++++++++++++----- src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 8 +- 10 files changed, 272 insertions(+), 61 deletions(-) create mode 100644 src/Gtk/Avalonia.Gtk3/FramebufferManager.cs create mode 100644 src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index f2634865b9..4abdce2657 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -18,7 +18,7 @@ namespace ControlCatalog // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. AppBuilder.Configure() - .UseDirect2D1() + .UseSkia() .UseGtk3() .Start(); } diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index ab52c8f9c4..06fc40458e 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -41,13 +41,16 @@ + + + diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs new file mode 100644 index 0000000000..c2685e8791 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; + +namespace Avalonia.Gtk3 +{ + class FramebufferManager : IFramebufferPlatformSurface, IDisposable + { + private readonly TopLevelImpl _window; + private ImageSurfaceFramebuffer _fb; + public FramebufferManager(TopLevelImpl window) + { + _window = window; + } + + public void Dispose() + { + _fb?.Deallocate(); + } + + public ILockedFramebuffer Lock() + { + if(_window.CurrentCairoContext == IntPtr.Zero) + throw new InvalidOperationException("Window is not in drawing state"); + + var ctx = _window.CurrentCairoContext; + var width = (int) _window.ClientSize.Width; + var height = (int) _window.ClientSize.Height; + if (_fb == null || _fb.Width != width || + _fb.Height != height) + { + _fb?.Dispose(); + _fb = new ImageSurfaceFramebuffer(width, height); + } + _fb.Prepare(ctx); + return _fb; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index faf1983250..625cd7096a 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -104,7 +104,11 @@ namespace Avalonia.Gtk3 public static class Gtk3AppBuilderExtensions { - public static T UseGtk3(this AppBuilderBase builder) where T : AppBuilderBase, new() - => builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + public static T UseGtk3(this AppBuilderBase builder, ICustomGtk3NativeLibraryResolver resolver = null) + where T : AppBuilderBase, new() + { + Resolver.Custom = resolver; + return builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + } } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs new file mode 100644 index 0000000000..e955f82764 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Gtk3.Interop; +using Avalonia.Platform; + + +namespace Avalonia.Gtk3 +{ + class ImageSurfaceFramebuffer : ILockedFramebuffer + { + private IntPtr _context; + private IntPtr _surface; + + public ImageSurfaceFramebuffer(int width, int height) + { + _surface = Native.CairoImageSurfaceCreate(1, width, height); + Width = width; + Height = height; + Address = Native.CairoImageSurfaceGetData(_surface); + RowBytes = Native.CairoImageSurfaceGetStride(_surface); + Native.CairoSurfaceFlush(_surface); + } + + public void Prepare(IntPtr context) + { + _context = context; + } + + public void Deallocate() + { + Native.CairoSurfaceDestroy(_surface); + _surface = IntPtr.Zero; + } + + public void Dispose() + { + if(_context == IntPtr.Zero || _surface == IntPtr.Zero) + return; + Native.CairoSurfaceMarkDirty(_surface); + Native.CairoSetSourceSurface(_context, _surface, 0, 0); + Native.CairoPaint(_context); + _context = IntPtr.Zero; + + } + + public IntPtr Address { get; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + + //TODO: Proper DPI detect + public Size Dpi => new Size(96, 96); + public PixelFormat Format + { + get + { + if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == + OperatingSystemType.WinNT) + return PixelFormat.Bgra8888; + return PixelFormat.Rgba8888; + } + } + } +} + + + diff --git a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs index 93ba7cc195..afe526e2c3 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; /* @@ -11,7 +12,7 @@ namespace Avalonia.Gtk3.Interop { internal interface IDynLoader { - IntPtr LoadLibrary(string basePath, string dll); + IntPtr LoadLibrary(string dll); IntPtr GetProcAddress(IntPtr dll, string proc); } @@ -21,7 +22,6 @@ namespace Avalonia.Gtk3.Interop // ReSharper disable InconsistentNaming static class LinuxImports { - [DllImport("libdl.so.2")] private static extern IntPtr dlopen(string path, int flags); @@ -36,7 +36,6 @@ namespace Avalonia.Gtk3.Interop DlOpen = dlopen; DlSym = dlsym; DlError = dlerror; - Postfix = ".so.0"; } } static class OsXImports @@ -57,7 +56,6 @@ namespace Avalonia.Gtk3.Interop DlOpen = dlopen; DlSym = dlsym; DlError = dlerror; - Postfix = ".dylib"; //TODO } } @@ -66,8 +64,6 @@ namespace Avalonia.Gtk3.Interop [DllImport("libc")] static extern int uname(IntPtr buf); - - static UnixLoader() { var buffer = Marshal.AllocHGlobal(0x1000); @@ -83,20 +79,12 @@ namespace Avalonia.Gtk3.Interop private static Func DlOpen; private static Func DlSym; private static Func DlError; - private static string Postfix; // ReSharper restore InconsistentNaming - static string DlErrorString() - { - - return Marshal.PtrToStringAnsi(DlError()); - } + static string DlErrorString() => Marshal.PtrToStringAnsi(DlError()); - public IntPtr LoadLibrary(string basePath, string dll) + public IntPtr LoadLibrary(string dll) { - dll += Postfix; - if (basePath != null) - dll = System.IO.Path.Combine(basePath, dll); var handle = DlOpen(dll, 1); if (handle == IntPtr.Zero) throw new NativeException(DlErrorString()); @@ -120,15 +108,14 @@ namespace Avalonia.Gtk3.Interop [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr LoadLibrary(string lpszLib); - IntPtr IDynLoader.LoadLibrary(string basePath, string dll) + IntPtr IDynLoader.LoadLibrary(string dll) { - dll += "-0.dll"; - if (basePath != null) - dll = System.IO.Path.Combine(basePath, dll); var handle = LoadLibrary(dll); - if (handle == IntPtr.Zero) - throw new NativeException("Error " + Marshal.GetLastWin32Error()); - return handle; + if (handle != IntPtr.Zero) + return handle; + var err = Marshal.GetLastWin32Error(); + + throw new NativeException("Error loading " + dll + " error " + err); } IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs new file mode 100644 index 0000000000..2f88b09896 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Gtk3.Interop; + +namespace Avalonia.Gtk3 +{ + public interface ICustomGtk3NativeLibraryResolver + { + string GetName(GtkDll dll); + string BasePath { get; } + bool TrySystemFirst { get; } + string Lookup(GtkDll dll); + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index d9cf9fff05..4ed7474fb1 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -80,6 +80,33 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_move(IntPtr gtkWindow, int x, int y); + + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate IntPtr cairo_image_surface_create(int format, int width, int height); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate IntPtr cairo_image_surface_get_data(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate int cairo_image_surface_get_stride(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_mark_dirty(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_flush(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_destroy(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_set_source_surface(IntPtr cr, IntPtr surface, double x, double y); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_paint(IntPtr context); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_queue_draw_area(IntPtr gtkWindow, int x, int y, int width, int height); @@ -130,7 +157,14 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_screen_get_width GdkScreenGetWidth; public static D.gdk_window_get_origin GdkWindowGetOrigin; - + public static D.cairo_image_surface_create CairoImageSurfaceCreate; + public static D.cairo_image_surface_get_data CairoImageSurfaceGetData; + public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride; + public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty; + public static D.cairo_surface_flush CairoSurfaceFlush; + public static D.cairo_surface_destroy CairoSurfaceDestroy; + public static D.cairo_set_source_surface CairoSetSourceSurface; + public static D.cairo_paint CairoPaint; } public enum GtkWindowType diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs index 15a2f90ade..a965453841 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Avalonia.Platform; namespace Avalonia.Gtk3.Interop { @@ -22,64 +24,108 @@ namespace Avalonia.Gtk3.Interop } } - internal enum GtkDll + public enum GtkDll { Gdk, Gtk, Glib, Gio, - Gobject + Gobject, + Cairo } static class Resolver { - [DllImport("kernel32.dll")] - static extern int GetVersion(); + private static Lazy Platform = + new Lazy( + () => AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem); - static bool IsWin32() + public static ICustomGtk3NativeLibraryResolver Custom { get; set; } + + + static string FormatName(string name, int version = 0) + { + if (Platform.Value == OperatingSystemType.WinNT) + return "lib" + name + "-" + version + ".dll"; + if (Platform.Value == OperatingSystemType.Linux) + return "lib" + name + ".so" + "." + version; + if (Platform.Value == OperatingSystemType.OSX) + return "lib" + name + "." + version + ".dylib"; + throw new Exception("Unknown platform, use custom name resolver"); + } + + + + static string GetDllName(GtkDll dll) { + var name = Custom?.GetName(dll); + if (name != null) + return name; + + switch (dll) + { + case GtkDll.Cairo: + return FormatName("cairo", 2); + case GtkDll.Gdk: + return FormatName("gdk-3"); + case GtkDll.Glib: + return FormatName("glib-2.0"); + case GtkDll.Gio: + return FormatName("gio-2.0"); + case GtkDll.Gtk: + return FormatName("gtk-3"); + case GtkDll.Gobject: + return FormatName("gobject-2.0"); + default: + throw new ArgumentException("Unknown lib: " + dll); + } + } + + static IntPtr LoadDll(IDynLoader loader, GtkDll dll) + { + + var exceptions = new List(); + + var name = GetDllName(dll); + if (Custom?.TrySystemFirst != false) + { + try + { + return loader.LoadLibrary(name); + } + catch (Exception e) + { + exceptions.Add(e); + } + } + var path = Custom?.Lookup(dll); + if (path == null && Custom?.BasePath != null) + path = Path.Combine(Custom.BasePath, name); + try { - GetVersion(); - return true; + return loader.LoadLibrary(path); } - catch + catch (Exception e) { - return false; + exceptions.Add(e); } + throw new AggregateException("Unable to load " + dll, exceptions); } - - public static void Resolve(string basePath = null) { - var loader = IsWin32() ? (IDynLoader)new Win32Loader() : new UnixLoader(); - + var loader = Platform.Value == OperatingSystemType.WinNT ? (IDynLoader)new Win32Loader() : new UnixLoader(); - var gdk = loader.LoadLibrary(basePath, "libgdk-3"); - var gtk = loader.LoadLibrary(basePath, "libgtk-3"); - var gio = loader.LoadLibrary(basePath, "libgio-2.0"); - var glib = loader.LoadLibrary(basePath, "libglib-2.0"); - var gobject = loader.LoadLibrary(basePath, "libgobject-2.0"); + var dlls = Enum.GetValues(typeof(GtkDll)).Cast().ToDictionary(x => x, x => LoadDll(loader, x)); + foreach (var fieldInfo in typeof(Native).GetTypeInfo().DeclaredFields) { var import = fieldInfo.FieldType.GetTypeInfo().GetCustomAttributes(typeof(GtkImportAttribute), true).Cast().FirstOrDefault(); if(import == null) continue; - IntPtr lib; - if (import.Dll == GtkDll.Gtk) - lib = gtk; - else if (import.Dll == GtkDll.Gdk) - lib = gdk; - else if (import.Dll == GtkDll.Gio) - lib = gio; - else if (import.Dll == GtkDll.Glib) - lib = glib; - else if (import.Dll == GtkDll.Gobject) - lib = gobject; - else - throw new ArgumentException("Invalid GtkImportAttribute for " + fieldInfo.FieldType); - + IntPtr lib = dlls[import.Dll]; + var funcPtr = loader.GetProcAddress(lib, import.Name ?? fieldInfo.FieldType.Name); fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType)); } @@ -90,7 +136,7 @@ namespace Avalonia.Gtk3.Interop try { Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal - .GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle)); + .GetDelegateForFunctionPointer(loader.GetProcAddress(dlls[GtkDll.Gdk], name), typeof(Native.D.gdk_get_native_handle)); break; } catch { } @@ -103,3 +149,4 @@ namespace Avalonia.Gtk3.Interop } } + diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index fc0893bdb8..4d5509f4f7 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -13,11 +13,13 @@ namespace Avalonia.Gtk3 { protected readonly IntPtr GtkWidget; private IInputRoot _inputRoot; + private readonly FramebufferManager _framebuffer; protected readonly List _disposables = new List(); public TopLevelImpl(IntPtr gtkWidget) { GtkWidget = gtkWidget; + _framebuffer = new FramebufferManager(this); Native.GtkWidgetSetEvents(gtkWidget, uint.MaxValue); Native.GtkWidgetRealize(gtkWidget); Connect("draw", OnDraw); @@ -126,9 +128,13 @@ namespace Avalonia.Gtk3 void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); + internal IntPtr CurrentCairoContext { get; private set; } + private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) { + CurrentCairoContext = cairocontext; Paint?.Invoke(new Rect(ClientSize)); + CurrentCairoContext = IntPtr.Zero; return true; } @@ -234,6 +240,6 @@ namespace Avalonia.Gtk3 } IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); - public IEnumerable Surfaces => new object[] {Handle}; + public IEnumerable Surfaces => new object[] {Handle, _framebuffer}; } } From 480801864a1b5d422dbeed7c8a87a327a60d980a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 23:15:52 +0300 Subject: [PATCH 38/96] Mono support --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 3 ++- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 4 ++-- src/Gtk/Avalonia.Gtk3/Interop/Signal.cs | 11 +++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 625cd7096a..2aee41e617 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -24,7 +24,8 @@ namespace Avalonia.Gtk3 { Resolver.Resolve(); Native.GtkInit(0, IntPtr.Zero); - App = Native.GtkApplicationNew("avalonia.app." + Guid.NewGuid(), 0); + using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid())) + App = Native.GtkApplicationNew(utf, 0); //Mark current thread as UI thread s_tlsMarker = true; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 4ed7474fb1..aa3e73f7c4 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -20,7 +20,7 @@ namespace Avalonia.Gtk3.Interop public static class D { [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_application_new([MarshalAs(UnmanagedType.AnsiBStr)] string appId, int flags); + public delegate IntPtr gtk_application_new(Utf8Buffer appId, int flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_main_iteration(); @@ -113,7 +113,7 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] public delegate void gdk_window_invalidate_rect(IntPtr window, ref GdkRectangle rect, bool invalidate_children); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] - public delegate ulong g_signal_connect_object(IntPtr instance, [MarshalAs(UnmanagedType.AnsiBStr)]string signal, IntPtr handler, IntPtr userData, int flags); + public delegate ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs index 5896c547f2..b2ccdcf408 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs @@ -36,10 +36,13 @@ namespace Avalonia.Gtk3.Interop { var handle = GCHandle.Alloc(handler); var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler); - var id = Native.GSignalConnectObject(obj, name, ptr, IntPtr.Zero, 0); - if (id == 0) - throw new ArgumentException("Unable to connect to signal " + name); - return new ConnectedSignal(obj, handle, id); + using (var utf = new Utf8Buffer(name)) + { + var id = Native.GSignalConnectObject(obj, utf, ptr, IntPtr.Zero, 0); + if (id == 0) + throw new ArgumentException("Unable to connect to signal " + name); + return new ConnectedSignal(obj, handle, id); + } } } } From 8f3e56963a05ee6260a789ea802de39ea0e0c803 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 26 Jan 2017 23:52:59 +0100 Subject: [PATCH 39/96] Picked some style nits. --- .../Platform/SkiaPlatform/WindowImpl.cs | 6 +- .../Platform/Surfaces/ILockedFramebuffer.cs | 9 +-- .../Platform/IPlatformRenderInterface.cs | 4 +- src/Gtk/Avalonia.Cairo/CairoPlatform.cs | 2 +- src/Gtk/Avalonia.Gtk/FramebufferManager.cs | 5 +- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 13 ++-- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 - .../Avalonia.Win32/WindowFramebuffer.cs | 71 +++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 11 +-- src/iOS/Avalonia.iOS/AvaloniaView.cs | 5 +- 10 files changed, 59 insertions(+), 69 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs index 7816a8af27..0ce7bdca22 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -96,6 +96,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform IPlatformHandle ITopLevelImpl.Handle => this; + public IEnumerable Surfaces => new object[] { this }; + public void Activate() { } @@ -194,7 +196,5 @@ namespace Avalonia.Android.Platform.SkiaPlatform { // No window icons for mobile platforms } - - public IEnumerable Surfaces => new object[] {this}; - } + } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs index e771401dfe..d6402d170d 100644 --- a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs +++ b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Avalonia.Controls.Platform.Surfaces { @@ -12,22 +8,27 @@ namespace Avalonia.Controls.Platform.Surfaces /// Address of the first pixel /// IntPtr Address { get; } + /// /// Framebuffer width /// int Width { get; } + /// /// Framebuffer height /// int Height { get; } + /// /// Number of bytes per row /// int RowBytes { get; } + /// /// DPI of underling screen /// Size Dpi { get; } + /// /// Pixel format /// diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index 34600c145f..cdec0a07a1 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -41,7 +41,9 @@ namespace Avalonia.Platform /// /// Creates a renderer. /// - /// The list of native platform's surfaces that can be used for output. + /// + /// The list of native platform surfaces that can be used for output. + /// /// An . IRenderTarget CreateRenderTarget(IEnumerable surfaces); diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index 8b3c6899d3..e6c493320f 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -59,7 +59,7 @@ namespace Avalonia.Cairo return new RenderTarget(accessor); throw new NotSupportedException(string.Format( - "Don't know how to create a Cairo renderer from any of provided surfaces")); + "Don't know how to create a Cairo renderer from any of the provided surfaces.")); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs index 3da3c3d70c..72c977b2b4 100644 --- a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Controls.Platform.Surfaces; namespace Avalonia.Gtk @@ -11,6 +7,7 @@ namespace Avalonia.Gtk { private readonly WindowImplBase _window; private PixbufFramebuffer _fb; + public FramebufferManager(WindowImplBase window) { _window = window; diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index bd48998dff..a8bd7836ec 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -3,15 +3,10 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; -using System.Runtime.InteropServices; -using Gdk; -using Avalonia.Controls; -using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Input; -using Avalonia.Threading; +using Gdk; using Action = System.Action; using WindowEdge = Avalonia.Controls.WindowEdge; @@ -23,8 +18,6 @@ namespace Avalonia.Gtk { private IInputRoot _inputRoot; protected Gtk.Widget _window; - public Gtk.Widget Widget => _window; - public Gdk.Drawable CurrentDrawable { get; private set; } private FramebufferManager _framebuffer; private Gtk.IMContext _imContext; @@ -60,6 +53,8 @@ namespace Avalonia.Gtk } public IPlatformHandle Handle { get; private set; } + public Gtk.Widget Widget => _window; + public Gdk.Drawable CurrentDrawable { get; private set; } void OnRealized (object sender, EventArgs eventArgs) { diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 56b334073b..4cfe1e3e40 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -55,8 +55,6 @@ namespace Avalonia.Skia return new Renderer(root, renderLoop); } - - public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) { if (width < 1) diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs index cd89c01131..f7ea79e78a 100644 --- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -18,8 +18,7 @@ namespace Avalonia.Win32 { private readonly IntPtr _handle; private IntPtr _pBitmap; - UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; - + private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; public WindowFramebuffer(IntPtr handle, int width, int height) { @@ -38,7 +37,38 @@ namespace Avalonia.Win32 _pBitmap = Marshal.AllocHGlobal(width * height * 4); } + ~WindowFramebuffer() + { + Deallocate(); + } + + public IntPtr Address => _pBitmap; + public int RowBytes => Width * 4; + public PixelFormat Format => PixelFormat.Bgra8888; + + public Size Dpi + { + get + { + if (UnmanagedMethods.ShCoreAvailable) + { + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow(_handle, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size(dpix, dpiy); + } + } + return new Size(96, 96); + } + } public int Width => _bmpInfo.biWidth; @@ -73,11 +103,9 @@ namespace Avalonia.Win32 return true; } - - public void Dispose() { - //It's not an *actual* dispose. This call meand "We are done drawing" + //It's not an *actual* dispose. This call means "We are done drawing" DrawToWindow(_handle); } @@ -89,38 +117,5 @@ namespace Avalonia.Win32 _pBitmap = IntPtr.Zero; } } - - ~WindowFramebuffer() - { - Deallocate(); - } - - public IntPtr Address => _pBitmap; - public int RowBytes => Width * 4; - public PixelFormat Format => PixelFormat.Bgra8888; - - public Size Dpi - { - get - { - if (UnmanagedMethods.ShCoreAvailable) - { - uint dpix, dpiy; - - var monitor = UnmanagedMethods.MonitorFromWindow(_handle, - UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); - - if (UnmanagedMethods.GetDpiForMonitor( - monitor, - UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, - out dpix, - out dpiy) == 0) - { - return new Size(dpix, dpiy); - } - } - return new Size(96, 96); - } - } } } \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 19a7ca5e58..90bc684292 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -38,6 +38,7 @@ namespace Avalonia.Win32 private double _scaling = 1; private WindowState _showWindowState; private FramebufferManager _framebuffer; + public WindowImpl() { CreateWindow(); @@ -164,6 +165,11 @@ namespace Avalonia.Win32 } } + public IEnumerable Surfaces => new object[] + { + Handle, _framebuffer + }; + public void Activate() { UnmanagedMethods.SetActiveWindow(_hwnd); @@ -764,10 +770,5 @@ namespace Avalonia.Win32 ShowWindow(WindowState.Maximized); } } - - public IEnumerable Surfaces => new object[] - { - Handle, _framebuffer - }; } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index e9d095483b..1ef034b87c 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -149,6 +149,9 @@ namespace Avalonia.iOS } public Size MaxClientSize => Bounds.Size.ToAvalonia(); + + public IEnumerable Surfaces => new object[] { this }; + public void SetTitle(string title) { //Not supported @@ -232,8 +235,6 @@ namespace Avalonia.iOS public void SetIcon(IWindowIconImpl icon) { } - - public IEnumerable Surfaces => new object[]{this}; } class AvaloniaViewController : UIViewController From d4a967d0626a07c56b48e8b7dbba31ea2a0d52d6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 27 Jan 2017 00:08:09 +0100 Subject: [PATCH 40/96] Missed a nit. --- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index a8bd7836ec..c82d510c64 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -125,6 +125,13 @@ namespace Avalonia.Gtk public Action ScalingChanged { get; set; } + public IEnumerable Surfaces => new object[] + { + Handle, + new Func(() => CurrentDrawable), + _framebuffer + }; + public IPopupImpl CreatePopup() { return new PopupImpl(); @@ -316,12 +323,5 @@ namespace Avalonia.Gtk _window.Dispose(); _window = null; } - - public IEnumerable Surfaces => new object[] - { - Handle, - new Func(() => CurrentDrawable), - _framebuffer - }; } } From 714145b13d914c18319105b4bbac7bef98ba1fb0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 27 Jan 2017 00:58:24 +0100 Subject: [PATCH 41/96] Added SharpDX.Direct3D11 to nuspec. --- build.cake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.cake b/build.cake index e902c5d24e..a4be2dc9e0 100644 --- a/build.cake +++ b/build.cake @@ -197,6 +197,7 @@ var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault(). var SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1; var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1; var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1; +var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1; var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1; Information("Package: Serilog, version: {0}", SerilogVersion); @@ -206,6 +207,7 @@ Information("Package: System.Reactive, version: {0}", SystemReactiveVersion); Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion); Information("Package: SharpDX, version: {0}", SharpDXVersion); Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version); +Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version); Information("Package: SharpDX.DXGI, version: {0}", SharpDXDXGIVersion); var SetNuGetNuspecCommonProperties = new Action ((nuspec) => { @@ -441,6 +443,7 @@ var nuspecNuGetSettingsDesktop = new [] new NuSpecDependency() { Id = "Avalonia", Version = version }, new NuSpecDependency() { Id = "SharpDX", Version = SharpDXVersion }, new NuSpecDependency() { Id = "SharpDX.Direct2D1", Version = SharpDXDirect2D1Version }, + new NuSpecDependency() { Id = "SharpDX.Direct3D11", Version = SharpDXDirect3D11Version }, new NuSpecDependency() { Id = "SharpDX.DXGI", Version = SharpDXDXGIVersion } }, Files = new [] From 54721a6941679b55f2afa6a8bf3fa913e4cfb99a Mon Sep 17 00:00:00 2001 From: Ivan Kochurkin Date: Fri, 27 Jan 2017 22:30:43 +0300 Subject: [PATCH 42/96] TwoWay binding by default for IsExpanded property in Expander control. fixes #865. --- src/Avalonia.Controls/Expander.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index 87eb427f3c..e7f75336f5 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -31,7 +31,8 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect( nameof(IsExpanded), o => o.IsExpanded, - (o, v) => o.IsExpanded = v); + (o, v) => o.IsExpanded = v, + defaultBindingMode: Data.BindingMode.TwoWay); static Expander() { From 7ad30a564c1eefdc6caf2d988b356b0e91cc0e60 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Jan 2017 16:43:37 +0300 Subject: [PATCH 43/96] Cairo surface format seems to be always Rgba8888 for some reason --- src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index e955f82764..a2b06d0d5e 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -54,16 +54,8 @@ namespace Avalonia.Gtk3 //TODO: Proper DPI detect public Size Dpi => new Size(96, 96); - public PixelFormat Format - { - get - { - if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == - OperatingSystemType.WinNT) - return PixelFormat.Bgra8888; - return PixelFormat.Rgba8888; - } - } + + public PixelFormat Format => PixelFormat.Bgra8888; } } From 65686fc00293bac46cea236b7d79a4d980c734d1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Jan 2017 18:17:30 +0300 Subject: [PATCH 44/96] Use ImageSurface instead of Pixbuf --- src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj | 4 +- src/Gtk/Avalonia.Gtk/FramebufferManager.cs | 6 +-- src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs | 58 ---------------------- src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs | 55 ++++++++++++++++++++ 4 files changed, 61 insertions(+), 62 deletions(-) delete mode 100644 src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs create mode 100644 src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj index 2c10b1188b..540e70cee0 100644 --- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj +++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj @@ -30,6 +30,8 @@ false + + @@ -50,7 +52,7 @@ - + diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs index 72c977b2b4..00a4727769 100644 --- a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs @@ -6,7 +6,7 @@ namespace Avalonia.Gtk class FramebufferManager : IFramebufferPlatformSurface, IDisposable { private readonly WindowImplBase _window; - private PixbufFramebuffer _fb; + private SurfaceFramebuffer _fb; public FramebufferManager(WindowImplBase window) { @@ -29,8 +29,8 @@ namespace Avalonia.Gtk if (_fb == null || _fb.Width != width || _fb.Height != height) { - _fb?.Dispose(); - _fb = new PixbufFramebuffer(width, height); + _fb?.Deallocate(); + _fb = new SurfaceFramebuffer(width, height); } _fb.SetDrawable(drawable); return _fb; diff --git a/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs b/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs deleted file mode 100644 index 76e9e8a307..0000000000 --- a/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Controls.Platform.Surfaces; -using Avalonia.Platform; -using Gdk; - -namespace Avalonia.Gtk -{ - class PixbufFramebuffer : ILockedFramebuffer - { - private Pixbuf _pixbuf; - private Drawable _drawable; - - public PixbufFramebuffer(int width, int height) - { - _pixbuf = new Pixbuf(Gdk.Colorspace.Rgb, false, 8, width, height); - } - - public void SetDrawable(Drawable drawable) - { - _drawable = drawable; - } - - public void Deallocate() - { - _pixbuf.Dispose(); - _pixbuf = null; - } - - public void Dispose() - { - using (var gc = new Gdk.GC(_drawable)) - _drawable.DrawPixbuf(gc, _pixbuf, 0, 0, 0, 0, Width, Height, RgbDither.None, 0, 0); - _drawable = null; - } - - public IntPtr Address => _pixbuf.Pixels; - public int Width => _pixbuf.Width; - public int Height => _pixbuf.Height; - public int RowBytes => _pixbuf.Rowstride; - //TODO: Proper DPI detect - public Size Dpi => new Size(96, 96); - public PixelFormat Format - { - get - { - if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == - OperatingSystemType.WinNT) - return PixelFormat.Bgra8888; - return PixelFormat.Rgba8888; - } - } - } -} - diff --git a/src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs new file mode 100644 index 0000000000..7e6da0e76a --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using Cairo; +using Gdk; + +namespace Avalonia.Gtk +{ + class SurfaceFramebuffer : ILockedFramebuffer + { + private Drawable _drawable; + private ImageSurface _surface; + + public SurfaceFramebuffer(int width, int height) + { + _surface = new ImageSurface(Cairo.Format.RGB24, width, height); + } + + public void SetDrawable(Drawable drawable) + { + _drawable = drawable; + _surface.Flush(); + } + + public void Deallocate() + { + _surface.Dispose(); + _surface = null; + } + + public void Dispose() + { + using (var ctx = CairoHelper.Create(_drawable)) + { + _surface.MarkDirty(); + ctx.SetSourceSurface(_surface, 0, 0); + ctx.Paint(); + } + _drawable = null; + } + + public IntPtr Address => _surface.DataPtr; + public int Width => _surface.Width; + public int Height => _surface.Height; + public int RowBytes => _surface.Stride; + //TODO: Proper DPI detect + public Size Dpi => new Size(96, 96); + public PixelFormat Format => PixelFormat.Bgra8888; + } +} + From c446aaf5a6a364d26a32d9c92ba126d58c50ca41 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Jan 2017 23:22:53 +0300 Subject: [PATCH 45/96] Removed IWindowImpl which was a lie, removed initialization hack, fixed rendering --- .../ControlCatalog.Android.csproj | 7 +- .../ControlCatalog.Android/MainActivity.cs | 24 ++---- .../Avalonia.Android/AndroidPlatform.cs | 25 +++--- .../AndroidThreadingInterface.cs | 12 ++- .../Avalonia.Android/Avalonia.Android.csproj | 7 +- .../Avalonia.Android/AvaloniaActivity.cs | 50 ++++++++++++ src/Android/Avalonia.Android/AvaloniaView.cs | 59 ++++++++++++++ .../Platform/SkiaPlatform/MainWindowImpl.cs | 44 ----------- .../{WindowImpl.cs => TopLevelImpl.cs} | 24 ++---- .../Platform/Specific/AvaloniaActivity.cs | 60 --------------- .../Helpers/AndroidKeyboardEventsHelper.cs | 2 +- .../Helpers/AndroidTouchEventsHelper.cs | 2 +- .../Platform/Specific/IAndroidActivity.cs | 12 --- .../Avalonia.AndroidTestApplication.csproj | 12 +-- .../MainActivity.cs | 32 ++------ .../Avalonia.Skia.Android.csproj | 1 + .../Avalonia.Skia.Android/NativeMethods.cs | 71 +++++++++++++++++ .../Avalonia.Skia.Android/RenderTarget.cs | 77 +++++++------------ src/Skia/Avalonia.Skia.Android/SkiaView.cs | 4 +- 19 files changed, 271 insertions(+), 254 deletions(-) create mode 100644 src/Android/Avalonia.Android/AvaloniaActivity.cs create mode 100644 src/Android/Avalonia.Android/AvaloniaView.cs delete mode 100644 src/Android/Avalonia.Android/Platform/SkiaPlatform/MainWindowImpl.cs rename src/Android/Avalonia.Android/Platform/SkiaPlatform/{WindowImpl.cs => TopLevelImpl.cs} (86%) delete mode 100644 src/Android/Avalonia.Android/Platform/Specific/AvaloniaActivity.cs delete mode 100644 src/Android/Avalonia.Android/Platform/Specific/IAndroidActivity.cs create mode 100644 src/Skia/Avalonia.Skia.Android/NativeMethods.cs diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index 5b39aa3dfb..7dd09df73c 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -29,12 +29,15 @@ 4 True None - False + True False False - armeabi,armeabi-v7a,x86 + armeabi;armeabi-v7a;x86 Xamarin False + False + False + False pdbonly diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 3f357b0e70..157609088f 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -1,9 +1,8 @@ using System; using Android.App; - using Android.OS; using Android.Content.PM; -using Avalonia.Android.Platform.Specific; +using Avalonia.Android; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Markup.Xaml; @@ -17,29 +16,16 @@ namespace ControlCatalog.Android [Activity(Label = "ControlCatalog.Android", MainLauncher = true, Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)] public class MainActivity : AvaloniaActivity { - public MainActivity() : base(typeof (App)) - { - - } - protected override void OnCreate(Bundle savedInstanceState) { - base.OnCreate(savedInstanceState); - - App app; - if (Avalonia.Application.Current != null) - app = (App)Avalonia.Application.Current; - else + if (Avalonia.Application.Current == null) { - app = new App(); - AppBuilder.Configure(app) + AppBuilder.Configure(new App()) .UseAndroid() - .UseSkia() .SetupWithoutStarting(); + Content = new MainView(); } - - var mainWindow = new MainWindow(); - app.Run(mainWindow); + base.OnCreate(savedInstanceState); } } } diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index afaa314e6c..5b3170a0c7 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -1,5 +1,8 @@ using System; using System.IO; +using System.Linq; +using Android.Content; +using Android.Views; using Avalonia.Android.Platform; using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.SkiaPlatform; @@ -8,6 +11,7 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.Shared.PlatformSupport; using Avalonia.Skia; @@ -17,7 +21,8 @@ namespace Avalonia { public static T UseAndroid(this T builder) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(Android.AndroidPlatform.Initialize, "Android"); + builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.Instance), "Android"); + builder.UseSkia(); return builder; } } @@ -25,7 +30,7 @@ namespace Avalonia namespace Avalonia.Android { - public class AndroidPlatform : IPlatformSettings, IWindowingPlatform + class AndroidPlatform : IPlatformSettings, IWindowingPlatform { public static readonly AndroidPlatform Instance = new AndroidPlatform(); public Size DoubleClickSize => new Size(4, 4); @@ -40,7 +45,7 @@ namespace Avalonia.Android _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity; } - public static void Initialize() + public static void Initialize(Avalonia.Application app) { AvaloniaLocator.CurrentMutable .Bind().ToTransient() @@ -51,24 +56,22 @@ namespace Avalonia.Android .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() .Bind().ToConstant(Instance) - .Bind().ToSingleton(); + .Bind().ToSingleton() + .Bind().ToConstant(new DefaultRenderLoop(60)) - SkiaPlatform.Initialize(); - } + .Bind().ToConstant(new AssetLoader(app.GetType().Assembly)); - public void Init(Type applicationType) - { - StandardRuntimePlatformServices.Register(applicationType.Assembly); + SkiaPlatform.Initialize(); } public IWindowImpl CreateWindow() { - return new WindowImpl(); + throw new NotSupportedException(); } public IEmbeddableWindowImpl CreateEmbeddableWindow() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public IPopupImpl CreatePopup() diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index b4059e3114..6327be12a5 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -51,10 +51,16 @@ namespace Avalonia.Android scheduled = true; EnsureInvokeOnMainThread(() => { - tick(); - lock (l) + try { - scheduled = false; + tick(); + } + finally + { + lock (l) + { + scheduled = false; + } } }); } diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 654cb13678..e84146ffb3 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -63,16 +63,15 @@ + + - - + - - diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs new file mode 100644 index 0000000000..a9b04c882b --- /dev/null +++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; + +namespace Avalonia.Android +{ + public abstract class AvaloniaActivity : Activity + { + AvaloniaView _view; + object _content; + + protected override void OnCreate(Bundle savedInstanceState) + { + RequestWindowFeature(WindowFeatures.NoTitle); + _view = new AvaloniaView(this); + if(_content != null) + _view.Content = _content; + SetContentView(_view); + TakeKeyEvents(true); + base.OnCreate(savedInstanceState); + } + + public object Content + { + get + { + return _content; + } + set + { + _content = value; + if (_view != null) + _view.Content = value; + } + } + + public override bool DispatchKeyEvent(KeyEvent e) + { + return _view.DispatchKeyEvent(e); + } + } +} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs new file mode 100644 index 0000000000..71eea14a1d --- /dev/null +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using Avalonia.Android.Platform.SkiaPlatform; +using Avalonia.Controls.Embedding; +using Avalonia.Platform; + +namespace Avalonia.Android +{ + public class AvaloniaView : FrameLayout + { + private readonly EmbeddableControlRoot _root; + private readonly ViewImpl _view; + + public AvaloniaView(Context context) : base(context) + { + _view = new ViewImpl(context); + AddView(_view); + _root = new EmbeddableControlRoot(_view); + _root.Prepare(); + } + + public object Content + { + get { return _root.Content; } + set { _root.Content = value; } + } + + public override bool DispatchKeyEvent(KeyEvent e) + { + return _view.DispatchKeyEvent(e); + } + + class ViewImpl : TopLevelImpl, IEmbeddableWindowImpl + { + public event Action LostFocus; + + public ViewImpl(Context context) : base(context) + { + Focusable = true; + FocusChange += ViewImpl_FocusChange; + } + + private void ViewImpl_FocusChange(object sender, FocusChangeEventArgs e) + { + if(!e.HasFocus) + LostFocus?.Invoke(); + } + } + } +} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/MainWindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/MainWindowImpl.cs deleted file mode 100644 index 690c509b53..0000000000 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/MainWindowImpl.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Android.Views; -using Avalonia.Android.Platform.Specific; -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Platform; - -namespace Avalonia.Android.Platform.SkiaPlatform -{ - public class MainWindowImpl : - WindowImpl - , IWindowImpl - { - public MainWindowImpl() - { - } - - public new WindowState WindowState - { - get { return WindowState.Normal; } - set { } - } - - protected override void Init() - { - base.Init(); - - HandleEvents = true; - _keyboardHelper.ActivateAutoShowKeybord(); - } - - void ITopLevelImpl.Show() - { - (Parent as ViewGroup)?.RemoveAllViews(); - AvaloniaLocator.Current.GetService().ContentView = this; - //this.Visibility = ViewStates.Visible; - } - - void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot) - { - base.SetInputRoot(inputRoot); - _keyboardHelper.UpdateKeyboardState(inputRoot); - } - } -} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs similarity index 86% rename from src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs rename to src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 17b1cfba0a..1687432bb3 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -14,25 +14,21 @@ using Avalonia.Controls; namespace Avalonia.Android.Platform.SkiaPlatform { - public class WindowImpl : SkiaView, IAndroidView, IWindowImpl, ISurfaceHolderCallback + class TopLevelImpl : SkiaView, IAndroidView, ITopLevelImpl, ISurfaceHolderCallback { - protected AndroidKeyboardEventsHelper _keyboardHelper; + protected AndroidKeyboardEventsHelper _keyboardHelper; - private AndroidTouchEventsHelper _touchHelper; + private AndroidTouchEventsHelper _touchHelper; - public WindowImpl(Context context) : base((Activity)context) + public TopLevelImpl(Context context) : base(context) { - _keyboardHelper = new AndroidKeyboardEventsHelper(this); - _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, p => GetAvaloniaPointFromEvent(p)); + _keyboardHelper = new AndroidKeyboardEventsHelper(this); + _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, p => GetAvaloniaPointFromEvent(p)); MaxClientSize = new Size(Resources.DisplayMetrics.WidthPixels, Resources.DisplayMetrics.HeightPixels); ClientSize = MaxClientSize; - Init(); - } - - public WindowImpl() : this(AvaloniaLocator.Current.GetService().Activity) - { } + void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height) { @@ -46,11 +42,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform base.SurfaceChanged(holder, format, width, height); } - - protected virtual void Init() - { - } - + private bool _handleEvents; public bool HandleEvents diff --git a/src/Android/Avalonia.Android/Platform/Specific/AvaloniaActivity.cs b/src/Android/Avalonia.Android/Platform/Specific/AvaloniaActivity.cs deleted file mode 100644 index 4ccff10455..0000000000 --- a/src/Android/Avalonia.Android/Platform/Specific/AvaloniaActivity.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Android.App; -using Android.OS; -using Android.Views; -using Android.Widget; - -namespace Avalonia.Android.Platform.Specific -{ - public class AvaloniaActivity : Activity, IAndroidActivity - { - private IAndroidView _contentView; - - public AvaloniaActivity(Type applicationType) - { - AndroidPlatform.Instance.Init(applicationType); - } - - public Activity Activity => this; - - public IAndroidView ContentView - { - get - { - return this._contentView; - } - - set - { - this._contentView = value; - var fl = new FrameLayout(this); - fl.AddView(this._contentView.View); - //this.SetContentView(value.View); - this.SetContentView(fl); - } - } - - protected override void OnCreate(Bundle savedInstanceState) - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(this); - RequestWindowFeature(WindowFeatures.NoTitle); - base.OnCreate(savedInstanceState); - } - - public override void SetContentView(View view) - { - base.SetContentView(view); - TakeKeyEvents(true); - } - - public override bool DispatchKeyEvent(KeyEvent e) - { - if (_contentView != null) - { - _contentView.View.DispatchKeyEvent(e); - } - - return base.DispatchKeyEvent(e); - } - } -} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index 17e1f62e8c..7bac0ff814 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -12,7 +12,7 @@ using System.ComponentModel; namespace Avalonia.Android.Platform.Specific.Helpers { - public class AndroidKeyboardEventsHelper : IDisposable where TView : View, IWindowImpl, IAndroidView + public class AndroidKeyboardEventsHelper : IDisposable where TView : View, ITopLevelImpl, IAndroidView { private TView _view; private IInputElement _lastFocusedElement; diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs index c6d1833d2d..a448044ee6 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs @@ -8,7 +8,7 @@ using System; namespace Avalonia.Android.Platform.Specific.Helpers { - public class AndroidTouchEventsHelper : IDisposable where TView : View, IWindowImpl, IAndroidView + public class AndroidTouchEventsHelper : IDisposable where TView : View, ITopLevelImpl, IAndroidView { private TView _view; public bool HandleEvents { get; set; } diff --git a/src/Android/Avalonia.Android/Platform/Specific/IAndroidActivity.cs b/src/Android/Avalonia.Android/Platform/Specific/IAndroidActivity.cs deleted file mode 100644 index b2a999d4be..0000000000 --- a/src/Android/Avalonia.Android/Platform/Specific/IAndroidActivity.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Android.App; -using Android.Views; - -namespace Avalonia.Android.Platform.Specific -{ - public interface IAndroidActivity - { - Activity Activity { get; } - - IAndroidView ContentView { get; set; } - } -} \ No newline at end of file diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index e004121323..ebe268adcf 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -20,7 +20,7 @@ Properties\AndroidManifest.xml - true + True full false bin\Debug\ @@ -29,16 +29,16 @@ 4 True None - - False + True False False - armeabi,armeabi-v7a,x86 - - + armeabi;armeabi-v7a;x86 Xamarin False True + False + False + False pdbonly diff --git a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs index ff27e12f6e..7973ad72e5 100644 --- a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs +++ b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs @@ -2,7 +2,7 @@ using System; using Android.App; using Android.Content.PM; using Android.OS; -using Avalonia.Android.Platform.Specific; +using Avalonia.Android; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Markup.Xaml; @@ -17,36 +17,24 @@ namespace Avalonia.AndroidTestApplication Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance/*, ScreenOrientation = ScreenOrientation.Landscape*/)] - public class MainBaseActivity : AvaloniaActivity + public class MainBaseActivity : Activity { - public MainBaseActivity() : base(typeof (App)) - { - - } - protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); - - App app; - if (Avalonia.Application.Current != null) - app = (App)Avalonia.Application.Current; - else + if (Avalonia.Application.Current == null) { - app = new App(); - AppBuilder.Configure(app) + AppBuilder.Configure() .UseAndroid() - .UseSkia() .SetupWithoutStarting(); } - - app.Run(); + SetContentView(new AvaloniaView(this) { Content = App.CreateSimpleWindow() }); } } public class App : Application { - public void Run() + public override void Initialize() { Styles.Add(new DefaultTheme()); @@ -55,18 +43,14 @@ namespace Avalonia.AndroidTestApplication new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")); Styles.Add(baseLight); - var wnd = App.CreateSimpleWindow(); - wnd.AttachDevTools(); - Run(wnd); } // This provides a simple UI tree for testing input handling, drawing, etc - public static Window CreateSimpleWindow() + public static ContentControl CreateSimpleWindow() { - Window window = new Window + ContentControl window = new ContentControl() { - Title = "Avalonia Test Application", Background = Brushes.Red, Content = new StackPanel { diff --git a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj index fdde5553eb..35c7af454b 100644 --- a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj +++ b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj @@ -89,6 +89,7 @@ + diff --git a/src/Skia/Avalonia.Skia.Android/NativeMethods.cs b/src/Skia/Avalonia.Skia.Android/NativeMethods.cs new file mode 100644 index 0000000000..b9557c00c7 --- /dev/null +++ b/src/Skia/Avalonia.Skia.Android/NativeMethods.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; + +namespace Avalonia.Skia.Android +{ + static class NativeMethods + { + [DllImport("android")] + internal static extern IntPtr ANativeWindow_fromSurface(IntPtr jniEnv, IntPtr handle); + + [DllImport("android")] + internal static extern void ANativeWindow_release(IntPtr window); + [DllImport("android")] + internal static extern void ANativeWindow_unlockAndPost(IntPtr window); + + [DllImport("android")] + internal static extern int ANativeWindow_lock(IntPtr window, out ANativeWindow_Buffer outBuffer, ref ARect inOutDirtyBounds); + public enum AndroidPixelFormat + { + WINDOW_FORMAT_RGBA_8888 = 1, + WINDOW_FORMAT_RGBX_8888 = 2, + WINDOW_FORMAT_RGB_565 = 4, + } + + internal struct ARect + { + public int left; + public int top; + public int right; + public int bottom; + } + + + internal struct ANativeWindow_Buffer + { + // The number of pixels that are show horizontally. + public int width; + + // The number of pixels that are shown vertically. + public int height; + + // The number of *pixels* that a line in the buffer takes in + // memory. This may be >= width. + public int stride; + + // The format of the buffer. One of WINDOW_FORMAT_* + public AndroidPixelFormat format; + + // The actual bits. + public IntPtr bits; + + // Do not touch. + uint reserved1; + uint reserved2; + uint reserved3; + uint reserved4; + uint reserved5; + uint reserved6; + } + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs index 03ddf49851..2ee8d5a839 100644 --- a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs @@ -3,7 +3,9 @@ using Avalonia.Media; using Avalonia.Platform; using SkiaSharp; using Android.Graphics; +using Android.Runtime; using Android.Views; +using Avalonia.Skia.Android; namespace Avalonia.Skia { @@ -27,49 +29,38 @@ namespace Avalonia.Skia internal class WindowRenderTarget : RenderTarget { private readonly SurfaceView _surfaceView; - Bitmap _bitmap; - int Width { get; set; } - int Height { get; set; } + private IntPtr _window; public WindowRenderTarget(SurfaceView surfaceView) { _surfaceView = surfaceView; - FixSize(); } - private void FixSize() + private void PrepareForDraw() { - int width, height; - GetPlatformWindowSize(out width, out height); - if (Width == width && Height == height) - return; - - Width = width; - Height = height; - - if (Surface != null) - { - Surface.Dispose(); - } - - if (_bitmap != null) - { - _bitmap.Dispose(); - } - - _bitmap = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888); - Surface = SKSurface.Create(width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul, _bitmap.LockPixels(), width * 4); - } - - private void GetPlatformWindowSize(out int w, out int h) - { - w = _surfaceView.Width; - h = _surfaceView.Height; + int width = _surfaceView.Width; + var height = _surfaceView.Height; + + _window = NativeMethods.ANativeWindow_fromSurface(JNIEnv.Handle, _surfaceView.Holder.Surface.Handle); + var buffer = new NativeMethods.ANativeWindow_Buffer(); + var rc = new NativeMethods.ARect() {right = width, bottom = height}; + NativeMethods.ANativeWindow_lock(_window, out buffer, ref rc); + + var colorType = buffer.format == NativeMethods.AndroidPixelFormat.WINDOW_FORMAT_RGB_565 + ? SKColorType.Rgb565 : SKImageInfo.PlatformColorType; + + var stride = buffer.stride * (colorType == SKColorType.Rgb565 ? 2 : 4); + + Surface = SKSurface.Create(buffer.width, buffer.height, colorType, + SKAlphaType.Premul, buffer.bits, stride); + + if (Surface == null) + throw new Exception("Unable to create Skia surface"); } public override DrawingContext CreateDrawingContext() { - FixSize(); + PrepareForDraw(); var canvas = Surface.Canvas; canvas.RestoreToCount(0); @@ -84,23 +75,11 @@ namespace Avalonia.Skia public void Present() { - Canvas canvas = null; - try - { - canvas = _surfaceView.Holder.LockCanvas(null); - _bitmap.UnlockPixels(); - canvas.DrawBitmap(_bitmap, 0, 0, null); - } - catch (Exception) - { - } - finally - { - if (canvas != null) - _surfaceView.Holder.UnlockCanvasAndPost(canvas); - } - - _bitmap.UnlockPixels(); + Surface?.Dispose(); + Surface = null; + NativeMethods.ANativeWindow_unlockAndPost(_window); + NativeMethods.ANativeWindow_release(_window); + _window = IntPtr.Zero; } } } diff --git a/src/Skia/Avalonia.Skia.Android/SkiaView.cs b/src/Skia/Avalonia.Skia.Android/SkiaView.cs index a45a80cf18..f69462f087 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaView.cs +++ b/src/Skia/Avalonia.Skia.Android/SkiaView.cs @@ -18,12 +18,12 @@ namespace Avalonia.Skia.Android { public abstract class SkiaView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle { - private readonly Activity _context; + private readonly Context _context; bool _invalidateQueued; readonly object _lock = new object(); private readonly Handler _handler; - public SkiaView(Activity context) : base(context) + public SkiaView(Context context) : base(context) { _context = context; Holder.AddCallback(this); From 87de5719253cff9f82701ac0b0f51e16acb516de Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 29 Jan 2017 00:43:22 +0300 Subject: [PATCH 46/96] Use IFramebufferSurface for android. --- Avalonia.sln | 39 +---- .../Avalonia.Android/Avalonia.Android.csproj | 2 + .../SkiaPlatform/AndroidFramebuffer.cs} | 56 +++++-- .../InvalidationAwareSurfaceView.cs} | 6 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 10 +- src/Skia/Avalonia.Skia.Android.TestApp/App.cs | 7 - .../Avalonia.Skia.Android.TestApp.csproj | 138 ------------------ ...nia.Skia.Android.TestApp.v2.ncrunchproject | 26 ---- .../MainActivity.cs | 78 ---------- .../Properties/AndroidManifest.xml | 5 - .../Properties/AssemblyInfo.cs | 30 ---- .../Resources/Resource.Designer.cs | 114 --------------- .../Resources/drawable/Icon.png | Bin 4147 -> 0 bytes .../Resources/layout/Main.axml | 13 -- .../Resources/values/Strings.xml | 5 - .../AndroidPlatformRenderInterface.cs | 9 +- .../AndroidRenderTarget.cs | 24 --- .../Avalonia.Skia.Android.csproj | 5 - .../Avalonia.Skia.Android/RenderTarget.cs | 85 ----------- .../Avalonia.Skia.Android/SkiaRenderView.cs | 39 ----- .../Avalonia.Skia.iOS.csproj | 1 + .../WindowDrawingContextImpl.cs | 0 .../Avalonia.Skia/Avalonia.Skia.projitems | 1 - 23 files changed, 63 insertions(+), 630 deletions(-) rename src/{Skia/Avalonia.Skia.Android/NativeMethods.cs => Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs} (52%) rename src/{Skia/Avalonia.Skia.Android/SkiaView.cs => Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs} (90%) delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/App.cs delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.csproj delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.v2.ncrunchproject delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/MainActivity.cs delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Properties/AndroidManifest.xml delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Properties/AssemblyInfo.cs delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Resources/Resource.Designer.cs delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Resources/drawable/Icon.png delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Resources/layout/Main.axml delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Resources/values/Strings.xml delete mode 100644 src/Skia/Avalonia.Skia.Android/AndroidRenderTarget.cs delete mode 100644 src/Skia/Avalonia.Skia.Android/RenderTarget.cs delete mode 100644 src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs rename src/Skia/{Avalonia.Skia => Avalonia.Skia.iOS}/WindowDrawingContextImpl.cs (100%) diff --git a/Avalonia.sln b/Avalonia.sln index a6fdcbd421..d204e4116b 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26014.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" EndProject @@ -99,8 +99,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.Desktop", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.Android", "src\Skia\Avalonia.Skia.Android\Avalonia.Skia.Android.csproj", "{BD43F7C0-396B-4AA1-BAD9-DFDE54D51298}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.Android.TestApp", "src\Skia\Avalonia.Skia.Android.TestApp\Avalonia.Skia.Android.TestApp.csproj", "{F92E55A5-ED73-4CCB-AB4B-0541B6757F31}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.iOS.TestApp", "src\Skia\Avalonia.Skia.iOS.TestApp\Avalonia.Skia.iOS.TestApp.csproj", "{DA49C5F3-BE95-461C-B999-072128CCF59E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.iOS", "src\Skia\Avalonia.Skia.iOS\Avalonia.Skia.iOS.csproj", "{47BE08A7-5985-410B-9FFC-2264B8EA595F}" @@ -1469,38 +1467,6 @@ Global {BD43F7C0-396B-4AA1-BAD9-DFDE54D51298}.Release|Mono.ActiveCfg = Release|Any CPU {BD43F7C0-396B-4AA1-BAD9-DFDE54D51298}.Release|x86.ActiveCfg = Release|Any CPU {BD43F7C0-396B-4AA1-BAD9-DFDE54D51298}.Release|x86.Build.0 = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|x86.Deploy.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|Mono.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|x86.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|x86.Build.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|x86.Deploy.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|Mono.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|x86.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|x86.Build.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|x86.Deploy.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.Build.0 = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.Deploy.0 = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|iPhone.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Mono.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|x86.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|x86.Build.0 = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|x86.Deploy.0 = Release|Any CPU {DA49C5F3-BE95-461C-B999-072128CCF59E}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone {DA49C5F3-BE95-461C-B999-072128CCF59E}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone {DA49C5F3-BE95-461C-B999-072128CCF59E}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone @@ -2359,7 +2325,6 @@ Global {2F59F3D0-748D-4652-B01E-E0D954756308} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {925DD807-B651-475F-9F7C-CBEB974CE43D} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {BD43F7C0-396B-4AA1-BAD9-DFDE54D51298} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {DA49C5F3-BE95-461C-B999-072128CCF59E} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {47BE08A7-5985-410B-9FFC-2264B8EA595F} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {7B92AF71-6287-4693-9DCB-BD5B6E927E23} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F} diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index e84146ffb3..0dfab3f518 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -70,6 +70,8 @@ + + diff --git a/src/Skia/Avalonia.Skia.Android/NativeMethods.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs similarity index 52% rename from src/Skia/Avalonia.Skia.Android/NativeMethods.cs rename to src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs index b9557c00c7..982c79560b 100644 --- a/src/Skia/Avalonia.Skia.Android/NativeMethods.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs @@ -1,23 +1,54 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; - -using Android.App; -using Android.Content; -using Android.OS; using Android.Runtime; using Android.Views; -using Android.Widget; +using Avalonia.Controls.Platform.Surfaces; -namespace Avalonia.Skia.Android +namespace Avalonia.Android.Platform.SkiaPlatform { - static class NativeMethods + class AndroidFramebuffer : ILockedFramebuffer { + private IntPtr _window; + + public AndroidFramebuffer(Surface surface) + { + _window = ANativeWindow_fromSurface(JNIEnv.Handle, surface.Handle); + ANativeWindow_Buffer buffer; + var rc = new ARect() + { + right = Width = ANativeWindow_getWidth(_window), + bottom = Height = ANativeWindow_getHeight(_window) + }; + ANativeWindow_lock(_window, out buffer, ref rc); + + Format = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565 + ? PixelFormat.Rgb565 : PixelFormat.Rgba8888; + + RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4); + Address = buffer.bits; + } + + public void Dispose() + { + ANativeWindow_unlockAndPost(_window); + ANativeWindow_release(_window); + _window = IntPtr.Zero; + Address = IntPtr.Zero; + } + + public IntPtr Address { get; set; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Size Dpi { get; } = new Size(96, 96); + public PixelFormat Format { get; } + [DllImport("android")] internal static extern IntPtr ANativeWindow_fromSurface(IntPtr jniEnv, IntPtr handle); - + [DllImport("android")] + internal static extern int ANativeWindow_getWidth(IntPtr window); + [DllImport("android")] + internal static extern int ANativeWindow_getHeight(IntPtr window); [DllImport("android")] internal static extern void ANativeWindow_release(IntPtr window); [DllImport("android")] @@ -39,8 +70,7 @@ namespace Avalonia.Skia.Android public int right; public int bottom; } - - + internal struct ANativeWindow_Buffer { // The number of pixels that are show horizontally. diff --git a/src/Skia/Avalonia.Skia.Android/SkiaView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs similarity index 90% rename from src/Skia/Avalonia.Skia.Android/SkiaView.cs rename to src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs index f69462f087..2213ebddcc 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaView.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs @@ -14,16 +14,16 @@ using Android.Widget; using Avalonia.Media; using Avalonia.Platform; -namespace Avalonia.Skia.Android +namespace Avalonia.Android { - public abstract class SkiaView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle + public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle { private readonly Context _context; bool _invalidateQueued; readonly object _lock = new object(); private readonly Handler _handler; - public SkiaView(Context context) : base(context) + public InvalidationAwareSurfaceView(Context context) : base(context) { _context = context; Holder.AddCallback(this); diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1687432bb3..26d2b0aad9 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -7,14 +7,16 @@ using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Skia.Android; using System; using System.Collections.Generic; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; namespace Avalonia.Android.Platform.SkiaPlatform { - class TopLevelImpl : SkiaView, IAndroidView, ITopLevelImpl, ISurfaceHolderCallback + class TopLevelImpl : InvalidationAwareSurfaceView, IAndroidView, ITopLevelImpl, + ISurfaceHolderCallback, IFramebufferPlatformSurface + { protected AndroidKeyboardEventsHelper _keyboardHelper; @@ -183,5 +185,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform { // No window icons for mobile platforms } - } + + ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(Holder.Surface); + } } \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/App.cs b/src/Skia/Avalonia.Skia.Android.TestApp/App.cs deleted file mode 100644 index ca4d36b820..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/App.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace Avalonia.Skia.Android.TestApp -{ - public class App : Application - { - } -} diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.csproj b/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.csproj deleted file mode 100644 index 041c8d9ecb..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.csproj +++ /dev/null @@ -1,138 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Avalonia.Skia.Android.TestApp - Avalonia.Skia.Android.TestApp - 512 - true - Resources\Resource.Designer.cs - Off - False - v4.4 - Properties\AndroidManifest.xml - - - True - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - True - None - False - False - False - armeabi;armeabi-v7a;x86 - Xamarin - False - True - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - False - Full - True - False - False - Xamarin - False - False - False - False - False - armeabi;armeabi-v7a;x86 - - - - - - - - - - - - - - - - - - - - - - - - {7b92af71-6287-4693-9dcb-bd5b6e927e23} - Avalonia.Android - - - {d211e587-d8bc-45b9-95a4-f297c8fa5200} - Avalonia.Animation - - - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} - Avalonia.Base - - - {d2221c82-4a25-4583-9b43-d791e3f6820c} - Avalonia.Controls - - - {4a1abb09-9047-4bd5-a4ad-a055e52c5ee0} - Avalonia.DotNetFrameworkRuntime - - - {62024b2d-53eb-4638-b26b-85eeaa54866e} - Avalonia.Input - - - {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} - Avalonia.Interactivity - - - {42472427-4774-4c81-8aff-9f27b8e31721} - Avalonia.Layout - - - {eb582467-6abb-43a1-b052-e981ba910e3a} - Avalonia.Visuals - - - {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} - Avalonia.Styling - - - {bd43f7c0-396b-4aa1-bad9-dfde54d51298} - Avalonia.Skia.Android - - - - - - - - - \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.v2.ncrunchproject b/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.v2.ncrunchproject deleted file mode 100644 index e1b4d7cf28..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - true - false - false - false - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/MainActivity.cs b/src/Skia/Avalonia.Skia.Android.TestApp/MainActivity.cs deleted file mode 100644 index 9ac833a559..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/MainActivity.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Android.App; -using Android.OS; -using Android.Views; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Media; - -namespace Avalonia.Skia.Android.TestApp -{ - [Activity(Label = "Avalonia.Skia.Android.TestApp", MainLauncher = true, Icon = "@drawable/icon")] - public class MainActivity : Activity - { - - protected override void OnCreate(Bundle bundle) - { - base.OnCreate(bundle); - - App app; - if (Avalonia.Application.Current != null) - app = (App)Avalonia.Application.Current; - else - { - app = new App(); - AppBuilder.Configure(app) - .UseAndroid() - .UseSkia() - .SetupWithoutStarting(); - } - - SetContentView(new MainView(this)); - } - - class MainView : SkiaRenderView - { - float _radians = 0; - public MainView(Activity context) : base(context) - { - } - - protected override void OnRender(DrawingContext ctx) - { - ctx.FillRectangle(Brushes.Green, new Rect(0, 0, Width, Height)); - - var rc = new Rect(0, 0, Width/3, Height/3); - using (ctx.PushPostTransform( - Avalonia.Matrix.CreateTranslation(-Width/6, -Width/6)* - Avalonia.Matrix.CreateRotation(_radians)* - Avalonia.Matrix.CreateTranslation(Width/2, Height/2))) - { - ctx.FillRectangle(new LinearGradientBrush() - { - GradientStops = - { - new GradientStop() {Color = Colors.Blue}, - new GradientStop(Colors.Red, 1) - } - }, rc, 5); - } - - - } - - public override bool OnTouchEvent(MotionEvent e) - { - if (e.Action == MotionEventActions.Down) - return true; - if (e.Action == MotionEventActions.Move) - { - _radians = (e.RawY + e.RawY)/100; - Invalidate(); - return true; - } - return base.OnTouchEvent(e); - } - } - } -} - diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AndroidManifest.xml b/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AndroidManifest.xml deleted file mode 100644 index 5af8811830..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AssemblyInfo.cs b/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AssemblyInfo.cs deleted file mode 100644 index 68efca2668..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Android.App; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Avalonia.Skia.Android.TestApp")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Avalonia.Skia.Android.TestApp")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/Resource.Designer.cs b/src/Skia/Avalonia.Skia.Android.TestApp/Resources/Resource.Designer.cs deleted file mode 100644 index 7d9036603f..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/Resource.Designer.cs +++ /dev/null @@ -1,114 +0,0 @@ -#pragma warning disable 1591 -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -[assembly: global::Android.Runtime.ResourceDesignerAttribute("Avalonia.Skia.Android.TestApp.Resource", IsApplication=true)] - -namespace Avalonia.Skia.Android.TestApp -{ - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] - public partial class Resource - { - - static Resource() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - public static void UpdateIdValues() - { - global::Avalonia.Android.Resource.String.ApplicationName = global::Avalonia.Skia.Android.TestApp.Resource.String.ApplicationName; - global::Avalonia.Android.Resource.String.Hello = global::Avalonia.Skia.Android.TestApp.Resource.String.Hello; - } - - public partial class Attribute - { - - static Attribute() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Attribute() - { - } - } - - public partial class Drawable - { - - // aapt resource value: 0x7f020000 - public const int Icon = 2130837504; - - static Drawable() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Drawable() - { - } - } - - public partial class Id - { - - // aapt resource value: 0x7f050000 - public const int MyButton = 2131034112; - - static Id() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Id() - { - } - } - - public partial class Layout - { - - // aapt resource value: 0x7f030000 - public const int Main = 2130903040; - - static Layout() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Layout() - { - } - } - - public partial class String - { - - // aapt resource value: 0x7f040001 - public const int ApplicationName = 2130968577; - - // aapt resource value: 0x7f040000 - public const int Hello = 2130968576; - - static String() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private String() - { - } - } - } -} -#pragma warning restore 1591 diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/drawable/Icon.png b/src/Skia/Avalonia.Skia.Android.TestApp/Resources/drawable/Icon.png deleted file mode 100644 index 8074c4c571b8cd19e27f4ee5545df367420686d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4147 zcmV-35X|q1P)OwvMs$Q8_8nISM!^>PxsujeDCl4&hPxrxkp%Qc^^|l zp6LqAcf3zf1H4aA1Gv-O6ha)ktct9Y+VA@N^9i;p0H%6v>ZJZYQ`zEa396z-gi{r_ zDz)D=vgRv62GCVeRjK{15j7V@v6|2nafFX6W7z2j1_T0a zLyT3pGTubf1lB5)32>bl0*BflrA!$|_(WD2)iJIfV}37=ZKAC zSe3boYtQ=;o0i>)RtBvsI#iT{0!oF1VFeW`jDjF2Q4aE?{pGCAd>o8Kg#neIh*AMY zLl{;F!vLiem7s*x0<9FKAd6LoPz3~G32P+F+cuGOJ5gcC@pU_?C2fmix7g2)SUaQO$NS07~H)#fn!Q<}KQWtX}wW`g2>cMld+`7Rxgq zChaey66SG560JhO66zA!;sK1cWa2AG$9k~VQY??6bOmJsw9@3uL*z;WWa7(Nm{^TA zilc?y#N9O3LcTo2c)6d}SQl-v-pE4^#wb=s(RxaE28f3FQW(yp$ulG9{KcQ7r>7mQ zE!HYxUYex~*7IinL+l*>HR*UaD;HkQhkL(5I@UwN%Wz504M^d!ylo>ANvKPF_TvA< zkugG5;F6x}$s~J8cnev->_(Ic7%lGQgUi3n#XVo36lUpcS9s z)ympRr7}@|6WF)Ae;D{owN1;aZSR50al9h~?-WhbtKK%bDd zhML131oi1Bu1&Qb$Cp199LJ#;j5d|FhW8_i4KO1OI>}J^p2DfreMSVGY9aFlr&90t zyI2FvxQiKMFviSQeP$Ixh#70qj5O%I+O_I2t2XHWqmh2!1~tHpN3kA4n=1iHj?`@c<~3q^X6_Q$AqTDjBU`|!y<&lkqL|m5tG(b z8a!z&j^m(|;?SW(l*?tZ*{m2H9d&3jqBtXh>O-5e4Qp-W*a5=2NL&Oi62BUM)>zE3 zbSHb>aU3d@3cGggA`C-PsT9^)oy}%dHCaO~nwOrm5E54=aDg(&HR4S23Oa#-a^=}w%g?ZP-1iq8PSjE8jYaGZu z$I)?YN8he?F9>)2d$G6a*zm0XB*Rf&gZAjq(8l@CUDSY1tB#!i> zW$VfG%#SYSiZ};)>pHA`qlfDTEYQEwN6>NNEp+uxuqx({Fgr zjI@!4xRc?vk^9+~eU|mzH__dCDI=xb{Cd}4bELS9xRaS!*FXMwtMR-RR%SLMh0Cjl zencr8#Su<4(%}$yGVBU-HX{18v=yPH*+%^Vtknc>2A;%-~DrYFx^3XfuVgvZ{#1tA== zm3>IzAM2{3Iv_d1XG{P6^tN3|PkJMnjs&CWN7%7_CmjoVakUhsa&dMv==2~^ri?&x zVdv*rnfVyM+I1^Kg*S=23mR@+0T9BWFZUu~@toA8d)fw6be=`Yb6DSX6D?jB%2YT~ z*aHjtIOozfMhA!Jd*?u5_n!SnX>vX`=Ti-1HA4RiE>eI3vTn zz+>Ccf0HX6Ans-ebOB>RJST-Cyr#4XAk+mAlJgdQnoE{^iIN)OcYFSpgJUmXtl@tT z-^ZuUeSj5hSFrQwqX>~EtZ*{>Gi8Bu9_|o06oNtaXP?E936!a@DsvS*tsB@fa6kEA z5GkjwmH?EgpiG&itsB_Tb1NxtFnvxh_s@9KYX1Sttf?AlI~)z zT=6Y7ulx=}<8Scr_UqU-_z)5gPo%050PsbM*ZLno;_-ow&k?FZJtYmb2hPA$LkP)8 z=^d0Q6PImh6Y|QT?{grxj)S=uBKvY2EQUbm@ns9^yKiP~$DcD)c$5Em`zDSScH%iH zVov&m=cMo`1tYwA=!a}vb_ef_{)Q2?FUqn>BR$6phXQRv^1%=YfyE-F$AR4Q?9D!f zCzB^^#td~4u&l~l#rp2QLfe3+_ub9@+|x+m;=2(sQ`s%gO|j$XBb>A7Q(UydipiMw%igcweV#Cr~SP);q>w`bxts_4} znKHg?X==JDkQl3Y>Ckt%`s{n?Nq-1Fw5~%Mq$CAsi-`yu_bKm zxs#QdE7&vgJD%M84f4SNzSDv)S|V?|$!d5a#lhT5>>YWE4NGqa9-fbmV$=)@k&32kdEYetna>=j@0>V8+wRsL;po!3ivVwh<9tn z2S<1u9DAAQ>x1Sn=fk`)At|quvleV($B|#Kap_lB-F^*yV=wZ{9baUu(uXfokr95^ zA*!*W=5a>$2Ps`-F^+qRQT^{*cN>vipT*4!r#p%{(#I7s z0NN94*q?ib$KJjfDI_sjHNdmEVp5wB&j54O#VoFqBwy)gfA$%)4d_X4q${L9Xom2R3xy&ZBSNgt4a1d7K^CDWa9r zVb-_52m}Vp)`9;ZSKd#|U4ZYj5}Gp49{4utST|=c`~(#>KHF6}CCov1iHYw zt{bWo)A@yF2$~c(nR$rSAaFQ$(Wh{vkG1AlutDMw=mM`C`T=X&|Ad9fb5Od}ROt1z zOpczHqrb4Jo^rSCiW#&o(m7jFamnrsTpQb;*h4o8r#$aZ}2RaT-x2u^^ z%u@YyIv$U^u~@9(XGbSwU@fk6SikH>j+D1jQrYTKGJpW%vUT{!d}7THI5&Sa?~MKy zS0-mvMl+BOcroEJ@hN!2H_?coTEJ5Q<;Nd?yx;eIj4{$$E2?YUO|NtNPJ-PdDf;s} zab;}Mz0kbOI}5*w@3gROcnl#5)wQnEhDBfn!Xhy`u>C}*E~vWpO^HS)FC>8^umI=+ z&H;LW6w#;EF`}vQd_9Muru`KnQVPI9U?(sD)&Dg-0j3#(!fNKVZ_GoYH{la~d*1Yh$TI-TL>mI4vpNb@sU2=IZ8vL%AXUx0 zz{K0|nK(yizLHaeW#ZhRfQXoK^}1$=$#1{Yn002ovPDHLkV1n#w+^+xt diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/layout/Main.axml b/src/Skia/Avalonia.Skia.Android.TestApp/Resources/layout/Main.axml deleted file mode 100644 index 98be1643ef..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/layout/Main.axml +++ /dev/null @@ -1,13 +0,0 @@ - - -